import { describe, test, expect } from 'vitest'; import { defineLexicon, validateLexicons, validateRecord } from './lexicon.js'; import { atprotoValidateLexicons, atprotoValidateRecord } from './test/atproto-oracle.js'; // Schema inputs import * as schemaInputs from './test/inputs/schema/index.js'; // Data inputs import * as dataInputs from './test/inputs/data/index.js'; // Format inputs import * as formatInputs from './test/inputs/format/index.js'; // E2E inputs import * as e2eInputs from './test/inputs/e2e/index.js'; // Collect all schema inputs const 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 inputs const allFormatInputs = [ ...formatInputs.datetimeFormatInputs, ...formatInputs.handleFormatInputs, ...formatInputs.didFormatInputs, ...formatInputs.uriFormatInputs, ...formatInputs.atUriFormatInputs, ...formatInputs.cidFormatInputs, ...formatInputs.tidFormatInputs, ...formatInputs.nsidFormatInputs, ...formatInputs.languageFormatInputs, ...formatInputs.recordKeyFormatInputs, ]; // Collect all e2e inputs const allE2eInputs = [ ...e2eInputs.bskyPostInputs, ...e2eInputs.bskyProfileInputs, ...e2eInputs.integrationInputs, ]; // Helper: determine expected result from test name // Tests with 'valid' in name should pass, 'invalid' should fail function 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 schemas describe('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 schemas describe('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); }); } }); });