import assert from 'node:assert/strict'; import test from 'node:test'; import { countInitRequests, firstPaintHeavyHits, percentile, redactResourceUrl, summarizeLatencies, summarizeResources, } from './initialLoadMetrics'; test('redactResourceUrl drops query and origin', () => { assert.equal( redactResourceUrl('http://127.0.0.1:8080/api/v1/chat/sessions?project_id=1&token=secret'), '/api/v1/chat/sessions', ); assert.equal(redactResourceUrl('/assets/index-abc.js'), '/assets/index-abc.js'); }); test('summarizeResources flags deferred heavy chunks', () => { const summary = summarizeResources([ { path: '/assets/index-abc.js', initiator: 'script', durationMs: 12, transferBytes: 100, decodedBytes: 400 }, { path: '/assets/katex-xyz.js', initiator: 'script', durationMs: 4, transferBytes: 50, decodedBytes: 200 }, ]); assert.equal(summary.jsCount, 2); assert.equal(summary.transferBytes, 150); assert.deepEqual(summary.heavyChunks, ['/assets/katex-xyz.js']); }); test('firstPaintHeavyHits lists only deferred module paths', () => { assert.deepEqual( firstPaintHeavyHits([ { path: '/assets/index-abc.js', initiator: 'script', durationMs: 1, transferBytes: 10, decodedBytes: 40 }, { path: '/assets/mermaid-abc.js', initiator: 'script', durationMs: 2, transferBytes: 20, decodedBytes: 80 }, { path: '/assets/docx-abc.js', initiator: 'script', durationMs: 3, transferBytes: 30, decodedBytes: 120 }, ]), ['/assets/mermaid-abc.js', '/assets/docx-abc.js'], ); assert.deepEqual( firstPaintHeavyHits([ { path: '/assets/index-abc.js', initiator: 'script', durationMs: 1, transferBytes: 10, decodedBytes: 40 }, ]), [], ); }); test('countInitRequests separates member fanout from session lists', () => { const counts = countInitRequests([ { method: 'GET', path: '/api/v1/projects/1/members' }, { method: 'GET', path: '/api/v1/projects/2/members' }, { method: 'POST', path: '/api/v1/projects/2/members' }, { method: 'GET', path: '/api/v1/chat/sessions' }, { method: 'GET', path: '/api/v1/chat/sessions' }, ]); assert.equal(counts.memberGetCount, 2); assert.equal(counts.distinctMemberProjects, 2); assert.equal(counts.sessionListGetCount, 2); }); test('percentile uses nearest-rank and stays null on empty input', () => { assert.equal(percentile([], 75), null); assert.equal(percentile([10, 20, 30, 40], 75), 30); }); test('summarizeLatencies reports p50/p75/p95', () => { assert.deepEqual(summarizeLatencies([]), { n: 0, p50: null, p75: null, p95: null, min: null, max: null, }); // 20 samples: p50=rank10 → 1000; p75=rank15 → 1500; p95=rank19 → 1900 const values = Array.from({ length: 20 }, (_, i) => (i + 1) * 100); const summary = summarizeLatencies(values); assert.equal(summary.n, 20); assert.equal(summary.p50, 1000); assert.equal(summary.p75, 1500); assert.equal(summary.p95, 1900); assert.equal(summary.min, 100); assert.equal(summary.max, 2000); });