import { describe, expect, it, vi } from 'vitest'; import { validateFinalIngestArtifacts, validateProvenanceRawPaths } from '../../../src/context/ingest/artifact-gates.js'; function wikiServiceWithPages( pages: Record, ) { return { listPageKeys: vi.fn().mockResolvedValue(Object.keys(pages)), readPage: vi.fn().mockImplementation((_scope: string, _scopeId: string | null, pageKey: string) => { const page = pages[pageKey]; if (!page) { return Promise.resolve(null); } return Promise.resolve({ pageKey, frontmatter: { summary: pageKey, usage_mode: 'auto', refs: page.refs, sl_refs: page.slRefs, }, content: page.content ?? '', }); }), }; } describe('artifact gates', () => { it('fails the final tree when wiki body references a stale semantic-layer measure', async () => { const wikiService = wikiServiceWithPages({ 'account-segments': { slRefs: ['mart_account_segments'], content: 'ARR is `mart_account_segments.total_contract_arr_cents`.', }, }); const semanticLayerService = { loadAllSources: vi.fn().mockResolvedValue({ sources: [ { name: 'mart_account_segments', grain: ['account_id'], columns: [{ name: 'account_id', type: 'string' }], joins: [], measures: [{ name: 'total_contract_arr', expr: 'sum(contract_arr)' }], table: 'analytics.mart_account_segments', }, ], loadErrors: [], }), }; await expect( validateFinalIngestArtifacts({ connectionIds: ['warehouse'], changedWikiPageKeys: ['account-segments'], touchedSlSources: [{ connectionId: 'warehouse', sourceName: 'mart_account_segments' }], wikiService: wikiService as never, semanticLayerService: semanticLayerService as never, validateTouchedSources: async () => ({ invalidSources: [], validSources: ['mart_account_segments'] }), tableExists: async () => true, }), ).rejects.toThrow(/unknown semantic-layer entity mart_account_segments\.total_contract_arr_cents/); }); it('fails before provenance insertion when a raw path cannot be tied to the current snapshot or eviction set', () => { expect(() => validateProvenanceRawPaths({ rows: [{ rawPath: 'cards/missing.json' }], currentRawPaths: new Set(['cards/present.json']), deletedRawPaths: new Set(['cards/deleted.json']), }), ).toThrow(/provenance row references raw path outside this snapshot: cards\/missing\.json/); }); it('fails measure-level wiki frontmatter sl_refs that point at missing entities', async () => { const wikiService = wikiServiceWithPages({ 'account-segments': { slRefs: ['mart_account_segments.total_contract_arr_cents'], content: 'ARR uses a renamed measure.', }, }); const semanticLayerService = { loadAllSources: vi.fn().mockResolvedValue({ sources: [ { name: 'mart_account_segments', grain: ['account_id'], columns: [{ name: 'account_id', type: 'string' }], joins: [], measures: [{ name: 'total_contract_arr', expr: 'sum(contract_arr)' }], table: 'analytics.mart_account_segments', }, ], loadErrors: [], }), }; await expect( validateFinalIngestArtifacts({ connectionIds: ['warehouse'], changedWikiPageKeys: ['account-segments'], touchedSlSources: [{ connectionId: 'warehouse', sourceName: 'mart_account_segments' }], wikiService: wikiService as never, semanticLayerService: semanticLayerService as never, validateTouchedSources: async () => ({ invalidSources: [], validSources: ['warehouse:mart_account_segments'] }), tableExists: async () => true, }), ).rejects.toThrow(/unknown sl_refs entity mart_account_segments\.total_contract_arr_cents/); }); it('validates direct declared-join neighbors of touched semantic-layer sources', async () => { const semanticLayerService = { loadAllSources: vi.fn().mockResolvedValue({ sources: [ { name: 'orders', grain: ['order_id'], columns: [ { name: 'order_id', type: 'string' }, { name: 'account_id', type: 'string' }, ], joins: [{ to: 'accounts', on: 'orders.account_id = accounts.account_id', relationship: 'many_to_one' }], measures: [{ name: 'order_count', expr: 'count(*)' }], }, { name: 'accounts', grain: ['account_id'], columns: [{ name: 'account_id', type: 'string' }], joins: [], measures: [{ name: 'account_count', expr: 'count(*)' }], }, { name: 'segments', grain: ['segment_id'], columns: [ { name: 'segment_id', type: 'string' }, { name: 'account_id', type: 'string' }, ], joins: [{ to: 'accounts', on: 'segments.account_id = accounts.account_id', relationship: 'many_to_one' }], measures: [], }, ], loadErrors: [], }), }; const validateTouchedSources = vi.fn().mockResolvedValue({ invalidSources: [], validSources: [] }); await validateFinalIngestArtifacts({ connectionIds: ['warehouse'], changedWikiPageKeys: [], touchedSlSources: [{ connectionId: 'warehouse', sourceName: 'accounts' }], wikiService: { readPage: vi.fn() } as never, semanticLayerService: semanticLayerService as never, validateTouchedSources, tableExists: async () => true, }); expect(validateTouchedSources).toHaveBeenCalledWith([ { connectionId: 'warehouse', sourceName: 'accounts' }, { connectionId: 'warehouse', sourceName: 'orders' }, { connectionId: 'warehouse', sourceName: 'segments' }, ]); }); it('fails final gates when a changed wiki page references a missing wiki page', async () => { const wikiService = wikiServiceWithPages({ 'account-segments': { refs: ['missing-frontmatter-page'], content: 'See [[missing-inline-page]] for the related process.', }, }); const semanticLayerService = { loadAllSources: vi.fn().mockResolvedValue({ sources: [], loadErrors: [] }), }; await expect( validateFinalIngestArtifacts({ connectionIds: ['warehouse'], changedWikiPageKeys: ['account-segments'], touchedSlSources: [], wikiService: wikiService as never, semanticLayerService: semanticLayerService as never, validateTouchedSources: async () => ({ invalidSources: [], validSources: [] }), tableExists: async () => true, }), ).rejects.toThrow( /wiki references target missing page\(s\): account-segments -> missing-frontmatter-page, account-segments -> missing-inline-page/, ); }); });