Files
clinicpro/tests/Policy/PolicyFieldCoverageTest.php
T
hamedandClaude Opus 5 d98a0396a4 test(policy): fail if the schema advertises a field nothing ever supplies
Task 09 left this as its starred risk and deferred it to task 10, which then
shipped without it. The failure mode is silent and expensive: an operator
writes a rule on a field no call site puts in the context, activates it, and it
never matches — no error, no log, and the clinic believes the rule is running.

The test is structural rather than behavioural on purpose. Walking every real
path for every field would need a test rig larger than the engine; asserting
that each advertised field is populated somewhere in src/ catches the case that
actually happens, which is a field added to the schema and nowhere else.

Also closes the last few rows that had gone stale:

- evaluateIsolated: PolicyResolver::evaluateOne() landed with the sandbox
- forbid before candidate generation: the plan builder already reads
  prohibitions before the availability engine is reached
- appointments.applied_policies and app:policy:seed-examples are declined with
  their reasons rather than left open — the trace lives on the price snapshot
  and a second column would be a second source of truth, and the template
  registry does the seeding job from inside the UI where the user can see the
  result before creating anything
- the reserve list keeps its page in the URL like every other panel list

Every checklist across the sixteen tasks now has zero pending rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:37:32 +03:30

76 lines
2.6 KiB
PHP

<?php
namespace App\Tests\Policy;
use App\Policy\Service\PolicySchema;
use PHPUnit\Framework\TestCase;
/**
* هر فیلدی که schema تبلیغ می‌کند، باید جایی در کد **واقعاً پر شود**.
*
* خطر مشخص است و بی‌صداست: قانونی که روی فیلدی شرط بگذارد که هیچ فراخوانی آن را
* نمی‌فرستد، هرگز مطابقت نمی‌کند و هیچ خطایی هم نمی‌دهد. اپراتور قانون را می‌سازد،
* فعالش می‌کند، و تا ابد فکر می‌کند دارد کار می‌کند.
*
* این تست عمداً ساختاری است نه رفتاری: پیمایشِ همهٔ مسیرهای واقعی برای هر فیلد،
* دستگاه تستی می‌خواست بزرگ‌تر از خودِ موتور.
*/
class PolicyFieldCoverageTest extends TestCase
{
public function testEveryAdvertisedFieldIsSuppliedSomewhereInTheCode(): void
{
$fields = [];
foreach (PolicySchema::FIELDS as $category => $list) {
foreach ($list as $field) {
$fields[$field][] = $category;
}
}
$sources = $this->sourceFiles(dirname(__DIR__, 2) . '/src');
$missing = [];
foreach ($fields as $field => $categories) {
$found = false;
foreach ($sources as $file => $code) {
// خودِ schema فقط نام را اعلام می‌کند؛ پر کردنش جای دیگری است.
if (str_ends_with($file, 'PolicySchema.php')) {
continue;
}
if (str_contains($code, sprintf("'%s'", $field)) && str_contains($code, '=>')) {
$found = true;
break;
}
}
if (!$found) {
$missing[$field] = $categories;
}
}
self::assertSame(
[],
$missing,
'این فیلدها در schema هستند ولی هیچ‌جا در context پر نمی‌شوند: ' .
json_encode($missing, JSON_UNESCAPED_UNICODE),
);
}
/** @return array<string, string> مسیر => محتوا */
private function sourceFiles(string $root): array
{
$files = [];
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root));
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$files[$file->getPathname()] = (string) file_get_contents($file->getPathname());
}
}
return $files;
}
}