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
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?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)
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user