/**
* Offline tests for the conformance validator itself. The live conformance
* sweep only proves the API agrees with the spec if this detector actually
* detects — a checker that always returns [] would pass that sweep silently
* forever, so each class of drift it is meant to catch is pinned here.
*/
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { check, responseSchema, type Schema } from '../src/spec-check.js';
const pinRow: Schema = {
type: 'object',
additionalProperties: false,
required: ['id', 'cid', 'state'],
properties: {
id: { type: 'string' },
cid: { oneOf: [{ type: 'string' }, { type: 'null' }] },
state: { type: 'string', enum: ['queued', 'pinned', 'failed'] },
size_bytes: { type: 'integer' },
},
};
test('conforming responses produce no problems', () => {
assert.deepEqual(check(pinRow, { id: 'p1', cid: 'bafy1', state: 'pinned', size_bytes: 56 }), []);
assert.deepEqual(check(pinRow, { id: 'p1', cid: null, state: 'queued' }), [], 'optional absent, oneOf null branch');
assert.deepEqual(check(undefined, { anything: true }), [], 'an unknown schema passes rather than guessing');
assert.deepEqual(check({}, { anything: true }), []);
});
test('detects every class of drift', () => {
assert.match(check(pinRow, { id: 'p1', cid: 'bafy1' })[0]!, /\.state is required by the spec but absent/);
assert.match(
check(pinRow, { id: 'p1', cid: 'b', state: 'pinned', cid_v1: 'bafy2' })[0]!,
/\.cid_v1 is returned by the API but absent from the spec/,
);
assert.match(check(pinRow, { id: 'p1', cid: 'b', state: 'deleting' })[0]!, /not in the declared enum/);
assert.match(
check(pinRow, { id: 'p1', cid: 'b', state: 'pinned', size_bytes: '56' })[0]!,
/declared integer but the API returned string/,
);
assert.match(check(pinRow, { id: 'p1', cid: 42, state: 'pinned' })[0]!, /matches none of oneOf/);
assert.match(check({ const: 'embedding' }, 'llm-chat')[0]!, /not in the declared enum/);
assert.deepEqual(check({ type: ['string', 'null'] }, null), [], 'a type array accepts any listed type');
assert.match(check({ type: ['string', 'null'] }, 5)[0]!, /declared string\|null but the API returned integer/);
});
test('walks nested bodies and every array row', () => {
const page: Schema = { type: 'object', properties: { pins: { type: 'array', items: pinRow } } };
assert.deepEqual(
check(page, {
pins: [
{ id: 'a', cid: 'x', state: 'pinned' },
{ id: 'b', cid: 'y' },
],
}),
['.pins[1].state is required by the spec but absent from the response'],
);
// change-rdns first appeared at row 7 of the activity feed; sampling the
// head of the list is exactly how that stayed hidden
const rows = [...Array.from({ length: 20 }, () => ({ id: 'a', cid: 'x', state: 'pinned' })), { id: 'z', cid: 'x', state: 'not-a-state' }];
const problems = check(page, { pins: rows });
assert.equal(problems.length, 1);
assert.match(problems[0]!, /\.pins\[20\]\.state/);
});
test('responseSchema reads the spec by path, method and status', () => {
const spec = {
paths: { '/x': { get: { responses: { '200': { content: { 'application/json': { schema: pinRow } } } } } } },
};
assert.equal(responseSchema(spec, 'get', '/x', 200), pinRow);
assert.equal(responseSchema(spec, 'get', '/x', 404), undefined);
assert.equal(responseSchema(spec, 'post', '/x', 200), undefined);
assert.equal(responseSchema({}, 'get', '/y', 200), undefined);
});