Re_Backend/src/__tests__/api/smoke.test.ts

121 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API smoke tests for UAT / production readiness.
* Tests health, routing, and auth validation without requiring full DB/Redis in CI.
*
* Run: npm test -- smoke
*/
import request from 'supertest';
// Load app without starting server (server.ts is not imported)
// Suppress DB config console logs in test
const originalEnv = process.env.NODE_ENV;
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
let app: import('express').Application;
beforeAll(() => {
app = require('../../app').default;
});
afterAll(() => {
process.env.NODE_ENV = originalEnv;
});
describe('API Smoke Health & Routing', () => {
it('SMK-01: GET /health returns 200 and status OK', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('status', 'OK');
expect(res.body).toHaveProperty('timestamp');
expect(res.body).toHaveProperty('uptime');
});
it('SMK-02: GET /api/v1/health returns 200 and service name', async () => {
const res = await request(app).get('/api/v1/health');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('status', 'OK');
expect(res.body).toHaveProperty('service', 're-workflow-backend');
});
it('SMK-03: GET /api/v1/health/db returns 200 (connected) or 503 (disconnected)', async () => {
const res = await request(app).get('/api/v1/health/db');
expect([200, 503]).toContain(res.status);
if (res.status === 200) {
expect(res.body).toHaveProperty('database', 'connected');
} else {
expect(res.body).toHaveProperty('database', 'disconnected');
}
});
it('SMK-04: Invalid API route returns 404 with JSON', async () => {
const res = await request(app).get('/api/v1/invalid-route-xyz');
expect(res.status).toBe(404);
expect(res.body).toHaveProperty('success', false);
expect(res.body).toHaveProperty('message');
});
});
describe('API Smoke Authentication', () => {
it('AUTH-01: POST /api/v1/auth/sso-callback with empty body returns 400', async () => {
const res = await request(app)
.post('/api/v1/auth/sso-callback')
.send({})
.set('Content-Type', 'application/json');
expect(res.status).toBe(400);
expect(res.body).toHaveProperty('success', false);
// Auth route validator returns "Request body validation failed"; legacy app route returns "email and oktaSub required"
expect(res.body.message).toMatch(/email|oktaSub|required|validation failed/i);
});
it('AUTH-02: POST /api/v1/auth/sso-callback without oktaSub returns 400', async () => {
const res = await request(app)
.post('/api/v1/auth/sso-callback')
.send({ email: 'test@example.com' })
.set('Content-Type', 'application/json');
expect(res.status).toBe(400);
expect(res.body).toHaveProperty('success', false);
});
it('AUTH-03: POST /api/v1/auth/sso-callback without email returns 400', async () => {
const res = await request(app)
.post('/api/v1/auth/sso-callback')
.send({ oktaSub: 'okta-123' })
.set('Content-Type', 'application/json');
expect(res.status).toBe(400);
expect(res.body).toHaveProperty('success', false);
});
it('AUTH-04: GET /api/v1/users without token returns 401', async () => {
const res = await request(app).get('/api/v1/users');
expect(res.status).toBe(401);
});
it('AUTH-05: GET /api/v1/users with invalid token returns 401', async () => {
const res = await request(app)
.get('/api/v1/users')
.set('Authorization', 'Bearer invalid-token');
expect(res.status).toBe(401);
});
});
describe('API Smoke Security Headers (SEC-01, SEC-02, SEC-03)', () => {
it('SEC-01: Response includes Content-Security-Policy header', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.headers).toHaveProperty('content-security-policy');
expect(res.headers['content-security-policy']).toBeTruthy();
});
it('SEC-02: Response includes X-Frame-Options (SAMEORIGIN or deny)', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.headers).toHaveProperty('x-frame-options');
expect(res.headers['x-frame-options'].toUpperCase()).toMatch(/SAMEORIGIN|DENY/);
});
it('SEC-03: GET /metrics without admin auth returns 401', async () => {
const res = await request(app).get('/metrics');
expect(res.status).toBe(401);
});
});