import { Either, JsonResponse, PenguenoRequest, PenguenoResponse, getResponseMetrics, MetricValueTag, type BaseRequest, type ServerTrace, } from '../lib/index'; import { CollectingTrace, TestTraceable } from './test_utils'; const makeBaseRequest = (overrides: Partial = {}): BaseRequest => ({ url: 'https://example.com/hello?x=1', method: 'GET', header: () => ({}), formData: async () => new FormData(), json: async () => ({}), text: async () => '', param: () => undefined, query: () => ({}), queries: () => ({}), ...overrides, }); describe('server/request + server/response', () => { beforeEach(() => { jest.useFakeTimers(); jest.setSystemTime(new Date('2020-01-01T00:00:00.000Z')); }); afterEach(() => { jest.useRealTimers(); jest.restoreAllMocks(); }); test('PenguenoRequest.from creates response headers', () => { jest.spyOn(globalThis.crypto, 'randomUUID').mockReturnValue('00000000-0000-0000-0000-000000000000'); jest.spyOn(Math, 'random').mockReturnValue(0); const baseReq = makeBaseRequest(); const trace = new CollectingTrace(); const req = PenguenoRequest.from(TestTraceable.of(baseReq, trace)); jest.setSystemTime(new Date('2020-01-01T00:00:01.000Z')); const headers = req.get().getResponseHeaders(); expect(headers).toMatchObject({ RequestId: '00000000-0000-0000-0000-000000000000', Hai: 'hewwo :D', }); expect(headers.DeltaUnix).toBe('1000'); expect(headers.RequestReceivedUnix).toBe('1577836800000'); expect(headers.RequestHandleUnix).toBe('1577836801000'); }); test('PenguenoResponse sets headers and emits metrics', () => { jest.spyOn(globalThis.crypto, 'randomUUID').mockReturnValue('00000000-0000-0000-0000-000000000000'); jest.spyOn(Math, 'random').mockReturnValue(0); const trace = new CollectingTrace(); const req = PenguenoRequest.from(TestTraceable.of(makeBaseRequest(), trace)); jest.setSystemTime(new Date('2020-01-01T00:00:01.000Z')); const res = new PenguenoResponse(req, 'hi', { status: 200, headers: { X: 'Y' } }); expect(res.status).toBe(200); expect(res.statusText).toBe('OK'); expect(res.headers['Content-Type']).toBe('text/plain; charset=utf-8'); expect(res.headers.X).toBe('Y'); const lastTrace = trace.events[trace.events.length - 1]; expect(lastTrace).toBeDefined(); const metricValues = lastTrace[lastTrace.length - 1] as any[]; expect(Array.isArray(metricValues)).toBe(true); expect( metricValues.some((m) => m._tag === MetricValueTag && m.name === 'response.2xx.count' && m.value === 1), ).toBe(true); }); test('JsonResponse formats Either bodies', () => { jest.spyOn(globalThis.crypto, 'randomUUID').mockReturnValue('00000000-0000-0000-0000-000000000000'); const trace = new CollectingTrace(); const req = PenguenoRequest.from(TestTraceable.of(makeBaseRequest(), trace)); const ok = new JsonResponse(req, Either.right('yay'), { status: 200, headers: {} }); expect(ok.headers['Content-Type']).toBe('application/json; charset=utf-8'); expect(ok.body()).toBe('{"ok":"yay"}'); const err = new JsonResponse(req, Either.left('nope'), { status: 400, headers: {} }); expect(err.body()).toBe('{"error":"nope"}'); }); test('getResponseMetrics returns one active bucket', () => { jest.setSystemTime(new Date('2020-01-01T00:00:00.000Z')); const metrics = getResponseMetrics(201, 12); expect(metrics).toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'response.2xx.count', value: 1 }), expect.objectContaining({ name: 'response.2xx.time', value: 12 }), ]), ); const inactive = metrics.filter((m) => m.value === 0); expect(inactive).toHaveLength(5); }); });