/** * tests/analytics/dbt-contract.test.ts * ===================================== * BI-P022 — DBT CORE CANONICAL TRANSFORMATION (the CONTRACT). * * This repo does not run dbt; N0D (n0d#38) does, against this contract. So * what is testable here is exactly what the contract must refuse, and the * interesting refusals are the ones that look like successes: * * * `status: "success"` with ZERO nodes executed — a dbt selector that * matches nothing exits 0. The dbt-shaped false green. * * a result whose observed dbt version differs from the pin — the models * mean something different and nothing says so. * * a branch name as the revision — "main" today and "main" tomorrow are * different projects, and BI-P022's DoD is about reproducibility. * * a production run against an unreviewed runtime, which is BI-P022's * "after current license/security verification" turned into a gate. * * The scaffold itself is asserted against the filesystem, because a directory * convention that nothing checks is a directory convention that drifts. */ import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { SiaxError } from '@/src/platform/errors'; import { assertDbtRuntimeCertified, assertLayerDependenciesLegal, assertValidDbtJobRequest, assertValidDbtJobResult, DBT_LAYER_TIERS, DBT_LAYERS, DBT_RUNTIME_PIN, REQUIRED_DBT_ARTIFACTS, REQUIRED_DBT_DIRECTORIES, tierForModelPath, type DbtJobRequest, type DbtJobResult, } from '@/src/analytics/transform/dbt-contract'; const DBT_ROOT = path.join(process.cwd(), 'analytics/dbt'); const SHA = 'a'.repeat(40); const CERTIFIED_PIN = { ...DBT_RUNTIME_PIN, licenseReviewed: true, securityReviewed: true, reviewedBy: 'u_1', reviewedAt: '2026-09-07T00:00:00Z', }; function request(over: Partial = {}): DbtJobRequest { return { tenant_id: 't_alpha', project_id: 'p_main', dbt_project: 'siax_analytics', revision: SHA, command: 'build', target_layer: 'marts', environment: 'ci', runtime: DBT_RUNTIME_PIN, idempotency_key: 'idem-1', expected_target_schema: 't_alpha_marts', ...over, }; } function result(over: Partial = {}): DbtJobResult { return { tenant_id: 't_alpha', dbt_project: 'siax_analytics', revision: SHA, command: 'build', status: 'success', started_at: '2026-09-07T00:00:00Z', finished_at: '2026-09-07T00:05:00Z', artifacts: { 'manifest.json': 'st0re://a/manifest.json', 'run_results.json': 'st0re://a/run_results.json', }, nodes: [ { unique_id: 'model.siax_analytics.mart_sales_daily', resource_type: 'model', status: 'success' }, ], observed_dbt_version: DBT_RUNTIME_PIN.version, ...over, }; } describe('BI-P022 — the scaffold exists and matches the declared conventions', () => { it.each([...REQUIRED_DBT_DIRECTORIES])('analytics/dbt/%s exists', (dir) => { expect(fs.existsSync(path.join(DBT_ROOT, dir)), `${dir} is missing`).toBe(true); }); it('declares a reference project covering raw -> staging -> curated -> serving', () => { expect(fs.existsSync(path.join(DBT_ROOT, 'models/sources/sources.yml'))).toBe(true); expect(fs.existsSync(path.join(DBT_ROOT, 'models/staging/stg_sales_events.sql'))).toBe(true); expect(fs.existsSync(path.join(DBT_ROOT, 'models/intermediate/int_sales_enriched.sql'))).toBe(true); expect(fs.existsSync(path.join(DBT_ROOT, 'models/marts/mart_sales_daily.sql'))).toBe(true); expect(fs.existsSync(path.join(DBT_ROOT, 'snapshots/snap_customers.sql'))).toBe(true); }); it('the staging model applies the tenant predicate at the first modelled layer', () => { const sql = fs.readFileSync(path.join(DBT_ROOT, 'models/staging/stg_sales_events.sql'), 'utf8'); expect(sql).toContain('where tenant_id = \'{{ var("siax_tenant_id") }}\''); }); it('the mart reads intermediate, never a source directly', () => { const sql = fs.readFileSync(path.join(DBT_ROOT, 'models/marts/mart_sales_daily.sql'), 'utf8'); expect(sql).toContain("ref('int_sales_enriched')"); expect(sql).not.toContain('source('); }); it('ships a test that fails on an EMPTY mart', () => { // Without it, every column test passes trivially over zero rows. const sql = fs.readFileSync(path.join(DBT_ROOT, 'tests/assert_mart_is_not_empty.sql'), 'utf8'); expect(sql).toContain('= 0'); }); it('ships a cross-tenant test even though it should be impossible to fail', () => { const sql = fs.readFileSync(path.join(DBT_ROOT, 'tests/assert_no_cross_tenant_rows.sql'), 'utf8'); expect(sql).toContain('tenant_id !='); }); it("maps the four layers onto BI-P003's tiers", () => { expect(DBT_LAYER_TIERS).toEqual({ sources: 'RAW', staging: 'NORMALIZED', intermediate: 'CURATED', marts: 'SERVING', }); expect([...DBT_LAYERS]).toEqual(['sources', 'staging', 'intermediate', 'marts']); }); it('assigns a tier from a model path, and refuses one it cannot place', () => { expect(tierForModelPath('models/marts/mart_sales_daily.sql')).toBe('SERVING'); expect(tierForModelPath('analytics/dbt/models/staging/x.sql')).toBe('NORMALIZED'); expect(() => tierForModelPath('models/scratch/x.sql')).toThrowError(SiaxError); }); }); describe('BI-P022 — the version rule is a GATE, not a paragraph', () => { it('the shipped pin is honestly UNREVIEWED', () => { // If someone flips these without doing the review, that is a deliberate // act visible in a diff — which is the point of storing the state. expect(DBT_RUNTIME_PIN.licenseReviewed).toBe(false); expect(DBT_RUNTIME_PIN.securityReviewed).toBe(false); expect(DBT_RUNTIME_PIN.line).toBe('stable'); }); it('refuses a PRODUCTION run against an unreviewed runtime', () => { expect(() => assertDbtRuntimeCertified(DBT_RUNTIME_PIN, 'production')).toThrowError(SiaxError); }); it('allows development and CI against the same unreviewed runtime', () => { expect(() => assertDbtRuntimeCertified(DBT_RUNTIME_PIN, 'ci')).not.toThrow(); expect(() => assertDbtRuntimeCertified(DBT_RUNTIME_PIN, 'development')).not.toThrow(); }); it('allows production once both reviews are recorded', () => { expect(() => assertDbtRuntimeCertified(CERTIFIED_PIN, 'production')).not.toThrow(); }); it('refuses an ALPHA line in production without a separate certification reference', () => { const alpha = { ...CERTIFIED_PIN, line: 'alpha' as const }; expect(() => assertDbtRuntimeCertified(alpha, 'production')).toThrowError(SiaxError); expect(() => assertDbtRuntimeCertified({ ...alpha, separateCertificationRef: 'CERT-2026-11' }, 'production'), ).not.toThrow(); }); it('refuses a version range — a pin is exact or it is not a pin', () => { expect(() => assertDbtRuntimeCertified({ ...CERTIFIED_PIN, version: '1.10' }, 'ci')).toThrowError( SiaxError, ); expect(() => assertDbtRuntimeCertified({ ...CERTIFIED_PIN, version: '^1.10.0' }, 'ci')).toThrowError( SiaxError, ); }); }); describe('BI-P022 — the job request', () => { it('accepts a well-formed request', () => { expect(() => assertValidDbtJobRequest(request())).not.toThrow(); }); it("refuses a branch name as the revision — BI-P022's DoD is reproducibility", () => { for (const rev of ['main', 'HEAD', 'v1.0.0', 'a'.repeat(39)]) { expect(() => assertValidDbtJobRequest(request({ revision: rev }))).toThrowError(SiaxError); } }); it('refuses a target schema that is not scoped to the tenant', () => { let thrown: unknown; try { assertValidDbtJobRequest(request({ expected_target_schema: 't_beta_marts' })); } catch (e) { thrown = e; } expect((thrown as SiaxError).code).toBe('AUTHORIZATION_DENIED'); }); it('refuses a selector carrying shell metacharacters', () => { for (const selector of ['stg_sales; rm -rf /', '$(whoami)', 'a`b`', 'x && y', 'a|b']) { expect(() => assertValidDbtJobRequest(request({ select: [selector] }))).toThrowError(SiaxError); } // ...while accepting real dbt selector syntax. expect(() => assertValidDbtJobRequest( request({ select: ['tag:tier:SERVING+', 'state:modified', '+mart_sales_daily'] }), ), ).not.toThrow(); }); it('refuses a job targeting the sources layer — sources are declared, not built', () => { expect(() => assertValidDbtJobRequest(request({ target_layer: 'sources' }))).toThrowError(SiaxError); }); it('refuses a request with no idempotency key', () => { expect(() => assertValidDbtJobRequest(request({ idempotency_key: '' }))).toThrowError(SiaxError); }); it('refuses a production request on the unreviewed pin', () => { expect(() => assertValidDbtJobRequest( request({ environment: 'production', expected_target_schema: 't_alpha_marts' }), ), ).toThrowError(SiaxError); }); }); describe('BI-P022 — the job RESULT is untrusted input', () => { it('accepts a well-formed result', () => { expect(() => assertValidDbtJobResult(result(), request())).not.toThrow(); }); it('REFUSES success with zero nodes — a selector matching nothing exits 0', () => { let thrown: unknown; try { assertValidDbtJobResult(result({ nodes: [] }), request()); } catch (e) { thrown = e; } expect(thrown).toBeInstanceOf(SiaxError); expect((thrown as SiaxError).message).toContain('zero nodes'); }); it('refuses success while nodes failed', () => { expect(() => assertValidDbtJobResult( result({ nodes: [{ unique_id: 't1', resource_type: 'test', status: 'fail' }] }), request(), ), ).toThrowError(SiaxError); }); it('refuses a runtime that is not the pinned one', () => { expect(() => assertValidDbtJobResult(result({ observed_dbt_version: '1.9.0' }), request())).toThrowError( SiaxError, ); }); it('refuses a result for a different revision', () => { expect(() => assertValidDbtJobResult(result({ revision: 'b'.repeat(40) }), request())).toThrowError( SiaxError, ); }); it('refuses a result for a different tenant', () => { expect(() => assertValidDbtJobResult(result({ tenant_id: 't_beta' }), request())).toThrowError(SiaxError); }); it.each([...REQUIRED_DBT_ARTIFACTS])('refuses a result missing %s', (artifact) => { const artifacts = { ...result().artifacts }; delete artifacts[artifact]; expect(() => assertValidDbtJobResult(result({ artifacts }), request())).toThrowError(SiaxError); }); }); describe('BI-P022 — layer dependencies are checked against the graph, not the convention', () => { it("accepts the reference project's own edges", () => { expect(() => assertLayerDependenciesLegal([ { from: 'models/staging/stg_sales_events.sql', to: 'models/sources/sources.yml' }, { from: 'models/intermediate/int_sales_enriched.sql', to: 'models/staging/stg_sales_events.sql' }, { from: 'models/marts/mart_sales_daily.sql', to: 'models/intermediate/int_sales_enriched.sql' }, ]), ).not.toThrow(); }); it('refuses a mart reading a source directly — raw data reaching a dashboard', () => { expect(() => assertLayerDependenciesLegal([ { from: 'models/marts/mart_sales_daily.sql', to: 'models/sources/sources.yml' }, ]), ).toThrowError(SiaxError); }); it('refuses a mart reading staging — one layer skipped is still a skip', () => { expect(() => assertLayerDependenciesLegal([ { from: 'models/marts/mart_sales_daily.sql', to: 'models/staging/stg_sales_events.sql' }, ]), ).toThrowError(SiaxError); }); it('refuses a staging model reading a mart — an upward edge', () => { expect(() => assertLayerDependenciesLegal([ { from: 'models/staging/stg_sales_events.sql', to: 'models/marts/mart_sales_daily.sql' }, ]), ).toThrowError(SiaxError); }); it('allows a model reading another in the same layer', () => { expect(() => assertLayerDependenciesLegal([ { from: 'models/staging/stg_sales_events.sql', to: 'models/staging/stg_customers.sql' }, ]), ).not.toThrow(); }); });