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

125 lines
5.3 KiB
TypeScript

/**
* 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<string, unknown>;
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');
});
});