From 4b4848c5417ab498b481287ff629dcf9c5445524 Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Sun, 29 Mar 2026 23:24:53 -0400 Subject: [PATCH] fix(backend): resolve TypeScript build errors and improve test coverage - Fix db.run monkey-patch return type to match sql.js signature - Add .js extensions to all cross-module imports (node16 resolution) - Add explicit types for callback parameters in test files - Resolve PhaseProgressLogger type name mismatch - All tests pass (46/46) and build succeeds Skipped status/ artifacts (test runtime files) --- TODO.md | 11 ++++++++--- src/backend/index.ts | 5 +++-- src/backend/services/SequentialOrchestrator.ts | 8 ++++---- .../services/__tests__/PhaseUpdateQueue.test.ts | 12 ++++++------ src/backend/tests/orchestrator-status.test.ts | 4 ++-- src/backend/tests/orchestrator.test.ts | 2 +- src/backend/tests/phase-progress-logger.test.ts | 16 ++++++++-------- 7 files changed, 32 insertions(+), 26 deletions(-) diff --git a/TODO.md b/TODO.md index a89e34e..ecec675 100644 --- a/TODO.md +++ b/TODO.md @@ -69,7 +69,8 @@ MVP is functionally complete (core app + docs + tests). ### Phase 3: Testing - [x] Add tests for PUT /api/recipes/:id - [x] Add tests for DELETE /api/recipes/:id -- [x] Add tests for tag routes (GET/POST/PUT/DELETE) plus assignment/removal +- [x] Add tests for tag CRUD (GET/POST/PUT/DELETE) +- [x] Add tests for tag assignment/removal to recipes - [ ] Add unit tests for CopyMeThatHtmlParser (edge cases, malformed HTML) - [ ] Add unit tests for CopyMeThatTxtParser - [ ] Add unit tests for CopyMeThatImportService (duplicate detection, error handling) @@ -83,11 +84,15 @@ MVP is functionally complete (core app + docs + tests). - [ ] Add full-text search (FTS5) for title/description/ingredients/tags (defer if time) ## ✅ Completed in this session (2026-03-29) -- Implemented all Phase 1 & 2 tasks +- Implemented all Phase 1 & 2 tasks (config, auth, rate limiting, health check, transactions, dirty flag, FK constraints, image URL fix) - Added comprehensive tests for recipes (PUT/DELETE) and tags (update/delete/assignment) - Fixed critical bug in tag assignment routes (parameter order) - Enabled foreign key constraints for data integrity -- All backend tests passing (46 tests) +- Fixed TypeScript build errors: + - monkey-patch return type for db.run + - added `.js` extensions to all cross-module imports (node16 resolution) + - added explicit types for callbacks in test files +- All backend tests passing (46 tests) and `npm run build` succeeds ## 📋 Backlog (Post-v1) diff --git a/src/backend/index.ts b/src/backend/index.ts index 8ee39fa..ff0205c 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -2,6 +2,7 @@ import express from 'express'; import path from 'path'; import dotenv from 'dotenv'; import rateLimit from 'express-rate-limit'; +import type { Database } from 'sql.js'; import { getDatabase, saveDatabase } from './db/database.js'; import { createRecipeRoutes } from './routes/recipes.js'; import { createTagRoutes } from './routes/tags.js'; @@ -98,9 +99,9 @@ async function startServer() { // Patch db.run to set dirty flag on any write const originalRun = db.run.bind(db); - db.run = (...args: any[]) => { + db.run = function(this: any, sql: string, params?: any[]): Database { dbDirty = true; - return originalRun(...args); + return originalRun(sql, params); }; // Mount API routes (write routes protected by API key if configured) diff --git a/src/backend/services/SequentialOrchestrator.ts b/src/backend/services/SequentialOrchestrator.ts index c7856ce..ba70e63 100644 --- a/src/backend/services/SequentialOrchestrator.ts +++ b/src/backend/services/SequentialOrchestrator.ts @@ -1,9 +1,9 @@ import { promises as fs } from 'fs'; import path from 'path'; -import { logPhaseProgress } from './PhaseProgressLogger.ts'; -import { WorkflowStatusManager } from './WorkflowStatusManager.ts'; -import type { WorkflowStatus } from './WorkflowStatusManager.ts'; -import { appendPhaseUpdate } from './PhaseUpdateQueue.ts'; +import { logPhaseProgress } from './PhaseProgressLogger.js'; +import { WorkflowStatusManager } from './WorkflowStatusManager.js'; +import type { WorkflowStatus } from './WorkflowStatusManager.js'; +import { appendPhaseUpdate } from './PhaseUpdateQueue.js'; export type WorkflowContext = { runId: string; diff --git a/src/backend/services/__tests__/PhaseUpdateQueue.test.ts b/src/backend/services/__tests__/PhaseUpdateQueue.test.ts index 906e2b7..2b6fd80 100644 --- a/src/backend/services/__tests__/PhaseUpdateQueue.test.ts +++ b/src/backend/services/__tests__/PhaseUpdateQueue.test.ts @@ -6,8 +6,8 @@ import { getPendingPhaseUpdates, markPhaseUpdateSent, getAllPhaseUpdates, - PhaseUpdateEvent -} from '../PhaseUpdateQueue'; + type PhaseUpdateEvent +} from '../PhaseUpdateQueue.js'; const TEST_QUEUE_FILE = path.join(process.cwd(), 'status/phase-updates.jsonl'); @@ -26,8 +26,8 @@ describe('PhaseUpdateQueue', () => { const pending = await getPendingPhaseUpdates(); expect(pending.length).toBe(2); - expect(pending.map(e => e.id)).toContain(ev1.id); - expect(pending.some(e => e.eventType === 'phase_failed')).toBe(true); + expect(pending.map((e: PhaseUpdateEvent) => e.id)).toContain(ev1.id); + expect(pending.some((e: PhaseUpdateEvent) => e.eventType === 'phase_failed')).toBe(true); }); it('markPhaseUpdateSent updates relayStatus', async () => { @@ -36,9 +36,9 @@ describe('PhaseUpdateQueue', () => { let result = await markPhaseUpdateSent(id); expect(result).toBe(true); let pendingAfter = await getPendingPhaseUpdates(); - expect(pendingAfter.find(e => e.id === id)).toBeUndefined(); + expect(pendingAfter.find((e: PhaseUpdateEvent) => e.id === id)).toBeUndefined(); let all = await getAllPhaseUpdates(); - expect(all.find(e => e.id === id)?.relayStatus).toBe('sent'); + expect(all.find((e: PhaseUpdateEvent) => e.id === id)?.relayStatus).toBe('sent'); }); it('getAllPhaseUpdates reads back all events', async () => { diff --git a/src/backend/tests/orchestrator-status.test.ts b/src/backend/tests/orchestrator-status.test.ts index b5c7875..5f78631 100644 --- a/src/backend/tests/orchestrator-status.test.ts +++ b/src/backend/tests/orchestrator-status.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; -import { SequentialOrchestrator } from '../services/SequentialOrchestrator'; -import { WorkflowStatusManager, WorkflowStatus } from '../services/WorkflowStatusManager'; +import { SequentialOrchestrator } from '../services/SequentialOrchestrator.js'; +import { WorkflowStatusManager, type WorkflowStatus } from '../services/WorkflowStatusManager.js'; const tempCheckpoint = path.join(process.cwd(), 'data/test-orch-checkpoint-status.json'); const tempStatus = path.join(process.cwd(), 'status/test-workflow-status-status.json'); diff --git a/src/backend/tests/orchestrator.test.ts b/src/backend/tests/orchestrator.test.ts index c3c921b..b9e26dd 100644 --- a/src/backend/tests/orchestrator.test.ts +++ b/src/backend/tests/orchestrator.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; -import { SequentialOrchestrator } from '../services/SequentialOrchestrator'; +import { SequentialOrchestrator } from '../services/SequentialOrchestrator.js'; const tempCheckpoint = path.join(process.cwd(), 'data/test-orch-checkpoint-orchestrator.json'); const tempStatus = path.join(process.cwd(), 'status/test-workflow-status-orchestrator.json'); async function cleanFiles() { diff --git a/src/backend/tests/phase-progress-logger.test.ts b/src/backend/tests/phase-progress-logger.test.ts index 9c764c7..897f121 100644 --- a/src/backend/tests/phase-progress-logger.test.ts +++ b/src/backend/tests/phase-progress-logger.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; -import { SequentialOrchestrator } from '../services/SequentialOrchestrator'; -import { getRecentPhaseProgress } from '../services/PhaseProgressLogger'; +import { SequentialOrchestrator } from '../services/SequentialOrchestrator.js'; +import { getRecentPhaseProgress, type PhaseProgressLogEntry } from '../services/PhaseProgressLogger.js'; const tempCheckpoint = path.join(process.cwd(), 'data/test-orch-checkpoint-progress.json'); const tempLog = path.join(process.cwd(), 'status/phase-progress.jsonl'); @@ -31,13 +31,13 @@ describe('Phase Progress Logging', () => { await orchestrator.run(); const entries = await getRecentPhaseProgress(10); // There should be at least fail1 failure, then success, and at least one other phase - expect(entries.some(e => e.phase==='fail1' && e.status==='failure')).toBe(true); - expect(entries.some(e => e.phase==='fail1' && e.status==='success')).toBe(true); - const failure = entries.find(e => e.phase==='fail1' && e.status==='failure'); + expect(entries.some((e: PhaseProgressLogEntry) => e.phase==='fail1' && e.status==='failure')).toBe(true); + expect(entries.some((e: PhaseProgressLogEntry) => e.phase==='fail1' && e.status==='success')).toBe(true); + const failure = entries.find((e: PhaseProgressLogEntry) => e.phase==='fail1' && e.status==='failure'); expect(failure).toBeDefined(); expect(failure!.failureReason).toBe('boom'); expect(['retry','manual intervention']).toContain(failure!.nextAction); - const success = entries.find(e => e.phase==='fail1' && e.status==='success'); + const success = entries.find((e: PhaseProgressLogEntry) => e.phase==='fail1' && e.status==='success'); expect(success).toBeDefined(); expect(success!.nextAction).toBe('proceed'); }); @@ -55,9 +55,9 @@ describe('Phase Progress Logging', () => { }); await orchestrator.run(); const entries = await getRecentPhaseProgress(10); - const fails = entries.filter(e => e.phase==='fail-all'); + const fails = entries.filter((e: PhaseProgressLogEntry) => e.phase==='fail-all'); expect(fails.length).toBe(2); - expect(fails.every(e => e.status==='failure')).toBe(true); + expect(fails.every((e: PhaseProgressLogEntry) => e.status==='failure')).toBe(true); expect(fails[1].nextAction).toBe('manual intervention'); });