| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- import assert from 'node:assert/strict';
- import { readFile } from 'node:fs/promises';
- import test from 'node:test';
- import type { ParseRestClient } from '../src/db/parse-rest.client.js';
- import { VOC_PARSE_CLASSES, VOC_PARSE_SCHEMAS } from '../src/db/parse-rest.schema.js';
- import type { Queryable } from '../src/db/types.js';
- import { ParseRestVocRepository } from '../src/modules/saas-platform/parse-rest-voc.repository.js';
- import { PostgresPlatformRepository } from '../src/modules/saas-platform/postgres-platform.repository.js';
- const timestamp = '2026-08-07T08:00:00.000Z';
- const decisionInput = {
- id: 'decision-2',
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- decision: 'confirmed' as const,
- reviewedEvidenceIds: ['evidence-1', 'evidence-1', 'evidence-2'],
- comment: 'Evidence reviewed by the product owner.',
- decidedBy: 'user-1',
- };
- const previousParseDecision = {
- objectId: 'parse-decision-1',
- publicId: 'decision-1',
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- decision: 'needs_more_evidence' as const,
- reviewedEvidenceIds: ['evidence-1'],
- comment: 'Review another sample.',
- decidedBy: 'user-1',
- decidedAt: { __type: 'Date', iso: timestamp },
- version: 1,
- supersedesId: null,
- isCurrent: true,
- createdAt: timestamp,
- updatedAt: timestamp,
- };
- test('Parse REST insight decisions append a version and retire the prior current record', async () => {
- let createdBody: Record<string, unknown> | null = null;
- const updates: Array<{ objectId: string; patch: Record<string, unknown> }> = [];
- const client = {
- async findOne(className: string) {
- assert.equal(className, VOC_PARSE_CLASSES.insightDecision);
- return null;
- },
- async findAll(className: string, where: Record<string, unknown>) {
- assert.equal(className, VOC_PARSE_CLASSES.insightDecision);
- assert.deepEqual(where, {
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- });
- return [previousParseDecision];
- },
- async create(className: string, body: Record<string, unknown>) {
- assert.equal(className, VOC_PARSE_CLASSES.insightDecision);
- createdBody = body;
- return { objectId: 'parse-decision-2', createdAt: timestamp, updatedAt: timestamp };
- },
- async update(className: string, objectId: string, patch: Record<string, unknown>) {
- assert.equal(className, VOC_PARSE_CLASSES.insightDecision);
- updates.push({ objectId, patch });
- return { updatedAt: timestamp };
- },
- } as unknown as ParseRestClient;
- const decision = await new ParseRestVocRepository(client).createInsightDecision(decisionInput);
- assert.equal((createdBody as unknown as { version: number }).version, 2);
- assert.equal((createdBody as unknown as { supersedesId: string }).supersedesId, 'decision-1');
- assert.deepEqual(
- (createdBody as unknown as { reviewedEvidenceIds: string[] }).reviewedEvidenceIds,
- ['evidence-1', 'evidence-2'],
- );
- assert.deepEqual(updates, [{ objectId: 'parse-decision-1', patch: { isCurrent: false } }]);
- assert.equal(decision.version, 2);
- assert.equal(decision.supersedesId, 'decision-1');
- assert.equal(decision.isCurrent, true);
- });
- test('Parse REST insight decision create returns an existing public ID without appending', async () => {
- let writes = 0;
- const client = {
- async findOne() {
- return previousParseDecision;
- },
- async findAll() {
- throw new Error('findAll should not run for an existing decision ID');
- },
- async create() {
- writes += 1;
- throw new Error('create should not run for an existing decision ID');
- },
- } as unknown as ParseRestClient;
- const decision = await new ParseRestVocRepository(client).createInsightDecision({
- ...decisionInput,
- id: 'decision-1',
- });
- assert.equal(decision.id, 'decision-1');
- assert.equal(writes, 0);
- });
- test('Parse REST insight decisions support filtered list and workspace-scoped get', async () => {
- const client = {
- async findAll(className: string, where: Record<string, unknown>) {
- assert.equal(className, VOC_PARSE_CLASSES.insightDecision);
- assert.deepEqual(where, { workspaceId: 'workspace-1' });
- return [previousParseDecision, { ...previousParseDecision, publicId: 'decision-retired', isCurrent: false }];
- },
- async findOne(className: string, where: Record<string, unknown>) {
- assert.equal(className, VOC_PARSE_CLASSES.insightDecision);
- assert.deepEqual(where, { workspaceId: 'workspace-1', publicId: 'decision-1' });
- return previousParseDecision;
- },
- } as unknown as ParseRestClient;
- const repository = new ParseRestVocRepository(client);
- const page = await repository.listInsightDecisions({
- workspaceId: 'workspace-1',
- limit: 10,
- cursor: null,
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- currentOnly: true,
- });
- const decision = await repository.getInsightDecision('workspace-1', 'decision-1');
- assert.deepEqual(page.items.map((item) => item.id), ['decision-1']);
- assert.equal(page.nextCursor, null);
- assert.equal(decision?.decision, 'needs_more_evidence');
- });
- test('Postgres insight decision create uses one append-and-retire statement and maps the version chain', async () => {
- const calls: Array<{ text: string; values: readonly unknown[] }> = [];
- const database = {
- async query(text: string, values: readonly unknown[] = []) {
- calls.push({ text, values });
- assert.match(text, /WITH target AS/);
- assert.match(text, /FOR UPDATE OF analysis/);
- assert.match(text, /SET is_current = false/);
- assert.match(text, /CROSS JOIN \(SELECT count\(\*\) FROM retired\)/);
- return {
- rows: [{
- public_id: 'decision-2',
- workspace_public_id: 'workspace-1',
- source_analysis_public_id: 'analysis-1',
- source_insight_id: 'insight-1',
- decision: 'confirmed',
- reviewed_evidence_ids: ['evidence-1', 'evidence-2'],
- comment: 'Evidence reviewed by the product owner.',
- decided_by_external_id: 'user-1',
- decided_at: timestamp,
- version: 2,
- supersedes_public_id: 'decision-1',
- is_current: true,
- created_at: timestamp,
- updated_at: timestamp,
- }],
- rowCount: 1,
- };
- },
- } as unknown as Queryable;
- const decision = await new PostgresPlatformRepository(database).createInsightDecision(decisionInput);
- assert.equal(calls.length, 1);
- assert.deepEqual(calls[0]!.values, [
- 'workspace-1',
- 'decision-2',
- 'analysis-1',
- 'insight-1',
- 'confirmed',
- JSON.stringify(['evidence-1', 'evidence-2']),
- 'Evidence reviewed by the product owner.',
- 'user-1',
- ]);
- assert.equal(decision.version, 2);
- assert.equal(decision.supersedesId, 'decision-1');
- assert.equal(decision.decidedAt, timestamp);
- });
- test('Postgres insight decisions support filtered list and workspace-scoped get', async () => {
- const calls: Array<{ text: string; values: readonly unknown[] }> = [];
- const row = {
- cursor_id: '42',
- public_id: 'decision-1',
- workspace_public_id: 'workspace-1',
- source_analysis_public_id: 'analysis-1',
- source_insight_id: 'insight-1',
- decision: 'needs_more_evidence',
- reviewed_evidence_ids: ['evidence-1'],
- comment: 'Review another sample.',
- decided_by_external_id: 'user-1',
- decided_at: timestamp,
- version: 1,
- supersedes_public_id: null,
- is_current: true,
- created_at: timestamp,
- updated_at: timestamp,
- };
- const database = {
- async query(text: string, values: readonly unknown[] = []) {
- calls.push({ text, values });
- return { rows: [row], rowCount: 1 };
- },
- } as unknown as Queryable;
- const repository = new PostgresPlatformRepository(database);
- const page = await repository.listInsightDecisions({
- workspaceId: 'workspace-1',
- limit: 10,
- cursor: null,
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- currentOnly: true,
- });
- const decision = await repository.getInsightDecision('workspace-1', 'decision-1');
- assert.deepEqual(calls[0]!.values, [
- 'workspace-1',
- '9223372036854775807',
- 'analysis-1',
- 'insight-1',
- true,
- 11,
- ]);
- assert.deepEqual(calls[1]!.values, ['workspace-1', 'decision-1']);
- assert.equal(page.items[0]?.id, 'decision-1');
- assert.equal(decision?.sourceAnalysisId, 'analysis-1');
- });
- test('Parse schema and PostgreSQL migrations declare decision, idempotency, and source guards', async () => {
- const decisionSchema = VOC_PARSE_SCHEMAS.find((schema) => schema.className === VOC_PARSE_CLASSES.insightDecision);
- const actionSchema = VOC_PARSE_SCHEMAS.find((schema) => schema.className === VOC_PARSE_CLASSES.actionItem);
- assert.ok(decisionSchema);
- assert.equal(decisionSchema.fields.isCurrent?.type, 'Boolean');
- assert.deepEqual(decisionSchema.indexes?.voc_insight_decision_source_current_idx, {
- workspaceId: 1,
- sourceAnalysisId: 1,
- sourceInsightId: 1,
- isCurrent: 1,
- });
- assert.equal(actionSchema?.fields.creationKey?.type, 'String');
- assert.deepEqual(actionSchema?.indexes?.voc_action_workspace_creation_key_idx, {
- workspaceId: 1,
- creationKey: 1,
- });
- const migration = await readFile(
- new URL('../migrations/005_insight_decision_action_idempotency.sql', import.meta.url),
- 'utf8',
- );
- assert.match(migration, /CREATE UNIQUE INDEX IF NOT EXISTS insight_decision_source_current_unique/);
- assert.match(migration, /CREATE UNIQUE INDEX IF NOT EXISTS action_item_workspace_creation_key_unique/);
- assert.match(migration, /CREATE TRIGGER insight_decision_append_only_guard/);
- assert.match(migration, /CREATE TRIGGER action_item_source_decision_guard/);
- const hardeningMigration = await readFile(
- new URL('../migrations/006_insight_action_guard_hardening.sql', import.meta.url),
- 'utf8',
- );
- assert.match(hardeningMigration, /reviewed evidence must cover every evidence ID/);
- assert.match(hardeningMigration, /non-insight actions cannot carry insight analysis, insight, or decision sources/);
- assert.match(hardeningMigration, /insight actions require analysis, insight, and decision sources/);
- assert.match(hardeningMigration, /UPDATE OF[\s\S]*action_type,[\s\S]*validation_metric,[\s\S]*evidence_ids/);
- assert.match(hardeningMigration, /ADD CONSTRAINT action_item_source_decision_kind_check[\s\S]*NOT VALID/);
- });
|