import request from 'supertest'; import app from '../../src/app'; import { pool } from '../../src/db'; import { closeDb } from './setup/testDb'; // No resetDb: this endpoint never touches the database. The pool still has to // be closed or Jest reports an open handle. afterAll(async () => { await pool.end(); await closeDb(); }); describe('POST /api/client-errors', () => { let errorSpy: jest.SpyInstance; beforeEach(() => { errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined); }); afterEach(() => { errorSpy.mockRestore(); }); it('accepts a well-formed report and logs it', async () => { const res = await request(app).post('/api/client-errors').send({ context: 'catalogue', message: 'Cannot read properties of undefined', stack: 'Error: Cannot read properties of undefined\n at ItemCard', componentStack: '\n at Catalogue\n at App', path: '/?category=3' }); expect(res.status).toBe(204); expect(errorSpy).toHaveBeenCalledTimes(1); const logged = errorSpy.mock.calls[0][0] as string; expect(logged).toContain('[client-error]'); expect(logged).toContain('context=catalogue'); expect(logged).toContain('Cannot read properties of undefined'); }); it('refuses a context it does not recognise rather than logging under a guess', async () => { const res = await request(app) .post('/api/client-errors') .send({ context: 'checkout', message: 'boom' }); expect(res.status).toBe(400); expect(res.body.error).toBe('invalid context'); expect(errorSpy).not.toHaveBeenCalled(); }); it('refuses a report with no context at all', async () => { const res = await request(app).post('/api/client-errors').send({ message: 'boom' }); expect(res.status).toBe(400); expect(errorSpy).not.toHaveBeenCalled(); }); // Truncated rather than refused: an over-long report is still the only record // of the failure, and dropping it would lose the thing worth having. it('truncates an oversized message instead of refusing it', async () => { const res = await request(app).post('/api/client-errors').send({ context: 'page', message: 'x'.repeat(5000) }); expect(res.status).toBe(204); const logged = errorSpy.mock.calls[0][0] as string; expect(logged).toContain('[truncated]'); expect(logged.length).toBeLessThan(2000); }); });