feat: expose qualityReview via updateInitiativeConfig tRPC mutation

Adds qualityReview: z.boolean().optional() to the updateInitiativeConfig
input schema so the field passes through to the repository layer.
Includes integration tests verifying set-true, set-false, and
omit-preserves-existing round-trip behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas May
2026-03-06 21:53:58 +01:00
parent 5137a60e70
commit bb770407db
2 changed files with 98 additions and 0 deletions

View File

@@ -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
});
});

View File

@@ -221,6 +221,7 @@ export function initiativeProcedures(publicProcedure: ProcedureBuilder) {
initiativeId: z.string().min(1), initiativeId: z.string().min(1),
executionMode: z.enum(['yolo', 'review_per_phase']).optional(), executionMode: z.enum(['yolo', 'review_per_phase']).optional(),
branch: z.string().nullable().optional(), branch: z.string().nullable().optional(),
qualityReview: z.boolean().optional(),
})) }))
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const repo = requireInitiativeRepository(ctx); const repo = requireInitiativeRepository(ctx);