@teneo-protocol/sdk 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/.github/workflows/publish-npm.yml +8 -6
  2. package/CHANGELOG.md +265 -0
  3. package/README.md +406 -53
  4. package/dist/core/websocket-client.d.ts +12 -0
  5. package/dist/core/websocket-client.d.ts.map +1 -1
  6. package/dist/core/websocket-client.js +22 -2
  7. package/dist/core/websocket-client.js.map +1 -1
  8. package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts +76 -0
  9. package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts.map +1 -0
  10. package/dist/handlers/message-handlers/agent-room-operation-response-handler.js +70 -0
  11. package/dist/handlers/message-handlers/agent-room-operation-response-handler.js.map +1 -0
  12. package/dist/handlers/message-handlers/agent-selected-handler.d.ts +92 -38
  13. package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -1
  14. package/dist/handlers/message-handlers/agent-status-update-handler.d.ts +904 -0
  15. package/dist/handlers/message-handlers/agent-status-update-handler.d.ts.map +1 -0
  16. package/dist/handlers/message-handlers/agent-status-update-handler.js +51 -0
  17. package/dist/handlers/message-handlers/agent-status-update-handler.js.map +1 -0
  18. package/dist/handlers/message-handlers/auth-error-handler.d.ts +45 -31
  19. package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -1
  20. package/dist/handlers/message-handlers/auth-message-handler.d.ts +6 -0
  21. package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -1
  22. package/dist/handlers/message-handlers/auth-message-handler.js +65 -5
  23. package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -1
  24. package/dist/handlers/message-handlers/auth-required-handler.d.ts +49 -31
  25. package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -1
  26. package/dist/handlers/message-handlers/auth-success-handler.d.ts +6 -0
  27. package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -1
  28. package/dist/handlers/message-handlers/auth-success-handler.js +46 -4
  29. package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -1
  30. package/dist/handlers/message-handlers/challenge-handler.d.ts +45 -31
  31. package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -1
  32. package/dist/handlers/message-handlers/error-message-handler.d.ts +49 -31
  33. package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -1
  34. package/dist/handlers/message-handlers/index.d.ts +5 -0
  35. package/dist/handlers/message-handlers/index.d.ts.map +1 -1
  36. package/dist/handlers/message-handlers/index.js +23 -1
  37. package/dist/handlers/message-handlers/index.js.map +1 -1
  38. package/dist/handlers/message-handlers/list-available-agents-handler.d.ts +877 -0
  39. package/dist/handlers/message-handlers/list-available-agents-handler.d.ts.map +1 -0
  40. package/dist/handlers/message-handlers/list-available-agents-handler.js +38 -0
  41. package/dist/handlers/message-handlers/list-available-agents-handler.js.map +1 -0
  42. package/dist/handlers/message-handlers/list-room-agents-handler.d.ts +886 -0
  43. package/dist/handlers/message-handlers/list-room-agents-handler.d.ts.map +1 -0
  44. package/dist/handlers/message-handlers/list-room-agents-handler.js +51 -0
  45. package/dist/handlers/message-handlers/list-room-agents-handler.js.map +1 -0
  46. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +178 -89
  47. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -1
  48. package/dist/handlers/message-handlers/ping-pong-handler.d.ts +62 -58
  49. package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -1
  50. package/dist/handlers/message-handlers/regular-message-handler.d.ts +31 -29
  51. package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -1
  52. package/dist/handlers/message-handlers/room-operation-response-handler.d.ts +328 -0
  53. package/dist/handlers/message-handlers/room-operation-response-handler.d.ts.map +1 -0
  54. package/dist/handlers/message-handlers/room-operation-response-handler.js +92 -0
  55. package/dist/handlers/message-handlers/room-operation-response-handler.js.map +1 -0
  56. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +53 -31
  57. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -1
  58. package/dist/handlers/message-handlers/types.d.ts +2 -0
  59. package/dist/handlers/message-handlers/types.d.ts.map +1 -1
  60. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +53 -31
  61. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -1
  62. package/dist/managers/agent-room-manager.d.ts +222 -0
  63. package/dist/managers/agent-room-manager.d.ts.map +1 -0
  64. package/dist/managers/agent-room-manager.js +508 -0
  65. package/dist/managers/agent-room-manager.js.map +1 -0
  66. package/dist/managers/index.d.ts +2 -0
  67. package/dist/managers/index.d.ts.map +1 -1
  68. package/dist/managers/index.js +5 -1
  69. package/dist/managers/index.js.map +1 -1
  70. package/dist/managers/room-management-manager.d.ts +213 -0
  71. package/dist/managers/room-management-manager.d.ts.map +1 -0
  72. package/dist/managers/room-management-manager.js +440 -0
  73. package/dist/managers/room-management-manager.js.map +1 -0
  74. package/dist/managers/room-manager.d.ts +4 -4
  75. package/dist/managers/room-manager.d.ts.map +1 -1
  76. package/dist/managers/room-manager.js.map +1 -1
  77. package/dist/teneo-sdk.d.ts +333 -13
  78. package/dist/teneo-sdk.d.ts.map +1 -1
  79. package/dist/teneo-sdk.js +468 -1
  80. package/dist/teneo-sdk.js.map +1 -1
  81. package/dist/types/config.d.ts +63 -54
  82. package/dist/types/config.d.ts.map +1 -1
  83. package/dist/types/config.js +8 -4
  84. package/dist/types/config.js.map +1 -1
  85. package/dist/types/error-codes.d.ts +2 -0
  86. package/dist/types/error-codes.d.ts.map +1 -1
  87. package/dist/types/error-codes.js +3 -0
  88. package/dist/types/error-codes.js.map +1 -1
  89. package/dist/types/events.d.ts +132 -68
  90. package/dist/types/events.d.ts.map +1 -1
  91. package/dist/types/events.js.map +1 -1
  92. package/dist/types/index.d.ts +1 -1
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/dist/types/index.js +27 -2
  95. package/dist/types/index.js.map +1 -1
  96. package/dist/types/messages.d.ts +11396 -2559
  97. package/dist/types/messages.d.ts.map +1 -1
  98. package/dist/types/messages.js +294 -27
  99. package/dist/types/messages.js.map +1 -1
  100. package/examples/.env.example +1 -1
  101. package/examples/agent-room-management-example.ts +334 -0
  102. package/examples/claude-agent-x-follower/.env.example +2 -2
  103. package/examples/claude-agent-x-follower/QUICKSTART.md +1 -1
  104. package/examples/claude-agent-x-follower/README.md +1 -1
  105. package/examples/n8n-teneo/.env.example +2 -2
  106. package/examples/n8n-teneo/README.md +1 -1
  107. package/examples/openai-teneo/.env.example +2 -2
  108. package/examples/openai-teneo/README.md +1 -1
  109. package/examples/production-dashboard/.env.example +2 -2
  110. package/examples/production-dashboard/README.md +89 -12
  111. package/examples/production-dashboard/public/dashboard.html +1173 -601
  112. package/examples/production-dashboard/server.ts +347 -5
  113. package/examples/room-management-example.ts +285 -0
  114. package/examples/usage/.env.example +1 -1
  115. package/examples/usage/01-connect.ts +1 -1
  116. package/examples/usage/02-list-agents.ts +1 -1
  117. package/examples/usage/03-pick-agent.ts +1 -1
  118. package/examples/usage/04-find-by-capability.ts +1 -1
  119. package/examples/usage/05-webhook-example.ts +1 -1
  120. package/examples/usage/06-simple-api-server.ts +1 -1
  121. package/examples/usage/07-event-listener.ts +1 -1
  122. package/examples/usage/README.md +1 -1
  123. package/package.json +9 -1
  124. package/src/core/websocket-client.ts +26 -2
  125. package/src/handlers/message-handlers/agent-room-operation-response-handler.ts +83 -0
  126. package/src/handlers/message-handlers/agent-status-update-handler.ts +58 -0
  127. package/src/handlers/message-handlers/auth-message-handler.ts +73 -5
  128. package/src/handlers/message-handlers/auth-success-handler.ts +58 -6
  129. package/src/handlers/message-handlers/index.ts +19 -0
  130. package/src/handlers/message-handlers/list-available-agents-handler.ts +41 -0
  131. package/src/handlers/message-handlers/list-room-agents-handler.ts +61 -0
  132. package/src/handlers/message-handlers/room-operation-response-handler.ts +105 -0
  133. package/src/handlers/message-handlers/types.ts +6 -0
  134. package/src/managers/agent-room-manager.ts +609 -0
  135. package/src/managers/index.ts +2 -0
  136. package/src/managers/room-management-manager.ts +523 -0
  137. package/src/managers/room-manager.ts +4 -5
  138. package/src/teneo-sdk.ts +505 -4
  139. package/src/types/config.ts +10 -5
  140. package/src/types/error-codes.ts +4 -0
  141. package/src/types/events.ts +24 -0
  142. package/src/types/index.ts +55 -0
  143. package/src/types/messages.ts +374 -41
  144. package/tests/integration/room-management.test.ts +514 -0
  145. package/tests/integration/websocket.test.ts +1 -1
  146. package/tests/unit/handlers/agent-room-operation-response-handler.test.ts +394 -0
  147. package/tests/unit/handlers/agent-status-update-handler.test.ts +407 -0
  148. package/tests/unit/handlers/auth-success-handler-rooms.test.ts +699 -0
  149. package/tests/unit/handlers/list-available-agents-handler.test.ts +256 -0
  150. package/tests/unit/handlers/list-room-agents-handler.test.ts +294 -0
  151. package/tests/unit/handlers/room-operation-response-handler.test.ts +527 -0
  152. package/tests/unit/managers/agent-room-manager.test.ts +534 -0
  153. package/tests/unit/managers/room-management-manager.test.ts +438 -0
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Unit tests for RoomOperationResponseHandler
3
+ * Tests response handling for room CRUD operations
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, vi } from "vitest";
7
+ import { RoomOperationResponseHandler } from "../../../src/handlers/message-handlers/room-operation-response-handler";
8
+ import { HandlerContext } from "../../../src/handlers/message-handlers/types";
9
+ import { RoomInfo, Logger } from "../../../src/types";
10
+ import { SDKError } from "../../../src/types/events";
11
+ import { ErrorCode } from "../../../src/types/error-codes";
12
+
13
+ describe("RoomOperationResponseHandler", () => {
14
+ let handler: RoomOperationResponseHandler;
15
+ let mockContext: HandlerContext;
16
+ let mockLogger: Logger;
17
+ let mockRoomManagementManager: any;
18
+ let emitSpy: ReturnType<typeof vi.fn>;
19
+ let sendWebhookSpy: ReturnType<typeof vi.fn>;
20
+
21
+ beforeEach(() => {
22
+ // Create mock logger
23
+ mockLogger = {
24
+ debug: vi.fn(),
25
+ info: vi.fn(),
26
+ warn: vi.fn(),
27
+ error: vi.fn()
28
+ };
29
+
30
+ // Create mock room management manager
31
+ mockRoomManagementManager = {
32
+ upsertRoom: vi.fn(),
33
+ removeRoom: vi.fn()
34
+ };
35
+
36
+ // Create spies
37
+ emitSpy = vi.fn();
38
+ sendWebhookSpy = vi.fn().mockResolvedValue(undefined);
39
+
40
+ // Create mock context
41
+ mockContext = {
42
+ emit: emitSpy,
43
+ sendWebhook: sendWebhookSpy,
44
+ logger: mockLogger,
45
+ getConnectionState: vi.fn(),
46
+ getAuthState: vi.fn(),
47
+ updateConnectionState: vi.fn(),
48
+ updateAuthState: vi.fn(),
49
+ sendMessage: vi.fn(),
50
+ roomManagementManager: mockRoomManagementManager
51
+ };
52
+
53
+ // Create handler instance
54
+ handler = new RoomOperationResponseHandler();
55
+ });
56
+
57
+ describe("Handler Metadata", () => {
58
+ it("should have correct type", () => {
59
+ expect(handler.type).toBe("room_operation_response");
60
+ });
61
+
62
+ it("should have schema defined", () => {
63
+ expect(handler.schema).toBeDefined();
64
+ });
65
+
66
+ it("should identify messages it can handle", () => {
67
+ const message = { type: "room_operation_response", data: {} };
68
+ expect(handler.canHandle(message as any)).toBe(true);
69
+ });
70
+
71
+ it("should not handle other message types", () => {
72
+ const message = { type: "other_type", data: {} };
73
+ expect(handler.canHandle(message as any)).toBe(false);
74
+ });
75
+ });
76
+
77
+ describe("Success Responses - Create/Update (with room object)", () => {
78
+ it("should handle successful create/update with room object", async () => {
79
+ const room: RoomInfo = {
80
+ id: "room-123",
81
+ name: "Test Room",
82
+ description: "Test Description",
83
+ is_public: false,
84
+ created_by: "user-123",
85
+ created_at: new Date().toISOString(),
86
+ updated_at: new Date().toISOString(),
87
+ is_owner: true
88
+ };
89
+
90
+ const message = {
91
+ type: "room_operation_response" as const,
92
+ data: {
93
+ success: true,
94
+ room
95
+ }
96
+ };
97
+
98
+ await handler.handle(message, mockContext);
99
+
100
+ // Should update cache
101
+ expect(mockRoomManagementManager.upsertRoom).toHaveBeenCalledWith(room);
102
+
103
+ // Should emit both created and updated events
104
+ expect(emitSpy).toHaveBeenCalledWith("room:created", room);
105
+ expect(emitSpy).toHaveBeenCalledWith("room:updated", room);
106
+
107
+ // Should send webhook
108
+ expect(sendWebhookSpy).toHaveBeenCalledWith(
109
+ "room_operation",
110
+ expect.objectContaining({
111
+ success: true,
112
+ room
113
+ }),
114
+ undefined
115
+ );
116
+
117
+ // Should log
118
+ expect(mockLogger.info).toHaveBeenCalledWith(
119
+ "Room operation succeeded",
120
+ expect.objectContaining({
121
+ roomId: room.id,
122
+ roomName: room.name
123
+ })
124
+ );
125
+ });
126
+
127
+ it("should handle success with minimal room data", async () => {
128
+ const room: RoomInfo = {
129
+ id: "room-456",
130
+ name: "Minimal Room",
131
+ is_owner: false
132
+ } as RoomInfo;
133
+
134
+ const message = {
135
+ type: "room_operation_response" as const,
136
+ data: {
137
+ success: true,
138
+ room
139
+ }
140
+ };
141
+
142
+ await handler.handle(message, mockContext);
143
+
144
+ expect(mockRoomManagementManager.upsertRoom).toHaveBeenCalledWith(room);
145
+ expect(emitSpy).toHaveBeenCalledWith("room:created", room);
146
+ expect(emitSpy).toHaveBeenCalledWith("room:updated", room);
147
+ });
148
+
149
+ it("should work without roomManagementManager in context", async () => {
150
+ const contextWithoutManager = { ...mockContext, roomManagementManager: undefined };
151
+ const room: RoomInfo = {
152
+ id: "room-789",
153
+ name: "Test Room",
154
+ is_owner: true
155
+ } as RoomInfo;
156
+
157
+ const message = {
158
+ type: "room_operation_response" as const,
159
+ data: {
160
+ success: true,
161
+ room
162
+ }
163
+ };
164
+
165
+ await handler.handle(message, contextWithoutManager);
166
+
167
+ // Should still emit events
168
+ expect(emitSpy).toHaveBeenCalledWith("room:created", room);
169
+ expect(emitSpy).toHaveBeenCalledWith("room:updated", room);
170
+ });
171
+ });
172
+
173
+ describe("Success Responses - Delete (with room_id only)", () => {
174
+ it("should handle successful delete with room_id", async () => {
175
+ const roomId = "room-123";
176
+
177
+ const message = {
178
+ type: "room_operation_response" as const,
179
+ data: {
180
+ success: true,
181
+ room_id: roomId
182
+ }
183
+ };
184
+
185
+ await handler.handle(message, mockContext);
186
+
187
+ // Should remove from cache
188
+ expect(mockRoomManagementManager.removeRoom).toHaveBeenCalledWith(roomId);
189
+
190
+ // Should emit deleted event
191
+ expect(emitSpy).toHaveBeenCalledWith("room:deleted", roomId);
192
+
193
+ // Should send webhook
194
+ expect(sendWebhookSpy).toHaveBeenCalledWith(
195
+ "room_deleted",
196
+ expect.objectContaining({
197
+ success: true,
198
+ room_id: roomId
199
+ }),
200
+ undefined
201
+ );
202
+
203
+ // Should log
204
+ expect(mockLogger.info).toHaveBeenCalledWith("Room deleted", { roomId });
205
+ });
206
+
207
+ it("should work without roomManagementManager for delete", async () => {
208
+ const contextWithoutManager = { ...mockContext, roomManagementManager: undefined };
209
+ const roomId = "room-456";
210
+
211
+ const message = {
212
+ type: "room_operation_response" as const,
213
+ data: {
214
+ success: true,
215
+ room_id: roomId
216
+ }
217
+ };
218
+
219
+ await handler.handle(message, contextWithoutManager);
220
+
221
+ // Should still emit event
222
+ expect(emitSpy).toHaveBeenCalledWith("room:deleted", roomId);
223
+ });
224
+ });
225
+
226
+ describe("Error Responses", () => {
227
+ it("should handle error response with message", async () => {
228
+ const errorMessage = "Room name already exists";
229
+ const roomId = "room-123";
230
+
231
+ const message = {
232
+ type: "room_operation_response" as const,
233
+ data: {
234
+ success: false,
235
+ message: errorMessage,
236
+ room_id: roomId
237
+ }
238
+ };
239
+
240
+ await handler.handle(message, mockContext);
241
+
242
+ // Should emit all error events (since we can't determine operation type)
243
+ expect(emitSpy).toHaveBeenCalledWith(
244
+ "room:create_error",
245
+ expect.any(SDKError)
246
+ );
247
+ expect(emitSpy).toHaveBeenCalledWith(
248
+ "room:update_error",
249
+ expect.any(SDKError),
250
+ roomId
251
+ );
252
+ expect(emitSpy).toHaveBeenCalledWith(
253
+ "room:delete_error",
254
+ expect.any(SDKError),
255
+ roomId
256
+ );
257
+
258
+ // Verify error details
259
+ const createErrorCall = emitSpy.mock.calls.find(
260
+ (call) => call[0] === "room:create_error"
261
+ );
262
+ const error = createErrorCall[1] as SDKError;
263
+ expect(error.message).toBe(errorMessage);
264
+ expect(error.code).toBe(ErrorCode.OPERATION_FAILED);
265
+
266
+ // Should send webhook
267
+ expect(sendWebhookSpy).toHaveBeenCalledWith(
268
+ "room_operation_error",
269
+ expect.objectContaining({
270
+ success: false,
271
+ message: errorMessage,
272
+ room_id: roomId
273
+ }),
274
+ undefined
275
+ );
276
+
277
+ // Should not update cache
278
+ expect(mockRoomManagementManager.upsertRoom).not.toHaveBeenCalled();
279
+ expect(mockRoomManagementManager.removeRoom).not.toHaveBeenCalled();
280
+ });
281
+
282
+ it("should handle error without message", async () => {
283
+ const message = {
284
+ type: "room_operation_response" as const,
285
+ data: {
286
+ success: false
287
+ }
288
+ };
289
+
290
+ await handler.handle(message, mockContext);
291
+
292
+ // Should use default error message
293
+ const createErrorCall = emitSpy.mock.calls.find(
294
+ (call) => call[0] === "room:create_error"
295
+ );
296
+ const error = createErrorCall[1] as SDKError;
297
+ expect(error.message).toBe("Room operation failed");
298
+ });
299
+
300
+ it("should handle error without room_id", async () => {
301
+ const message = {
302
+ type: "room_operation_response" as const,
303
+ data: {
304
+ success: false,
305
+ message: "Generic error"
306
+ }
307
+ };
308
+
309
+ await handler.handle(message, mockContext);
310
+
311
+ // Should still emit error events
312
+ expect(emitSpy).toHaveBeenCalledWith("room:create_error", expect.any(SDKError));
313
+ expect(emitSpy).toHaveBeenCalledWith(
314
+ "room:update_error",
315
+ expect.any(SDKError),
316
+ undefined
317
+ );
318
+ expect(emitSpy).toHaveBeenCalledWith(
319
+ "room:delete_error",
320
+ expect.any(SDKError),
321
+ undefined
322
+ );
323
+ });
324
+ });
325
+
326
+ describe("Edge Cases", () => {
327
+ it("should handle success without room or room_id", async () => {
328
+ const message = {
329
+ type: "room_operation_response" as const,
330
+ data: {
331
+ success: true
332
+ }
333
+ };
334
+
335
+ await handler.handle(message, mockContext);
336
+
337
+ // Should log warning
338
+ expect(mockLogger.warn).toHaveBeenCalledWith(
339
+ "Room operation succeeded but no room data provided"
340
+ );
341
+
342
+ // Should not emit any success events
343
+ expect(emitSpy).not.toHaveBeenCalledWith("room:created", expect.anything());
344
+ expect(emitSpy).not.toHaveBeenCalledWith("room:updated", expect.anything());
345
+ expect(emitSpy).not.toHaveBeenCalledWith("room:deleted", expect.anything());
346
+
347
+ // Should not update cache
348
+ expect(mockRoomManagementManager.upsertRoom).not.toHaveBeenCalled();
349
+ expect(mockRoomManagementManager.removeRoom).not.toHaveBeenCalled();
350
+ });
351
+
352
+ it("should handle roomManagementManager without upsertRoom method", async () => {
353
+ const invalidManager = { someOtherMethod: vi.fn() };
354
+ const contextWithInvalidManager = {
355
+ ...mockContext,
356
+ roomManagementManager: invalidManager
357
+ };
358
+
359
+ const room: RoomInfo = {
360
+ id: "room-123",
361
+ name: "Test Room",
362
+ is_owner: true
363
+ } as RoomInfo;
364
+
365
+ const message = {
366
+ type: "room_operation_response" as const,
367
+ data: {
368
+ success: true,
369
+ room
370
+ }
371
+ };
372
+
373
+ // Should not throw
374
+ await handler.handle(message, contextWithInvalidManager);
375
+
376
+ // Should still emit events
377
+ expect(emitSpy).toHaveBeenCalledWith("room:created", room);
378
+ });
379
+
380
+ it("should handle roomManagementManager without removeRoom method", async () => {
381
+ const invalidManager = { someOtherMethod: vi.fn() };
382
+ const contextWithInvalidManager = {
383
+ ...mockContext,
384
+ roomManagementManager: invalidManager
385
+ };
386
+
387
+ const message = {
388
+ type: "room_operation_response" as const,
389
+ data: {
390
+ success: true,
391
+ room_id: "room-123"
392
+ }
393
+ };
394
+
395
+ // Should not throw
396
+ await handler.handle(message, contextWithInvalidManager);
397
+
398
+ // Should still emit event
399
+ expect(emitSpy).toHaveBeenCalledWith("room:deleted", "room-123");
400
+ });
401
+ });
402
+
403
+ describe("Webhook Errors", () => {
404
+ it("should handle webhook failures gracefully", async () => {
405
+ const webhookError = new Error("Webhook failed");
406
+ sendWebhookSpy.mockRejectedValueOnce(webhookError);
407
+
408
+ const room: RoomInfo = {
409
+ id: "room-123",
410
+ name: "Test Room",
411
+ is_owner: true
412
+ } as RoomInfo;
413
+
414
+ const message = {
415
+ type: "room_operation_response" as const,
416
+ data: {
417
+ success: true,
418
+ room
419
+ }
420
+ };
421
+
422
+ // Should not throw (webhook errors are logged but don't fail the handler)
423
+ await handler.handle(message, mockContext);
424
+
425
+ // Should still emit events and update cache
426
+ expect(emitSpy).toHaveBeenCalledWith("room:created", room);
427
+ expect(mockRoomManagementManager.upsertRoom).toHaveBeenCalledWith(room);
428
+ });
429
+ });
430
+
431
+ describe("Message Validation", () => {
432
+ it("should handle invalid message structure", async () => {
433
+ const invalidMessage = {
434
+ type: "room_operation_response",
435
+ // Missing data field
436
+ } as any;
437
+
438
+ await handler.handle(invalidMessage, mockContext);
439
+
440
+ // Should log error
441
+ expect(mockLogger.error).toHaveBeenCalledWith(
442
+ expect.stringContaining("Error handling room_operation_response"),
443
+ expect.any(Error)
444
+ );
445
+
446
+ // Should emit message:error event
447
+ expect(emitSpy).toHaveBeenCalledWith(
448
+ "message:error",
449
+ expect.any(Error),
450
+ invalidMessage
451
+ );
452
+ });
453
+
454
+ it("should accept valid message with extra fields", async () => {
455
+ const room: RoomInfo = {
456
+ id: "room-123",
457
+ name: "Test Room",
458
+ is_owner: true
459
+ } as RoomInfo;
460
+
461
+ const message = {
462
+ type: "room_operation_response" as const,
463
+ data: {
464
+ success: true,
465
+ room,
466
+ extra_field: "should be ignored", // Schema uses .passthrough()
467
+ another_extra: 123
468
+ }
469
+ };
470
+
471
+ // Should not throw
472
+ await handler.handle(message, mockContext);
473
+
474
+ expect(emitSpy).toHaveBeenCalledWith("room:created", room);
475
+ });
476
+ });
477
+
478
+ describe("Debug Logging", () => {
479
+ it("should log debug info for successful operation", async () => {
480
+ const room: RoomInfo = {
481
+ id: "room-123",
482
+ name: "Test Room",
483
+ is_owner: true
484
+ } as RoomInfo;
485
+
486
+ const message = {
487
+ type: "room_operation_response" as const,
488
+ data: {
489
+ success: true,
490
+ room
491
+ }
492
+ };
493
+
494
+ await handler.handle(message, mockContext);
495
+
496
+ expect(mockLogger.debug).toHaveBeenCalledWith(
497
+ "Handling room_operation_response",
498
+ expect.objectContaining({
499
+ success: true,
500
+ roomId: undefined, // room_id is undefined in this case
501
+ hasRoom: true
502
+ })
503
+ );
504
+ });
505
+
506
+ it("should log debug info for delete operation", async () => {
507
+ const message = {
508
+ type: "room_operation_response" as const,
509
+ data: {
510
+ success: true,
511
+ room_id: "room-456"
512
+ }
513
+ };
514
+
515
+ await handler.handle(message, mockContext);
516
+
517
+ expect(mockLogger.debug).toHaveBeenCalledWith(
518
+ "Handling room_operation_response",
519
+ expect.objectContaining({
520
+ success: true,
521
+ roomId: "room-456",
522
+ hasRoom: false
523
+ })
524
+ );
525
+ });
526
+ });
527
+ });