Skip to content
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
146 changes: 146 additions & 0 deletions middleware/tests/unit/compression.middleware.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Request, Response, NextFunction } from 'express';
import * as zlib from 'zlib';
import { CompressionMiddleware } from '../../src/compression/compression.middleware';
import { COMPRESSION_CONFIG } from '../../src/compression/compression.config';

function makeMiddleware(): CompressionMiddleware {
return new CompressionMiddleware();
}

/**
* Builds a mock req/res pair and runs the middleware, then
* simulates a response body being written via res.end().
*
* Returns a promise that resolves with the buffer passed to the
* *real* end() call so tests can inspect compressed/uncompressed output.
*/
function runMiddleware(
body: Buffer | string,
acceptEncoding: string,
contentType = 'application/json',
): Promise<{ buffer: Buffer; headers: Record<string, string> }> {
return new Promise((resolve, reject) => {
const mw = makeMiddleware();
const headers: Record<string, string> = { 'Content-Type': contentType };

const req: Partial<Request> = {
method: 'GET',
path: '/test',
headers: { 'accept-encoding': acceptEncoding },
} as any;

let capturedBuffer: Buffer | null = null;

const res: Partial<Response> & { [key: string]: any } = {
getHeader: (name: string) => headers[name],
setHeader: (name: string, value: any) => { headers[name] = value; },
removeHeader: jest.fn(),
write: jest.fn(),
end: (chunk?: any) => {
// This is the *original* end — capture what was passed
capturedBuffer = chunk ? (Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) : Buffer.alloc(0);
resolve({ buffer: capturedBuffer, headers });
},
} as any;

const next: NextFunction = jest.fn(() => {
// After next() the middleware has patched res.end — call it with the body
try {
(res as any).end(Buffer.isBuffer(body) ? body : Buffer.from(body));
} catch (err) {
reject(err);
}
});

mw.use(req as Request, res as Response, next);
});
}

describe('CompressionMiddleware', () => {
describe('passthrough — body below threshold', () => {
it('does not compress when body is smaller than threshold', async () => {
const smallBody = 'hi'; // < 1024 bytes
const { buffer, headers } = await runMiddleware(smallBody, 'gzip');
expect(headers['Content-Encoding']).toBeUndefined();
expect(buffer.toString()).toBe(smallBody);
});
});

describe('passthrough — skipped content types', () => {
it('does not compress image/* responses even when body is large', async () => {
const largeBody = Buffer.alloc(COMPRESSION_CONFIG.threshold + 1, 'x');
const { headers } = await runMiddleware(largeBody, 'gzip', 'image/png');
expect(headers['Content-Encoding']).toBeUndefined();
});

it('does not compress video/* responses', async () => {
const largeBody = Buffer.alloc(COMPRESSION_CONFIG.threshold + 1, 'x');
const { headers } = await runMiddleware(largeBody, 'gzip', 'video/mp4');
expect(headers['Content-Encoding']).toBeUndefined();
});

it('does not compress audio/* responses', async () => {
const largeBody = Buffer.alloc(COMPRESSION_CONFIG.threshold + 1, 'x');
const { headers } = await runMiddleware(largeBody, 'gzip', 'audio/mpeg');
expect(headers['Content-Encoding']).toBeUndefined();
});

it('does not compress application/zip responses', async () => {
const largeBody = Buffer.alloc(COMPRESSION_CONFIG.threshold + 1, 'x');
const { headers } = await runMiddleware(largeBody, 'gzip', 'application/zip');
expect(headers['Content-Encoding']).toBeUndefined();
});
});

describe('no accept-encoding header', () => {
it('does not compress when client sends no accept-encoding', async () => {
const largeBody = Buffer.alloc(COMPRESSION_CONFIG.threshold + 1, 'x').toString();
const { headers } = await runMiddleware(largeBody, '', 'application/json');
expect(headers['Content-Encoding']).toBeUndefined();
});
});

describe('gzip compression', () => {
it('sets Content-Encoding: gzip and returns decompressible data', async () => {
const largeBody = 'a'.repeat(COMPRESSION_CONFIG.threshold + 1);
const { buffer, headers } = await runMiddleware(largeBody, 'gzip', 'application/json');
expect(headers['Content-Encoding']).toBe('gzip');
const decompressed = zlib.gunzipSync(buffer).toString();
expect(decompressed).toBe(largeBody);
});
});

describe('brotli compression', () => {
it('sets Content-Encoding: br and returns decompressible data', async () => {
const largeBody = 'b'.repeat(COMPRESSION_CONFIG.threshold + 1);
const { buffer, headers } = await runMiddleware(largeBody, 'br', 'application/json');
expect(headers['Content-Encoding']).toBe('br');
const decompressed = zlib.brotliDecompressSync(buffer).toString();
expect(decompressed).toBe(largeBody);
});
});

describe('deflate compression', () => {
it('sets Content-Encoding: deflate and returns decompressible data', async () => {
const largeBody = 'c'.repeat(COMPRESSION_CONFIG.threshold + 1);
const { buffer, headers } = await runMiddleware(largeBody, 'deflate', 'application/json');
expect(headers['Content-Encoding']).toBe('deflate');
const decompressed = zlib.inflateSync(buffer).toString();
expect(decompressed).toBe(largeBody);
});
});

describe('algorithm preference (brotli > gzip > deflate)', () => {
it('prefers brotli when both br and gzip are advertised', async () => {
const largeBody = 'd'.repeat(COMPRESSION_CONFIG.threshold + 1);
const { headers } = await runMiddleware(largeBody, 'gzip, br', 'application/json');
expect(headers['Content-Encoding']).toBe('br');
});

it('uses gzip when br is not listed', async () => {
const largeBody = 'e'.repeat(COMPRESSION_CONFIG.threshold + 1);
const { headers } = await runMiddleware(largeBody, 'gzip, deflate', 'application/json');
expect(headers['Content-Encoding']).toBe('gzip');
});
});
});
125 changes: 125 additions & 0 deletions middleware/tests/unit/correlation-id.middleware.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Request, Response, NextFunction } from 'express';
import { CorrelationIdMiddleware } from '../../src/monitoring/correlation-id.middleware';
import { CorrelationIdStorage } from '../../src/monitoring/correlation-id.storage';

const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function makeMiddleware(): CorrelationIdMiddleware {
return new CorrelationIdMiddleware();
}

function mockRes() {
const headers: Record<string, string> = {};
return {
headers,
setHeader: jest.fn((name: string, value: string) => { headers[name] = value; }),
getHeader: jest.fn((name: string) => headers[name]),
} as unknown as Response;
}

function mockReq(correlationId?: string, user?: { id: string }): Partial<Request> {
const headers: Record<string, string> = {};
if (correlationId) headers['x-correlation-id'] = correlationId;
return { method: 'GET', path: '/test', headers, user, header: (name: string) => headers[name.toLowerCase()] } as any;
}

describe('CorrelationIdMiddleware', () => {
let next: jest.Mock;

beforeEach(() => {
next = jest.fn();
});

it('calls next()', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
mw.use(req as Request, res, next);
expect(next).toHaveBeenCalledTimes(1);
});

it('generates a UUID v4 when no X-Correlation-ID header is provided', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
mw.use(req as Request, res, next);
const id = (req as any).correlationId as string;
expect(id).toMatch(UUID_REGEX);
});

it('reuses an existing X-Correlation-ID from the request header', () => {
const existingId = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee';
const mw = makeMiddleware();
const req = mockReq(existingId);
const res = mockRes();
mw.use(req as Request, res, next);
expect((req as any).correlationId).toBe(existingId);
});

it('sets X-Correlation-ID on the response', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
mw.use(req as Request, res, next);
expect((res as any).headers['X-Correlation-ID']).toBeDefined();
expect((res as any).headers['X-Correlation-ID']).toMatch(UUID_REGEX);
});

it('response X-Correlation-ID matches request correlationId', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
mw.use(req as Request, res, next);
expect((res as any).headers['X-Correlation-ID']).toBe((req as any).correlationId);
});

it('attaches correlationId to request headers for downstream propagation', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
mw.use(req as Request, res, next);
expect(req.headers!['x-correlation-id']).toBe((req as any).correlationId);
});

it('makes correlationId accessible via CorrelationIdStorage inside next()', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
let storedId: string | undefined;

next.mockImplementation(() => {
storedId = CorrelationIdStorage.getCorrelationId();
});

mw.use(req as Request, res, next);
expect(storedId).toBe((req as any).correlationId);
});

it('stores userId from req.user.id in CorrelationIdStorage', () => {
const mw = makeMiddleware();
const req = mockReq(undefined, { id: 'user-42' });
const res = mockRes();
let storedUserId: string | undefined;

next.mockImplementation(() => {
storedUserId = CorrelationIdStorage.getUserId();
});

mw.use(req as Request, res, next);
expect(storedUserId).toBe('user-42');
});

it('stores undefined userId when req.user is absent', () => {
const mw = makeMiddleware();
const req = mockReq();
const res = mockRes();
let storedUserId: string | undefined = 'sentinel';

next.mockImplementation(() => {
storedUserId = CorrelationIdStorage.getUserId();
});

mw.use(req as Request, res, next);
expect(storedUserId).toBeUndefined();
});
});
Loading
Loading