$validator->setLang($testLang)); function diagnosisEditExpect(bool $condition, string $message): void { if (!$condition) { throw new RuntimeException($message); } } // Install in-memory boundaries before loading the real validator and logic. // No application bootstrap, credentials, database, or external services are used. final class DiagnosisEditModelDouble { public static array $record; public static array $lastUpdate = []; public static int $updateCount = 0; public static function reset(): void { self::$record = [ 'id' => 42, 'patient_name' => '测试患者', 'id_card' => '110101199001010011', 'phone' => '13800138000', 'gender' => 1, 'age' => 36, 'remark' => '原备注', ]; self::$lastUpdate = []; self::$updateCount = 0; } public static function findOrEmpty($id): object { return new class((int) $id !== 42) { public function __construct(private bool $empty) {} public function isEmpty(): bool { return $this->empty; } }; } public static function find($id): ?object { // The post-update TRTC lookup is deliberately empty, so no service runs. return self::$updateCount === 0 && (int) $id === 42 ? (object) self::$record : null; } public static function update(array $params): void { self::$lastUpdate = $params; self::$record = array_replace(self::$record, $params); self::$updateCount++; } public static function where(...$args): object { // No duplicate patient exists in this isolated fixture. return new class { public function where(...$args): self { return $this; } public function find() { return null; } }; } } final class DiagnosisEditDbDouble { public static int $orderLookups = 0; public static function name(string $table): object { diagnosisEditExpect($table === 'tcm_prescription_order', 'only the basic-info order lock queries storage'); self::$orderLookups++; return new class { public function where(...$args): self { return $this; } public function whereNull(...$args): self { return $this; } public function order(...$args): self { return $this; } public function field(...$args): self { return $this; } public function find(): array { return ['fulfillment_status' => 1]; } }; } } final class DiagnosisEditAuthDouble { public static function getAuthByAdminId(int $adminId): array { return []; } } diagnosisEditExpect(class_alias(DiagnosisEditModelDouble::class, 'app\\common\\model\\tcm\\Diagnosis'), 'model double is installed'); diagnosisEditExpect(class_alias(DiagnosisEditDbDouble::class, 'think\\facade\\Db'), 'database double is installed'); diagnosisEditExpect(class_alias(DiagnosisEditAuthDouble::class, 'app\\adminapi\\logic\\auth\\AuthLogic'), 'permission double is installed'); function diagnosisEditCheck(array $payload, bool $expected, string $label, string $scene = 'edit'): void { $validator = (new DiagnosisValidate())->scene($scene); $actual = $validator->check($payload); diagnosisEditExpect($actual === $expected, $label . '; got: ' . json_encode($validator->getError(), JSON_UNESCAPED_UNICODE)); } $partial = [ 'id' => 42, 'diagnosis_type' => 'follow_up', 'local_hospital_name' => '测试医院', 'remark' => '新备注', ]; diagnosisEditCheck($partial, true, 'edit accepts omitted locked/basic/masked fields'); $basic = [ 'patient_name' => '测试患者', 'phone' => '13800138000', 'gender' => 1, 'age' => 36, ]; diagnosisEditCheck($partial + $basic, true, 'edit still accepts full basic information'); foreach (array_keys($basic) as $field) { diagnosisEditCheck($partial + array_diff_key($basic, [$field => true]), true, "edit permits omitted {$field} while other basic fields are supplied"); } diagnosisEditCheck($partial + ['phone' => '13900139000'], true, 'edit accepts a valid supplied phone'); diagnosisEditCheck($partial + ['gender' => 0, 'age' => 0], true, 'zero gender and age remain valid'); foreach (['', null, '156****7862', '123', ' 13800138000 ', false, []] as $phone) { diagnosisEditCheck($partial + ['phone' => $phone], false, 'edit rejects invalid explicit phone ' . json_encode($phone)); } foreach (['patient_name', 'gender', 'age'] as $field) { foreach (['', null] as $empty) { diagnosisEditCheck($partial + [$field => $empty], false, "edit rejects explicitly empty {$field}"); } } diagnosisEditCheck($partial + ['patient_name' => str_repeat('患', 51)], false, 'name length remains validated'); diagnosisEditCheck($partial + ['gender' => 2], false, 'gender range remains validated'); diagnosisEditCheck($partial + ['age' => 151], false, 'age range remains validated'); diagnosisEditCheck($partial + ['age' => 'invalid'], false, 'age type remains validated'); diagnosisEditCheck(array_diff_key($partial, ['id' => true]), false, 'edit still requires diagnosis ID'); diagnosisEditCheck(array_replace($partial, ['id' => 404]), false, 'edit still requires an existing diagnosis'); foreach (['diagnosis_type', 'local_hospital_name'] as $field) { diagnosisEditCheck(array_diff_key($partial, [$field => true]), false, "edit still requires unrelated {$field}"); } $add = array_diff_key($partial + $basic, ['id' => true]); diagnosisEditCheck($add, true, 'add accepts valid full payload', 'add'); foreach (array_keys($basic) as $field) { diagnosisEditCheck(array_diff_key($add, [$field => true]), false, "add still requires {$field}", 'add'); } foreach (['', null, '156****7862'] as $phone) { diagnosisEditCheck(array_replace($add, ['phone' => $phone]), false, 'add rejects empty or masked phone', 'add'); } $doctor = ['admin_id' => 7, 'root' => 0]; DiagnosisEditModelDouble::reset(); $before = DiagnosisEditModelDouble::$record; diagnosisEditExpect(DiagnosisLogic::edit($partial, $doctor), 'locked doctor can save clinical edits without basic fields'); diagnosisEditExpect(DiagnosisEditDbDouble::$orderLookups > 0, 'the existing pending-order lock is evaluated'); diagnosisEditExpect(DiagnosisEditModelDouble::$record['remark'] === '新备注', 'clinical edit reaches storage'); foreach (['patient_name', 'id_card', 'phone', 'gender', 'age'] as $field) { diagnosisEditExpect(!array_key_exists($field, DiagnosisEditModelDouble::$lastUpdate), "update does not synthesize {$field}"); diagnosisEditExpect(DiagnosisEditModelDouble::$record[$field] === $before[$field], "omitted {$field} remains unchanged"); } foreach (['patient_name' => '另一个患者', 'id_card' => '110101199001010029', 'phone' => '13900139000', 'gender' => 0, 'age' => 37] as $field => $value) { DiagnosisEditModelDouble::reset(); diagnosisEditExpect(!DiagnosisLogic::edit($partial + [$field => $value], $doctor), "pending order still locks {$field}"); diagnosisEditExpect(DiagnosisLogic::getError() === '最近业务订单未完成,无法修改患者基本信息', 'basic-info lock keeps its error'); diagnosisEditExpect(DiagnosisEditModelDouble::$updateCount === 0, 'denied change never reaches storage'); } DiagnosisEditModelDouble::reset(); diagnosisEditExpect(DiagnosisLogic::edit($partial + $basic, $doctor), 'unchanged supplied basic fields still pass the lock'); DiagnosisEditModelDouble::reset(); diagnosisEditExpect(DiagnosisLogic::edit($partial + ['phone' => '13900139000'], ['admin_id' => 1, 'root' => 1]), 'existing authorized basic-info editing remains available'); diagnosisEditExpect(DiagnosisEditModelDouble::$record['phone'] === '13900139000', 'valid authorized phone edit reaches storage'); echo "Diagnosis edit-scene validation and masked/basic-field preservation: OK\n";