import { describe, expect, test } from 'bun:test'; import { SubtleCryptoProvider } from '@shade/crypto-web'; import { createRecorder } from '@shade/observability'; import { TransferEngine, MemoryControlChannel, MemoryTransferTransport, type IncomingTransfer, type TransferHandle, type TransferResult, } from '../src/index.js'; const crypto = new SubtleCryptoProvider(); const ALICE = 'alice@trace-test.local'; const BOB = 'bob@trace-test.local'; const SECRET_PAYLOAD = new TextEncoder().encode( 'classified-shadow-token-DO-NOT-LOG', ); function makePair(observability: ReturnType) { const { a: ctrlA, b: ctrlB } = MemoryControlChannel.linked(ALICE, BOB); const { a: txA, b: txB } = MemoryTransferTransport.linked(ALICE, BOB); const sender = new TransferEngine({ crypto, controlChannel: ctrlA, transport: txA, myAddress: ALICE, observability, }); const receiver = new TransferEngine({ crypto, controlChannel: ctrlB, transport: txB, myAddress: BOB, observability, }); txB.setChunkHandler(async (from, sid, lane, seq, bytes) => receiver.receiveChunk(from, sid, lane, seq, bytes), ); return { sender, receiver }; } describe('TransferEngine observability', () => { test('emits upload+download spans with PII-safe attributes', async () => { const rec = createRecorder(); const { sender, receiver } = makePair(rec); let resolveRecv!: (h: TransferHandle) => void; const recvP = new Promise((r) => { resolveRecv = r; }); const unsub = receiver.onIncomingTransfer(async (incoming: IncomingTransfer) => { const h = await incoming.accept({ output: { kind: 'buffer' } }); resolveRecv(h); }); const handle = await sender.upload({ to: BOB, input: SECRET_PAYLOAD, lanes: 1, chunkSize: 4096, }); const recvH = await recvP; await Promise.all([handle.done(), recvH.done()]); unsub(); const upload = rec.spans.find((s) => s.name === 'shade.transfer.upload'); const download = rec.spans.find((s) => s.name === 'shade.transfer.download'); expect(upload).toBeDefined(); expect(download).toBeDefined(); expect(upload!.attributes['shade.direction']).toBe('upload'); expect(upload!.attributes['shade.peer.hash']).toMatch(/^[0-9a-f]{8}$/); expect(upload!.attributes['shade.bytes.bin']).toBe('≤4KB'); expect(upload!.attributes['shade.lane.count']).toBe(1); expect(upload!.attributes['shade.result']).toBe('ok'); expect(upload!.status).toBe('ok'); expect(upload!.ended).toBe(true); expect(download!.attributes['shade.direction']).toBe('download'); expect(download!.attributes['shade.peer.hash']).toMatch(/^[0-9a-f]{8}$/); expect(download!.attributes['shade.result']).toBe('ok'); expect(download!.ended).toBe(true); // PII guard: no plaintext addresses, no payload, no exact byte counts. const hits = rec.scanForPII([ ALICE, BOB, 'trace-test', 'classified-shadow-token', String(SECRET_PAYLOAD.length), ]); if (hits.length > 0) { throw new Error(`PII leak: ${JSON.stringify(hits, null, 2)}`); } }); test('failed upload marks span as error', async () => { const rec = createRecorder(); const { sender } = makePair(rec); // No receiver wired, so probe will succeed but accept() never fires. // Force failure by aborting before the upload completes. const ac = new AbortController(); const handle = await sender.upload({ to: BOB, input: new Uint8Array(8), lanes: 1, chunkSize: 4, signal: ac.signal, }); ac.abort(); await handle.done().catch(() => undefined); const upload = rec.spans.find((s) => s.name === 'shade.transfer.upload'); expect(upload).toBeDefined(); expect(upload!.ended).toBe(true); // Either resulted in error or completed cleanly before abort took effect. // We only assert that attributes never echoed peer addresses. const hits = rec.scanForPII([ALICE, BOB, 'trace-test']); if (hits.length > 0) { throw new Error(`PII leak (failure path): ${JSON.stringify(hits, null, 2)}`); } }); }); // Type-level guard so TransferResult import isn't unused. const _t: TransferResult | null = null; void _t;