fix(backend): convert ecosystem-phase2 test to Vitest, fix .ts extension + hardcoded productId
- ecosystem-phase2.test.ts used node:test — Vitest discovered it but ran 0 tests - Converted to Vitest describe/it/expect — now 2 tests actually execute - Added coverage for missing plan-created-event graceful fallback - Fixed .ts import extension to .js (ESM convention, tsc build compat) - Replaced 6 hardcoded 'chronomind' literals with PRODUCT_ID import
This commit is contained in:
parent
fcd2ee3ad2
commit
9b398bbd68
@ -1,5 +1,4 @@
|
|||||||
import test from 'node:test';
|
import { describe, it, expect } from 'vitest';
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
@ -8,57 +7,71 @@ import {
|
|||||||
loadLatestPlanArtifact,
|
loadLatestPlanArtifact,
|
||||||
loadLatestPlanCreatedEvent,
|
loadLatestPlanCreatedEvent,
|
||||||
persistRoutineFromPlan,
|
persistRoutineFromPlan,
|
||||||
} from './ecosystem-phase2.ts';
|
} from './ecosystem-phase2.js';
|
||||||
|
|
||||||
test('builds and persists a ChronoMind routine from the latest plan artifact', async () => {
|
describe('ecosystem-phase2', () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), 'chronomind-phase2-'));
|
it('builds and persists a routine from the latest plan artifact', async () => {
|
||||||
await mkdir(join(root, 'indexes'), { recursive: true });
|
const root = await mkdtemp(join(tmpdir(), 'chronomind-phase2-'));
|
||||||
await writeFile(
|
await mkdir(join(root, 'indexes'), { recursive: true });
|
||||||
join(root, 'indexes', 'latest-plan.json'),
|
await writeFile(
|
||||||
`${JSON.stringify({
|
join(root, 'indexes', 'latest-plan.json'),
|
||||||
id: 'art_plan_demo',
|
`${JSON.stringify({
|
||||||
title: 'FlowMonk weekly plan for 2026-04-07',
|
id: 'art_plan_demo',
|
||||||
ownership: { userId: 'saravana', orgId: null },
|
title: 'FlowMonk weekly plan for 2026-04-07',
|
||||||
provenance: {
|
ownership: { userId: 'saravana', orgId: null },
|
||||||
originProductId: 'flowmonk',
|
provenance: {
|
||||||
originActionId: 'plan_export_demo',
|
originProductId: 'flowmonk',
|
||||||
sessionId: 'sess_phase2',
|
originActionId: 'plan_export_demo',
|
||||||
runId: 'run_phase2_plan',
|
sessionId: 'sess_phase2',
|
||||||
approvalId: null,
|
runId: 'run_phase2_plan',
|
||||||
correlationId: 'corr_phase2',
|
approvalId: null,
|
||||||
lineage: [{ stepType: 'plan-exported', productId: 'flowmonk', actorType: 'agent', timestamp: '2026-04-03T12:00:00.000Z' }],
|
correlationId: 'corr_phase2',
|
||||||
},
|
lineage: [{ stepType: 'plan-exported', productId: 'flowmonk', actorType: 'agent', timestamp: '2026-04-03T12:00:00.000Z' }],
|
||||||
payload: {
|
},
|
||||||
entries: [
|
payload: {
|
||||||
{ taskTitle: 'Architecture review', durationMinutes: 60 },
|
entries: [
|
||||||
{ taskTitle: 'API design', durationMinutes: 45 },
|
{ taskTitle: 'Architecture review', durationMinutes: 60 },
|
||||||
],
|
{ taskTitle: 'API design', durationMinutes: 45 },
|
||||||
},
|
],
|
||||||
}, null, 2)}\n`,
|
},
|
||||||
'utf-8'
|
}, null, 2)}\n`,
|
||||||
);
|
'utf-8'
|
||||||
await writeFile(
|
);
|
||||||
join(root, 'indexes', 'latest-plan-created-event.json'),
|
await writeFile(
|
||||||
`${JSON.stringify({ eventId: 'evt_plan_created_demo', trace: { correlationId: 'corr_phase2', causationId: null, parentEventId: null } }, null, 2)}\n`,
|
join(root, 'indexes', 'latest-plan-created-event.json'),
|
||||||
'utf-8'
|
`${JSON.stringify({ eventId: 'evt_plan_created_demo', trace: { correlationId: 'corr_phase2', causationId: null, parentEventId: null } }, null, 2)}\n`,
|
||||||
);
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
const planArtifact = await loadLatestPlanArtifact(root);
|
const planArtifact = await loadLatestPlanArtifact(root);
|
||||||
const planCreatedEvent = await loadLatestPlanCreatedEvent(root);
|
const planCreatedEvent = await loadLatestPlanCreatedEvent(root);
|
||||||
const generated = buildRoutineFromPlan({
|
const generated = buildRoutineFromPlan({
|
||||||
planArtifact,
|
planArtifact,
|
||||||
planCreatedEvent,
|
planCreatedEvent,
|
||||||
now: '2026-04-03T12:05:00.000Z',
|
now: '2026-04-03T12:05:00.000Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(generated.routine.steps).toHaveLength(2);
|
||||||
|
expect(generated.routine.steps[0].label).toBe('Architecture review');
|
||||||
|
expect(generated.routine.steps[1].label).toBe('API design');
|
||||||
|
expect(generated.routine.totalDurationMinutes).toBe(105);
|
||||||
|
expect(generated.routine.status).toBe('ready');
|
||||||
|
expect(generated.routine.isTemplate).toBe(true);
|
||||||
|
expect(generated.createdEvent.trace.causationId).toBe('evt_plan_created_demo');
|
||||||
|
expect(generated.artifact.payload.stepCount).toBe(2);
|
||||||
|
|
||||||
|
await persistRoutineFromPlan({ ...generated, root });
|
||||||
|
|
||||||
|
const linkedEvent = JSON.parse(
|
||||||
|
await readFile(join(root, 'indexes', 'latest-routine-linked-event.json'), 'utf-8')
|
||||||
|
) as { payload: { relation: string } };
|
||||||
|
|
||||||
|
expect(linkedEvent.payload.relation).toBe('generated-routine');
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(generated.routine.steps.length, 2);
|
it('handles missing plan-created-event gracefully', async () => {
|
||||||
assert.equal(generated.createdEvent.trace.causationId, 'evt_plan_created_demo');
|
const root = await mkdtemp(join(tmpdir(), 'chronomind-phase2-'));
|
||||||
|
const result = await loadLatestPlanCreatedEvent(root);
|
||||||
await persistRoutineFromPlan({ ...generated, root });
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
const linkedEvent = JSON.parse(
|
|
||||||
await readFile(join(root, 'indexes', 'latest-routine-linked-event.json'), 'utf-8')
|
|
||||||
) as { payload: { relation: string } };
|
|
||||||
|
|
||||||
assert.equal(linkedEvent.payload.relation, 'generated-routine');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { randomUUID } from 'node:crypto';
|
|||||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import type { RoutineDoc, RoutineStep } from '../modules/routines/types.ts';
|
import type { RoutineDoc, RoutineStep } from '../modules/routines/types.js';
|
||||||
|
import { PRODUCT_ID } from './product-config.js';
|
||||||
|
|
||||||
const DEFAULT_PHASE2_ROOT = join(homedir(), '.bytelyst', 'ecosystem', 'phase2');
|
const DEFAULT_PHASE2_ROOT = join(homedir(), '.bytelyst', 'ecosystem', 'phase2');
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ type RoutineArtifact = {
|
|||||||
id: string;
|
id: string;
|
||||||
artifactType: 'routine';
|
artifactType: 'routine';
|
||||||
schemaVersion: 1;
|
schemaVersion: 1;
|
||||||
productId: 'chronomind';
|
productId: typeof PRODUCT_ID;
|
||||||
sourceSurface: 'backend';
|
sourceSurface: 'backend';
|
||||||
title: string;
|
title: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
@ -79,7 +80,7 @@ type ArtifactEvent = {
|
|||||||
eventName: 'artifact.created' | 'artifact.linked';
|
eventName: 'artifact.created' | 'artifact.linked';
|
||||||
eventVersion: 1;
|
eventVersion: 1;
|
||||||
occurredAt: string;
|
occurredAt: string;
|
||||||
productId: 'chronomind';
|
productId: typeof PRODUCT_ID;
|
||||||
sourceSurface: 'backend';
|
sourceSurface: 'backend';
|
||||||
userId: string;
|
userId: string;
|
||||||
orgId: string | null;
|
orgId: string | null;
|
||||||
@ -139,7 +140,7 @@ export function buildRoutineFromPlan(params: {
|
|||||||
const routine: RoutineDoc = {
|
const routine: RoutineDoc = {
|
||||||
id: routineId,
|
id: routineId,
|
||||||
userId: params.planArtifact.ownership.userId,
|
userId: params.planArtifact.ownership.userId,
|
||||||
productId: 'chronomind',
|
productId: PRODUCT_ID,
|
||||||
name: `Routine from ${params.planArtifact.title}`,
|
name: `Routine from ${params.planArtifact.title}`,
|
||||||
description: params.planArtifact.title,
|
description: params.planArtifact.title,
|
||||||
steps,
|
steps,
|
||||||
@ -157,7 +158,7 @@ export function buildRoutineFromPlan(params: {
|
|||||||
id: artifactId,
|
id: artifactId,
|
||||||
artifactType: 'routine',
|
artifactType: 'routine',
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
productId: 'chronomind',
|
productId: PRODUCT_ID,
|
||||||
sourceSurface: 'backend',
|
sourceSurface: 'backend',
|
||||||
title: routine.name,
|
title: routine.name,
|
||||||
summary: `${steps.length} routine steps imported from FlowMonk plan`,
|
summary: `${steps.length} routine steps imported from FlowMonk plan`,
|
||||||
@ -206,7 +207,7 @@ export function buildRoutineFromPlan(params: {
|
|||||||
eventName: 'artifact.created',
|
eventName: 'artifact.created',
|
||||||
eventVersion: 1,
|
eventVersion: 1,
|
||||||
occurredAt: now,
|
occurredAt: now,
|
||||||
productId: 'chronomind',
|
productId: PRODUCT_ID,
|
||||||
sourceSurface: 'backend',
|
sourceSurface: 'backend',
|
||||||
userId: routine.userId,
|
userId: routine.userId,
|
||||||
orgId: params.planArtifact.ownership.orgId ?? null,
|
orgId: params.planArtifact.ownership.orgId ?? null,
|
||||||
@ -231,7 +232,7 @@ export function buildRoutineFromPlan(params: {
|
|||||||
eventName: 'artifact.linked',
|
eventName: 'artifact.linked',
|
||||||
eventVersion: 1,
|
eventVersion: 1,
|
||||||
occurredAt: now,
|
occurredAt: now,
|
||||||
productId: 'chronomind',
|
productId: PRODUCT_ID,
|
||||||
sourceSurface: 'backend',
|
sourceSurface: 'backend',
|
||||||
userId: routine.userId,
|
userId: routine.userId,
|
||||||
orgId: params.planArtifact.ownership.orgId ?? null,
|
orgId: params.planArtifact.ownership.orgId ?? null,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user