Tests refactor

This commit is contained in:
Aerilyn Weber 2026-05-19 11:06:03 +09:00
parent 245520fb50
commit 99134d8556
165 changed files with 911 additions and 531 deletions

View file

@ -0,0 +1,191 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock mongoose to prevent real DB connections; provide class-based models
vi.mock('mongoose', () => {
class FakeSchema {
paths: Record<string, unknown> = {};
constructor(def: Record<string, unknown>, _opts?: unknown) {
for (const key of Object.keys(def)) {
this.paths[key] = { path: key };
}
this.paths['createdAt'] = { path: 'createdAt' };
this.paths['updatedAt'] = { path: 'updatedAt' };
}
index() {
return this;
}
}
const models: Record<string, unknown> = {};
function createFakeModel(name: string) {
const mockExec = vi.fn().mockResolvedValue(null);
const mockLean = vi.fn(() => ({ exec: mockExec }));
class Model {
_data: Record<string, unknown>;
constructor(data: Record<string, unknown>) {
this._data = data;
Object.assign(this, data);
}
save() {
return Promise.resolve(this);
}
toObject() {
return { _id: `${name}-id`, ...this._data };
}
static modelName = name;
static schema = new FakeSchema({});
static findOne = vi.fn(() => ({ lean: mockLean }));
static findById = vi.fn(() => ({ lean: mockLean }));
static findOneAndUpdate = vi.fn(() => ({ exec: mockExec }));
static findByIdAndUpdate = vi.fn(() => ({ exec: mockExec }));
}
return Model;
}
return {
default: {
Schema: FakeSchema,
model: vi.fn((name: string, _schema?: unknown) => {
if (!models[name]) models[name] = createFakeModel(name);
return models[name];
}),
connect: vi.fn().mockResolvedValue(undefined),
disconnect: vi.fn().mockResolvedValue(undefined),
},
};
});
vi.mock('jose', () => ({
createRemoteJWKSet: vi.fn(() => 'mock-jwks'),
jwtVerify: vi.fn(),
}));
import { buildApp } from '../src/main.js';
import * as jose from 'jose';
import { NotFoundError } from '../src/common/errors.js';
describe('buildApp', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('creates a Fastify app that is ready', async () => {
const app = await buildApp({ logger: false });
await app.ready();
expect(app).toBeDefined();
await app.close();
});
it('has the health route available', async () => {
const app = await buildApp({ logger: false });
await app.ready();
const res = await app.inject({ method: 'GET', url: '/api/v1/health' });
expect(res.statusCode).toBe(200);
expect(res.json().status).toBe('ok');
await app.close();
});
});
describe('error handler', () => {
async function getApp() {
// Set up a valid JWT mock for authenticated routes
vi.mocked(jose.jwtVerify).mockResolvedValue({
payload: {
sub: 'kc-1',
email: 'test@example.com',
preferred_username: 'testuser',
realm_access: { roles: ['member'] },
householdIds: ['hh1'],
},
protectedHeader: { alg: 'RS256' },
key: {} as never,
} as never);
const app = await buildApp({ logger: false });
// Register test routes that throw various errors
app.get('/test/app-error', { config: { public: true } as never }, async () => {
throw new NotFoundError('Test not found');
});
app.get('/test/generic-error', { config: { public: true } as never }, async () => {
const err = new Error('Something broke');
(err as unknown as Record<string, unknown>).statusCode = 422;
throw err;
});
app.get('/test/unknown-error', { config: { public: true } as never }, async () => {
throw new Error('Unexpected');
});
await app.ready();
return app;
}
beforeEach(() => {
vi.clearAllMocks();
});
it('handles AppError with correct status and body', async () => {
const app = await getApp();
const res = await app.inject({ method: 'GET', url: '/test/app-error' });
expect(res.statusCode).toBe(404);
const body = res.json();
expect(body.error).toBe('Not Found');
expect(body.message).toBe('Test not found');
expect(body.timestamp).toBeDefined();
expect(body.path).toBe('/test/app-error');
await app.close();
});
it('handles generic errors with statusCode', async () => {
const app = await getApp();
const res = await app.inject({ method: 'GET', url: '/test/generic-error' });
expect(res.statusCode).toBe(422);
const body = res.json();
expect(body.error).toBe('Error');
expect(body.message).toBe('Something broke');
await app.close();
});
it('handles unknown 500 errors without leaking messages', async () => {
const app = await getApp();
const res = await app.inject({ method: 'GET', url: '/test/unknown-error' });
expect(res.statusCode).toBe(500);
const body = res.json();
expect(body.error).toBe('Internal Server Error');
expect(body.message).toBe('An unexpected error occurred');
await app.close();
});
it('returns 404 for unknown routes', async () => {
const app = await getApp();
const res = await app.inject({
method: 'GET',
url: '/nonexistent',
headers: {
authorization: 'Bearer test-token',
'x-household-id': 'hh1',
},
});
expect(res.statusCode).toBe(404);
await app.close();
});
});