/** * BI-P009 DoD: "Provider contract tests added." * * This is the contract every `SiaxAnalyticsEngine` provider must satisfy * regardless of engine — ClickHouse today, DuckDB (BI-P014) and Trino * (BI-P015) later. It is written against the INTERFACE, so a future provider * imports `analyticsEngineContract` and gets the same bar. * * The identity rules here are the ones that matter most: this estate has * already shipped a live cross-tenant hole by reading the tenant out of a * REQUEST BODY (INF0 Wave 2). So the contract asserts that a request field * named `tenant_id` cannot influence the engine at all. */ import { describe, it, expect } from 'vitest'; import { ClickHouseAnalyticsEngine } from '@/src/analytics/engine/clickhouse-engine'; import { SIAX_ANALYTICS_ENGINE_CAPABILITIES, type AnalyticsIdentity, type SiaxAnalyticsEngine, } from '@/src/analytics/engine/siax-analytics-engine'; const IDENTITY: AnalyticsIdentity = { tenant_id: 't_alpha', project_id: 'p_default', principal_id: 'usr_1', }; /** Reusable contract suite — future providers call this with their factory. */ export function analyticsEngineContract(name: string, make: () => SiaxAnalyticsEngine): void { describe(`SiaxAnalyticsEngine contract: ${name}`, () => { it('implements all eight BI-P009 capabilities as callable methods', () => { const engine = make() as unknown as Record; for (const cap of SIAX_ANALYTICS_ENGINE_CAPABILITIES) { expect(typeof engine[cap], `${cap} is not a method`).toBe('function'); } expect(SIAX_ANALYTICS_ENGINE_CAPABILITIES).toEqual([ 'health', 'executeMetric', 'executeQuery', 'estimateQuery', 'cancelQuery', 'explain', 'ingestProjection', 'refreshProjection', ]); }); it('names its provider', () => { expect(make().provider.length).toBeGreaterThan(0); }); it('refuses every tenant-scoped capability without a verified identity', async () => { const engine = make(); const blank = { tenant_id: '', project_id: 'p', principal_id: 'usr_1' }; const noPrincipal = { tenant_id: 't_alpha', project_id: 'p', principal_id: '' }; for (const bad of [blank, noPrincipal]) { await expect(engine.executeMetric(bad, { metric: 'm', aggregate: 'sum' })).rejects.toMatchObject({ code: 'UNAUTHENTICATED', }); await expect(engine.estimateQuery(bad, { metric: 'm', aggregate: 'sum' })).rejects.toMatchObject({ code: 'UNAUTHENTICATED', }); await expect(engine.ingestProjection(bad, { dataset_id: 'ds', rows: [] })).rejects.toMatchObject({ code: 'UNAUTHENTICATED', }); await expect(engine.refreshProjection(bad, 'ds')).rejects.toMatchObject({ code: 'UNAUTHENTICATED' }); await expect(engine.cancelQuery(bad, 'q')).rejects.toMatchObject({ code: 'UNAUTHENTICATED' }); } }); it('a tenant_id smuggled into the REQUEST cannot influence the engine (INF0 Wave-2 bug class)', async () => { const engine = make(); const smuggled = { metric: 'm', aggregate: 'sum', tenant_id: 't_victim' } as never; // The request type has no tenant field; if a provider ever started // reading one, this call would target t_victim. The engine must still // fail on the IDENTITY, proving the request field is inert. await expect( engine.executeMetric({ tenant_id: '', project_id: 'p', principal_id: 'u' }, smuggled), ).rejects.toMatchObject({ code: 'UNAUTHENTICATED' }); }); it('cancelQuery refuses a query id belonging to another tenant', async () => { const engine = make(); await expect(engine.cancelQuery(IDENTITY, 'opt_deadbeefdead_someoneelses')).rejects.toMatchObject({ code: 'AUTHORIZATION_DENIED', }); }); it('health() reports unhealthy rather than throwing when the engine is unreachable', async () => { const report = await make().health(); expect(report.healthy).toBe(false); expect(report.provider.length).toBeGreaterThan(0); // Never leak an engine body or a DSN into the operator-facing detail. expect(report.detail ?? '').not.toMatch(/http|password|@/i); }); }); } analyticsEngineContract( 'clickhouse (unreachable host — contract-level assertions only)', () => new ClickHouseAnalyticsEngine({ baseUrl: 'http://clickhouse.invalid:8123', database: 'opt_contract_test', timeoutMs: 500, }), ); describe('BI-P009 refusals that are deliberate, not missing', () => { it('executeQuery (analyst SQL) is declared and REFUSED until BI-P017/BI-P033', async () => { const engine = new ClickHouseAnalyticsEngine({ baseUrl: 'http://clickhouse.invalid:8123' }); await expect(engine.executeQuery(IDENTITY, { sql: 'SELECT 1', dataset_ids: [] })).rejects.toMatchObject({ code: 'CAPABILITY_UNAVAILABLE', }); }); it('the PRESERVED ClickHouseAnalytics is reachable through the engine, not replaced by it', () => { const engine = new ClickHouseAnalyticsEngine({ baseUrl: 'http://clickhouse.invalid:8123' }); expect(engine.legacyAnalytics.constructor.name).toBe('ClickHouseAnalytics'); expect(typeof engine.legacyAnalytics.queryMetric).toBe('function'); expect(typeof engine.legacyAnalytics.ensureSchema).toBe('function'); expect(typeof engine.legacyAnalytics.ingest).toBe('function'); }); });