101 lines
6.2 KiB
PHP
101 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
namespace app\common\service\followupaudio {
|
|
final class FollowupAudioStore
|
|
{
|
|
public static function task(int $id): array
|
|
{
|
|
return ['id' => $id, 'diagnosis_id' => 91, 'patient_id' => 101];
|
|
}
|
|
}
|
|
}
|
|
namespace {
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
require dirname(__DIR__) . '/vendor/topthink/framework/src/helper.php';
|
|
// Explicit disposable local database only; no application initialization or .env loading.
|
|
$port = (int) getenv('FOLLOWUP_AUDIO_TEST_MYSQL_PORT');
|
|
if ($port <= 0 || getenv('FOLLOWUP_AUDIO_TEST_ALLOW_DISPOSABLE') !== '1') {
|
|
throw new \RuntimeException('Explicit local disposable MySQL port and FOLLOWUP_AUDIO_TEST_ALLOW_DISPOSABLE=1 required');
|
|
}
|
|
$password = (string) getenv('FOLLOWUP_AUDIO_TEST_MYSQL_PASSWORD');
|
|
$database = 'fa_access_' . bin2hex(random_bytes(6));
|
|
$control = new \PDO("mysql:host=127.0.0.1;port={$port};charset=utf8mb4", 'root', $password,
|
|
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
|
$control->exec("CREATE DATABASE `{$database}` CHARACTER SET utf8mb4");
|
|
new \think\App();
|
|
set_exception_handler(static function (\Throwable $e): void {
|
|
fwrite(STDERR, $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL); exit(1);
|
|
});
|
|
$manager = new \think\DbManager();
|
|
$manager->setConfig(['default' => 'mysql', 'connections' => ['mysql' => [
|
|
'type' => 'mysql', 'hostname' => '127.0.0.1', 'hostport' => $port,
|
|
'database' => $database, 'username' => 'root', 'password' => $password,
|
|
'charset' => 'utf8mb4', 'prefix' => 'faa_', 'debug' => false,
|
|
]]]);
|
|
\think\Container::getInstance()->instance('think\DbManager', $manager);
|
|
\think\Container::getInstance()->instance('config', new \think\Config());
|
|
$db = \think\facade\Db::class;
|
|
$tables = [
|
|
'admin' => 'id BIGINT PRIMARY KEY, name VARCHAR(30), root INT, disable INT, delete_time BIGINT NULL',
|
|
'admin_role' => 'admin_id BIGINT, role_id BIGINT, PRIMARY KEY(admin_id,role_id)',
|
|
'admin_dept' => 'admin_id BIGINT, dept_id BIGINT, PRIMARY KEY(admin_id,dept_id)',
|
|
'system_role_menu' => 'role_id BIGINT, menu_id BIGINT, PRIMARY KEY(role_id,menu_id)',
|
|
'system_menu' => 'id BIGINT PRIMARY KEY, perms VARCHAR(100), is_disable INT',
|
|
'tcm_diagnosis' => 'id BIGINT PRIMARY KEY, patient_id BIGINT, assistant_id BIGINT, status INT, delete_time BIGINT NULL',
|
|
];
|
|
foreach ($tables as $name => $fields) { $db::execute("DROP TABLE IF EXISTS faa_{$name}"); $db::execute("CREATE TABLE faa_{$name} ({$fields}) ENGINE=InnoDB"); }
|
|
$db::name('admin')->insertAll([
|
|
['id' => 7, 'name' => 'root fixture', 'root' => 1, 'disable' => 0],
|
|
['id' => 8, 'name' => 'assistant fixture', 'root' => 0, 'disable' => 0],
|
|
['id' => 9, 'name' => 'other fixture', 'root' => 0, 'disable' => 0],
|
|
]);
|
|
$db::name('admin_role')->insert(['admin_id' => 8, 'role_id' => 2]);
|
|
$db::name('system_menu')->insertAll([
|
|
['id' => 1, 'perms' => 'tcm.diagnosis/edit', 'is_disable' => 0],
|
|
['id' => 2, 'perms' => 'tcm.diagnosis/dailyRecord', 'is_disable' => 0],
|
|
]);
|
|
$db::name('system_role_menu')->insert(['role_id' => 2, 'menu_id' => 1]);
|
|
$db::name('tcm_diagnosis')->insertAll([
|
|
['id' => 91, 'patient_id' => 101, 'assistant_id' => 8, 'status' => 1],
|
|
['id' => 92, 'patient_id' => 102, 'assistant_id' => 9, 'status' => 1],
|
|
]);
|
|
$a = \app\common\service\followupaudio\FollowupAudioAccess::class;
|
|
$checks = 0;
|
|
$ok = function (bool $yes, string $label) use (&$checks): void { if (!$yes) { throw new \RuntimeException($label); } $checks++; };
|
|
$deny = function (callable $f, string $label) use ($ok): void {
|
|
try { $f(); } catch (\DomainException $e) { $ok(true, $label); return; } $ok(false, $label);
|
|
};
|
|
$other = new \PDO("mysql:host=127.0.0.1;port={$port};dbname={$database};charset=utf8mb4", 'root', $password, [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
|
try {
|
|
$ok((int) $a::diagnosis(91, 8, ['root' => 1])['id'] === 91, 'own patient');
|
|
$deny(fn () => $a::diagnosis(92, 8, ['root' => 1]), 'forged cached root must fail');
|
|
$ok(!$a::canDaily(8, ['root' => 1]), 'missing daily permission');
|
|
$deny(fn () => $a::diagnosis(91, 8, [], true), 'daily write denied');
|
|
$db::name('system_role_menu')->insert(['role_id' => 2, 'menu_id' => 2]);
|
|
$ok($a::canDaily(8, []), 'fresh daily grant');
|
|
// A consistent read exists before another connection commits permission revocation.
|
|
$db::startTrans(); $db::name('system_role_menu')->select();
|
|
$other->exec('DELETE FROM faa_system_role_menu WHERE role_id=2 AND menu_id=2');
|
|
$ok(!$a::canDaily(8, []), 'revocation must beat repeatable-read snapshot'); $db::rollback();
|
|
$db::startTrans(); $db::name('tcm_diagnosis')->where('id', 91)->find();
|
|
$other->exec('UPDATE faa_tcm_diagnosis SET assistant_id=9 WHERE id=91');
|
|
$deny(fn () => $a::diagnosis(91, 8, []), 'reassignment must beat repeatable-read snapshot'); $db::rollback();
|
|
$other->exec('UPDATE faa_tcm_diagnosis SET assistant_id=8 WHERE id=91');
|
|
$db::startTrans(); $db::name('admin')->where('id', 8)->find();
|
|
$other->exec('UPDATE faa_admin SET disable=1 WHERE id=8');
|
|
$deny(fn () => $a::diagnosis(91, 8, []), 'disabled actor must beat repeatable-read snapshot'); $db::rollback();
|
|
$other->exec('UPDATE faa_admin SET disable=0 WHERE id=8');
|
|
$ok((int) $a::task(1, 8, [])['id'] === 1, 'task binding before rebind');
|
|
$other->exec('UPDATE faa_tcm_diagnosis SET patient_id=999 WHERE id=91');
|
|
$deny(fn () => $a::task(1, 8, []), 'historical recording must not follow patient rebind');
|
|
$other->exec('UPDATE faa_tcm_diagnosis SET status=0 WHERE id=91');
|
|
$deny(fn () => $a::diagnosis(91, 7, ['root' => 1]), 'inactive diagnosis even root');
|
|
echo "Followup audio real authorization: {$checks} checks passed (isolated MySQL; current-read revocation/reassignment)\n";
|
|
} finally {
|
|
try { $db::rollback(); } catch (\Throwable $e) {}
|
|
$manager->connect()->close();
|
|
$control->exec("DROP DATABASE IF EXISTS `{$database}`");
|
|
}
|
|
}
|