| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- <?php
- /** Checks route coverage, authentication, references and examples. No database access. */
- require dirname(__DIR__) . '/vendor/autoload.php';
- require __DIR__ . '/support/MiniApiContract.php';
- \FastRoute\simpleDispatcher(function ($collector) {
- \Webman\Route::setCollector($collector);
- require dirname(__DIR__) . '/routes/mini_api.php';
- });
- $contract = new MiniApiContract();
- $spec = $contract->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";
|