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
58 lines
2.6 KiB
JavaScript
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`);
|