diff --git a/specs/055-ops-ux-rollout/tasks.md b/specs/055-ops-ux-rollout/tasks.md index cfd2bfd..ec3d024 100644 --- a/specs/055-ops-ux-rollout/tasks.md +++ b/specs/055-ops-ux-rollout/tasks.md @@ -145,15 +145,15 @@ ## Phase 6: User Story 4 — Regression-safe by default (Priority: P4) ### Tests for User Story 4 -- [ ] T044 [P] [US4] Add catalog coverage guard test in `tests/Feature/OpsUx/OperationCatalogCoverageTest.php` -- [ ] T045 [P] [US4] Add canonical “View run” helper usage guard test in `tests/Feature/OpsUx/CanonicalViewRunLinksTest.php` -- [ ] T046 [P] [US4] Add unknown-type runtime label test in `tests/Feature/OpsUx/UnknownOperationTypeLabelTest.php` +- [x] T044 [P] [US4] Add catalog coverage guard test in `tests/Feature/OpsUx/OperationCatalogCoverageTest.php` +- [x] T045 [P] [US4] Add canonical “View run” helper usage guard test in `tests/Feature/OpsUx/CanonicalViewRunLinksTest.php` +- [x] T046 [P] [US4] Add unknown-type runtime label test in `tests/Feature/OpsUx/UnknownOperationTypeLabelTest.php` ### Implementation for User Story 4 - [x] T047 [US4] Ensure OperationRunResource type label rendering never shows raw type in `app/Filament/Resources/OperationRunResource.php` - [x] T048 [US4] Ensure Monitoring Operations page type labels never show raw type in `app/Filament/Pages/Monitoring/Operations.php` -- [ ] T049 [US4] Ensure any remaining “View run” links use canonical helper in `app/Support/OperationRunLinks.php` +- [x] T049 [US4] Ensure any remaining “View run” links use canonical helper in `app/Support/OperationRunLinks.php` **Checkpoint**: Drift prevention is enforced in CI. diff --git a/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php b/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php new file mode 100644 index 0000000..dbf2372 --- /dev/null +++ b/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php @@ -0,0 +1,31 @@ +getRealPath(); + if (! is_string($path)) { + continue; + } + + // OperationRunLinks is the canonical wrapper. + if (str_ends_with($path, '/Support/OperationRunLinks.php')) { + continue; + } + + $contents = File::get($path); + + if (preg_match("/\\bOperationRunResource::getUrl\(\\s*'view'/", $contents) === 1) { + $violations[] = $path; + } + } + + expect($violations)->toBeEmpty(); +})->group('ops-ux'); diff --git a/tests/Feature/OpsUx/OperationCatalogCoverageTest.php b/tests/Feature/OpsUx/OperationCatalogCoverageTest.php new file mode 100644 index 0000000..b6c1365 --- /dev/null +++ b/tests/Feature/OpsUx/OperationCatalogCoverageTest.php @@ -0,0 +1,61 @@ +getRealPath(); + if (! is_string($path)) { + continue; + } + + // Skip the catalog itself to avoid tautology. + if (str_ends_with($path, '/Support/OperationCatalog.php')) { + continue; + } + + $contents = File::get($path); + + // Capture common patterns where operation type strings are produced in code. + // Example: ensureRun(type: 'inventory.sync', ...) + if (preg_match_all("/(?:\btype\s*:\s*|\btype\b\s*=>\s*)'([a-z0-9_]+\.[a-z0-9_]+)'/i", $contents, $matches)) { + foreach ($matches[1] as $type) { + $discoveredTypes[] = $type; + } + } + + // Example: if ($run->type === 'inventory.sync') + if (preg_match_all("/\btype\s*(?:===|!==|==|!=)\s*'([a-z0-9_]+\.[a-z0-9_]+)'/i", $contents, $matches)) { + foreach ($matches[1] as $type) { + $discoveredTypes[] = $type; + } + } + + // Example: in_array($run->type, ['a.b', 'c.d'], true) + if (preg_match_all("/\bin_array\([^\)]*\[([^\]]+)\]/i", $contents, $matches)) { + foreach ($matches[1] as $list) { + if (preg_match_all("/'([a-z0-9_]+\.[a-z0-9_]+)'/i", $list, $inner)) { + foreach ($inner[1] as $type) { + $discoveredTypes[] = $type; + } + } + } + } + } + + $discoveredTypes = array_values(array_unique($discoveredTypes)); + + $unknownTypes = array_values(array_diff($discoveredTypes, $knownTypes)); + + expect($unknownTypes) + ->toBeEmpty(); +})->group('ops-ux'); diff --git a/tests/Feature/OpsUx/UnknownOperationTypeLabelTest.php b/tests/Feature/OpsUx/UnknownOperationTypeLabelTest.php new file mode 100644 index 0000000..ec65048 --- /dev/null +++ b/tests/Feature/OpsUx/UnknownOperationTypeLabelTest.php @@ -0,0 +1,12 @@ +toBe('Unknown operation'); + expect($label)->not->toContain('some.new_operation'); +})->group('ops-ux');