71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
import 'reflect-metadata';
|
|
import { Test } from '@nestjs/testing';
|
|
import type { INestApplication } from '@nestjs/common';
|
|
import request from 'supertest';
|
|
import { AppModule } from '../src/app.module';
|
|
import { DatabaseService } from '../src/database/database.service';
|
|
import { ENVIRONMENT } from '../src/config/environment.module';
|
|
import { configureApp } from '../src/configure-app';
|
|
import { parseEnvironment } from '../src/config/environment';
|
|
|
|
describe('health API', () => {
|
|
let app: INestApplication;
|
|
const database = { ping: jest.fn() };
|
|
|
|
beforeAll(async () => {
|
|
const environment = parseEnvironment({
|
|
DATABASE_URL: 'postgresql://localhost/mani',
|
|
CORS_ORIGINS: 'https://shop.example.com',
|
|
});
|
|
const module = await Test.createTestingModule({ imports: [AppModule] })
|
|
.overrideProvider(DatabaseService)
|
|
.useValue(database)
|
|
.overrideProvider(ENVIRONMENT)
|
|
.useValue(environment)
|
|
.compile();
|
|
app = module.createNestApplication();
|
|
configureApp(app, environment);
|
|
await app.init();
|
|
});
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
beforeEach(() => {
|
|
database.ping.mockReset();
|
|
});
|
|
|
|
it('reports liveness without requiring the database', async () => {
|
|
await request(app.getHttpServer())
|
|
.get('/api/v1/health/live')
|
|
.expect(200, { status: 'ok' });
|
|
expect(database.ping).not.toHaveBeenCalled();
|
|
});
|
|
it('checks the database before reporting readiness', async () => {
|
|
database.ping.mockResolvedValue(undefined);
|
|
await request(app.getHttpServer())
|
|
.get('/api/v1/health/ready')
|
|
.expect(200, { status: 'ok' });
|
|
expect(database.ping).toHaveBeenCalledTimes(1);
|
|
});
|
|
it('returns 503 without leaking database errors', async () => {
|
|
database.ping.mockRejectedValue(new Error('postgresql://secret'));
|
|
const response = await request(app.getHttpServer())
|
|
.get('/api/v1/health/ready')
|
|
.expect(503);
|
|
expect(response.text).not.toContain('secret');
|
|
});
|
|
it('sets security headers and permits only configured browser origins', async () => {
|
|
const allowed = await request(app.getHttpServer())
|
|
.get('/api/v1/health/live')
|
|
.set('Origin', 'https://shop.example.com');
|
|
expect(allowed.headers['x-content-type-options']).toBe('nosniff');
|
|
expect(allowed.headers['access-control-allow-origin']).toBe(
|
|
'https://shop.example.com',
|
|
);
|
|
const denied = await request(app.getHttpServer())
|
|
.get('/api/v1/health/live')
|
|
.set('Origin', 'https://untrusted.example.com');
|
|
expect(denied.headers['access-control-allow-origin']).toBeUndefined();
|
|
});
|
|
});
|