diff --git a/apps/server/trpc/routers/initiative.test.ts b/apps/server/trpc/routers/initiative.test.ts new file mode 100644 index 0000000..13c24b2 --- /dev/null +++ b/apps/server/trpc/routers/initiative.test.ts @@ -0,0 +1,97 @@ +/** + * Integration tests for initiative tRPC router — qualityReview field. + * + * Verifies that updateInitiativeConfig accepts and persists qualityReview. + */ + +import { describe, it, expect, vi } from 'vitest'; +import { router, publicProcedure, createCallerFactory } from '../trpc.js'; +import { initiativeProcedures } from './initiative.js'; +import type { TRPCContext } from '../context.js'; +import { createTestDatabase } from '../../db/repositories/drizzle/test-helpers.js'; +import { DrizzleInitiativeRepository } from '../../db/repositories/drizzle/index.js'; + +// ============================================================================= +// Mock ensureProjectClone — prevents actual git cloning +// ============================================================================= + +vi.mock('../../git/project-clones.js', () => ({ + ensureProjectClone: vi.fn().mockResolvedValue('/fake/clone/path'), + getProjectCloneDir: vi.fn().mockReturnValue('repos/fake-project-id'), +})); + +// ============================================================================= +// Test router +// ============================================================================= + +const testRouter = router({ + ...initiativeProcedures(publicProcedure), +}); + +const createCaller = createCallerFactory(testRouter); + +// ============================================================================= +// Setup helper +// ============================================================================= + +function createMockEventBus(): TRPCContext['eventBus'] { + return { + emit: vi.fn(), + on: vi.fn(), + off: vi.fn(), + once: vi.fn(), + }; +} + +async function setup() { + const db = createTestDatabase(); + const initiativeRepo = new DrizzleInitiativeRepository(db); + const ctx: TRPCContext = { + eventBus: createMockEventBus(), + serverStartedAt: null, + processCount: 0, + initiativeRepository: initiativeRepo, + }; + const caller = createCaller(ctx); + const initiative = await initiativeRepo.create({ name: 'Test Initiative' }); + return { caller, initiativeRepo, initiative }; +} + +// ============================================================================= +// Tests +// ============================================================================= + +describe('updateInitiativeConfig — qualityReview', () => { + it('sets qualityReview to true', async () => { + const { caller, initiative } = await setup(); + const result = await caller.updateInitiativeConfig({ + initiativeId: initiative.id, + qualityReview: true, + }); + expect(result.qualityReview).toBe(true); + }); + + it('sets qualityReview to false', async () => { + const { caller, initiative, initiativeRepo } = await setup(); + // First set it to true + await initiativeRepo.update(initiative.id, { qualityReview: true }); + // Now flip it back + const result = await caller.updateInitiativeConfig({ + initiativeId: initiative.id, + qualityReview: false, + }); + expect(result.qualityReview).toBe(false); + }); + + it('does not change qualityReview when omitted', async () => { + const { caller, initiative, initiativeRepo } = await setup(); + // Set to true first + await initiativeRepo.update(initiative.id, { qualityReview: true }); + // Update without qualityReview + const result = await caller.updateInitiativeConfig({ + initiativeId: initiative.id, + executionMode: 'yolo', + }); + expect(result.qualityReview).toBe(true); // unchanged + }); +}); diff --git a/apps/server/trpc/routers/initiative.ts b/apps/server/trpc/routers/initiative.ts index 0077ad9..1696565 100644 --- a/apps/server/trpc/routers/initiative.ts +++ b/apps/server/trpc/routers/initiative.ts @@ -221,6 +221,7 @@ export function initiativeProcedures(publicProcedure: ProcedureBuilder) { initiativeId: z.string().min(1), executionMode: z.enum(['yolo', 'review_per_phase']).optional(), branch: z.string().nullable().optional(), + qualityReview: z.boolean().optional(), })) .mutation(async ({ ctx, input }) => { const repo = requireInitiativeRepository(ctx);