| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- import assert from 'node:assert/strict';
- import test from 'node:test';
- import type { ParseRestClient } from '../src/db/parse-rest.client.js';
- import type { Queryable } from '../src/db/types.js';
- import { ApiError } from '../src/http/api-error.js';
- import type { ActionItem } from '../src/modules/saas-platform/domain.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-06T08:00:00.000Z';
- const actionInput: Omit<ActionItem, 'completedAt' | 'createdAt' | 'updatedAt'> & {
- sourceDecisionId: string;
- sourceKind: 'insight';
- creationKey: string;
- } = {
- id: 'action-1',
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- sourceDecisionId: 'decision-1',
- sourceKind: 'insight',
- creationKey: 'workspace-1|analysis-1|insight-1|decision-1',
- evidenceIds: ['evidence-1', 'evidence-1', 'evidence-2'],
- validationMetric: 'Evidence coverage >= 80%',
- actionType: 'experience',
- title: 'Improve evidence traceability',
- description: 'Keep insight evidence attached to delivery.',
- priority: 'high',
- status: 'open',
- productKey: null,
- assigneeUserId: null,
- dueAt: null,
- createdBy: 'user-1',
- };
- const analysisRow = {
- public_id: 'analysis-1',
- workspace_public_id: 'workspace-1',
- analysis_type: 'voc_insight',
- target_kind: 'workspace',
- target_key: '',
- status: 'completed',
- input: {},
- result: { insights: [{ id: 'insight-1', evidenceIds: ['evidence-1', 'evidence-2'] }] },
- evidence_count: 2,
- requested_by_external_id: 'user-1',
- error_summary: null,
- requested_at: timestamp,
- started_at: timestamp,
- completed_at: timestamp,
- };
- const decisionRow = {
- public_id: 'decision-1',
- 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.',
- decided_by_external_id: 'user-1',
- decided_at: timestamp,
- version: 1,
- supersedes_public_id: null,
- is_current: true,
- created_at: timestamp,
- updated_at: timestamp,
- };
- test('Parse REST action repository writes and maps insight provenance', async () => {
- let createdBody: Record<string, unknown> | null = null;
- const client = {
- async findOne(className: string, where: Record<string, unknown>) {
- if (className === 'VocActionItem') {
- assert.equal(where.workspaceId, 'workspace-1');
- return null;
- }
- if (className === 'VocInsightDecision') {
- assert.deepEqual(where, { workspaceId: 'workspace-1', publicId: 'decision-1' });
- return {
- objectId: 'parse-decision-1',
- publicId: 'decision-1',
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- decision: 'confirmed',
- reviewedEvidenceIds: ['evidence-1', 'evidence-2'],
- comment: 'Evidence reviewed.',
- decidedBy: 'user-1',
- decidedAt: timestamp,
- version: 1,
- supersedesId: null,
- isCurrent: true,
- createdAt: timestamp,
- updatedAt: timestamp,
- };
- }
- assert.equal(className, 'VocAnalysisRun');
- assert.deepEqual(where, { workspaceId: 'workspace-1', publicId: 'analysis-1' });
- return {
- objectId: 'parse-analysis-1',
- createdAt: timestamp,
- publicId: 'analysis-1',
- workspaceId: 'workspace-1',
- analysisType: 'voc_insight',
- targetKind: 'workspace',
- targetKey: '',
- status: 'completed',
- input: {},
- result: { insights: [{ id: 'insight-1', evidenceIds: ['evidence-1', 'evidence-2'] }] },
- evidenceCount: 2,
- requestedBy: 'user-1',
- requestedAt: timestamp,
- startedAt: timestamp,
- completedAt: timestamp,
- };
- },
- async create(className: string, body: Record<string, unknown>) {
- assert.equal(className, 'VocActionItem');
- createdBody = body;
- return { objectId: 'parse-action-1', createdAt: timestamp };
- },
- } as unknown as ParseRestClient;
- const repository = new ParseRestVocRepository(client);
- const action = await repository.createAction(actionInput);
- assert.deepEqual((createdBody as unknown as { evidenceIds: string[] }).evidenceIds, ['evidence-1', 'evidence-2']);
- assert.equal((createdBody as unknown as { sourceAnalysisId: string }).sourceAnalysisId, 'analysis-1');
- assert.equal((createdBody as unknown as { sourceDecisionId: string }).sourceDecisionId, 'decision-1');
- assert.equal((createdBody as unknown as { sourceKind: string }).sourceKind, 'insight');
- assert.equal(
- (createdBody as unknown as { creationKey: string }).creationKey,
- 'workspace-1|analysis-1|insight-1|decision-1',
- );
- assert.equal(action.sourceInsightId, 'insight-1');
- assert.equal(action.sourceDecisionId, 'decision-1');
- assert.deepEqual(action.evidenceIds, ['evidence-1', 'evidence-2']);
- assert.equal(action.validationMetric, 'Evidence coverage >= 80%');
- await assert.rejects(
- repository.createAction({ ...actionInput, sourceInsightId: 'insight-unknown' }),
- (error) => error instanceof ApiError && error.code === 'source_insight_not_found',
- );
- await assert.rejects(
- repository.createAction({ ...actionInput, sourceAnalysisId: null }),
- (error) => error instanceof ApiError && error.code === 'source_insight_orphan',
- );
- });
- test('Postgres action repository converts the source public ID and maps provenance', async () => {
- const calls: Array<{ text: string; values: readonly unknown[] }> = [];
- const database = {
- async query(text: string, values: readonly unknown[] = []) {
- calls.push({ text, values });
- if (text.includes('action.creation_key = $2')) {
- return { rows: [], rowCount: 0 };
- }
- if (text.includes('FROM voc.analysis_run run')) {
- return { rows: [analysisRow], rowCount: 1 };
- }
- if (text.includes('FROM voc.insight_decision decision')) {
- return { rows: [decisionRow], rowCount: 1 };
- }
- assert.match(text, /INSERT INTO voc\.action_item/);
- assert.match(text, /source_analysis_id/);
- assert.match(text, /source_insight_id/);
- assert.match(text, /evidence_ids/);
- assert.match(text, /validation_metric/);
- return {
- rows: [{
- public_id: 'action-1',
- workspace_public_id: 'workspace-1',
- source_analysis_public_id: 'analysis-1',
- source_insight_id: 'insight-1',
- source_decision_id: 12,
- source_decision_public_id: 'decision-1',
- source_kind: 'insight',
- creation_key: 'workspace-1|analysis-1|insight-1|decision-1',
- evidence_ids: ['evidence-1', 'evidence-2'],
- validation_metric: 'Evidence coverage >= 80%',
- action_type: 'experience',
- title: 'Improve evidence traceability',
- description: 'Keep insight evidence attached to delivery.',
- priority: 'high',
- status: 'open',
- product_key: null,
- assignee_external_id: null,
- due_at: null,
- created_by_external_id: 'user-1',
- completed_at: null,
- created_at: timestamp,
- updated_at: timestamp,
- }],
- rowCount: 1,
- };
- },
- } as unknown as Queryable;
- const repository = new PostgresPlatformRepository(database);
- const action = await repository.createAction(actionInput);
- assert.equal(calls.length, 4);
- assert.equal(calls[3]!.values[11], 'analysis-1');
- assert.equal(calls[3]!.values[12], 'insight-1');
- assert.equal(calls[3]!.values[13], JSON.stringify(['evidence-1', 'evidence-2']));
- assert.equal(calls[3]!.values[14], 'Evidence coverage >= 80%');
- assert.equal(calls[3]!.values[15], 'decision-1');
- assert.equal(calls[3]!.values[16], 'insight');
- assert.equal(calls[3]!.values[17], 'workspace-1|analysis-1|insight-1|decision-1');
- assert.equal(action.sourceAnalysisId, 'analysis-1');
- assert.equal(action.sourceDecisionId, 'decision-1');
- assert.equal(action.sourceKind, 'insight');
- assert.equal(action.creationKey, 'workspace-1|analysis-1|insight-1|decision-1');
- assert.deepEqual(action.evidenceIds, ['evidence-1', 'evidence-2']);
- await assert.rejects(
- repository.createAction({ ...actionInput, evidenceIds: ['evidence-from-other-insight'] }),
- (error) => error instanceof ApiError && error.code === 'source_evidence_not_in_insight',
- );
- await assert.rejects(
- repository.createAction({ ...actionInput, sourceInsightId: null, evidenceIds: [] }),
- (error) => error instanceof ApiError && error.code === 'source_insight_required',
- );
- });
- test('action repositories return the existing creation key without creating another row', async () => {
- let parseCreates = 0;
- const parseClient = {
- async findOne(className: string, where: Record<string, unknown>) {
- if (className === 'VocAnalysisRun') {
- assert.deepEqual(where, { workspaceId: 'workspace-1', publicId: 'analysis-1' });
- return {
- objectId: 'parse-analysis-1',
- createdAt: timestamp,
- publicId: 'analysis-1',
- workspaceId: 'workspace-1',
- analysisType: 'voc_insight',
- targetKind: 'workspace',
- targetKey: '',
- status: 'completed',
- input: {},
- result: { insights: [{ id: 'insight-1', evidenceIds: ['evidence-1', 'evidence-2'] }] },
- evidenceCount: 2,
- requestedBy: 'user-1',
- requestedAt: timestamp,
- startedAt: timestamp,
- completedAt: timestamp,
- };
- }
- if (className === 'VocInsightDecision') {
- assert.deepEqual(where, { workspaceId: 'workspace-1', publicId: 'decision-1' });
- return {
- objectId: 'parse-decision-1',
- publicId: 'decision-1',
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- decision: 'confirmed',
- reviewedEvidenceIds: ['evidence-1', 'evidence-2'],
- comment: 'Evidence reviewed.',
- decidedBy: 'user-1',
- decidedAt: timestamp,
- version: 1,
- supersedesId: null,
- isCurrent: true,
- createdAt: timestamp,
- updatedAt: timestamp,
- };
- }
- assert.equal(className, 'VocActionItem');
- assert.deepEqual(where, {
- workspaceId: 'workspace-1',
- creationKey: 'workspace-1|analysis-1|insight-1|decision-1',
- });
- return {
- objectId: 'parse-action-1',
- publicId: 'action-existing',
- workspaceId: 'workspace-1',
- sourceAnalysisId: 'analysis-1',
- sourceInsightId: 'insight-1',
- sourceDecisionId: 'decision-1',
- sourceKind: 'insight',
- creationKey: 'workspace-1|analysis-1|insight-1|decision-1',
- evidenceIds: ['evidence-1', 'evidence-2'],
- validationMetric: 'Evidence coverage >= 80%',
- actionType: 'experience',
- title: 'Existing action',
- description: '',
- priority: 'high',
- status: 'open',
- createdBy: 'user-1',
- createdAt: timestamp,
- updatedAt: timestamp,
- };
- },
- async create() {
- parseCreates += 1;
- throw new Error('Parse create should not run for an existing creation key');
- },
- } as unknown as ParseRestClient;
- const parseAction = await new ParseRestVocRepository(parseClient).createAction(actionInput);
- assert.equal(parseAction.id, 'action-existing');
- assert.equal(parseCreates, 0);
- let postgresQueries = 0;
- const database = {
- async query(text: string, values: readonly unknown[] = []) {
- postgresQueries += 1;
- if (text.includes('FROM voc.analysis_run run')) {
- return { rows: [analysisRow], rowCount: 1 };
- }
- if (text.includes('FROM voc.insight_decision decision')) {
- return { rows: [decisionRow], rowCount: 1 };
- }
- assert.match(text, /action\.creation_key = \$2/);
- assert.deepEqual(values, ['workspace-1', 'workspace-1|analysis-1|insight-1|decision-1']);
- return {
- rows: [{
- public_id: 'action-existing',
- workspace_public_id: 'workspace-1',
- source_analysis_id: 10,
- source_analysis_public_id: 'analysis-1',
- source_insight_id: 'insight-1',
- source_decision_id: 11,
- source_decision_public_id: 'decision-1',
- source_kind: 'insight',
- creation_key: 'workspace-1|analysis-1|insight-1|decision-1',
- evidence_ids: ['evidence-1', 'evidence-2'],
- validation_metric: 'Evidence coverage >= 80%',
- action_type: 'experience',
- title: 'Existing action',
- description: '',
- priority: 'high',
- status: 'open',
- product_key: null,
- assignee_external_id: null,
- due_at: null,
- created_by_external_id: 'user-1',
- completed_at: null,
- created_at: timestamp,
- updated_at: timestamp,
- }],
- rowCount: 1,
- };
- },
- } as unknown as Queryable;
- const postgresAction = await new PostgresPlatformRepository(database).createAction(actionInput);
- assert.equal(postgresAction.id, 'action-existing');
- assert.equal(postgresQueries, 3);
- });
|