manicanldes-backend/test/security-http.spec.ts

72 lines
2.5 KiB
TypeScript

import { Logger } from '@nestjs/common';
import { identityApp, type IdentityApp } from './helpers/identity-app';
import { randomUUID } from 'node:crypto';
describe('HTTP security boundaries', () => {
let ctx: IdentityApp;
beforeAll(async () => {
ctx = await identityApp();
}, 60000);
afterAll(async () => {
await ctx?.close();
});
afterEach(() => jest.restoreAllMocks());
it('bounds bodies and rejects malformed JSON with distinct codes', async () => {
const large = await ctx
.api()
.post('/api/v1/products')
.auth(ctx.token, { type: 'bearer' })
.send({ name: 'A', slug: 'a', description: 'x'.repeat(40000) })
.expect(413);
expect(large.body.code).toBe('REQUEST_TOO_LARGE');
const invalid = await ctx
.api()
.post('/api/v1/products')
.set('Content-Type', 'application/json')
.send('{"invalid":')
.expect(400);
expect(invalid.body.code).toBe('REQUEST_MALFORMED');
});
it('generates its own request IDs and ignores cookie credentials', async () => {
const response = await ctx
.api()
.get('/api/v1/addresses')
.set('X-Request-Id', 'attacker-controlled')
.set('Cookie', 'accessToken=' + ctx.token)
.expect(401);
expect(response.body.requestId).toMatch(/^[a-f0-9-]{36}$/);
expect(response.headers['x-request-id']).toBe(response.body.requestId);
expect(response.headers['cache-control']).toBe('no-store');
});
it('treats SQL-looking input as data and prevents field injection', async () => {
const name = "Robert'); DROP TABLE users;--";
await ctx
.api()
.post('/api/v1/products')
.auth(ctx.token, { type: 'bearer' })
.send({ name, slug: randomUUID() })
.expect(201);
expect(await ctx.db.user.count()).toBeGreaterThan(0);
await ctx
.api()
.get('/api/v1/products?limit=999999')
.auth(ctx.token, { type: 'bearer' })
.expect(400);
});
it('logs an unexpected database failure without exposing sensitive text', async () => {
const logger = jest.spyOn(Logger.prototype, 'error').mockImplementation();
jest
.spyOn(ctx.db.product, 'findMany')
.mockRejectedValueOnce(new Error('SELECT secret password=SECRET'));
const response = await ctx
.api()
.get('/api/v1/products')
.auth(ctx.token, { type: 'bearer' })
.expect(500);
expect(response.body.code).toBe('INTERNAL_FAILURE');
expect(JSON.stringify([response.body, logger.mock.calls])).not.toContain(
'SECRET',
);
});
});