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>
76 lines
2.6 KiB
PHP
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;
|
|
}
|
|
}
|