import { test, expect, describe, beforeEach } from "bun:test"; import { SFUClientManager } from "../../src/services/SFUClientManager.ts"; import type { ISFUClient } from "../../src/interfaces/ISFUTypes.ts"; // Mock WebRTC Transport class MockWebRtcTransport { id: string; closed = false; constructor(id: string) { this.id = id; } close() { this.closed = true; } } // Mock Producer class MockProducer { id: string; closed = false; constructor(id: string) { this.id = id; } close() { this.closed = true; } } // Mock Consumer class MockConsumer { id: string; closed = false; constructor(id: string) { this.id = id; } close() { this.closed = true; } } // Mock WebSocket class MockWebSocket { sentMessages: string[] = []; send(data: string) { this.sentMessages.push(data); } } describe("SFUClientManager", () => { let clientManager: SFUClientManager; beforeEach(() => { clientManager = new SFUClientManager(); }); test("should add and retrieve clients", () => { const mockWs = new MockWebSocket(); const client: ISFUClient = { id: "client-1", role: "publisher", ws: mockWs, consumers: new Map() }; clientManager.addClient(client); const retrievedClient = clientManager.getClient("client-1"); expect(retrievedClient).toBe(client); }); test("should set and manage client transport", () => { const mockWs = new MockWebSocket(); const client: ISFUClient = { id: "client-1", role: "publisher", ws: mockWs, consumers: new Map() }; clientManager.addClient(client); const transport = new MockWebRtcTransport("transport-1") as any; clientManager.setClientTransport("client-1", transport); const retrievedClient = clientManager.getClient("client-1"); expect(retrievedClient?.transport).toBe(transport); }); test("should manage client producer", () => { const mockWs = new MockWebSocket(); const client: ISFUClient = { id: "publisher-1", role: "publisher", ws: mockWs, consumers: new Map() }; clientManager.addClient(client); const producer = new MockProducer("producer-1") as any; clientManager.setClientProducer("publisher-1", producer); const retrievedClient = clientManager.getClient("publisher-1"); expect(retrievedClient?.producer).toBe(producer); const retrievedProducer = clientManager.getProducer("producer-1"); expect(retrievedProducer).toBe(producer); }); test("should manage client consumers", () => { const mockWs = new MockWebSocket(); const client: ISFUClient = { id: "subscriber-1", role: "subscriber", ws: mockWs, consumers: new Map() }; clientManager.addClient(client); const consumer = new MockConsumer("consumer-1") as any; clientManager.addClientConsumer("subscriber-1", consumer); const retrievedClient = clientManager.getClient("subscriber-1"); expect(retrievedClient?.consumers.has("consumer-1")).toBe(true); expect(retrievedClient?.consumers.get("consumer-1")).toBe(consumer); const retrievedConsumer = clientManager.getConsumer("consumer-1"); expect(retrievedConsumer).toBe(consumer); }); test("should remove client consumer", () => { const mockWs = new MockWebSocket(); const client: ISFUClient = { id: "subscriber-1", role: "subscriber", ws: mockWs, consumers: new Map() }; clientManager.addClient(client); const consumer = new MockConsumer("consumer-1") as any; clientManager.addClientConsumer("subscriber-1", consumer); // Verify consumer is added expect(clientManager.getConsumer("consumer-1")).toBe(consumer); // Remove consumer clientManager.removeClientConsumer("subscriber-1", "consumer-1"); // Verify consumer is removed and closed expect(clientManager.getConsumer("consumer-1")).toBeUndefined(); expect(consumer.closed).toBe(true); const retrievedClient = clientManager.getClient("subscriber-1"); expect(retrievedClient?.consumers.has("consumer-1")).toBe(false); }); test("should get publishers and subscribers separately", () => { const publisherWs = new MockWebSocket(); const subscriberWs1 = new MockWebSocket(); const subscriberWs2 = new MockWebSocket(); const publisher: ISFUClient = { id: "publisher-1", role: "publisher", ws: publisherWs, consumers: new Map(), producer: new MockProducer("producer-1") as any }; const subscriber1: ISFUClient = { id: "subscriber-1", role: "subscriber", ws: subscriberWs1, consumers: new Map() }; const subscriber2: ISFUClient = { id: "subscriber-2", role: "subscriber", ws: subscriberWs2, consumers: new Map() }; clientManager.addClient(publisher); clientManager.addClient(subscriber1); clientManager.addClient(subscriber2); const publishers = clientManager.getPublishers(); const subscribers = clientManager.getSubscribers(); expect(publishers).toHaveLength(1); expect(publishers[0]).toBe(publisher); expect(subscribers).toHaveLength(2); expect(subscribers).toContain(subscriber1); expect(subscribers).toContain(subscriber2); }); test("should get all producers", () => { const publisherWs1 = new MockWebSocket(); const publisherWs2 = new MockWebSocket(); const publisher1: ISFUClient = { id: "publisher-1", role: "publisher", ws: publisherWs1, consumers: new Map() }; const publisher2: ISFUClient = { id: "publisher-2", role: "publisher", ws: publisherWs2, consumers: new Map() }; clientManager.addClient(publisher1); clientManager.addClient(publisher2); const producer1 = new MockProducer("producer-1") as any; const producer2 = new MockProducer("producer-2") as any; clientManager.setClientProducer("publisher-1", producer1); clientManager.setClientProducer("publisher-2", producer2); const allProducers = clientManager.getAllProducers(); expect(allProducers).toHaveLength(2); expect(allProducers).toContain(producer1); expect(allProducers).toContain(producer2); }); test("should remove client and cleanup resources", () => { const mockWs = new MockWebSocket(); const client: ISFUClient = { id: "client-1", role: "publisher", ws: mockWs, consumers: new Map() }; clientManager.addClient(client); // Add transport, producer, and consumer const transport = new MockWebRtcTransport("transport-1") as any; const producer = new MockProducer("producer-1") as any; const consumer = new MockConsumer("consumer-1") as any; clientManager.setClientTransport("client-1", transport); clientManager.setClientProducer("client-1", producer); clientManager.addClientConsumer("client-1", consumer); // Remove client clientManager.removeClient("client-1"); // Verify client is removed expect(clientManager.getClient("client-1")).toBeUndefined(); // Verify resources are cleaned up expect(transport.closed).toBe(true); expect(producer.closed).toBe(true); expect(consumer.closed).toBe(true); // Verify global maps are cleaned up expect(clientManager.getProducer("producer-1")).toBeUndefined(); expect(clientManager.getConsumer("consumer-1")).toBeUndefined(); }); test("should provide correct statistics", () => { const publisherWs = new MockWebSocket(); const subscriberWs1 = new MockWebSocket(); const subscriberWs2 = new MockWebSocket(); const publisher: ISFUClient = { id: "publisher-1", role: "publisher", ws: publisherWs, consumers: new Map(), producer: new MockProducer("producer-1") as any }; const subscriber1: ISFUClient = { id: "subscriber-1", role: "subscriber", ws: subscriberWs1, consumers: new Map() }; const subscriber2: ISFUClient = { id: "subscriber-2", role: "subscriber", ws: subscriberWs2, consumers: new Map() }; clientManager.addClient(publisher); clientManager.addClient(subscriber1); clientManager.addClient(subscriber2); const producer = new MockProducer("producer-1") as any; const consumer1 = new MockConsumer("consumer-1") as any; const consumer2 = new MockConsumer("consumer-2") as any; clientManager.setClientProducer("publisher-1", producer); clientManager.addClientConsumer("subscriber-1", consumer1); clientManager.addClientConsumer("subscriber-2", consumer2); const stats = clientManager.getStats(); expect(stats.clients).toBe(3); expect(stats.publishers).toBe(1); expect(stats.subscribers).toBe(2); expect(stats.producers).toBe(1); expect(stats.consumers).toBe(2); }); test("should handle removing non-existent client gracefully", () => { expect(() => { clientManager.removeClient("non-existent"); }).not.toThrow(); }); test("should handle operations on non-existent clients gracefully", () => { const transport = new MockWebRtcTransport("transport-1") as any; const producer = new MockProducer("producer-1") as any; const consumer = new MockConsumer("consumer-1") as any; expect(() => { clientManager.setClientTransport("non-existent", transport); clientManager.setClientProducer("non-existent", producer); clientManager.addClientConsumer("non-existent", consumer); clientManager.removeClientConsumer("non-existent", "consumer-1"); }).not.toThrow(); }); });