import { describe, expect, test } from 'bun:test'; import { createRecorder } from '../src/index.ts'; describe('createRecorder', () => { test('captures attributes and end-state', () => { const rec = createRecorder(); const span = rec.startSpan('shade.test', { initial: 'on' }); span.setAttribute('extra', 1); span.setAttributes({ batch1: 'a', batch2: 'b' }); span.setStatus('ok'); span.end(); expect(rec.spans).toHaveLength(1); const s = rec.spans[0]!; expect(s.name).toBe('shade.test'); expect(s.attributes).toEqual({ initial: 'on', extra: 1, batch1: 'a', batch2: 'b' }); expect(s.status).toBe('ok'); expect(s.ended).toBe(true); }); test('records exceptions', () => { const rec = createRecorder(); const span = rec.startSpan('shade.test'); const err = new Error('boom'); span.recordException(err); span.setStatus('error', 'boom'); span.end(); expect(rec.spans[0]?.exceptions).toEqual([err]); expect(rec.spans[0]?.status).toBe('error'); expect(rec.spans[0]?.statusMessage).toBe('boom'); }); test('scanForPII catches forbidden substrings', () => { const rec = createRecorder(); const safe = rec.startSpan('shade.upload', { 'shade.peer.hash': 'abc12345' }); safe.end(); const leaky = rec.startSpan('shade.upload', { 'shade.peer.address': 'alice@example.com' }); leaky.end(); const hits = rec.scanForPII(['@', 'alice', 'peer.address']); expect(hits.length).toBeGreaterThan(0); // The safe span should not be in the hits. const safeHit = hits.find((h) => h.spanName === 'shade.upload' && h.value === 'abc12345'); expect(safeHit).toBeUndefined(); }); test('clear() drops the buffer', () => { const rec = createRecorder(); rec.startSpan('a').end(); expect(rec.spans).toHaveLength(1); rec.clear(); expect(rec.spans).toHaveLength(0); }); });