import { describe, it, expect, vi, afterEach } from 'vitest'; import { AuditCollector } from '../src/audit/collector.js'; import type { McpdClient } from '../src/http/mcpd-client.js'; function mockClient(): McpdClient { return { post: vi.fn(async () => ({})), get: vi.fn(async () => ({})), put: vi.fn(async () => ({})), delete: vi.fn(async () => {}), forward: vi.fn(async () => ({ status: 200, body: {} })), withHeaders: vi.fn(() => mockClient()), } as unknown as McpdClient; } describe('AuditCollector userName', () => { let collector: AuditCollector; let client: McpdClient; afterEach(async () => { if (collector) await collector.dispose(); }); it('auto-fills userName from session map', async () => { client = mockClient(); collector = new AuditCollector(client, 'test-project'); collector.setSessionUserName('sess-1', 'michal'); collector.emit({ timestamp: new Date().toISOString(), sessionId: 'sess-1', eventKind: 'session_bind', source: 'mcplocal', verified: true, payload: {}, }); await collector.flush(); const posted = vi.mocked(client.post).mock.calls[0]![1] as Array>; expect(posted).toHaveLength(1); expect(posted[0]!['userName']).toBe('michal'); expect(posted[0]!['projectName']).toBe('test-project'); }); it('does not overwrite explicitly set userName', async () => { client = mockClient(); collector = new AuditCollector(client, 'test-project'); collector.setSessionUserName('sess-1', 'michal'); collector.emit({ timestamp: new Date().toISOString(), sessionId: 'sess-1', eventKind: 'tool_call_trace', source: 'mcplocal', verified: true, userName: 'admin', payload: {}, }); await collector.flush(); const posted = vi.mocked(client.post).mock.calls[0]![1] as Array>; expect(posted[0]!['userName']).toBe('admin'); }); it('leaves userName undefined when no session mapping exists', async () => { client = mockClient(); collector = new AuditCollector(client, 'test-project'); collector.emit({ timestamp: new Date().toISOString(), sessionId: 'sess-unknown', eventKind: 'gate_decision', source: 'client', verified: false, payload: {}, }); await collector.flush(); const posted = vi.mocked(client.post).mock.calls[0]![1] as Array>; expect(posted[0]!['userName']).toBeUndefined(); }); it('fills userName for multiple sessions independently', async () => { client = mockClient(); collector = new AuditCollector(client, 'test-project'); collector.setSessionUserName('sess-a', 'alice'); collector.setSessionUserName('sess-b', 'bob'); collector.emit({ timestamp: new Date().toISOString(), sessionId: 'sess-a', eventKind: 'session_bind', source: 'mcplocal', verified: true, payload: {}, }); collector.emit({ timestamp: new Date().toISOString(), sessionId: 'sess-b', eventKind: 'session_bind', source: 'mcplocal', verified: true, payload: {}, }); await collector.flush(); const posted = vi.mocked(client.post).mock.calls[0]![1] as Array>; expect(posted).toHaveLength(2); expect(posted[0]!['userName']).toBe('alice'); expect(posted[1]!['userName']).toBe('bob'); }); });