From 55ddabb0ee316e0283c1ecdbe23d578d88199d2a Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Sun, 16 Aug 2026 13:00:21 +0200 Subject: [PATCH] test(api): lock program-registry contract for TRU-19 (#377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The api does NOT expose a /programs endpoint by design — program names ("FF Uvs", "10min", "SF", etc.) live on the wash bay hardware itself, not in the api. This test locks that architecture so any future /programs endpoint must be explicitly added and documented, and so the machine-types endpoint remains reachable as the api-side closest equivalent. Three assertions: 1. No /programs endpoint exists in any route file (or per-module route file) 2. /department/selfserve/machine-types is wired with the list permission and returns the success() envelope 3. /modules/self-serve/lane/relay/machine_program_picker/{status,set,enable} endpoints exist Refs TRU-19 --- .../SelfserveProgramRegistryContractTest.php | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 services/nginx/app/tests/Unit/Selfserve/SelfserveProgramRegistryContractTest.php diff --git a/services/nginx/app/tests/Unit/Selfserve/SelfserveProgramRegistryContractTest.php b/services/nginx/app/tests/Unit/Selfserve/SelfserveProgramRegistryContractTest.php new file mode 100644 index 00000000..73a771a5 --- /dev/null +++ b/services/nginx/app/tests/Unit/Selfserve/SelfserveProgramRegistryContractTest.php @@ -0,0 +1,97 @@ + glob($dir . '/*.php') ?: [], $moduleRoutesDirs)) + ); + + expect($routeFiles)->not->toBeEmpty('Expected to find at least one route file'); + + foreach ($routeFiles as $file) { + $source = file_get_contents($file); + expect($source)->not->toBeFalse("Failed to read route file: {$file}"); + + // Check for any route that would expose a /programs-style endpoint. + // The regex matches a $this->get(...) or $this->post(...) call with a + // /programs URI segment. We use word boundaries to avoid false + // positives on /modules/self-serve/lane/relay/machine_program_picker/*. + $matches = preg_match_all( + '/\$this->(?:get|post|put|delete|patch)\s*\(\s*[\'"]\/[^\'"]*\/programs[\'"]/', + $source, + $ignored + ); + expect($matches)->toBe( + 0, + "Found a /programs endpoint in {$file}. Program names live on the wash bay hardware — " + . 'the api should not expose them. If you intentionally want to add one, update this test ' + . 'and document the new endpoint in docs/.' + ); + } +}); + +it('exposes /department/selfserve/machine-types as the api-side program-adjacent endpoint', function (): void { + $machineTypesRoute = file_get_contents(app_path('routes/departmentSelfserveMachineTypesRoute.php')); + expect($machineTypesRoute)->not->toBeFalse(); + expect($machineTypesRoute)->toContain('/department/selfserve/machine-types'); + expect($machineTypesRoute)->toContain("'list_department_selfserve_machine_types'"); + + // The route must call $response->success(...) which is the standard + // "200 OK with JSON body" envelope. The contract is: a GET to this + // endpoint returns a JSON list of machine types. + expect($machineTypesRoute)->toContain('$response->success('); + + // The route must enforce the list_* permission so unauthorized callers + // cannot enumerate machine types. + expect($machineTypesRoute)->toContain("requirePermission('list_department_selfserve_machine_types')"); +}); + +it('exposes /modules/self-serve/lane/relay/machine_program_picker/* for program picker relay control', function (): void { + $selfServeRoute = file_get_contents(app_path('routes/moduleSelfServeRoute.php')); + expect($selfServeRoute)->not->toBeFalse(); + + // The program picker relay endpoints must exist. Pest's toContain does + // not accept a custom failure message, so we collect failures into a + // single assert at the end with a list of missing endpoints. + $expectedEndpoints = [ + '/modules/self-serve/lane/relay/machine_program_picker/status', + '/modules/self-serve/lane/relay/machine_program_picker/set', + '/modules/self-serve/lane/relay/machine_program_picker/enable', + ]; + + $missing = array_values(array_filter( + $expectedEndpoints, + static fn(string $endpoint): bool => !str_contains($selfServeRoute, $endpoint) + )); + + expect($missing)->toBe( + [], + 'Missing program picker relay endpoints: ' . implode(', ', $missing) + ); +});