Files
stack/apps/api/src/job-steps/job-steps.controller.spec.ts
Jason Woltje f6d4e07d31 fix(#182): fix Prisma enum import in job-steps tests
Fixed failing tests in job-steps.service.spec.ts and job-steps.controller.spec.ts
caused by undefined Prisma enum imports in the test environment.

Root cause: When importing JobStepPhase, JobStepType, and JobStepStatus from
@prisma/client in the test environment with mocked Prisma, the enums were
undefined, causing "Cannot read properties of undefined" errors.

Solution: Used vi.mock() with importOriginal to mock the @prisma/client module
and explicitly provide enum values while preserving other exports like PrismaClient.

Changes:
- Added vi.mock() for @prisma/client in both test files
- Defined all three enums (JobStepPhase, JobStepType, JobStepStatus) with their values
- Moved imports after the mock setup to ensure proper initialization

Test results: All 16 job-steps tests now passing (13 service + 3 controller)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 11:41:11 -06:00

177 lines
4.8 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { ExecutionContext } from "@nestjs/common";
// Mock @prisma/client BEFORE importing other modules
vi.mock("@prisma/client", async (importOriginal) => {
const actual = await importOriginal<typeof import("@prisma/client")>();
return {
...actual,
JobStepPhase: {
SETUP: "SETUP",
EXECUTION: "EXECUTION",
VALIDATION: "VALIDATION",
CLEANUP: "CLEANUP",
},
JobStepType: {
COMMAND: "COMMAND",
AI_ACTION: "AI_ACTION",
GATE: "GATE",
ARTIFACT: "ARTIFACT",
},
JobStepStatus: {
PENDING: "PENDING",
RUNNING: "RUNNING",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
SKIPPED: "SKIPPED",
},
};
});
// Import after mocking
import { JobStepsController } from "./job-steps.controller";
import { JobStepsService } from "./job-steps.service";
import { JobStepPhase, JobStepType, JobStepStatus } from "@prisma/client";
import { AuthGuard } from "../auth/guards/auth.guard";
import { WorkspaceGuard } from "../common/guards/workspace.guard";
import { PermissionGuard } from "../common/guards/permission.guard";
describe("JobStepsController", () => {
let controller: JobStepsController;
let service: JobStepsService;
const mockJobStepsService = {
findAllByJob: vi.fn(),
findOne: vi.fn(),
create: vi.fn(),
update: vi.fn(),
startStep: vi.fn(),
completeStep: vi.fn(),
failStep: vi.fn(),
};
const mockAuthGuard = {
canActivate: vi.fn((context: ExecutionContext) => {
const request = context.switchToHttp().getRequest();
request.user = {
id: "user-123",
workspaceId: "workspace-123",
};
return true;
}),
};
const mockWorkspaceGuard = {
canActivate: vi.fn(() => true),
};
const mockPermissionGuard = {
canActivate: vi.fn(() => true),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [JobStepsController],
providers: [
{
provide: JobStepsService,
useValue: mockJobStepsService,
},
],
})
.overrideGuard(AuthGuard)
.useValue(mockAuthGuard)
.overrideGuard(WorkspaceGuard)
.useValue(mockWorkspaceGuard)
.overrideGuard(PermissionGuard)
.useValue(mockPermissionGuard)
.compile();
controller = module.get<JobStepsController>(JobStepsController);
service = module.get<JobStepsService>(JobStepsService);
// Clear all mocks before each test
vi.clearAllMocks();
});
it("should be defined", () => {
expect(controller).toBeDefined();
});
describe("findAll", () => {
it("should return all steps for a job", async () => {
const jobId = "job-123";
const mockSteps = [
{
id: "step-1",
jobId,
ordinal: 1,
phase: JobStepPhase.SETUP,
name: "Clone repo",
type: JobStepType.COMMAND,
status: JobStepStatus.COMPLETED,
output: "Cloned successfully",
tokensInput: null,
tokensOutput: null,
startedAt: new Date("2024-01-01T10:00:00Z"),
completedAt: new Date("2024-01-01T10:00:05Z"),
durationMs: 5000,
},
{
id: "step-2",
jobId,
ordinal: 2,
phase: JobStepPhase.EXECUTION,
name: "Run tests",
type: JobStepType.COMMAND,
status: JobStepStatus.RUNNING,
output: null,
tokensInput: null,
tokensOutput: null,
startedAt: new Date("2024-01-01T10:00:05Z"),
completedAt: null,
durationMs: null,
},
];
mockJobStepsService.findAllByJob.mockResolvedValue(mockSteps);
const result = await controller.findAll(jobId);
expect(result).toEqual(mockSteps);
expect(service.findAllByJob).toHaveBeenCalledWith(jobId);
});
});
describe("findOne", () => {
it("should return a single step by ID", async () => {
const jobId = "job-123";
const stepId = "step-123";
const mockStep = {
id: stepId,
jobId,
ordinal: 1,
phase: JobStepPhase.SETUP,
name: "Clone repo",
type: JobStepType.COMMAND,
status: JobStepStatus.COMPLETED,
output: "Cloned successfully",
tokensInput: null,
tokensOutput: null,
startedAt: new Date("2024-01-01T10:00:00Z"),
completedAt: new Date("2024-01-01T10:00:05Z"),
durationMs: 5000,
};
mockJobStepsService.findOne.mockResolvedValue(mockStep);
const result = await controller.findOne(jobId, stepId);
expect(result).toEqual(mockStep);
expect(service.findOne).toHaveBeenCalledWith(stepId, jobId);
});
});
});