/** * Tests for RoboticsProducer - equivalent to Python's test_producer.py */ import { test, expect, describe, beforeEach, afterEach } from "bun:test"; import { robotics } from "../src/index"; import { TEST_SERVER_URL, TestRoomManager, MessageCollector, sleep, assertIsConnected, assertIsDisconnected } from "./setup"; const { RoboticsProducer } = robotics; describe("RoboticsProducer", () => { let producer: robotics.RoboticsProducer; let roomManager: TestRoomManager; beforeEach(() => { producer = new RoboticsProducer(TEST_SERVER_URL); roomManager = new TestRoomManager(); }); afterEach(async () => { if (producer.isConnected()) { await producer.disconnect(); } await roomManager.cleanup(producer); }); test("producer connection", async () => { // Create room first const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); expect(producer.isConnected()).toBe(false); const success = await producer.connect(workspaceId, roomId); expect(success).toBe(true); assertIsConnected(producer, workspaceId, roomId); const info = producer.getConnectionInfo(); expect(info.role).toBe("producer"); await producer.disconnect(); assertIsDisconnected(producer); }); test("producer connection info", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); await producer.connect(workspaceId, roomId); const info = producer.getConnectionInfo(); expect(info.connected).toBe(true); expect(info.room_id).toBe(roomId); expect(info.workspace_id).toBe(workspaceId); expect(info.role).toBe("producer"); expect(info.participant_id).toBeTruthy(); expect(info.base_url).toBe(TEST_SERVER_URL); }); test("send joint update", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); await producer.connect(workspaceId, roomId); const joints = [ { name: "shoulder", value: 45.0 }, { name: "elbow", value: -20.0 }, { name: "wrist", value: 10.0 } ]; // Should not throw an exception await producer.sendJointUpdate(joints); }); test("send state sync", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); await producer.connect(workspaceId, roomId); const state = { shoulder: 45.0, elbow: -20.0, wrist: 10.0 }; // Should not throw an exception await producer.sendStateSync(state); }); test("send emergency stop", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); await producer.connect(workspaceId, roomId); // Should not throw an exception await producer.sendEmergencyStop("Test emergency stop"); await producer.sendEmergencyStop(); // Default reason }); test("producer callbacks", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); let connectedCalled = false; let disconnectedCalled = false; let errorCalled = false; let errorMessage: string | null = null; producer.onConnected(() => { connectedCalled = true; }); producer.onDisconnected(() => { disconnectedCalled = true; }); producer.onError((error) => { errorCalled = true; errorMessage = error; }); // Connect and disconnect await producer.connect(workspaceId, roomId); await sleep(100); // Give callbacks time to execute expect(connectedCalled).toBe(true); await producer.disconnect(); await sleep(100); // Give callbacks time to execute expect(disconnectedCalled).toBe(true); }); test("send without connection", async () => { expect(producer.isConnected()).toBe(false); await expect(producer.sendJointUpdate([{ name: "test", value: 0 }])) .rejects.toThrow("Must be connected"); await expect(producer.sendStateSync({ test: 0 })) .rejects.toThrow("Must be connected"); await expect(producer.sendEmergencyStop()) .rejects.toThrow("Must be connected"); }); test("multiple connections", async () => { // Connect to first room const { workspaceId: workspaceId1, roomId: roomId1 } = await producer.createRoom(); roomManager.addRoom(workspaceId1, roomId1); await producer.connect(workspaceId1, roomId1); expect(producer.getConnectionInfo().room_id).toBe(roomId1); expect(producer.getConnectionInfo().workspace_id).toBe(workspaceId1); // Create second room const { workspaceId: workspaceId2, roomId: roomId2 } = await producer.createRoom(); roomManager.addRoom(workspaceId2, roomId2); // Connect to second room (should disconnect from first) await producer.connect(workspaceId2, roomId2); expect(producer.getConnectionInfo().room_id).toBe(roomId2); expect(producer.getConnectionInfo().workspace_id).toBe(workspaceId2); expect(producer.isConnected()).toBe(true); }); test("duplicate producer connection", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); const producer2 = new RoboticsProducer(TEST_SERVER_URL); try { // First producer connects successfully const success1 = await producer.connect(workspaceId, roomId); expect(success1).toBe(true); // Second producer should fail to connect as producer const success2 = await producer2.connect(workspaceId, roomId); expect(success2).toBe(false); // Should fail since room already has producer } finally { if (producer2.isConnected()) { await producer2.disconnect(); } } }); test("custom participant id", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); const customId = "custom-producer-123"; await producer.connect(workspaceId, roomId, customId); const info = producer.getConnectionInfo(); expect(info.participant_id).toBe(customId); }); test("large joint update", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); await producer.connect(workspaceId, roomId); // Create a large joint update const joints = Array.from({ length: 100 }, (_, i) => ({ name: `joint_${i}`, value: i })); // Should handle large updates without issue await producer.sendJointUpdate(joints); }); test("rapid updates", async () => { const { workspaceId, roomId } = await producer.createRoom(); roomManager.addRoom(workspaceId, roomId); await producer.connect(workspaceId, roomId); // Send multiple rapid updates for (let i = 0; i < 10; i++) { await producer.sendStateSync({ joint1: i, joint2: i * 2 }); await sleep(10); // Small delay } }); });