Files
stack/apps/api/src/quality-gate-config/quality-gate-config.controller.spec.ts
Jason Woltje 4a2909ce1e feat(#135): implement Quality Gate Configuration System
Add database-backed quality gate configuration for workspaces with
full CRUD operations and default gate seeding.

Schema:
- Add QualityGate model with workspace relation
- Support for custom commands and regex patterns
- Enable/disable and ordering support

Service:
- CRUD operations for quality gates
- findEnabled: Get ordered, enabled gates
- reorder: Bulk reorder with transaction
- seedDefaults: Seed 4 default gates
- toOrchestratorFormat: Convert to orchestrator interface

Endpoints:
- GET /workspaces/:id/quality-gates - List
- GET /workspaces/:id/quality-gates/:gateId - Get one
- POST /workspaces/:id/quality-gates - Create
- PATCH /workspaces/:id/quality-gates/:gateId - Update
- DELETE /workspaces/:id/quality-gates/:gateId - Delete
- POST /workspaces/:id/quality-gates/reorder
- POST /workspaces/:id/quality-gates/seed-defaults

Default gates: Build, Lint, Test, Coverage (85%)

Tests: 25 passing with 95.16% coverage

Fixes #135

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 13:33:04 -06:00

165 lines
4.7 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { QualityGateConfigController } from "./quality-gate-config.controller";
import { QualityGateConfigService } from "./quality-gate-config.service";
describe("QualityGateConfigController", () => {
let controller: QualityGateConfigController;
let service: QualityGateConfigService;
const mockService = {
create: vi.fn(),
findAll: vi.fn(),
findEnabled: vi.fn(),
findOne: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
reorder: vi.fn(),
seedDefaults: vi.fn(),
};
const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440001";
const mockGateId = "550e8400-e29b-41d4-a716-446655440002";
const mockQualityGate = {
id: mockGateId,
workspaceId: mockWorkspaceId,
name: "Build Check",
description: "Verify code compiles without errors",
type: "build",
command: "pnpm build",
expectedOutput: null,
isRegex: false,
required: true,
order: 1,
isEnabled: true,
createdAt: new Date(),
updatedAt: new Date(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [QualityGateConfigController],
providers: [
{
provide: QualityGateConfigService,
useValue: mockService,
},
],
}).compile();
controller = module.get<QualityGateConfigController>(QualityGateConfigController);
service = module.get<QualityGateConfigService>(QualityGateConfigService);
vi.clearAllMocks();
});
it("should be defined", () => {
expect(controller).toBeDefined();
});
describe("findAll", () => {
it("should return all quality gates for a workspace", async () => {
const mockGates = [mockQualityGate];
mockService.findAll.mockResolvedValue(mockGates);
const result = await controller.findAll(mockWorkspaceId);
expect(result).toEqual(mockGates);
expect(service.findAll).toHaveBeenCalledWith(mockWorkspaceId);
});
});
describe("findOne", () => {
it("should return a single quality gate", async () => {
mockService.findOne.mockResolvedValue(mockQualityGate);
const result = await controller.findOne(mockWorkspaceId, mockGateId);
expect(result).toEqual(mockQualityGate);
expect(service.findOne).toHaveBeenCalledWith(mockWorkspaceId, mockGateId);
});
});
describe("create", () => {
it("should create a new quality gate", async () => {
const createDto = {
name: "Build Check",
description: "Verify code compiles without errors",
type: "build" as const,
command: "pnpm build",
required: true,
order: 1,
};
mockService.create.mockResolvedValue(mockQualityGate);
const result = await controller.create(mockWorkspaceId, createDto);
expect(result).toEqual(mockQualityGate);
expect(service.create).toHaveBeenCalledWith(mockWorkspaceId, createDto);
});
});
describe("update", () => {
it("should update a quality gate", async () => {
const updateDto = {
name: "Updated Build Check",
required: false,
};
const updatedGate = {
...mockQualityGate,
...updateDto,
};
mockService.update.mockResolvedValue(updatedGate);
const result = await controller.update(mockWorkspaceId, mockGateId, updateDto);
expect(result).toEqual(updatedGate);
expect(service.update).toHaveBeenCalledWith(mockWorkspaceId, mockGateId, updateDto);
});
});
describe("delete", () => {
it("should delete a quality gate", async () => {
mockService.delete.mockResolvedValue(undefined);
await controller.delete(mockWorkspaceId, mockGateId);
expect(service.delete).toHaveBeenCalledWith(mockWorkspaceId, mockGateId);
});
});
describe("reorder", () => {
it("should reorder quality gates", async () => {
const gateIds = ["gate1", "gate2", "gate3"];
const mockGates = gateIds.map((id, index) => ({
...mockQualityGate,
id,
order: index,
}));
mockService.reorder.mockResolvedValue(mockGates);
const result = await controller.reorder(mockWorkspaceId, { gateIds });
expect(result).toEqual(mockGates);
expect(service.reorder).toHaveBeenCalledWith(mockWorkspaceId, gateIds);
});
});
describe("seedDefaults", () => {
it("should seed default quality gates", async () => {
const mockGates = [mockQualityGate];
mockService.seedDefaults.mockResolvedValue(mockGates);
const result = await controller.seedDefaults(mockWorkspaceId);
expect(result).toEqual(mockGates);
expect(service.seedDefaults).toHaveBeenCalledWith(mockWorkspaceId);
});
});
});