Files
mostovik-backend/tools/openapi/check-runtime.mjs
Aleksandr Meshchryakov 18971d33ec
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
feat: complete published registry contracts and gated SRO ingestion
2026-09-14 17:01:02 +02:00

58 lines
2.6 KiB
JavaScript

import { readFile } from 'node:fs/promises';
import { isDeepStrictEqual } from 'node:util';
import {
ApiV2OrganizationSourceRecordsRead200Response,
ApiV2OrganizationSourceRecordsRead400Response,
ApiV2OrganizationSourceRecordsRead404Response,
V2OrganizationSourceRecordsList200Response,
V2OrganizationSourceRecordsList400Response,
} from './generated-zod/contract.ts';
const schemas = new Map([
['list:200', V2OrganizationSourceRecordsList200Response],
['list:400', V2OrganizationSourceRecordsList400Response],
['detail:200', ApiV2OrganizationSourceRecordsRead200Response],
['detail:400', ApiV2OrganizationSourceRecordsRead400Response],
['detail:404', ApiV2OrganizationSourceRecordsRead404Response],
]);
const requiredCases = new Set([
'mixed-list', 'empty-list', 'invalid-list', 'invalid-detail', 'missing-detail',
...['budget_ubpandnubp', 'fns_sme_support_recipients', 'sro_membership_check']
.flatMap(source => [`${source}-list`, `${source}-detail`]),
]);
const path = process.argv[2];
if (!path || process.argv.length !== 3) {
throw new Error('Usage: bun run contract-check /absolute/runtime-fixtures.json');
}
const fixtures = JSON.parse(await readFile(path, 'utf8'));
if (fixtures.version !== 1 || !Array.isArray(fixtures.cases)) {
throw new Error('Unsupported runtime fixture format');
}
const seen = new Set();
for (const fixture of fixtures.cases) {
if (seen.has(fixture.name)) throw new Error(`Duplicate case: ${fixture.name}`);
seen.add(fixture.name);
const schema = schemas.get(`${fixture.operation}:${fixture.status}`);
if (!schema) throw new Error(`No generated response schema: ${fixture.name}`);
const parsed = schema.safeParse(fixture.body);
if (!parsed.success) {
const issues = parsed.error.issues.slice(0, 5)
.map(issue => `${issue.path.join('.') || '<root>'}: ${issue.code}`);
throw new Error(`${fixture.name} violates generated Zod: ${issues.join('; ')}`);
}
// A successful parse must also retain every arbitrary upstream/raw JSON key.
if (!isDeepStrictEqual(parsed.data, fixture.body)) {
throw new Error(`${fixture.name}: generated Zod changed or stripped response data`);
}
}
for (const name of requiredCases) {
if (!seen.has(name)) throw new Error(`Missing required runtime case: ${name}`);
}
const empty = fixtures.cases.find(fixture => fixture.name === 'empty-list').body;
if (empty.data.length !== 0 || empty.meta.pagination.total_count !== 0
|| empty.meta.pagination.total_pages !== 0) {
throw new Error('Empty-list fixture must exercise empty arrays and zero counts');
}
console.log(`Generated Zod runtime contract: ${seen.size} API responses passed`);