mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
- Introduced comprehensive unit tests for the pipeline routes, covering handlers for getting, saving, adding, updating, deleting, and reordering steps. - Added tests for the pipeline service, ensuring correct behavior for methods like getting and saving pipeline configurations, adding, updating, and deleting steps, as well as reordering them. - Implemented error handling tests to verify graceful degradation in case of missing parameters or service failures. - Enhanced test coverage for the `getNextStatus` and `getStep` methods to ensure accurate status transitions and step retrieval. These tests improve the reliability of the pipeline feature by ensuring that all critical functionalities are validated against expected behaviors.
500 lines
16 KiB
TypeScript
500 lines
16 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import type { Request, Response } from 'express';
|
|
import { createGetConfigHandler } from '@/routes/pipeline/routes/get-config.js';
|
|
import { createSaveConfigHandler } from '@/routes/pipeline/routes/save-config.js';
|
|
import { createAddStepHandler } from '@/routes/pipeline/routes/add-step.js';
|
|
import { createUpdateStepHandler } from '@/routes/pipeline/routes/update-step.js';
|
|
import { createDeleteStepHandler } from '@/routes/pipeline/routes/delete-step.js';
|
|
import { createReorderStepsHandler } from '@/routes/pipeline/routes/reorder-steps.js';
|
|
import type { PipelineService } from '@/services/pipeline-service.js';
|
|
import type { PipelineConfig, PipelineStep } from '@automaker/types';
|
|
import { createMockExpressContext } from '../../utils/mocks.js';
|
|
|
|
describe('pipeline routes', () => {
|
|
let mockPipelineService: PipelineService;
|
|
let req: Request;
|
|
let res: Response;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
mockPipelineService = {
|
|
getPipelineConfig: vi.fn(),
|
|
savePipelineConfig: vi.fn(),
|
|
addStep: vi.fn(),
|
|
updateStep: vi.fn(),
|
|
deleteStep: vi.fn(),
|
|
reorderSteps: vi.fn(),
|
|
} as any;
|
|
|
|
const context = createMockExpressContext();
|
|
req = context.req;
|
|
res = context.res;
|
|
});
|
|
|
|
describe('get-config', () => {
|
|
it('should return pipeline config successfully', async () => {
|
|
const config: PipelineConfig = {
|
|
version: 1,
|
|
steps: [],
|
|
};
|
|
|
|
vi.mocked(mockPipelineService.getPipelineConfig).mockResolvedValue(config);
|
|
req.body = { projectPath: '/test/project' };
|
|
|
|
const handler = createGetConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(mockPipelineService.getPipelineConfig).toHaveBeenCalledWith('/test/project');
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: true,
|
|
config,
|
|
});
|
|
});
|
|
|
|
it('should return 400 if projectPath is missing', async () => {
|
|
req.body = {};
|
|
|
|
const handler = createGetConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'projectPath is required',
|
|
});
|
|
expect(mockPipelineService.getPipelineConfig).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const error = new Error('Read failed');
|
|
vi.mocked(mockPipelineService.getPipelineConfig).mockRejectedValue(error);
|
|
req.body = { projectPath: '/test/project' };
|
|
|
|
const handler = createGetConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'Read failed',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('save-config', () => {
|
|
it('should save pipeline config successfully', async () => {
|
|
const config: PipelineConfig = {
|
|
version: 1,
|
|
steps: [
|
|
{
|
|
id: 'step1',
|
|
name: 'Step 1',
|
|
order: 0,
|
|
instructions: 'Instructions',
|
|
colorClass: 'blue',
|
|
createdAt: '2024-01-01T00:00:00.000Z',
|
|
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
},
|
|
],
|
|
};
|
|
|
|
vi.mocked(mockPipelineService.savePipelineConfig).mockResolvedValue(undefined);
|
|
req.body = { projectPath: '/test/project', config };
|
|
|
|
const handler = createSaveConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(mockPipelineService.savePipelineConfig).toHaveBeenCalledWith('/test/project', config);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: true,
|
|
});
|
|
});
|
|
|
|
it('should return 400 if projectPath is missing', async () => {
|
|
req.body = { config: { version: 1, steps: [] } };
|
|
|
|
const handler = createSaveConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'projectPath is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if config is missing', async () => {
|
|
req.body = { projectPath: '/test/project' };
|
|
|
|
const handler = createSaveConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'config is required',
|
|
});
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const error = new Error('Save failed');
|
|
vi.mocked(mockPipelineService.savePipelineConfig).mockRejectedValue(error);
|
|
req.body = {
|
|
projectPath: '/test/project',
|
|
config: { version: 1, steps: [] },
|
|
};
|
|
|
|
const handler = createSaveConfigHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'Save failed',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('add-step', () => {
|
|
it('should add step successfully', async () => {
|
|
const stepData = {
|
|
name: 'New Step',
|
|
order: 0,
|
|
instructions: 'Do something',
|
|
colorClass: 'blue',
|
|
};
|
|
|
|
const newStep: PipelineStep = {
|
|
...stepData,
|
|
id: 'step1',
|
|
createdAt: '2024-01-01T00:00:00.000Z',
|
|
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
};
|
|
|
|
vi.mocked(mockPipelineService.addStep).mockResolvedValue(newStep);
|
|
req.body = { projectPath: '/test/project', step: stepData };
|
|
|
|
const handler = createAddStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(mockPipelineService.addStep).toHaveBeenCalledWith('/test/project', stepData);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: true,
|
|
step: newStep,
|
|
});
|
|
});
|
|
|
|
it('should return 400 if projectPath is missing', async () => {
|
|
req.body = { step: { name: 'Step', order: 0, instructions: 'Do', colorClass: 'blue' } };
|
|
|
|
const handler = createAddStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'projectPath is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if step is missing', async () => {
|
|
req.body = { projectPath: '/test/project' };
|
|
|
|
const handler = createAddStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'step is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if step.name is missing', async () => {
|
|
req.body = {
|
|
projectPath: '/test/project',
|
|
step: { order: 0, instructions: 'Do', colorClass: 'blue' },
|
|
};
|
|
|
|
const handler = createAddStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'step.name is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if step.instructions is missing', async () => {
|
|
req.body = {
|
|
projectPath: '/test/project',
|
|
step: { name: 'Step', order: 0, colorClass: 'blue' },
|
|
};
|
|
|
|
const handler = createAddStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'step.instructions is required',
|
|
});
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const error = new Error('Add failed');
|
|
vi.mocked(mockPipelineService.addStep).mockRejectedValue(error);
|
|
req.body = {
|
|
projectPath: '/test/project',
|
|
step: { name: 'Step', order: 0, instructions: 'Do', colorClass: 'blue' },
|
|
};
|
|
|
|
const handler = createAddStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'Add failed',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('update-step', () => {
|
|
it('should update step successfully', async () => {
|
|
const updates = {
|
|
name: 'Updated Name',
|
|
instructions: 'Updated instructions',
|
|
};
|
|
|
|
const updatedStep: PipelineStep = {
|
|
id: 'step1',
|
|
name: 'Updated Name',
|
|
order: 0,
|
|
instructions: 'Updated instructions',
|
|
colorClass: 'blue',
|
|
createdAt: '2024-01-01T00:00:00.000Z',
|
|
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
};
|
|
|
|
vi.mocked(mockPipelineService.updateStep).mockResolvedValue(updatedStep);
|
|
req.body = { projectPath: '/test/project', stepId: 'step1', updates };
|
|
|
|
const handler = createUpdateStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(mockPipelineService.updateStep).toHaveBeenCalledWith(
|
|
'/test/project',
|
|
'step1',
|
|
updates
|
|
);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: true,
|
|
step: updatedStep,
|
|
});
|
|
});
|
|
|
|
it('should return 400 if projectPath is missing', async () => {
|
|
req.body = { stepId: 'step1', updates: { name: 'New' } };
|
|
|
|
const handler = createUpdateStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'projectPath is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if stepId is missing', async () => {
|
|
req.body = { projectPath: '/test/project', updates: { name: 'New' } };
|
|
|
|
const handler = createUpdateStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'stepId is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if updates is missing', async () => {
|
|
req.body = { projectPath: '/test/project', stepId: 'step1' };
|
|
|
|
const handler = createUpdateStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'updates is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if updates is empty object', async () => {
|
|
req.body = { projectPath: '/test/project', stepId: 'step1', updates: {} };
|
|
|
|
const handler = createUpdateStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'updates is required',
|
|
});
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const error = new Error('Update failed');
|
|
vi.mocked(mockPipelineService.updateStep).mockRejectedValue(error);
|
|
req.body = {
|
|
projectPath: '/test/project',
|
|
stepId: 'step1',
|
|
updates: { name: 'New' },
|
|
};
|
|
|
|
const handler = createUpdateStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'Update failed',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('delete-step', () => {
|
|
it('should delete step successfully', async () => {
|
|
vi.mocked(mockPipelineService.deleteStep).mockResolvedValue(undefined);
|
|
req.body = { projectPath: '/test/project', stepId: 'step1' };
|
|
|
|
const handler = createDeleteStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(mockPipelineService.deleteStep).toHaveBeenCalledWith('/test/project', 'step1');
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: true,
|
|
});
|
|
});
|
|
|
|
it('should return 400 if projectPath is missing', async () => {
|
|
req.body = { stepId: 'step1' };
|
|
|
|
const handler = createDeleteStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'projectPath is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if stepId is missing', async () => {
|
|
req.body = { projectPath: '/test/project' };
|
|
|
|
const handler = createDeleteStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'stepId is required',
|
|
});
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const error = new Error('Delete failed');
|
|
vi.mocked(mockPipelineService.deleteStep).mockRejectedValue(error);
|
|
req.body = { projectPath: '/test/project', stepId: 'step1' };
|
|
|
|
const handler = createDeleteStepHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'Delete failed',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('reorder-steps', () => {
|
|
it('should reorder steps successfully', async () => {
|
|
vi.mocked(mockPipelineService.reorderSteps).mockResolvedValue(undefined);
|
|
req.body = { projectPath: '/test/project', stepIds: ['step2', 'step1', 'step3'] };
|
|
|
|
const handler = createReorderStepsHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(mockPipelineService.reorderSteps).toHaveBeenCalledWith('/test/project', [
|
|
'step2',
|
|
'step1',
|
|
'step3',
|
|
]);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: true,
|
|
});
|
|
});
|
|
|
|
it('should return 400 if projectPath is missing', async () => {
|
|
req.body = { stepIds: ['step1', 'step2'] };
|
|
|
|
const handler = createReorderStepsHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'projectPath is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if stepIds is missing', async () => {
|
|
req.body = { projectPath: '/test/project' };
|
|
|
|
const handler = createReorderStepsHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'stepIds array is required',
|
|
});
|
|
});
|
|
|
|
it('should return 400 if stepIds is not an array', async () => {
|
|
req.body = { projectPath: '/test/project', stepIds: 'not-an-array' };
|
|
|
|
const handler = createReorderStepsHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'stepIds array is required',
|
|
});
|
|
});
|
|
|
|
it('should handle errors gracefully', async () => {
|
|
const error = new Error('Reorder failed');
|
|
vi.mocked(mockPipelineService.reorderSteps).mockRejectedValue(error);
|
|
req.body = { projectPath: '/test/project', stepIds: ['step1', 'step2'] };
|
|
|
|
const handler = createReorderStepsHandler(mockPipelineService);
|
|
await handler(req, res);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.json).toHaveBeenCalledWith({
|
|
success: false,
|
|
error: 'Reorder failed',
|
|
});
|
|
});
|
|
});
|
|
});
|