feat(#175): Implement E2E test harness
- Create comprehensive E2E test suite for job orchestration - Add test fixtures for Discord, BullMQ, and Prisma mocks - Implement 9 end-to-end test scenarios covering: * Happy path: webhook → job → step execution → completion * Event emission throughout job lifecycle * Step failure and retry handling * Job failure after max retries * Discord command parsing and job creation * WebSocket status updates integration * Job cancellation workflow * Job retry mechanism * Progress percentage tracking - Add helper methods to services for simplified testing: * JobStepsService: start(), complete(), fail(), findByJob() * RunnerJobsService: updateStatus(), updateProgress() * JobEventsService: findByJob() - Configure vitest.e2e.config.ts for E2E test execution - All 9 E2E tests passing - All 1405 unit tests passing - Quality gates: typecheck, lint, build all passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -194,4 +194,27 @@ export class JobEventsService {
|
||||
payload,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all events for a job (no pagination)
|
||||
* Alias for getEventsByJobId without pagination
|
||||
*/
|
||||
async findByJob(
|
||||
jobId: string
|
||||
): Promise<Awaited<ReturnType<typeof this.prisma.jobEvent.findMany>>> {
|
||||
// Verify job exists
|
||||
const job = await this.prisma.runnerJob.findUnique({
|
||||
where: { id: jobId },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!job) {
|
||||
throw new NotFoundException(`RunnerJob with ID ${jobId} not found`);
|
||||
}
|
||||
|
||||
return this.prisma.jobEvent.findMany({
|
||||
where: { jobId },
|
||||
orderBy: { timestamp: "asc" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,4 +324,76 @@ export class RunnerJobsService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update job status
|
||||
*/
|
||||
async updateStatus(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
status: RunnerJobStatus,
|
||||
data?: { result?: unknown; error?: string }
|
||||
): Promise<Awaited<ReturnType<typeof this.prisma.runnerJob.update>>> {
|
||||
// Verify job exists
|
||||
const existingJob = await this.prisma.runnerJob.findUnique({
|
||||
where: { id, workspaceId },
|
||||
});
|
||||
|
||||
if (!existingJob) {
|
||||
throw new NotFoundException(`RunnerJob with ID ${id} not found`);
|
||||
}
|
||||
|
||||
const updateData: Prisma.RunnerJobUpdateInput = {
|
||||
status,
|
||||
};
|
||||
|
||||
// Set timestamps based on status
|
||||
if (status === RunnerJobStatus.RUNNING && !existingJob.startedAt) {
|
||||
updateData.startedAt = new Date();
|
||||
}
|
||||
|
||||
if (
|
||||
status === RunnerJobStatus.COMPLETED ||
|
||||
status === RunnerJobStatus.FAILED ||
|
||||
status === RunnerJobStatus.CANCELLED
|
||||
) {
|
||||
updateData.completedAt = new Date();
|
||||
}
|
||||
|
||||
// Add optional data
|
||||
if (data?.result !== undefined) {
|
||||
updateData.result = data.result as Prisma.InputJsonValue;
|
||||
}
|
||||
if (data?.error !== undefined) {
|
||||
updateData.error = data.error;
|
||||
}
|
||||
|
||||
return this.prisma.runnerJob.update({
|
||||
where: { id, workspaceId },
|
||||
data: updateData,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update job progress percentage
|
||||
*/
|
||||
async updateProgress(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
progressPercent: number
|
||||
): Promise<Awaited<ReturnType<typeof this.prisma.runnerJob.update>>> {
|
||||
// Verify job exists
|
||||
const existingJob = await this.prisma.runnerJob.findUnique({
|
||||
where: { id, workspaceId },
|
||||
});
|
||||
|
||||
if (!existingJob) {
|
||||
throw new NotFoundException(`RunnerJob with ID ${id} not found`);
|
||||
}
|
||||
|
||||
return this.prisma.runnerJob.update({
|
||||
where: { id, workspaceId },
|
||||
data: { progressPercent },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user