Files
0pt/tests/analytics/dbt-contract.test.ts
T

322 lines
12 KiB
TypeScript

/**
* 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> = {}): 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> = {}): 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();
});
});