Skip to content
This repository was archived by the owner on Feb 1, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/backend/tests/helpers/uuid-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ describe('UUID Validation Helper', () => {
expect(typeof testInvalidUuidRejection).toBe('function');
});

it('should have correct function signature (2 required params, 1 optional)', () => {
// Function.length returns number of required parameters
expect(testInvalidUuidRejection.length).toBe(2);
it('should have correct function signature (3 required params, 1 optional)', () => {
// Function.length returns number of required parameters (app, endpoint, paramName)
expect(testInvalidUuidRejection.length).toBe(3);
});

it('should be importable from helpers directory', () => {
Expand Down
59 changes: 18 additions & 41 deletions packages/backend/tests/helpers/uuid-validation.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,40 @@
import type { FastifyInstance } from 'fastify';
import { describe, expect, it } from 'vitest';
import { expect } from 'vitest';

/**
* Shared test helper for UUID validation in API endpoints
* Test helper for UUID validation in API endpoints
*
* Creates a test suite that verifies:
* - Invalid UUID format returns 400 with VALIDATION_ERROR
* - Missing UUID returns 404 (route not found)
* - Valid UUID v4 is accepted (returns 200 or 404 if entity doesn't exist)
* Verifies that invalid UUID format returns 400 error.
* Can be used directly inside an `it` block.
*
* @param app - Fastify application instance
* @param endpoint - API endpoint URL with :id placeholder (e.g., '/api/v1/projects/:id')
* @param endpoint - API endpoint URL with parameter placeholder (e.g., '/api/v1/projects/:projectId/runs')
* @param paramName - Name of the path parameter to replace (e.g., 'projectId')
* @param method - HTTP method to test (default: 'GET')
*
* @example
* ```typescript
* import { testInvalidUuidRejection } from '../../helpers/uuid-validation';
*
* describe('GET /api/v1/projects/:projectId', () => {
* testInvalidUuidRejection(app, '/api/v1/projects/:id', 'GET');
*
* // Endpoint-specific tests continue...
* it('should validate UUID format for projectId', async () => {
* await testInvalidUuidRejection(app, '/api/v1/projects/:projectId/runs', 'projectId');
* });
* ```
*/
export function testInvalidUuidRejection(
export async function testInvalidUuidRejection(
app: FastifyInstance,
endpoint: string,
paramName: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
) {
describe('UUID validation', () => {
it('should return 400 for invalid UUID format', async () => {
const response = await app.inject({
method,
url: endpoint.replace(':id', 'invalid-uuid'),
});

expect(response.statusCode).toBe(400);
expect(response.json().error.code).toBe('VALIDATION_ERROR');
});

it('should return 400 for missing UUID', async () => {
const response = await app.inject({
method,
url: endpoint.replace(':id', ''),
});
const url = endpoint.replace(`:${paramName}`, 'invalid-uuid');

expect(response.statusCode).toBe(404); // Route not found
});

it('should accept valid UUID v4', async () => {
const validUuid = '550e8400-e29b-41d4-a716-446655440000';
const response = await app.inject({
method,
url: endpoint.replace(':id', validUuid),
});

// Should not fail on UUID validation (may 404 if entity doesn't exist)
expect([200, 404]).toContain(response.statusCode);
});
const response = await app.inject({
method,
url,
});

expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
expect(body).toBeDefined();
}
12 changes: 2 additions & 10 deletions packages/backend/tests/integration/api/page-diff-endpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ProjectRepository } from '../../../src/storage/repositories/project-rep
import { RunRepository } from '../../../src/storage/repositories/run-repository.js';
import { SnapshotRepository } from '../../../src/storage/repositories/snapshot-repository.js';
import { createTestApp } from '../../helpers/test-db.js';
import { testInvalidUuidRejection } from '../../helpers/uuid-validation.js';

describe('GET /api/v1/pages/:pageId/diff', () => {
let app: FastifyInstance;
Expand Down Expand Up @@ -445,15 +446,6 @@ describe('GET /api/v1/pages/:pageId/diff', () => {
});

it('should validate UUID format for pageId', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/v1/pages/invalid-uuid/diff',
});

expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
// @ts-rest validation error format may differ from old API
// Just verify we got a 400 status for invalid UUID
expect(body).toBeDefined();
await testInvalidUuidRejection(app, '/api/v1/pages/:pageId/diff', 'pageId');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { ProjectRepository } from '../../../src/storage/repositories/project-repository.js';
import { RunRepository } from '../../../src/storage/repositories/run-repository.js';
import { createTestApp } from '../../helpers/test-db.js';
import { testInvalidUuidRejection } from '../../helpers/uuid-validation.js';

describe('GET /api/v1/projects/:projectId/runs', () => {
let app: FastifyInstance;
Expand Down Expand Up @@ -237,15 +238,6 @@ describe('GET /api/v1/projects/:projectId/runs', () => {
});

it('should validate UUID format for projectId', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/v1/projects/invalid-uuid/runs',
});

expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
// @ts-rest validation error format may differ from old API
// Just verify we got a 400 status for invalid UUID
expect(body).toBeDefined();
await testInvalidUuidRejection(app, '/api/v1/projects/:projectId/runs', 'projectId');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ProjectRepository } from '../../../src/storage/repositories/project-rep
import { RunRepository } from '../../../src/storage/repositories/run-repository.js';
import { SnapshotRepository } from '../../../src/storage/repositories/snapshot-repository.js';
import { createTestApp } from '../../helpers/test-db.js';
import { testInvalidUuidRejection } from '../../helpers/uuid-validation.js';

describe('GET /api/v1/runs/:runId', () => {
let app: FastifyInstance;
Expand Down Expand Up @@ -199,15 +200,6 @@ describe('GET /api/v1/runs/:runId', () => {
});

it('should validate UUID format for runId', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/v1/runs/invalid-uuid',
});

expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
// @ts-rest validation error format may differ from old API
// Just verify we got a 400 status for invalid UUID
expect(body).toBeDefined();
await testInvalidUuidRejection(app, '/api/v1/runs/:runId', 'runId');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ProjectRepository } from '../../../src/storage/repositories/project-rep
import { RunRepository } from '../../../src/storage/repositories/run-repository.js';
import { SnapshotRepository } from '../../../src/storage/repositories/snapshot-repository.js';
import { createTestApp } from '../../helpers/test-db.js';
import { testInvalidUuidRejection } from '../../helpers/uuid-validation.js';

describe('GET /api/v1/runs/:runId/pages', () => {
let app: FastifyInstance;
Expand Down Expand Up @@ -306,15 +307,6 @@ describe('GET /api/v1/runs/:runId/pages', () => {
});

it('should validate UUID format for runId', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/v1/runs/invalid-uuid/pages',
});

expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
// @ts-rest returns Zod validation errors in pathParameterErrors
expect(body.pathParameterErrors).toBeDefined();
expect(body.pathParameterErrors.issues[0].validation).toBe('uuid');
await testInvalidUuidRejection(app, '/api/v1/runs/:runId/pages', 'runId');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
type DatabaseInstance,
} from '../../../src/storage/database.js';
import { createTestApp } from '../../helpers/test-db.js';
import { testInvalidUuidRejection } from '../../helpers/uuid-validation.js';

describe('GET /api/v1/tasks/:taskId', () => {
let app: FastifyInstance;
Expand Down Expand Up @@ -181,15 +182,6 @@ describe('GET /api/v1/tasks/:taskId', () => {
});

it('should validate UUID format for taskId', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/v1/tasks/invalid-uuid',
});

expect(response.statusCode).toBe(400);
const body = JSON.parse(response.body);
// @ts-rest validation error format may differ from old API
// Just verify we got a 400 status for invalid UUID
expect(body).toBeDefined();
await testInvalidUuidRejection(app, '/api/v1/tasks/:taskId', 'taskId');
});
});
104 changes: 39 additions & 65 deletions packages/frontend/tests/unit/components/PageStatusBadge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,44 @@
*/

import { PageStatus } from '@gander-tools/diff-voyager-shared';
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import PageStatusBadge from '../../../src/components/PageStatusBadge.vue';

describe('PageStatusBadge', () => {
it('should render PENDING status as Pending with default badge', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.PENDING },
});

expect(wrapper.text()).toContain('Pending');
expect(wrapper.find('.badge-animated').exists()).toBe(false);
});

it('should render IN_PROGRESS status as Processing with blue badge and animation', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.IN_PROGRESS },
});

expect(wrapper.text()).toContain('Processing');
expect(wrapper.find('.badge-animated').exists()).toBe(true);
});

it('should render COMPLETED status as Completed with green badge', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.COMPLETED },
});

expect(wrapper.text()).toContain('Completed');
expect(wrapper.find('.badge-animated').exists()).toBe(false);
});

it('should render PARTIAL status as Partial with yellow badge', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.PARTIAL },
});

expect(wrapper.text()).toContain('Partial');
expect(wrapper.find('.badge-animated').exists()).toBe(false);
});

it('should render ERROR status as Error with red badge', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.ERROR },
});

expect(wrapper.text()).toContain('Error');
expect(wrapper.find('.badge-animated').exists()).toBe(false);
});

it('should support different sizes', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.COMPLETED, size: 'small' },
});

expect(wrapper.exists()).toBe(true);
});

it('should have data-test attribute', () => {
const wrapper = mount(PageStatusBadge, {
props: { status: PageStatus.COMPLETED },
});

const badge = wrapper.find('[data-test="page-status-badge"]');
expect(badge.exists()).toBe(true);
});
import { createBadgeTestSuite } from '../../utils/badge-test-factory.js';

createBadgeTestSuite(PageStatusBadge, {
propName: 'status',
statuses: [
{
value: PageStatus.PENDING,
expectedText: 'Pending',
expectedType: 'default',
shouldAnimate: false,
},
{
value: PageStatus.IN_PROGRESS,
expectedText: 'Processing',
expectedType: 'info',
shouldAnimate: true,
},
{
value: PageStatus.COMPLETED,
expectedText: 'Completed',
expectedType: 'success',
shouldAnimate: false,
},
{
value: PageStatus.PARTIAL,
expectedText: 'Partial',
expectedType: 'warning',
shouldAnimate: false,
},
{
value: PageStatus.ERROR,
expectedText: 'Error',
expectedType: 'error',
shouldAnimate: false,
},
],
defaultSize: 'small',
defaultDataTestId: 'page-status-badge',
animationClass: 'badge-animated',
});
Loading