Something went wrong. Try again.
A plain JavaScript validator for AT Protocol lexicon schemas
Something went wrong. Try again.
lexicon.js lexicon.test.js
7.6 kB · 222 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223import { describe, test, expect } from 'vitest';import { defineLexicon, validateLexicons, validateRecord } from './lexicon.js';import { atprotoValidateLexicons, atprotoValidateRecord } from './test/atproto-oracle.js';
// Schema inputsimport * as schemaInputs from './test/inputs/schema/index.js';
// Data inputsimport * as dataInputs from './test/inputs/data/index.js';
// Format inputsimport * as formatInputs from './test/inputs/format/index.js';
// E2E inputsimport * as e2eInputs from './test/inputs/e2e/index.js';
// Collect all schema inputsconst allSchemaInputs = [ ...schemaInputs.stringSchemaInputs, ...schemaInputs.integerSchemaInputs, ...schemaInputs.booleanSchemaInputs, ...schemaInputs.objectSchemaInputs, ...schemaInputs.arraySchemaInputs, ...schemaInputs.unionSchemaInputs, ...schemaInputs.refSchemaInputs, ...schemaInputs.blobSchemaInputs, ...schemaInputs.bytesSchemaInputs, ...schemaInputs.tokenSchemaInputs, ...schemaInputs.unknownSchemaInputs, ...schemaInputs.recordSchemaInputs, ...schemaInputs.paramsSchemaInputs, ...schemaInputs.querySchemaInputs, ...schemaInputs.procedureSchemaInputs, ...schemaInputs.subscriptionSchemaInputs,];
// Collect all data inputs (record types only - validateRecord doesn't handle query/procedure/params)const allDataInputs = [ ...dataInputs.stringDataInputs, ...dataInputs.integerDataInputs, ...dataInputs.objectDataInputs, ...dataInputs.arrayDataInputs, ...dataInputs.blobDataInputs, ...dataInputs.bytesDataInputs, ...dataInputs.tokenDataInputs, ...dataInputs.unknownDataInputs, ...dataInputs.unionDataInputs, ...dataInputs.cidLinkDataInputs,];
// Collect all format inputsconst allFormatInputs = [ ...formatInputs.datetimeFormatInputs, ...formatInputs.handleFormatInputs, ...formatInputs.didFormatInputs, ...formatInputs.uriFormatInputs, ...formatInputs.atUriFormatInputs, ...formatInputs.cidFormatInputs, ...formatInputs.tidFormatInputs, ...formatInputs.nsidFormatInputs, ...formatInputs.languageFormatInputs, ...formatInputs.recordKeyFormatInputs,];
// Collect all e2e inputsconst allE2eInputs = [ ...e2eInputs.bskyPostInputs, ...e2eInputs.bskyProfileInputs, ...e2eInputs.integrationInputs,];
// Helper: determine expected result from test name// Tests with 'valid' in name should pass, 'invalid' should failfunction expectedValid(name) { if (name.includes('-valid-')) return true; if (name.includes('-invalid-')) return false; throw new Error(`Test name must contain '-valid-' or '-invalid-': ${name}`);}
// ==========================================================================// SPEC-BASED TESTS// ==========================================================================
describe('schema validation', () => { for (const input of allSchemaInputs) { test(input.name, () => { const lexicons = input.additionalLexicons ? [input.lexicon, ...input.additionalLexicons] : [input.lexicon]; const result = validateLexicons(lexicons); const isValid = result === null; expect(isValid).toBe(expectedValid(input.name)); }); }});
describe('data validation', () => { for (const input of allDataInputs) { test(input.name, () => { const result = validateRecord(input.lexicons, input.collection, input.record); const isValid = result === null; expect(isValid).toBe(expectedValid(input.name)); }); }});
describe('format validation', () => { for (const input of allFormatInputs) { test(input.name, () => { const result = validateRecord(input.lexicons, input.collection, input.record); const isValid = result === null; expect(isValid).toBe(expectedValid(input.name)); }); }});
describe('e2e validation', () => { for (const input of allE2eInputs) { test(input.name, () => { const result = validateRecord(input.lexicons, input.collection, input.record); const isValid = result === null; expect(isValid).toBe(expectedValid(input.name)); }); }});
// Ref data validation - validates data against resolved ref schemasdescribe('ref data validation', () => { for (const input of dataInputs.refDataInputs) { test(input.name, () => { const result = validateRecord(input.lexicons, input.collection, input.record); const isValid = result === null; expect(isValid).toBe(expectedValid(input.name)); }); }});
// Union ref data validation - validates union data against resolved ref schemasdescribe('union ref data validation', () => { for (const input of dataInputs.unionRefDataInputs) { test(input.name, () => { const result = validateRecord(input.lexicons, input.collection, input.record); const isValid = result === null; expect(isValid).toBe(expectedValid(input.name)); }); }});
// ==========================================================================// @ATPROTO/LEXICON COMPARISON (informational - differences are expected)// See docs/oracle-differences.md for details on behavioral differences.// ==========================================================================
describe.skip('@atproto/lexicon comparison', () => { describe('schema validation', () => { for (const input of allSchemaInputs) { test(input.name, () => { const lexicons = input.additionalLexicons ? [input.lexicon, ...input.additionalLexicons] : [input.lexicon]; const atprotoResult = atprotoValidateLexicons(lexicons); const ourResult = validateLexicons(lexicons);
const atprotoValid = atprotoResult === null; const ourValid = ourResult === null; expect(ourValid).toBe(atprotoValid); }); } });
describe('data validation', () => { for (const input of allDataInputs) { test(input.name, () => { const atprotoResult = atprotoValidateRecord(input.lexicons, input.collection, input.record); const ourResult = validateRecord(input.lexicons, input.collection, input.record);
const atprotoValid = atprotoResult === null; const ourValid = ourResult === null; expect(ourValid).toBe(atprotoValid); }); } });
describe('format validation', () => { for (const input of allFormatInputs) { test(input.name, () => { const atprotoResult = atprotoValidateRecord(input.lexicons, input.collection, input.record); const ourResult = validateRecord(input.lexicons, input.collection, input.record);
const atprotoValid = atprotoResult === null; const ourValid = ourResult === null; expect(ourValid).toBe(atprotoValid); }); } });
describe('e2e validation', () => { for (const input of allE2eInputs) { test(input.name, () => { const atprotoResult = atprotoValidateRecord(input.lexicons, input.collection, input.record); const ourResult = validateRecord(input.lexicons, input.collection, input.record);
const atprotoValid = atprotoResult === null; const ourValid = ourResult === null; expect(ourValid).toBe(atprotoValid); }); } });
describe('ref data validation', () => { for (const input of dataInputs.refDataInputs) { test(input.name, () => { const atprotoResult = atprotoValidateRecord(input.lexicons, input.collection, input.record); const ourResult = validateRecord(input.lexicons, input.collection, input.record);
const atprotoValid = atprotoResult === null; const ourValid = ourResult === null; expect(ourValid).toBe(atprotoValid); }); } });});