344 lines
9.4 KiB
TypeScript
344 lines
9.4 KiB
TypeScript
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();
|
|
});
|
|
}); |