diff --git a/apps/api/src/coordinator-integration/coordinator-integration.service.spec.ts b/apps/api/src/coordinator-integration/coordinator-integration.service.spec.ts index 8b206bd..da339d2 100644 --- a/apps/api/src/coordinator-integration/coordinator-integration.service.spec.ts +++ b/apps/api/src/coordinator-integration/coordinator-integration.service.spec.ts @@ -58,7 +58,10 @@ describe("CoordinatorIntegrationService", () => { create: vi.fn(), findUnique: vi.fn(), update: vi.fn(), + updateMany: vi.fn(), }, + $transaction: vi.fn(), + $queryRaw: vi.fn(), }; const mockJobEventsService = { @@ -97,6 +100,9 @@ describe("CoordinatorIntegrationService", () => { jobEventsService = module.get(JobEventsService); heraldService = module.get(HeraldService); bullMqService = module.get(BullMqService); + + // Set default mock return values + mockPrismaService.runnerJob.updateMany.mockResolvedValue({ count: 1 }); }); describe("createJob", () => { @@ -145,8 +151,26 @@ describe("CoordinatorIntegrationService", () => { it("should update job status to RUNNING", async () => { const updatedJob = { ...mockJob, status: RunnerJobStatus.RUNNING, startedAt: new Date() }; - mockPrismaService.runnerJob.findUnique.mockResolvedValue(mockJob); - mockPrismaService.runnerJob.update.mockResolvedValue(updatedJob); + // Mock transaction that passes through the callback + mockPrismaService.$transaction.mockImplementation(async (callback) => { + const mockTx = { + $queryRaw: vi + .fn() + .mockResolvedValue([ + { + id: mockJob.id, + status: mockJob.status, + workspace_id: mockJob.workspaceId, + version: 1, + }, + ]), + runnerJob: { + update: vi.fn().mockResolvedValue(updatedJob), + }, + }; + return callback(mockTx); + }); + mockJobEventsService.emitJobStarted.mockResolvedValue(mockEvent); mockHeraldService.broadcastJobEvent.mockResolvedValue(undefined); @@ -160,7 +184,16 @@ describe("CoordinatorIntegrationService", () => { }); it("should throw NotFoundException if job does not exist", async () => { - mockPrismaService.runnerJob.findUnique.mockResolvedValue(null); + // Mock transaction with empty result + mockPrismaService.$transaction.mockImplementation(async (callback) => { + const mockTx = { + $queryRaw: vi.fn().mockResolvedValue([]), + runnerJob: { + update: vi.fn(), + }, + }; + return callback(mockTx); + }); await expect( service.updateJobStatus("non-existent", { status: "RUNNING" as const }) @@ -168,8 +201,25 @@ describe("CoordinatorIntegrationService", () => { }); it("should throw BadRequestException for invalid status transition", async () => { - const completedJob = { ...mockJob, status: RunnerJobStatus.COMPLETED }; - mockPrismaService.runnerJob.findUnique.mockResolvedValue(completedJob); + // Mock transaction with completed job + mockPrismaService.$transaction.mockImplementation(async (callback) => { + const mockTx = { + $queryRaw: vi + .fn() + .mockResolvedValue([ + { + id: mockJob.id, + status: RunnerJobStatus.COMPLETED, + workspace_id: mockJob.workspaceId, + version: 1, + }, + ]), + runnerJob: { + update: vi.fn(), + }, + }; + return callback(mockTx); + }); await expect( service.updateJobStatus("job-123", { status: "RUNNING" as const }) @@ -179,11 +229,12 @@ describe("CoordinatorIntegrationService", () => { describe("updateJobProgress", () => { it("should update job progress percentage", async () => { - const runningJob = { ...mockJob, status: RunnerJobStatus.RUNNING }; - const updatedJob = { ...runningJob, progressPercent: 50 }; + const runningJob = { ...mockJob, status: RunnerJobStatus.RUNNING, version: 1 }; + const updatedJob = { ...runningJob, progressPercent: 50, version: 2 }; mockPrismaService.runnerJob.findUnique.mockResolvedValue(runningJob); - mockPrismaService.runnerJob.update.mockResolvedValue(updatedJob); + mockPrismaService.runnerJob.updateMany.mockResolvedValue({ count: 1 }); + mockPrismaService.runnerJob.findUnique.mockResolvedValue(updatedJob); mockJobEventsService.emitEvent.mockResolvedValue(mockEvent); const result = await service.updateJobProgress("job-123", { @@ -217,8 +268,26 @@ describe("CoordinatorIntegrationService", () => { completedAt: new Date(), }; - mockPrismaService.runnerJob.findUnique.mockResolvedValue(runningJob); - mockPrismaService.runnerJob.update.mockResolvedValue(completedJob); + // Mock transaction with running job + mockPrismaService.$transaction.mockImplementation(async (callback) => { + const mockTx = { + $queryRaw: vi + .fn() + .mockResolvedValue([ + { + id: mockJob.id, + status: RunnerJobStatus.RUNNING, + workspace_id: mockJob.workspaceId, + version: 1, + }, + ]), + runnerJob: { + update: vi.fn().mockResolvedValue(completedJob), + }, + }; + return callback(mockTx); + }); + mockJobEventsService.emitJobCompleted.mockResolvedValue(mockEvent); mockHeraldService.broadcastJobEvent.mockResolvedValue(undefined); @@ -243,8 +312,26 @@ describe("CoordinatorIntegrationService", () => { completedAt: new Date(), }; - mockPrismaService.runnerJob.findUnique.mockResolvedValue(runningJob); - mockPrismaService.runnerJob.update.mockResolvedValue(failedJob); + // Mock transaction with running job + mockPrismaService.$transaction.mockImplementation(async (callback) => { + const mockTx = { + $queryRaw: vi + .fn() + .mockResolvedValue([ + { + id: mockJob.id, + status: RunnerJobStatus.RUNNING, + workspace_id: mockJob.workspaceId, + version: 1, + }, + ]), + runnerJob: { + update: vi.fn().mockResolvedValue(failedJob), + }, + }; + return callback(mockTx); + }); + mockJobEventsService.emitJobFailed.mockResolvedValue(mockEvent); mockHeraldService.broadcastJobEvent.mockResolvedValue(undefined);