ts / test / spec-check.test.ts
494 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
 * 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);
});

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/ts.git