- Introduced a new `/modules/self-serve/lane/wash/my-active-wash` endpoint to retrieve the authenticated customer's active self-serve wash. - Implemented authentication and permission checks for secure access. - Added detailed response handling for various scenarios, including 401, 403, and 404 statuses. - Extended API documentation and OpenAPI spec to support the new endpoint. - Updated unit and API tests to validate endpoint functionality and route wiring.
147 lines
7.5 KiB
PHP
147 lines
7.5 KiB
PHP
<?php
|
|
|
|
function selfserve_openapi_content_or_skip(): string
|
|
{
|
|
$candidates = [
|
|
dirname(__DIR__, 6) . DIRECTORY_SEPARATOR . 'openapi.yaml',
|
|
dirname(__DIR__, 5) . DIRECTORY_SEPARATOR . 'openapi.yaml',
|
|
dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'openapi.yaml',
|
|
dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'openapi.yaml',
|
|
dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'openapi.yaml',
|
|
];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
if (!is_file($candidate)) {
|
|
continue;
|
|
}
|
|
|
|
$content = file_get_contents($candidate);
|
|
if ($content !== false) {
|
|
return $content;
|
|
}
|
|
}
|
|
|
|
test()->markTestSkipped('openapi.yaml is not available in this runtime environment.');
|
|
}
|
|
|
|
function selfserve_openapi_path_block_or_fail(string $content, string $path): string
|
|
{
|
|
$path_marker = ' ' . $path . ':';
|
|
$start = strpos($content, $path_marker);
|
|
if ($start === false) {
|
|
throw new RuntimeException("OpenAPI path block not found: {$path}");
|
|
}
|
|
|
|
$rest = substr($content, $start + strlen($path_marker));
|
|
$next_path = strpos($rest, "\n /");
|
|
if ($next_path === false) {
|
|
return substr($content, $start);
|
|
}
|
|
|
|
return substr($content, $start, strlen($path_marker) + $next_path);
|
|
}
|
|
|
|
it('documents self-serve machine type, eligibility, summary, and webhook endpoints', function (): void {
|
|
$content = selfserve_openapi_content_or_skip();
|
|
$allowedPathBlock = selfserve_openapi_path_block_or_fail($content, '/department/selfserve/vehicle/allowed');
|
|
$summaryPathBlock = selfserve_openapi_path_block_or_fail($content, '/department/selfserve/washes/summary');
|
|
|
|
expect($content)->toContain('/department/selfserve/machine-types:');
|
|
expect($content)->toContain('/department/selfserve/vehicle/allowed:');
|
|
expect($content)->toContain('/department/selfserve/washes/summary:');
|
|
expect($content)->toContain('/relay/button/press/post:');
|
|
expect($content)->toContain('/relay/machine/on/post:');
|
|
expect($content)->toContain('/modules/self-serve/lane/wash/in-progress:');
|
|
expect($content)->toContain('/modules/self-serve/lane/wash/my-active-wash:');
|
|
expect($content)->toContain('/modules/self-serve/lane/relay/machine/status:');
|
|
expect($content)->toContain('/modules/self-serve/lane/relay/machine/set:');
|
|
expect($content)->toContain('/modules/self-serve/lane/gate/open:');
|
|
expect($content)->toContain('/modules/self-serve/lane/relay/machine_program_picker/status:');
|
|
expect($content)->toContain('/modules/self-serve/lane/relay/machine_program_picker/set:');
|
|
expect($content)->toContain('/modules/self-serve/lane/relay/machine_cleaner/status:');
|
|
expect($content)->toContain('/modules/self-serve/lane/relay/machine_cleaner/set:');
|
|
expect($allowedPathBlock)->toContain('name: vehicle_type_id');
|
|
expect($allowedPathBlock)->toContain('name: vehicle_type');
|
|
expect($allowedPathBlock)->toContain('may evaluate any registration plate');
|
|
expect($allowedPathBlock)->toContain('Persisted self-serve answers are only applied');
|
|
expect($summaryPathBlock)->toContain('name: vehicle_type_id');
|
|
expect($summaryPathBlock)->toContain('name: vehicle_type');
|
|
});
|
|
|
|
it('documents the all-in-one self-serve studio replacement API', function (): void {
|
|
$content = selfserve_openapi_content_or_skip();
|
|
|
|
expect($content)->toContain('/department/selfserve/studio/graph:');
|
|
expect($content)->toContain('/department/selfserve/studio/layout:');
|
|
expect($content)->toContain('/department/selfserve/studio/validate:');
|
|
expect($content)->toContain('/department/selfserve/studio/simulate:');
|
|
expect($content)->toContain('/department/selfserve/studio/path-outcomes:');
|
|
expect($content)->toContain('/department/selfserve/studio/path-outcomes/stream:');
|
|
expect($content)->toContain('/department/selfserve/studio/path-confirmations:');
|
|
expect($content)->toContain('/department/selfserve/studio/publish:');
|
|
expect($content)->toContain('/department/selfserve/studio/rollback:');
|
|
expect($content)->toContain('/department/selfserve/studio/gateway-action:');
|
|
expect($content)->toContain('SelfserveStudioGraph:');
|
|
expect($content)->toContain('SelfserveStudioGraphSaveRequest:');
|
|
expect($content)->toContain('SelfserveStudioLayout:');
|
|
expect($content)->toContain('SelfserveStudioPathOutcomesRequest:');
|
|
expect($content)->toContain('SelfserveStudioPathOutcomesResponse:');
|
|
expect($content)->toContain('SelfserveStudioPathResult:');
|
|
expect($content)->toContain('SelfserveStudioPathProgress:');
|
|
expect($content)->toContain('SelfserveStudioPathConfirmationRequest:');
|
|
expect($content)->toContain('SelfserveStudioPathConfirmation:');
|
|
expect($content)->toContain('projectSelfserveStudioPathOutcomes');
|
|
expect($content)->toContain('streamSelfserveStudioPathOutcomes');
|
|
expect($content)->toContain('confirmSelfserveStudioPath');
|
|
expect($content)->toContain('runSelfserveStudioGatewayAction');
|
|
});
|
|
|
|
it('defines reusable self-serve wash and machine type schemas', function (): void {
|
|
$content = selfserve_openapi_content_or_skip();
|
|
|
|
expect($content)->toContain('SelfserveMachineType:');
|
|
expect($content)->toContain('SelfserveVehicleAllowedResponse:');
|
|
expect($content)->toContain('SelfserveWashSummary:');
|
|
expect($content)->toContain('MachineButtonPressWebhookResponse:');
|
|
expect($content)->toContain('DepartmentSelfserveVehicleConditionMutationResponse:');
|
|
expect($content)->toContain('DepartmentLane:');
|
|
expect($content)->toContain('selfserve_enabled:');
|
|
expect($content)->toContain('blocked_reason:');
|
|
expect($content)->toContain('machine_type_id:');
|
|
expect($content)->toContain('SelfServeLaneMachineRelayStatus:');
|
|
expect($content)->toContain(' wash_started_at:');
|
|
});
|
|
|
|
it('documents in-progress self-serve wash start and machine relay fields', function (): void {
|
|
$content = selfserve_openapi_content_or_skip();
|
|
$inProgressPathBlock = selfserve_openapi_path_block_or_fail($content, '/modules/self-serve/lane/wash/in-progress');
|
|
|
|
expect($inProgressPathBlock)->toContain('included_minutes:');
|
|
expect($inProgressPathBlock)->toContain('machine_relay_enabled:');
|
|
expect($inProgressPathBlock)->toContain('machine_relay_enabled_at:');
|
|
expect($inProgressPathBlock)->toContain('machine_start_triggered_at:');
|
|
expect($inProgressPathBlock)->toContain('wash_started_at:');
|
|
});
|
|
|
|
it('documents self-serve machine wash included minutes in config schemas', function (): void {
|
|
$content = selfserve_openapi_content_or_skip();
|
|
|
|
expect($content)->toContain('SelfServeConfigEntry:');
|
|
expect($content)->toContain('machine_wash_minutes_included');
|
|
expect($content)->toContain('SelfServeConfig:');
|
|
});
|
|
|
|
it('documents property gate lane commands and sanitized gate failure responses', function (): void {
|
|
$content = selfserve_openapi_content_or_skip();
|
|
$commandPathBlock = selfserve_openapi_path_block_or_fail($content, '/modules/self-serve/lane/command');
|
|
$gateOpenPathBlock = selfserve_openapi_path_block_or_fail($content, '/modules/self-serve/lane/gate/open');
|
|
|
|
expect($commandPathBlock)->toContain('OPEN_PROPERTY_ACCESS_GATE');
|
|
expect($commandPathBlock)->toContain('OPEN_PROPERTY_EXIT_GATE');
|
|
expect($commandPathBlock)->toContain('Command execution failed');
|
|
expect($commandPathBlock)->toContain('Failed to execute command: Failed to open property access gate.');
|
|
|
|
expect($gateOpenPathBlock)->toContain('Gate open failed');
|
|
expect($gateOpenPathBlock)->toContain('Failed to open entrance gate.');
|
|
});
|