chat.test.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. import { describe, expect, it, vi, beforeEach } from "vitest";
  2. import { appRouter } from "./routers";
  3. import type { TrpcContext } from "./_core/context";
  4. type AuthenticatedUser = NonNullable<TrpcContext["user"]>;
  5. function createPublicContext(): TrpcContext {
  6. return {
  7. user: null,
  8. req: { protocol: "https", headers: {} } as TrpcContext["req"],
  9. res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"],
  10. };
  11. }
  12. function createAuthContext(): TrpcContext {
  13. const user: AuthenticatedUser = {
  14. id: 1,
  15. openId: "agent-user",
  16. email: "agent@homelegance.com",
  17. name: "Test Agent",
  18. loginMethod: "manus",
  19. role: "admin",
  20. createdAt: new Date(),
  21. updatedAt: new Date(),
  22. lastSignedIn: new Date(),
  23. };
  24. return {
  25. user,
  26. req: { protocol: "https", headers: {} } as TrpcContext["req"],
  27. res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"],
  28. };
  29. }
  30. // Mock the database module
  31. vi.mock("./db", () => {
  32. const conversations = new Map<string, any>();
  33. const messageStore = new Map<number, any[]>();
  34. let convIdCounter = 1;
  35. let msgIdCounter = 1;
  36. return {
  37. createConversation: vi.fn(async (data: any) => {
  38. const id = convIdCounter++;
  39. const conv = { id, ...data, createdAt: new Date(), updatedAt: new Date() };
  40. conversations.set(data.sessionId, conv);
  41. messageStore.set(id, []);
  42. return conv;
  43. }),
  44. getConversations: vi.fn(async (status?: string) => {
  45. const all = Array.from(conversations.values());
  46. if (status) return all.filter((c) => c.status === status);
  47. return all;
  48. }),
  49. getConversationById: vi.fn(async (id: number) => {
  50. return Array.from(conversations.values()).find((c) => c.id === id);
  51. }),
  52. getConversationBySessionId: vi.fn(async (sessionId: string) => {
  53. return conversations.get(sessionId);
  54. }),
  55. updateConversationStatus: vi.fn(async (id: number, status: string, agentId?: number) => {
  56. const conv = Array.from(conversations.values()).find((c) => c.id === id);
  57. if (conv) {
  58. conv.status = status;
  59. if (agentId) conv.assignedAgentId = agentId;
  60. conversations.set(conv.sessionId, conv);
  61. }
  62. return conv;
  63. }),
  64. getConversationStats: vi.fn(async () => ({
  65. total: conversations.size,
  66. active: Array.from(conversations.values()).filter((c) => c.status === "active").length,
  67. escalated: Array.from(conversations.values()).filter((c) => c.status === "escalated").length,
  68. resolved: 0,
  69. closed: 0,
  70. })),
  71. addMessage: vi.fn(async (data: any) => {
  72. const id = msgIdCounter++;
  73. const msg = { id, ...data, createdAt: new Date() };
  74. const msgs = messageStore.get(data.conversationId) || [];
  75. msgs.push(msg);
  76. messageStore.set(data.conversationId, msgs);
  77. return msg;
  78. }),
  79. getMessagesByConversation: vi.fn(async (conversationId: number) => {
  80. return messageStore.get(conversationId) || [];
  81. }),
  82. saveWorkflow: vi.fn(async (workflowId: string, nodes: any[], edges: any[]) => ({
  83. workflowId,
  84. nodeCount: nodes.length,
  85. edgeCount: edges.length,
  86. })),
  87. getWorkflow: vi.fn(async () => ({ nodes: [], edges: [] })),
  88. getWorkflowSuggestions: vi.fn(async () => []),
  89. updateWorkflowSuggestionStatus: vi.fn(async (id: number, status: string, reviewedById: number) => ({ id, status })),
  90. bulkCreateWorkflowSuggestions: vi.fn(async (suggestions: any[]) => ({ created: suggestions.length })),
  91. getDb: vi.fn(async () => ({
  92. select: vi.fn().mockReturnThis(),
  93. from: vi.fn().mockReturnThis(),
  94. where: vi.fn().mockReturnThis(),
  95. orderBy: vi.fn().mockReturnThis(),
  96. limit: vi.fn(async () => [
  97. { content: "What is the shipping time?", sender: "visitor" },
  98. { content: "How do I check my order status?", sender: "visitor" },
  99. { content: "Do you have modern bedroom sets?", sender: "visitor" },
  100. ]),
  101. })),
  102. upsertUser: vi.fn(),
  103. getUserByOpenId: vi.fn(),
  104. // Analytics
  105. trackAnalyticsEvent: vi.fn(async (data: any) => ({ id: 1, ...data, createdAt: new Date() })),
  106. getAnalyticsEvents: vi.fn(async () => []),
  107. getAnalyticsSummary: vi.fn(async () => ({
  108. totalConversations: 150,
  109. resolvedByBot: 95,
  110. escalatedToAgent: 40,
  111. abandoned: 15,
  112. avgResponseTime: 1.8,
  113. resolutionRate: 63.3,
  114. topIntents: [
  115. { intent: "order_status", count: 45 },
  116. { intent: "shipping", count: 35 },
  117. { intent: "returns", count: 25 },
  118. ],
  119. })),
  120. // Data Sources
  121. getDataSources: vi.fn(async () => []),
  122. createDataSource: vi.fn(async (data: any) => ({ id: 1, ...data, status: "active", createdAt: new Date(), updatedAt: new Date() })),
  123. updateDataSource: vi.fn(async (id: number, data: any) => ({ id, ...data })),
  124. deleteDataSource: vi.fn(async () => true),
  125. // API Connections
  126. getApiConnections: vi.fn(async () => []),
  127. createApiConnection: vi.fn(async (data: any) => ({ id: 1, ...data, status: "active", createdAt: new Date(), updatedAt: new Date() })),
  128. updateApiConnection: vi.fn(async (id: number, data: any) => ({ id, ...data })),
  129. deleteApiConnection: vi.fn(async () => true),
  130. testApiConnection: vi.fn(async () => ({ success: true, responseTime: 120, statusCode: 200 })),
  131. };
  132. });
  133. // Mock the LLM module
  134. vi.mock("./_core/llm", () => ({
  135. invokeLLM: vi.fn(async () => ({
  136. id: "test",
  137. created: Date.now(),
  138. model: "test",
  139. choices: [
  140. {
  141. index: 0,
  142. message: { role: "assistant", content: "I can help you find furniture! What style are you looking for?" },
  143. finish_reason: "stop",
  144. },
  145. ],
  146. })),
  147. }));
  148. describe("chat.startSession", () => {
  149. it("creates a new conversation and returns sessionId", async () => {
  150. const ctx = createPublicContext();
  151. const caller = appRouter.createCaller(ctx);
  152. const result = await caller.chat.startSession({});
  153. expect(result).toHaveProperty("sessionId");
  154. expect(result).toHaveProperty("conversationId");
  155. expect(typeof result.sessionId).toBe("string");
  156. expect(result.sessionId.length).toBeGreaterThan(0);
  157. });
  158. it("creates a session with visitor name", async () => {
  159. const ctx = createPublicContext();
  160. const caller = appRouter.createCaller(ctx);
  161. const result = await caller.chat.startSession({ visitorName: "John Doe" });
  162. expect(result.sessionId).toBeTruthy();
  163. expect(result.conversationId).toBeGreaterThan(0);
  164. });
  165. });
  166. describe("chat.sendMessage", () => {
  167. it("sends a message and receives an AI response", async () => {
  168. const ctx = createPublicContext();
  169. const caller = appRouter.createCaller(ctx);
  170. // Start a session first
  171. const session = await caller.chat.startSession({});
  172. // Send a message
  173. const result = await caller.chat.sendMessage({
  174. sessionId: session.sessionId,
  175. content: "I'm looking for a sofa",
  176. });
  177. expect(result).toHaveProperty("reply");
  178. expect(result.reply).toBeTruthy();
  179. expect(typeof result.reply).toBe("string");
  180. });
  181. it("triggers escalation when user asks for human agent", async () => {
  182. const ctx = createPublicContext();
  183. const caller = appRouter.createCaller(ctx);
  184. const session = await caller.chat.startSession({});
  185. const result = await caller.chat.sendMessage({
  186. sessionId: session.sessionId,
  187. content: "I want to speak to human agent please",
  188. });
  189. expect(result.status).toBe("escalated");
  190. expect(result.reply).toContain("team member");
  191. });
  192. it("throws error for invalid session", async () => {
  193. const ctx = createPublicContext();
  194. const caller = appRouter.createCaller(ctx);
  195. await expect(
  196. caller.chat.sendMessage({ sessionId: "nonexistent", content: "Hello" })
  197. ).rejects.toThrow("Conversation not found");
  198. });
  199. });
  200. describe("chat.getMessages", () => {
  201. it("returns messages for a valid session", async () => {
  202. const ctx = createPublicContext();
  203. const caller = appRouter.createCaller(ctx);
  204. const session = await caller.chat.startSession({});
  205. const result = await caller.chat.getMessages({ sessionId: session.sessionId });
  206. expect(result).toHaveProperty("messages");
  207. expect(result).toHaveProperty("status");
  208. expect(Array.isArray(result.messages)).toBe(true);
  209. });
  210. it("returns empty for invalid session", async () => {
  211. const ctx = createPublicContext();
  212. const caller = appRouter.createCaller(ctx);
  213. const result = await caller.chat.getMessages({ sessionId: "nonexistent" });
  214. expect(result.messages).toEqual([]);
  215. expect(result.status).toBe("closed");
  216. });
  217. });
  218. describe("agent.stats", () => {
  219. it("requires authentication", async () => {
  220. const ctx = createPublicContext();
  221. const caller = appRouter.createCaller(ctx);
  222. await expect(caller.agent.stats()).rejects.toThrow();
  223. });
  224. it("returns stats for authenticated user", async () => {
  225. const ctx = createAuthContext();
  226. const caller = appRouter.createCaller(ctx);
  227. const stats = await caller.agent.stats();
  228. expect(stats).toHaveProperty("total");
  229. expect(stats).toHaveProperty("active");
  230. expect(stats).toHaveProperty("escalated");
  231. expect(stats).toHaveProperty("resolved");
  232. expect(stats).toHaveProperty("closed");
  233. });
  234. });
  235. describe("agent.reply", () => {
  236. it("requires authentication", async () => {
  237. const ctx = createPublicContext();
  238. const caller = appRouter.createCaller(ctx);
  239. await expect(
  240. caller.agent.reply({ conversationId: 1, content: "Hello" })
  241. ).rejects.toThrow();
  242. });
  243. });
  244. describe("workflow.save", () => {
  245. it("requires authentication", async () => {
  246. const ctx = createPublicContext();
  247. const caller = appRouter.createCaller(ctx);
  248. await expect(
  249. caller.workflow.save({
  250. workflowId: "test",
  251. nodes: [],
  252. edges: [],
  253. })
  254. ).rejects.toThrow();
  255. });
  256. it("saves workflow for authenticated user", async () => {
  257. const ctx = createAuthContext();
  258. const caller = appRouter.createCaller(ctx);
  259. const result = await caller.workflow.save({
  260. workflowId: "test-workflow",
  261. nodes: [
  262. {
  263. workflowId: "test-workflow",
  264. nodeId: "n1",
  265. type: "greeting",
  266. label: "Welcome",
  267. config: { message: "Hello!" },
  268. positionX: 100,
  269. positionY: 100,
  270. },
  271. ],
  272. edges: [],
  273. });
  274. expect(result.workflowId).toBe("test-workflow");
  275. expect(result.nodeCount).toBe(1);
  276. expect(result.edgeCount).toBe(0);
  277. });
  278. });
  279. describe("workflow.load", () => {
  280. it("loads workflow for authenticated user", async () => {
  281. const ctx = createAuthContext();
  282. const caller = appRouter.createCaller(ctx);
  283. const result = await caller.workflow.load({ workflowId: "test-workflow" });
  284. expect(result).toHaveProperty("nodes");
  285. expect(result).toHaveProperty("edges");
  286. });
  287. });
  288. describe("workflow.save with new node types", () => {
  289. it("saves customer_data node", async () => {
  290. const ctx = createAuthContext();
  291. const caller = appRouter.createCaller(ctx);
  292. const result = await caller.workflow.save({
  293. workflowId: "test-workflow",
  294. nodes: [
  295. {
  296. workflowId: "test-workflow",
  297. nodeId: "n_cust",
  298. type: "customer_data",
  299. label: "Customer Lookup",
  300. config: { apiEndpoint: "/api/crm/customer", lookupField: "customerId", returnFields: ["name", "email"] },
  301. positionX: 100,
  302. positionY: 200,
  303. },
  304. ],
  305. edges: [],
  306. });
  307. expect(result.nodeCount).toBe(1);
  308. });
  309. it("saves sales_order node", async () => {
  310. const ctx = createAuthContext();
  311. const caller = appRouter.createCaller(ctx);
  312. const result = await caller.workflow.save({
  313. workflowId: "test-workflow",
  314. nodes: [
  315. {
  316. workflowId: "test-workflow",
  317. nodeId: "n_ord",
  318. type: "sales_order",
  319. label: "Order History",
  320. config: { apiEndpoint: "/api/orders/history", lookupField: "customerId" },
  321. positionX: 200,
  322. positionY: 300,
  323. },
  324. ],
  325. edges: [],
  326. });
  327. expect(result.nodeCount).toBe(1);
  328. });
  329. it("saves guardrail node with blocked topics", async () => {
  330. const ctx = createAuthContext();
  331. const caller = appRouter.createCaller(ctx);
  332. const result = await caller.workflow.save({
  333. workflowId: "test-workflow",
  334. nodes: [
  335. {
  336. workflowId: "test-workflow",
  337. nodeId: "n_guard",
  338. type: "guardrail",
  339. label: "Content Filter",
  340. config: {
  341. blockedTopics: ["revenue", "margin", "internal pricing"],
  342. blockedMessage: "Sorry, I cannot share that information.",
  343. },
  344. positionX: 300,
  345. positionY: 100,
  346. },
  347. ],
  348. edges: [],
  349. });
  350. expect(result.nodeCount).toBe(1);
  351. });
  352. it("saves greeting node with customer ID detection config", async () => {
  353. const ctx = createAuthContext();
  354. const caller = appRouter.createCaller(ctx);
  355. const result = await caller.workflow.save({
  356. workflowId: "test-workflow",
  357. nodes: [
  358. {
  359. workflowId: "test-workflow",
  360. nodeId: "n_greet",
  361. type: "greeting",
  362. label: "Welcome & Identify",
  363. config: {
  364. message: "Welcome!",
  365. detectCustomerId: true,
  366. customerIdSource: "session",
  367. personalizedGreeting: "Welcome back, {{customerName}}!",
  368. },
  369. positionX: 400,
  370. positionY: 50,
  371. },
  372. ],
  373. edges: [],
  374. });
  375. expect(result.nodeCount).toBe(1);
  376. });
  377. it("saves mixed workflow with all new node types", async () => {
  378. const ctx = createAuthContext();
  379. const caller = appRouter.createCaller(ctx);
  380. const result = await caller.workflow.save({
  381. workflowId: "full-workflow",
  382. nodes: [
  383. { workflowId: "full-workflow", nodeId: "n1", type: "greeting", label: "Welcome", config: { detectCustomerId: true }, positionX: 0, positionY: 0 },
  384. { workflowId: "full-workflow", nodeId: "n2", type: "guardrail", label: "Filter", config: { blockedTopics: ["revenue"] }, positionX: 100, positionY: 0 },
  385. { workflowId: "full-workflow", nodeId: "n3", type: "customer_data", label: "CRM", config: { apiEndpoint: "/api/crm" }, positionX: 200, positionY: 0 },
  386. { workflowId: "full-workflow", nodeId: "n4", type: "sales_order", label: "Orders", config: { apiEndpoint: "/api/orders" }, positionX: 300, positionY: 0 },
  387. { workflowId: "full-workflow", nodeId: "n5", type: "intent", label: "Detect", config: { intents: ["search"] }, positionX: 400, positionY: 0 },
  388. { workflowId: "full-workflow", nodeId: "n6", type: "end", label: "End", config: {}, positionX: 500, positionY: 0 },
  389. ],
  390. edges: [
  391. { workflowId: "full-workflow", sourceNodeId: "n1", targetNodeId: "n2" },
  392. { workflowId: "full-workflow", sourceNodeId: "n2", targetNodeId: "n3" },
  393. { workflowId: "full-workflow", sourceNodeId: "n3", targetNodeId: "n4" },
  394. { workflowId: "full-workflow", sourceNodeId: "n4", targetNodeId: "n5" },
  395. { workflowId: "full-workflow", sourceNodeId: "n5", targetNodeId: "n6" },
  396. ],
  397. });
  398. expect(result.nodeCount).toBe(6);
  399. expect(result.edgeCount).toBe(5);
  400. });
  401. });
  402. describe("workflow.getSuggestions", () => {
  403. it("requires authentication", async () => {
  404. const ctx = createPublicContext();
  405. const caller = appRouter.createCaller(ctx);
  406. await expect(
  407. caller.workflow.getSuggestions({ workflowId: "test" })
  408. ).rejects.toThrow();
  409. });
  410. it("returns suggestions for authenticated admin", async () => {
  411. const ctx = createAuthContext();
  412. const caller = appRouter.createCaller(ctx);
  413. const result = await caller.workflow.getSuggestions({ workflowId: "test-workflow" });
  414. expect(Array.isArray(result)).toBe(true);
  415. });
  416. it("accepts optional status filter", async () => {
  417. const ctx = createAuthContext();
  418. const caller = appRouter.createCaller(ctx);
  419. const result = await caller.workflow.getSuggestions({ workflowId: "test-workflow", status: "pending" });
  420. expect(Array.isArray(result)).toBe(true);
  421. });
  422. });
  423. describe("workflow.reviewSuggestion", () => {
  424. it("requires authentication", async () => {
  425. const ctx = createPublicContext();
  426. const caller = appRouter.createCaller(ctx);
  427. await expect(
  428. caller.workflow.reviewSuggestion({ suggestionId: 1, status: "approved" })
  429. ).rejects.toThrow();
  430. });
  431. it("approves a suggestion", async () => {
  432. const ctx = createAuthContext();
  433. const caller = appRouter.createCaller(ctx);
  434. const result = await caller.workflow.reviewSuggestion({ suggestionId: 1, status: "approved" });
  435. expect(result.id).toBe(1);
  436. expect(result.status).toBe("approved");
  437. });
  438. it("declines a suggestion", async () => {
  439. const ctx = createAuthContext();
  440. const caller = appRouter.createCaller(ctx);
  441. const result = await caller.workflow.reviewSuggestion({ suggestionId: 2, status: "declined" });
  442. expect(result.id).toBe(2);
  443. expect(result.status).toBe("declined");
  444. });
  445. it("sets a suggestion to waiting", async () => {
  446. const ctx = createAuthContext();
  447. const caller = appRouter.createCaller(ctx);
  448. const result = await caller.workflow.reviewSuggestion({ suggestionId: 3, status: "waiting" });
  449. expect(result.id).toBe(3);
  450. expect(result.status).toBe("waiting");
  451. });
  452. });
  453. describe("workflow.generateSuggestions", () => {
  454. it("requires authentication", async () => {
  455. const ctx = createPublicContext();
  456. const caller = appRouter.createCaller(ctx);
  457. await expect(
  458. caller.workflow.generateSuggestions({ workflowId: "test" })
  459. ).rejects.toThrow();
  460. });
  461. it("generates suggestions from conversation data", async () => {
  462. const ctx = createAuthContext();
  463. const caller = appRouter.createCaller(ctx);
  464. const result = await caller.workflow.generateSuggestions({ workflowId: "test-workflow" });
  465. expect(result).toHaveProperty("suggestions");
  466. expect(result).toHaveProperty("message");
  467. });
  468. });
  469. /* ─── Analytics Tests ─── */
  470. describe("analytics.summary", () => {
  471. it("requires admin authentication", async () => {
  472. const ctx = createPublicContext();
  473. const caller = appRouter.createCaller(ctx);
  474. await expect(
  475. caller.analytics.summary({})
  476. ).rejects.toThrow();
  477. });
  478. it("returns analytics summary data", async () => {
  479. const ctx = createAuthContext();
  480. const caller = appRouter.createCaller(ctx);
  481. const result = await caller.analytics.summary({});
  482. expect(result).toHaveProperty("totalConversations");
  483. expect(result).toHaveProperty("resolvedByBot");
  484. expect(result).toHaveProperty("resolutionRate");
  485. expect(result).toHaveProperty("topIntents");
  486. expect(result.totalConversations).toBe(150);
  487. expect(result.resolutionRate).toBeCloseTo(63.3);
  488. });
  489. });
  490. describe("analytics.track", () => {
  491. it("tracks an analytics event", async () => {
  492. const ctx = createAuthContext();
  493. const caller = appRouter.createCaller(ctx);
  494. const result = await caller.analytics.track({
  495. eventType: "resolved_by_bot",
  496. conversationId: 1,
  497. metadata: { intent: "order_status" },
  498. });
  499. expect(result).toHaveProperty("id");
  500. });
  501. });
  502. /* ─── Data Sources Tests ─── */
  503. describe("dataSources.list", () => {
  504. it("requires admin authentication", async () => {
  505. const ctx = createPublicContext();
  506. const caller = appRouter.createCaller(ctx);
  507. await expect(
  508. caller.dataSources.list()
  509. ).rejects.toThrow();
  510. });
  511. it("returns list of data sources", async () => {
  512. const ctx = createAuthContext();
  513. const caller = appRouter.createCaller(ctx);
  514. const result = await caller.dataSources.list();
  515. expect(Array.isArray(result)).toBe(true);
  516. });
  517. });
  518. describe("dataSources.create", () => {
  519. it("creates a new data source", async () => {
  520. const ctx = createAuthContext();
  521. const caller = appRouter.createCaller(ctx);
  522. const result = await caller.dataSources.create({
  523. name: "Product Catalog",
  524. type: "url",
  525. config: { url: "https://api.homelegance.com/products" },
  526. });
  527. expect(result).toHaveProperty("id");
  528. });
  529. });
  530. describe("dataSources.delete", () => {
  531. it("deletes a data source", async () => {
  532. const ctx = createAuthContext();
  533. const caller = appRouter.createCaller(ctx);
  534. const result = await caller.dataSources.delete({ id: 1 });
  535. expect(result).toHaveProperty("success");
  536. });
  537. });
  538. /* ─── API Connections Tests ─── */
  539. describe("apiConnections.list", () => {
  540. it("requires admin authentication", async () => {
  541. const ctx = createPublicContext();
  542. const caller = appRouter.createCaller(ctx);
  543. await expect(
  544. caller.apiConnections.list()
  545. ).rejects.toThrow();
  546. });
  547. it("returns list of API connections", async () => {
  548. const ctx = createAuthContext();
  549. const caller = appRouter.createCaller(ctx);
  550. const result = await caller.apiConnections.list();
  551. expect(Array.isArray(result)).toBe(true);
  552. });
  553. });
  554. describe("apiConnections.create", () => {
  555. it("creates a new API connection", async () => {
  556. const ctx = createAuthContext();
  557. const caller = appRouter.createCaller(ctx);
  558. const result = await caller.apiConnections.create({
  559. name: "Order Status API",
  560. description: "Check order status",
  561. category: "orders",
  562. method: "GET",
  563. endpoint: "https://api.homelegance.com/orders/{orderId}",
  564. headers: { Authorization: "Bearer {{API_KEY}}" },
  565. inputVariables: [{ name: "orderId", type: "string", description: "Order ID" }],
  566. outputVariables: [{ name: "status", type: "string", description: "Order status" }],
  567. });
  568. expect(result).toHaveProperty("id");
  569. });
  570. });
  571. describe("apiConnections.delete", () => {
  572. it("deletes an API connection", async () => {
  573. const ctx = createAuthContext();
  574. const caller = appRouter.createCaller(ctx);
  575. const result = await caller.apiConnections.delete({ id: 1 });
  576. expect(result).toHaveProperty("success");
  577. });
  578. });