Files
api/services/nginx/app/tests/Unit/Selfserve/SelfserveProgramRegistryContractTest.php
T
Jeppe B 55ddabb0ee test(api): lock program-registry contract for TRU-19 (#377)
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
2026-08-16 13:00:21 +02:00

98 lines
4.4 KiB
PHP

<?php
/**
* Program-registry contract tests for TRU-19.
*
* Locks the architecture decision that the api does NOT expose a /programs
* endpoint that returns user-facing program names ("FF Uvs", "10min", "SF",
* etc.). Those names live on the wash bay hardware itself, not in the api.
*
* The api exposes MACHINE TYPES (e.g. "Mafa 5", "Washtec") via
* /department/selfserve/machine-types and PROGRAM PICKER relay control
* via /modules/self-serve/lane/relay/machine_program_picker/{status,set,enable,...}.
*
* The dashboard (pleno-vue) renders a numeric button registry (0-11) that
* maps to the physical programs on the wash bay. If a /programs endpoint
* ever appears in the api by accident, this test will fail and force the
* author to either (a) document the new endpoint and update this test, or
* (b) remove the spurious endpoint.
*
* Also locks the /department/selfserve/machine-types endpoint as a
* reachable, list-returning smoke target — this is the closest thing to
* a /programs endpoint that the api offers, and it should remain stable.
*/
it('does not expose a /programs endpoint (program names live on the wash bay)', function (): void {
$routesDir = app_path('routes');
$moduleRoutesDirs = glob(app_path('modules') . '/*/routes') ?: [];
$routeFiles = array_merge(
glob($routesDir . '/*.php') ?: [],
// Collect per-module route files
array_merge(...array_map(static fn($dir) => 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)
);
});