document; $checks = 0; function check(bool $ok, string $message): void { global $checks; $checks++; if (!$ok) { throw new RuntimeException($message); } } check($spec['openapi'] === '3.0.3', 'OpenAPI version'); check($spec['security'] === [['MiniBearer' => []]], 'default bearer security'); $walk = function (array $node) use (&$walk, $contract) { if (isset($node['$ref'])) { check((bool) $contract->resolve($node['$ref']), 'local reference resolves'); } foreach ($node as $child) { if (is_array($child)) { $walk($child); } } }; $walk($spec); $actual = []; foreach (\Webman\Route::getRoutes() as $route) { $path = $route->getPath(); if (!str_starts_with($path, '/api/mini/v1/')) { continue; } $callback = $route->getCallback(); if (is_array($callback) && $callback[1] === 'methodNotAllowed') { continue; } $path = substr($path, strlen('/api/mini/v1')); $variants = [$path]; if (preg_match('/\{(?:section|action):([^}]+)\}/', $path, $match)) { $variants = array_map(fn ($option) => str_replace($match[0], $option, $path), explode('|', $match[1])); } foreach ($variants as $variant) { $variant = preg_replace('/\{(\w+):[^}]+\}/', '{$1}', $variant); foreach ($route->getMethods() as $method) { $method = strtolower($method); $actual[] = $method . ' ' . $variant; $operation = $spec['paths'][$variant][$method] ?? null; check(is_array($operation), 'documented route: ' . $method . ' ' . $variant); $authenticated = in_array(\app\miniapi\middleware\Authenticate::class, $route->getMiddleware(), true); check((($operation['security'] ?? $spec['security']) !== []) === $authenticated, 'route security: ' . $variant); } } } $documented = []; $operationIds = []; foreach ($spec['paths'] as $path => $methods) { foreach ($methods as $method => $operation) { $documented[] = $method . ' ' . $path; check(!in_array($operation['operationId'], $operationIds, true), 'unique operationId'); $operationIds[] = $operation['operationId']; preg_match_all('/\{(\w+)\}/', $path, $matches); $parameters = []; foreach ($operation['parameters'] as $parameter) { if ($parameter['in'] === 'path') { check($parameter['required'] === true, 'path parameter required'); $parameters[] = $parameter['name']; } } sort($parameters); sort($matches[1]); check($parameters === $matches[1], 'path parameter coverage: ' . $path); $content = $operation['requestBody']['content']['application/json'] ?? []; if (isset($content['example'])) { $contract->validate($content['schema'], json_decode(json_encode($content['example'])), $path . ' example'); check(true, 'request example matches schema'); } } } sort($actual); sort($documented); check($actual === $documented, 'no missing or invented business routes'); check(isset($spec['paths']['/enterprises/{enterprise}/checkouts/{id}/pay']['post']['responses']['200']), 'balance payment success is documented'); check(isset($spec['paths']['/enterprises/{enterprise}/checkouts/{id}/pay']['post']['responses']['503']), 'unconfigured WeChat payment failure is documented'); check(!isset($spec['paths']['/agents/{agent}/withdrawals']['post']['responses']['200']), 'no fictional withdrawal success'); check(isset($spec['paths']['/photos/{id}']['get']['responses']['200']['content']['image/jpeg']), 'private photos documented as binary'); $contract->validate($contract->resolve('#/components/schemas/Error'), json_decode(json_encode($spec['components']['responses']['Error']['content']['application/json']['example']))); echo 'PASS: ' . $checks . ' contract checks; ' . count($actual) . " business operations documented.\n";