81 lines
6.2 KiB
PHP
81 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
require __DIR__ . '/fixtures/followup_audio/database.php';
|
|
if ($source = getenv('FOLLOWUP_EXERCISE_APPLY_SOURCE')) { require $source; }
|
|
|
|
use app\common\service\followupaudio\FollowupAudioApply as Apply;
|
|
use app\common\service\followupaudio\FollowupAudioStore as Store;
|
|
use app\adminapi\logic\tcm\ExerciseRecordLogic;
|
|
use app\common\model\tcm\ExerciseRecord;
|
|
use think\facade\Db;
|
|
|
|
$f = followupAudioTestDatabase();
|
|
$checks = 0;
|
|
$expect = static function (bool $ok, string $why) use (&$checks): void { if (!$ok) { throw new RuntimeException($why); } $checks++; };
|
|
$make = static function (array $values, string $date, ?int $target = null) use ($f, $expect): array {
|
|
$data = str_repeat(pack('v', 1000), 16000) . random_bytes(2);
|
|
$path = $f['private'] . '/exercise.wav';
|
|
file_put_contents($path, 'RIFF' . pack('V', 36 + strlen($data)) . 'WAVEfmt ' . pack('VvvVVvv', 16, 1, 1, 16000, 32000, 2, 16) . 'data' . pack('V', strlen($data)) . $data);
|
|
chmod($path, 0600);
|
|
$upload = followupAudioTestUpload($f, $path, 'synthetic-exercise.wav');
|
|
$task = Store::create($upload, '2026-09-29 20:00:00', 'qwen', 1, $f['actor']);
|
|
$claim = Store::claim(); $expect($claim['id'] === $task['id'], 'isolated task claimed');
|
|
$text = $date . '散步,运动信息以人工核对的合成测试输入为准。';
|
|
$expect(Store::complete($task['id'], $claim['lease_token'], [
|
|
'summary' => '合成运动测试', 'transcript' => $text, 'uncertainties' => [], 'items' => [[
|
|
'kind' => 'exercise', 'values' => $values, 'record_date' => $date, 'record_time' => null,
|
|
'date_text' => $date, 'time_text' => '', 'time_period' => null, 'time_estimated' => true,
|
|
'evidence' => [['text' => $text]], 'needs_review' => true,
|
|
]],
|
|
]), 'real encrypted extraction/review complete');
|
|
$detail = Store::detail($task['id']);
|
|
$expect(count($detail['items']) === 1, 'one exercise item normalized');
|
|
$items = $detail['items']; $items[0]['target_id'] = $target;
|
|
$items[0]['selected'] = false; $items[0]['needs_review'] = true;
|
|
$draft = Store::saveDraft($task['id'], $detail['version'], $items, 1);
|
|
$items = $draft['items']; $items[0]['selected'] = true; $items[0]['needs_review'] = false;
|
|
$draft = Store::saveDraft($task['id'], $draft['version'], $items, 1);
|
|
$result = Apply::apply($task['id'], $draft['version'], $draft['items'], 1, $f['actor']);
|
|
$expect($result['status'] === 'applied', 'real Apply transaction completed');
|
|
$id = $result['applied_items'][0]['record_id'];
|
|
return ExerciseRecordLogic::detail(['id' => $id]);
|
|
};
|
|
try {
|
|
// Mirror verified deployed nullable numeric columns AND their fact-fabricating defaults.
|
|
$f['pdo']->exec('ALTER TABLE zyt_patient_exercise_record MODIFY duration INT NULL DEFAULT 0, MODIFY intensity TINYINT NULL DEFAULT 1, ADD images TEXT NULL');
|
|
$row = $make(['exercise_type' => '散步'], '2026-09-29');
|
|
echo 'EXERCISE_INSERT_RELOAD ' . json_encode(['duration' => $row['duration'], 'intensity' => $row['intensity']], JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
|
$expect($row['duration'] === null && $row['intensity'] === null, 'missing duration/intensity must reload NULL, never database defaults 0/1');
|
|
$expect(ExerciseRecord::find($row['id'])->append(['intensity_text'])->toArray()['intensity_text'] === '未知', 'real read label is unknown, not low intensity');
|
|
$expect(ExerciseRecordLogic::edit(['id' => $row['id'], 'record_date' => '2026-09-29', 'note' => '仅更改备注', 'duration' => null, 'intensity' => null]), 'DailyMatrix null edit accepted');
|
|
$reload = ExerciseRecordLogic::detail(['id' => $row['id']]);
|
|
$expect($reload['duration'] === null && $reload['intensity'] === null && $reload['note'] === '仅更改备注', 'manual edit/reload retains nulls');
|
|
// Exercise editor uses the public controller and app Request with the global trim filter.
|
|
$request = (new app\Request())->withServer(['REQUEST_METHOD' => 'POST'])->withPost([
|
|
'id' => $row['id'], 'record_date' => '2026-09-29', 'duration' => null, 'intensity' => null,
|
|
'note' => ' 只改备注 ',
|
|
]);
|
|
$controller = (new ReflectionClass(app\adminapi\controller\tcm\ExerciseRecordController::class))->newInstanceWithoutConstructor();
|
|
$property = new ReflectionProperty($controller, 'request');
|
|
$property->setAccessible(true); $property->setValue($controller, $request);
|
|
$response = $controller->edit()->getData();
|
|
echo 'EXERCISE_CONTROLLER_NULL_EDIT code=' . $response['code'] . PHP_EOL;
|
|
$expect($response['code'] === 1, 'real editor controller must accept JSON null through global trim filter');
|
|
$reload = ExerciseRecordLogic::detail(['id' => $row['id']]);
|
|
$expect($reload['duration'] === null && $reload['intensity'] === null, 'controller edit/reload preserves null scalars');
|
|
$expect($reload['note'] === '只改备注', 'existing text trimming remains unchanged');
|
|
$known = $make(['exercise_type' => '散步', 'duration' => 30, 'intensity' => 2], '2026-09-28');
|
|
$expect((int) $known['duration'] === 30 && (int) $known['intensity'] === 2, 'explicit values retained');
|
|
$updated = $make(['note' => '只补充备注'], '2026-09-28', (int) $known['id']);
|
|
$expect((int) $updated['duration'] === 30 && (int) $updated['intensity'] === 2, 'audio update omitted fields retain known values');
|
|
$durationOnly = $make(['exercise_type' => '散步', 'duration' => 20], '2026-09-27');
|
|
$expect((int) $durationOnly['duration'] === 20 && $durationOnly['intensity'] === null, 'duration only leaves intensity unknown');
|
|
$intensityOnly = $make(['exercise_type' => '散步', 'intensity' => 1], '2026-09-26');
|
|
$expect($intensityOnly['duration'] === null && (int) $intensityOnly['intensity'] === 1, 'intensity only leaves duration unknown');
|
|
$expect(ExerciseRecordLogic::edit(['id' => $row['id'], 'duration' => 0]), 'manual historical zero accepted');
|
|
$zero = ExerciseRecordLogic::detail(['id' => $row['id']]);
|
|
$expect((int) $zero['duration'] === 0 && $zero['duration'] !== null, 'manual zero differs from missing; audio validator unchanged');
|
|
echo 'FOLLOWUP_AUDIO_MISSING_EXERCISE assertions=' . $checks . ' PASS real_mysql=1 actual_apply_reload=1 manual_edit_reload=1 update_omission_preserved=1' . PHP_EOL;
|
|
} finally { followupAudioTestDatabaseCleanup($f); }
|