Files
stack/apps/api/src/quality-gate-config/quality-gate-config.service.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

363 lines
10 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { QualityGateConfigService } from "./quality-gate-config.service";
import { PrismaService } from "../prisma/prisma.service";
import { NotFoundException, ConflictException } from "@nestjs/common";
describe("QualityGateConfigService", () => {
let service: QualityGateConfigService;
let prisma: PrismaService;
const mockPrismaService = {
qualityGate: {
create: vi.fn(),
findMany: vi.fn(),
findUnique: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
count: vi.fn(),
},
$transaction: 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({
providers: [
QualityGateConfigService,
{
provide: PrismaService,
useValue: mockPrismaService,
},
],
}).compile();
service = module.get<QualityGateConfigService>(QualityGateConfigService);
prisma = module.get<PrismaService>(PrismaService);
vi.clearAllMocks();
});
it("should be defined", () => {
expect(service).toBeDefined();
});
describe("create", () => {
it("should create a quality gate successfully", async () => {
const createDto = {
name: "Build Check",
description: "Verify code compiles without errors",
type: "build" as const,
command: "pnpm build",
required: true,
order: 1,
};
mockPrismaService.qualityGate.create.mockResolvedValue(mockQualityGate);
const result = await service.create(mockWorkspaceId, createDto);
expect(result).toEqual(mockQualityGate);
expect(prisma.qualityGate.create).toHaveBeenCalledWith({
data: {
workspaceId: mockWorkspaceId,
name: createDto.name,
description: createDto.description,
type: createDto.type,
command: createDto.command,
expectedOutput: null,
isRegex: false,
required: true,
order: 1,
isEnabled: true,
},
});
});
it("should use default values when optional fields are not provided", async () => {
const createDto = {
name: "Test Gate",
type: "test" as const,
};
mockPrismaService.qualityGate.create.mockResolvedValue({
...mockQualityGate,
name: createDto.name,
type: createDto.type,
});
await service.create(mockWorkspaceId, createDto);
expect(prisma.qualityGate.create).toHaveBeenCalledWith({
data: {
workspaceId: mockWorkspaceId,
name: createDto.name,
description: null,
type: createDto.type,
command: null,
expectedOutput: null,
isRegex: false,
required: true,
order: 0,
isEnabled: true,
},
});
});
});
describe("findAll", () => {
it("should return all quality gates for a workspace", async () => {
const mockGates = [mockQualityGate];
mockPrismaService.qualityGate.findMany.mockResolvedValue(mockGates);
const result = await service.findAll(mockWorkspaceId);
expect(result).toEqual(mockGates);
expect(prisma.qualityGate.findMany).toHaveBeenCalledWith({
where: { workspaceId: mockWorkspaceId },
orderBy: { order: "asc" },
});
});
it("should return empty array when no gates exist", async () => {
mockPrismaService.qualityGate.findMany.mockResolvedValue([]);
const result = await service.findAll(mockWorkspaceId);
expect(result).toEqual([]);
});
});
describe("findEnabled", () => {
it("should return only enabled quality gates ordered by priority", async () => {
const mockGates = [mockQualityGate];
mockPrismaService.qualityGate.findMany.mockResolvedValue(mockGates);
const result = await service.findEnabled(mockWorkspaceId);
expect(result).toEqual(mockGates);
expect(prisma.qualityGate.findMany).toHaveBeenCalledWith({
where: {
workspaceId: mockWorkspaceId,
isEnabled: true,
},
orderBy: { order: "asc" },
});
});
});
describe("findOne", () => {
it("should return a quality gate by id", async () => {
mockPrismaService.qualityGate.findUnique.mockResolvedValue(mockQualityGate);
const result = await service.findOne(mockWorkspaceId, mockGateId);
expect(result).toEqual(mockQualityGate);
expect(prisma.qualityGate.findUnique).toHaveBeenCalledWith({
where: {
id: mockGateId,
workspaceId: mockWorkspaceId,
},
});
});
it("should throw NotFoundException when gate does not exist", async () => {
mockPrismaService.qualityGate.findUnique.mockResolvedValue(null);
await expect(service.findOne(mockWorkspaceId, mockGateId)).rejects.toThrow(NotFoundException);
});
});
describe("update", () => {
it("should update a quality gate successfully", async () => {
const updateDto = {
name: "Updated Build Check",
required: false,
};
const updatedGate = {
...mockQualityGate,
...updateDto,
};
mockPrismaService.qualityGate.findUnique.mockResolvedValue(mockQualityGate);
mockPrismaService.qualityGate.update.mockResolvedValue(updatedGate);
const result = await service.update(mockWorkspaceId, mockGateId, updateDto);
expect(result).toEqual(updatedGate);
expect(prisma.qualityGate.update).toHaveBeenCalledWith({
where: { id: mockGateId },
data: updateDto,
});
});
it("should throw NotFoundException when gate does not exist", async () => {
const updateDto = { name: "Updated" };
mockPrismaService.qualityGate.findUnique.mockResolvedValue(null);
await expect(service.update(mockWorkspaceId, mockGateId, updateDto)).rejects.toThrow(
NotFoundException
);
});
});
describe("delete", () => {
it("should delete a quality gate successfully", async () => {
mockPrismaService.qualityGate.findUnique.mockResolvedValue(mockQualityGate);
mockPrismaService.qualityGate.delete.mockResolvedValue(mockQualityGate);
await service.delete(mockWorkspaceId, mockGateId);
expect(prisma.qualityGate.delete).toHaveBeenCalledWith({
where: { id: mockGateId },
});
});
it("should throw NotFoundException when gate does not exist", async () => {
mockPrismaService.qualityGate.findUnique.mockResolvedValue(null);
await expect(service.delete(mockWorkspaceId, mockGateId)).rejects.toThrow(NotFoundException);
});
});
describe("reorder", () => {
it("should reorder gates successfully", async () => {
const gateIds = ["gate1", "gate2", "gate3"];
const mockGates = gateIds.map((id, index) => ({
...mockQualityGate,
id,
order: index,
}));
mockPrismaService.$transaction.mockImplementation((callback) => {
return callback(mockPrismaService);
});
mockPrismaService.qualityGate.update.mockResolvedValue(mockGates[0]);
mockPrismaService.qualityGate.findMany.mockResolvedValue(mockGates);
const result = await service.reorder(mockWorkspaceId, gateIds);
expect(result).toEqual(mockGates);
expect(prisma.$transaction).toHaveBeenCalled();
});
});
describe("seedDefaults", () => {
it("should seed default quality gates for a workspace", async () => {
const mockDefaultGates = [
{
id: "1",
workspaceId: mockWorkspaceId,
name: "Build Check",
description: null,
type: "build",
command: "pnpm build",
expectedOutput: null,
isRegex: false,
required: true,
order: 1,
isEnabled: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: "2",
workspaceId: mockWorkspaceId,
name: "Lint Check",
description: null,
type: "lint",
command: "pnpm lint",
expectedOutput: null,
isRegex: false,
required: true,
order: 2,
isEnabled: true,
createdAt: new Date(),
updatedAt: new Date(),
},
];
mockPrismaService.$transaction.mockImplementation((callback) => {
return callback(mockPrismaService);
});
mockPrismaService.qualityGate.create.mockImplementation((args) =>
Promise.resolve({
...mockDefaultGates[0],
...args.data,
})
);
mockPrismaService.qualityGate.findMany.mockResolvedValue(mockDefaultGates);
const result = await service.seedDefaults(mockWorkspaceId);
expect(result.length).toBeGreaterThan(0);
expect(prisma.$transaction).toHaveBeenCalled();
});
});
describe("toOrchestratorFormat", () => {
it("should convert database gates to orchestrator format", () => {
const gates = [mockQualityGate];
const result = service.toOrchestratorFormat(gates);
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
id: mockQualityGate.id,
name: mockQualityGate.name,
description: mockQualityGate.description,
type: mockQualityGate.type,
command: mockQualityGate.command,
required: mockQualityGate.required,
order: mockQualityGate.order,
});
});
it("should handle regex patterns correctly", () => {
const gateWithRegex = {
...mockQualityGate,
expectedOutput: "Coverage: (\\d+)%",
isRegex: true,
};
const result = service.toOrchestratorFormat([gateWithRegex]);
expect(result[0]?.expectedOutput).toBeInstanceOf(RegExp);
});
it("should handle string patterns correctly", () => {
const gateWithString = {
...mockQualityGate,
expectedOutput: "All tests passed",
isRegex: false,
};
const result = service.toOrchestratorFormat([gateWithString]);
expect(typeof result[0]?.expectedOutput).toBe("string");
});
});
});