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:
97
apps/server/trpc/routers/initiative.test.ts
Normal file
97
apps/server/trpc/routers/initiative.test.ts
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user