Files
zyt/server/tests/AssistantServiceMysqlMigrationTest.php
T
2026-11-12 09:07:07 +08:00

101 lines
6.8 KiB
PHP

<?php
declare(strict_types=1);
/** 只连接显式指定、目录指纹已核对的临时 MySQL;不加载应用或 .env。 */
$port = (int) getenv('ASSISTANT_SERVICE_TEST_PORT');
$expectedDirectory = str_replace('\\', '/', (string) getenv('ASSISTANT_SERVICE_TEST_DATADIR'));
if (getenv('ASSISTANT_SERVICE_TEST_ALLOW_DISPOSABLE') !== '1' || $port < 1024
|| $expectedDirectory === '' || !str_contains($expectedDirectory, '/.spool/assistant-service-mysql')) {
throw new RuntimeException('必须显式提供本项目隔离测试库端口和数据目录');
}
$pdo = new PDO("mysql:host=127.0.0.1;port={$port};charset=utf8mb4", 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$actual = str_replace('\\', '/', (string) $pdo->query('SELECT @@datadir')->fetchColumn());
if (strtolower(rtrim($actual, '/')) !== strtolower(rtrim($expectedDirectory, '/'))) {
throw new RuntimeException('数据目录不匹配,拒绝创建测试数据库');
}
$database = 'assistant_service_test_' . bin2hex(random_bytes(6));
$checks = 0;
$expect = static function (bool $ok, string $message) use (&$checks): void {
if (!$ok) {
throw new RuntimeException($message);
}
$checks++;
};
$runSql = static function (string $path) use ($pdo): void {
$sql = preg_replace('/^--.*$/m', '', (string) file_get_contents($path));
foreach (explode(';', (string) $sql) as $statement) {
if (trim($statement) !== '') {
$cursor = $pdo->query($statement);
$cursor->closeCursor();
}
}
};
$pdo->exec("CREATE DATABASE `{$database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
try {
$pdo->exec("USE `{$database}`");
$pdo->exec("CREATE TABLE zyt_system_menu (
id int unsigned AUTO_INCREMENT PRIMARY KEY, pid int NOT NULL, type varchar(1), name varchar(100),
icon varchar(100), sort int, perms varchar(100), paths varchar(100), component varchar(150),
selected varchar(100), params varchar(100), is_cache tinyint, is_show tinyint, is_disable tinyint,
create_time int, update_time int) ENGINE=InnoDB");
$pdo->exec('CREATE TABLE zyt_system_role(id int PRIMARY KEY,name varchar(100),delete_time int NULL) ENGINE=InnoDB');
$pdo->exec('CREATE TABLE zyt_system_role_menu(role_id int,menu_id int,UNIQUE KEY uk_role_menu(role_id,menu_id)) ENGINE=InnoDB');
$pdo->exec("INSERT INTO zyt_system_role VALUES(1,'医生',NULL),(2,'医助',NULL),(3,'经理',NULL),(7,'诊室组长',NULL),(8,'管理员',NULL),(9,'已删除角色',1)");
$runSql(dirname(__DIR__) . '/sql/1.9.20260505/add_tcm_diagnosis_todo.sql');
$pdo->exec("INSERT INTO zyt_tcm_diagnosis_todo(diagnosis_id,content,status) VALUES(99,'历史提醒哨兵',1)");
$beforeTodo = $pdo->query('SELECT * FROM zyt_tcm_diagnosis_todo')->fetchAll(PDO::FETCH_ASSOC);
$migration = dirname(__DIR__) . '/sql/1.9.20261109/';
$runSql($migration . '01_assistant_service_tables.sql');
$runSql($migration . '02_assistant_service_menus.sql');
$beforeMenus = $pdo->query('SELECT * FROM zyt_system_menu ORDER BY id')->fetchAll(PDO::FETCH_ASSOC);
$beforeGrants = $pdo->query('SELECT * FROM zyt_system_role_menu ORDER BY role_id,menu_id')->fetchAll(PDO::FETCH_ASSOC);
$runSql($migration . '01_assistant_service_tables.sql');
$runSql($migration . '02_assistant_service_menus.sql');
$expect($beforeMenus === $pdo->query('SELECT * FROM zyt_system_menu ORDER BY id')->fetchAll(PDO::FETCH_ASSOC), '重复迁移改变菜单');
$expect($beforeGrants === $pdo->query('SELECT * FROM zyt_system_role_menu ORDER BY role_id,menu_id')->fetchAll(PDO::FETCH_ASSOC), '重复迁移改变授权');
$expect($beforeTodo === $pdo->query('SELECT * FROM zyt_tcm_diagnosis_todo')->fetchAll(PDO::FETCH_ASSOC), '迁移改变历史提醒');
$expect(count($beforeMenus) === 9, '模块菜单/权限数量错误');
$grants = $pdo->query('SELECT m.perms FROM zyt_system_role_menu rm JOIN zyt_system_menu m ON m.id=rm.menu_id WHERE rm.role_id=2')->fetchAll(PDO::FETCH_COLUMN);
$expect(in_array('tcm.assistantService/view', $grants, true), '医助缺少工作台权限');
$expect(in_array('tcm.assistantService/analyze', $grants, true), '医助缺少分析权限');
$expect(!in_array('tcm.assistantService/manageKnowledge', $grants, true), '医助被错误授予知识发布权限');
$expect(!in_array('tcm.assistantService/reviewQuality', $grants, true), '医助被错误授予主管质检权限');
$managerGrants = $pdo->query('SELECT m.perms FROM zyt_system_role_menu rm JOIN zyt_system_menu m ON m.id=rm.menu_id WHERE rm.role_id=3')->fetchAll(PDO::FETCH_COLUMN);
$expect(in_array('tcm.assistantService/reviewQuality', $managerGrants, true), '经理缺少质检权限');
$expect(in_array('tcm.assistantService/manageKnowledge', $managerGrants, true), '经理缺少知识发布权限');
$expect((int) $pdo->query('SELECT COUNT(*) FROM zyt_system_role_menu WHERE role_id IN(1,9)')->fetchColumn() === 0, '非目标角色被新增授权');
foreach (['record', 'job', 'review', 'action', 'knowledge'] as $table) {
$expect($pdo->query("SHOW CREATE TABLE zyt_assistant_service_{$table}")->fetch() !== false, '缺少模块表');
}
// 分析任务、动作唯一约束在数据库层生效。
$pdo->exec("INSERT INTO zyt_assistant_service_job(diagnosis_id,actor_id,type,scenario,dedupe_key,source_hash,scope_hash,payload_json,created_at,updated_at)
VALUES(1,2,'analysis','initial',REPEAT('a',64),REPEAT('b',64),REPEAT('c',64),'{}',1,1)");
$duplicateRejected = false;
try {
$pdo->exec("INSERT INTO zyt_assistant_service_job(diagnosis_id,actor_id,type,scenario,dedupe_key,source_hash,scope_hash,payload_json,created_at,updated_at)
VALUES(1,2,'analysis','initial',REPEAT('a',64),REPEAT('b',64),REPEAT('c',64),'{}',1,1)");
} catch (PDOException $e) {
$duplicateRejected = $e->getCode() === '23000';
}
$expect($duplicateRejected, '重复任务未被唯一约束拦截');
$pdo->exec("INSERT INTO zyt_assistant_service_action(diagnosis_id,job_id,action_key,todo_id,assignee_id,content,due_at,created_at,updated_at)
VALUES(1,1,'contact',99,2,'合成跟进',1,1,1)");
$duplicateRejected = false;
try {
$pdo->exec("INSERT INTO zyt_assistant_service_action(diagnosis_id,job_id,action_key,todo_id,assignee_id,content,due_at,created_at,updated_at)
VALUES(1,1,'contact',100,2,'重复跟进',1,1,1)");
} catch (PDOException $e) {
$duplicateRejected = $e->getCode() === '23000';
}
$expect($duplicateRejected, '重复采纳动作未被唯一约束拦截');
echo "AssistantServiceMysqlMigrationTest PASS ({$checks} checks)\n";
} finally {
// 名称由本脚本生成且已核对临时数据目录;只移除本次创建的数据库。
if (preg_match('/^assistant_service_test_[a-f0-9]{12}$/D', $database)) {
$pdo->exec("DROP DATABASE `{$database}`");
}
}