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/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.'); });