From 9b398bbd685367f61d5169f3a324f92b356e2fc7 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Mon, 13 Apr 2026 15:51:58 -0700 Subject: [PATCH] fix(backend): convert ecosystem-phase2 test to Vitest, fix .ts extension + hardcoded productId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- backend/src/lib/ecosystem-phase2.test.ts | 115 +++++++++++++---------- backend/src/lib/ecosystem-phase2.ts | 15 +-- 2 files changed, 72 insertions(+), 58 deletions(-) diff --git a/backend/src/lib/ecosystem-phase2.test.ts b/backend/src/lib/ecosystem-phase2.test.ts index 8de2313..5b8ca1c 100644 --- a/backend/src/lib/ecosystem-phase2.test.ts +++ b/backend/src/lib/ecosystem-phase2.test.ts @@ -1,5 +1,4 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; +import { describe, it, expect } from 'vitest'; import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -8,57 +7,71 @@ import { loadLatestPlanArtifact, loadLatestPlanCreatedEvent, persistRoutineFromPlan, -} from './ecosystem-phase2.ts'; +} from './ecosystem-phase2.js'; -test('builds and persists a ChronoMind routine from the latest plan artifact', async () => { - const root = await mkdtemp(join(tmpdir(), 'chronomind-phase2-')); - await mkdir(join(root, 'indexes'), { recursive: true }); - await writeFile( - join(root, 'indexes', 'latest-plan.json'), - `${JSON.stringify({ - id: 'art_plan_demo', - title: 'FlowMonk weekly plan for 2026-04-07', - ownership: { userId: 'saravana', orgId: null }, - provenance: { - originProductId: 'flowmonk', - originActionId: 'plan_export_demo', - sessionId: 'sess_phase2', - runId: 'run_phase2_plan', - approvalId: null, - correlationId: 'corr_phase2', - lineage: [{ stepType: 'plan-exported', productId: 'flowmonk', actorType: 'agent', timestamp: '2026-04-03T12:00:00.000Z' }], - }, - payload: { - entries: [ - { taskTitle: 'Architecture review', durationMinutes: 60 }, - { taskTitle: 'API design', durationMinutes: 45 }, - ], - }, - }, null, 2)}\n`, - 'utf-8' - ); - await writeFile( - join(root, 'indexes', 'latest-plan-created-event.json'), - `${JSON.stringify({ eventId: 'evt_plan_created_demo', trace: { correlationId: 'corr_phase2', causationId: null, parentEventId: null } }, null, 2)}\n`, - 'utf-8' - ); +describe('ecosystem-phase2', () => { + it('builds and persists a routine from the latest plan artifact', async () => { + const root = await mkdtemp(join(tmpdir(), 'chronomind-phase2-')); + await mkdir(join(root, 'indexes'), { recursive: true }); + await writeFile( + join(root, 'indexes', 'latest-plan.json'), + `${JSON.stringify({ + id: 'art_plan_demo', + title: 'FlowMonk weekly plan for 2026-04-07', + ownership: { userId: 'saravana', orgId: null }, + provenance: { + originProductId: 'flowmonk', + originActionId: 'plan_export_demo', + sessionId: 'sess_phase2', + runId: 'run_phase2_plan', + approvalId: null, + correlationId: 'corr_phase2', + lineage: [{ stepType: 'plan-exported', productId: 'flowmonk', actorType: 'agent', timestamp: '2026-04-03T12:00:00.000Z' }], + }, + payload: { + entries: [ + { taskTitle: 'Architecture review', durationMinutes: 60 }, + { taskTitle: 'API design', durationMinutes: 45 }, + ], + }, + }, null, 2)}\n`, + 'utf-8' + ); + await writeFile( + join(root, 'indexes', 'latest-plan-created-event.json'), + `${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 planCreatedEvent = await loadLatestPlanCreatedEvent(root); - const generated = buildRoutineFromPlan({ - planArtifact, - planCreatedEvent, - now: '2026-04-03T12:05:00.000Z', + const planArtifact = await loadLatestPlanArtifact(root); + const planCreatedEvent = await loadLatestPlanCreatedEvent(root); + const generated = buildRoutineFromPlan({ + planArtifact, + planCreatedEvent, + 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); - assert.equal(generated.createdEvent.trace.causationId, 'evt_plan_created_demo'); - - await persistRoutineFromPlan({ ...generated, root }); - - 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'); + it('handles missing plan-created-event gracefully', async () => { + const root = await mkdtemp(join(tmpdir(), 'chronomind-phase2-')); + const result = await loadLatestPlanCreatedEvent(root); + expect(result).toBeNull(); + }); }); diff --git a/backend/src/lib/ecosystem-phase2.ts b/backend/src/lib/ecosystem-phase2.ts index 46b7f03..c93aa70 100644 --- a/backend/src/lib/ecosystem-phase2.ts +++ b/backend/src/lib/ecosystem-phase2.ts @@ -2,7 +2,8 @@ import { randomUUID } from 'node:crypto'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { homedir } from 'node:os'; 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'); @@ -45,7 +46,7 @@ type RoutineArtifact = { id: string; artifactType: 'routine'; schemaVersion: 1; - productId: 'chronomind'; + productId: typeof PRODUCT_ID; sourceSurface: 'backend'; title: string; summary: string; @@ -79,7 +80,7 @@ type ArtifactEvent = { eventName: 'artifact.created' | 'artifact.linked'; eventVersion: 1; occurredAt: string; - productId: 'chronomind'; + productId: typeof PRODUCT_ID; sourceSurface: 'backend'; userId: string; orgId: string | null; @@ -139,7 +140,7 @@ export function buildRoutineFromPlan(params: { const routine: RoutineDoc = { id: routineId, userId: params.planArtifact.ownership.userId, - productId: 'chronomind', + productId: PRODUCT_ID, name: `Routine from ${params.planArtifact.title}`, description: params.planArtifact.title, steps, @@ -157,7 +158,7 @@ export function buildRoutineFromPlan(params: { id: artifactId, artifactType: 'routine', schemaVersion: 1, - productId: 'chronomind', + productId: PRODUCT_ID, sourceSurface: 'backend', title: routine.name, summary: `${steps.length} routine steps imported from FlowMonk plan`, @@ -206,7 +207,7 @@ export function buildRoutineFromPlan(params: { eventName: 'artifact.created', eventVersion: 1, occurredAt: now, - productId: 'chronomind', + productId: PRODUCT_ID, sourceSurface: 'backend', userId: routine.userId, orgId: params.planArtifact.ownership.orgId ?? null, @@ -231,7 +232,7 @@ export function buildRoutineFromPlan(params: { eventName: 'artifact.linked', eventVersion: 1, occurredAt: now, - productId: 'chronomind', + productId: PRODUCT_ID, sourceSurface: 'backend', userId: routine.userId, orgId: params.planArtifact.ownership.orgId ?? null,