Add comprehensive backend test suite (#339)
* add test suite, mostly covers integration testing, tests are only backend side * workflow runs the correct script * workflow runs the correct script * workflow runs the correct script * unit tests incoming * Fix multer silent rejections and error handler info leak - Revert cb(null, false) to cb(new Error(...)) in auth.ts, collab.ts, and files.ts so invalid uploads return an error instead of silently dropping the file - Error handler in app.ts now always returns 500 / "Internal server error" instead of forwarding err.message to the client * Use statusCode consistently for multer errors and error handler - Error handler in app.ts reads err.statusCode to forward the correct HTTP status while keeping the response body generic
This commit is contained in:
70
server/tests/unit/services/auditLog.test.ts
Normal file
70
server/tests/unit/services/auditLog.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// Prevent file I/O side effects at module load time
|
||||
vi.mock('fs', () => ({
|
||||
default: {
|
||||
mkdirSync: vi.fn(),
|
||||
existsSync: vi.fn(() => false),
|
||||
statSync: vi.fn(() => ({ size: 0 })),
|
||||
appendFileSync: vi.fn(),
|
||||
renameSync: vi.fn(),
|
||||
},
|
||||
mkdirSync: vi.fn(),
|
||||
existsSync: vi.fn(() => false),
|
||||
statSync: vi.fn(() => ({ size: 0 })),
|
||||
appendFileSync: vi.fn(),
|
||||
renameSync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../src/db/database', () => ({
|
||||
db: { prepare: () => ({ get: vi.fn(), run: vi.fn() }) },
|
||||
}));
|
||||
|
||||
import { getClientIp } from '../../../src/services/auditLog';
|
||||
import type { Request } from 'express';
|
||||
|
||||
function makeReq(options: {
|
||||
xff?: string | string[];
|
||||
remoteAddress?: string;
|
||||
} = {}): Request {
|
||||
return {
|
||||
headers: {
|
||||
...(options.xff !== undefined ? { 'x-forwarded-for': options.xff } : {}),
|
||||
},
|
||||
socket: { remoteAddress: options.remoteAddress ?? undefined },
|
||||
} as unknown as Request;
|
||||
}
|
||||
|
||||
describe('getClientIp', () => {
|
||||
it('returns first IP from comma-separated X-Forwarded-For string', () => {
|
||||
expect(getClientIp(makeReq({ xff: '1.2.3.4, 5.6.7.8, 9.10.11.12' }))).toBe('1.2.3.4');
|
||||
});
|
||||
|
||||
it('returns single IP when X-Forwarded-For has no comma', () => {
|
||||
expect(getClientIp(makeReq({ xff: '10.0.0.1' }))).toBe('10.0.0.1');
|
||||
});
|
||||
|
||||
it('returns first element when X-Forwarded-For is an array', () => {
|
||||
expect(getClientIp(makeReq({ xff: ['203.0.113.1', '10.0.0.1'] }))).toBe('203.0.113.1');
|
||||
});
|
||||
|
||||
it('trims whitespace from extracted IP', () => {
|
||||
expect(getClientIp(makeReq({ xff: ' 192.168.1.1 , 10.0.0.1' }))).toBe('192.168.1.1');
|
||||
});
|
||||
|
||||
it('falls back to req.socket.remoteAddress when no X-Forwarded-For', () => {
|
||||
expect(getClientIp(makeReq({ remoteAddress: '172.16.0.1' }))).toBe('172.16.0.1');
|
||||
});
|
||||
|
||||
it('returns null when no forwarded header and no socket address', () => {
|
||||
expect(getClientIp(makeReq({}))).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for empty string X-Forwarded-For', () => {
|
||||
const req = {
|
||||
headers: { 'x-forwarded-for': '' },
|
||||
socket: { remoteAddress: undefined },
|
||||
} as unknown as Request;
|
||||
expect(getClientIp(req)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user