@zapier/zapier-sdk 0.18.2 → 0.18.3

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.
@@ -4,37 +4,35 @@ import { listAuthenticationsPlugin } from "./index";
4
4
  import { createSdk } from "../../sdk";
5
5
  import { eventEmissionPlugin } from "../eventEmission";
6
6
  const mockAuthenticationsResponse = {
7
- results: [
7
+ data: [
8
8
  {
9
- id: 123,
9
+ id: "123",
10
10
  date: "2021-01-01",
11
- account_id: 456,
12
- selected_api: "SlackCLIAPI@1.21.1",
11
+ account_id: "456",
12
+ implementation_id: "SlackCLIAPI@1.21.1",
13
13
  is_invite_only: false,
14
14
  is_private: false,
15
15
  shared_with_all: false,
16
- is_stale: "false",
17
- marked_stale_at: null,
16
+ is_expired: "false",
17
+ expired_at: null,
18
18
  label: "My Slack Workspace",
19
19
  title: "My Slack Workspace",
20
20
  },
21
21
  {
22
- id: 789,
22
+ id: "789",
23
23
  date: "2021-02-01",
24
- account_id: 456,
25
- selected_api: "GitHubAPI@2.1.0",
24
+ account_id: "456",
25
+ implementation_id: "GitHubAPI@2.1.0",
26
26
  is_invite_only: false,
27
27
  is_private: false,
28
28
  shared_with_all: true,
29
- is_stale: "true",
30
- marked_stale_at: "2021-06-01",
29
+ is_expired: "true",
30
+ expired_at: "2021-06-01",
31
31
  label: "GitHub Integration",
32
32
  title: "GitHub Integration",
33
33
  },
34
34
  ],
35
- count: 2,
36
- next: null,
37
- previous: null,
35
+ nextCursor: undefined,
38
36
  };
39
37
  describe("listAuthentications plugin", () => {
40
38
  let mockApiClient;
@@ -205,7 +203,7 @@ describe("listAuthentications plugin", () => {
205
203
  });
206
204
  });
207
205
  describe("data mapping", () => {
208
- it("should map is_stale to is_expired and marked_stale_at to expired_at", async () => {
206
+ it("should pass through is_expired and expired_at fields", async () => {
209
207
  const sdk = createTestSdk();
210
208
  const result = await sdk.listAuthentications();
211
209
  expect(result.data[0].is_expired).toBe("false");
@@ -213,12 +211,12 @@ describe("listAuthentications plugin", () => {
213
211
  expect(result.data[1].is_expired).toBe("true");
214
212
  expect(result.data[1].expired_at).toBe("2021-06-01");
215
213
  });
216
- it("should coerce title from label when title is missing", async () => {
214
+ it("should pass through label field when title is missing", async () => {
217
215
  const responseWithoutTitle = {
218
216
  ...mockAuthenticationsResponse,
219
- results: [
217
+ data: [
220
218
  {
221
- ...mockAuthenticationsResponse.results[0],
219
+ ...mockAuthenticationsResponse.data[0],
222
220
  title: undefined,
223
221
  label: "Label Only Auth",
224
222
  },
@@ -227,14 +225,16 @@ describe("listAuthentications plugin", () => {
227
225
  mockApiClient.get = vi.fn().mockResolvedValue(responseWithoutTitle);
228
226
  const sdk = createTestSdk();
229
227
  const result = await sdk.listAuthentications({});
230
- expect(result.data[0].title).toBe("Label Only Auth");
228
+ // New API passes through fields directly - title coercion happens server-side
229
+ expect(result.data[0].title).toBeUndefined();
230
+ expect(result.data[0].label).toBe("Label Only Auth");
231
231
  });
232
232
  it("should handle authentications with neither title nor label", async () => {
233
233
  const responseWithoutTitleOrLabel = {
234
234
  ...mockAuthenticationsResponse,
235
- results: [
235
+ data: [
236
236
  {
237
- ...mockAuthenticationsResponse.results[0],
237
+ ...mockAuthenticationsResponse.data[0],
238
238
  title: undefined,
239
239
  label: undefined,
240
240
  },
@@ -249,85 +249,90 @@ describe("listAuthentications plugin", () => {
249
249
  });
250
250
  });
251
251
  describe("filtering", () => {
252
- it("should filter by exact title match", async () => {
253
- const sdk = createTestSdk();
254
- const result = await sdk.listAuthentications({
255
- title: "My Slack Workspace",
256
- });
257
- // Should filter to exact match
258
- expect(result.data).toHaveLength(1);
259
- expect(result.data[0].title).toBe("My Slack Workspace");
260
- });
261
- it("should use search parameter when provided", async () => {
252
+ it("should pass search parameter to API when provided", async () => {
262
253
  const sdk = createTestSdk();
263
254
  await sdk.listAuthentications({ search: "workspace" });
264
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
255
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
265
256
  searchParams: expect.objectContaining({
266
257
  search: "workspace",
267
258
  }),
268
259
  }));
269
260
  });
270
- it("should use title as search when search not provided", async () => {
261
+ it("should pass title parameter separately to API", async () => {
271
262
  const sdk = createTestSdk();
272
263
  await sdk.listAuthentications({ title: "My Slack Workspace" });
273
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
264
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
274
265
  searchParams: expect.objectContaining({
275
- search: "My Slack Workspace",
266
+ title: "My Slack Workspace",
276
267
  }),
277
268
  }));
278
269
  });
279
- it("should prefer search over title when both provided", async () => {
270
+ it("should pass both search and title when both provided", async () => {
280
271
  const sdk = createTestSdk();
281
272
  await sdk.listAuthentications({
282
273
  search: "explicit search",
283
274
  title: "My Title",
284
275
  });
285
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
276
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
286
277
  searchParams: expect.objectContaining({
287
278
  search: "explicit search",
279
+ title: "My Title",
288
280
  }),
289
281
  }));
290
282
  });
291
283
  it("should pass accountId filter to API", async () => {
292
284
  const sdk = createTestSdk();
293
285
  await sdk.listAuthentications({ accountId: "acc_123" });
294
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
286
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
295
287
  searchParams: expect.objectContaining({
296
- account_id: "acc_123",
288
+ accountId: "acc_123",
297
289
  }),
298
290
  }));
299
291
  });
300
292
  it("should pass owner filter to API", async () => {
301
293
  const sdk = createTestSdk();
302
294
  await sdk.listAuthentications({ owner: "me" });
303
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
295
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
304
296
  searchParams: expect.objectContaining({
305
297
  owner: "me",
306
298
  }),
307
299
  }));
308
300
  });
309
- it("should pass authenticationIds as ids parameter to API", async () => {
301
+ it("should pass authenticationIds as comma-separated string to API", async () => {
310
302
  const sdk = createTestSdk();
311
303
  await sdk.listAuthentications({
312
304
  authenticationIds: ["123", "456", "789"],
313
305
  });
314
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
306
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
315
307
  searchParams: expect.objectContaining({
316
- ids: "123,456,789",
308
+ authenticationIds: "123,456,789",
317
309
  }),
318
310
  }));
319
311
  });
320
- it("should not include ids parameter when authenticationIds is empty", async () => {
312
+ it("should not include authenticationIds parameter when array is empty", async () => {
321
313
  const sdk = createTestSdk();
322
314
  await sdk.listAuthentications({
323
315
  authenticationIds: [],
324
316
  });
325
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
317
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
326
318
  searchParams: expect.not.objectContaining({
327
- ids: expect.anything(),
319
+ authenticationIds: expect.anything(),
328
320
  }),
329
321
  }));
330
322
  });
323
+ it("should not include undefined optional parameters in searchParams", async () => {
324
+ const sdk = createTestSdk();
325
+ await sdk.listAuthentications({});
326
+ const callArgs = mockApiClient.get.mock.calls[0];
327
+ const searchParams = callArgs[1].searchParams;
328
+ // Should only have pageSize (default), not undefined params
329
+ expect(searchParams).not.toHaveProperty("search");
330
+ expect(searchParams).not.toHaveProperty("title");
331
+ expect(searchParams).not.toHaveProperty("accountId");
332
+ expect(searchParams).not.toHaveProperty("owner");
333
+ expect(searchParams).not.toHaveProperty("authenticationIds");
334
+ expect(searchParams).not.toHaveProperty("appKey");
335
+ });
331
336
  });
332
337
  describe("pagination", () => {
333
338
  it("should handle pagination with maxItems by collecting all items", async () => {
@@ -335,27 +340,25 @@ describe("listAuthentications plugin", () => {
335
340
  mockApiClient.get = vi
336
341
  .fn()
337
342
  .mockResolvedValueOnce({
338
- results: mockAuthenticationsResponse.results,
339
- count: 3,
340
- next: "http://example.com/api/v4/authentications/?offset=2",
343
+ data: mockAuthenticationsResponse.data,
344
+ nextCursor: "2",
341
345
  })
342
346
  .mockResolvedValueOnce({
343
- results: [
347
+ data: [
344
348
  {
345
- id: 999,
349
+ id: "999",
346
350
  date: "2021-03-01",
347
- account_id: 456,
348
- selected_api: "DropboxAPI@3.0.0",
351
+ account_id: "456",
352
+ implementation_id: "DropboxAPI@3.0.0",
349
353
  is_invite_only: false,
350
354
  is_private: false,
351
355
  shared_with_all: false,
352
- is_stale: "false",
353
- marked_stale_at: null,
356
+ is_expired: "false",
357
+ expired_at: null,
354
358
  title: "Dropbox Integration",
355
359
  },
356
360
  ],
357
- count: 3,
358
- next: null,
361
+ nextCursor: undefined,
359
362
  });
360
363
  const sdk = createTestSdk();
361
364
  // Collect all items using the items() iterator
@@ -370,9 +373,8 @@ describe("listAuthentications plugin", () => {
370
373
  });
371
374
  it("should return first page when awaited", async () => {
372
375
  mockApiClient.get = vi.fn().mockResolvedValue({
373
- results: mockAuthenticationsResponse.results,
374
- count: 2,
375
- next: null, // No more pages to prevent automatic fetching
376
+ data: mockAuthenticationsResponse.data,
377
+ nextCursor: undefined,
376
378
  });
377
379
  const sdk = createTestSdk();
378
380
  const result = await sdk.listAuthentications({});
@@ -384,14 +386,12 @@ describe("listAuthentications plugin", () => {
384
386
  mockApiClient.get = vi
385
387
  .fn()
386
388
  .mockResolvedValueOnce({
387
- results: mockAuthenticationsResponse.results.slice(0, 1),
388
- count: 2,
389
- next: "http://example.com/api/v4/authentications/?offset=1",
389
+ data: mockAuthenticationsResponse.data.slice(0, 1),
390
+ nextCursor: "1",
390
391
  })
391
392
  .mockResolvedValueOnce({
392
- results: mockAuthenticationsResponse.results.slice(1, 2),
393
- count: 2,
394
- next: null,
393
+ data: mockAuthenticationsResponse.data.slice(1, 2),
394
+ nextCursor: undefined,
395
395
  });
396
396
  const sdk = createTestSdk();
397
397
  const pages = [];
@@ -418,43 +418,79 @@ describe("listAuthentications plugin", () => {
418
418
  expect(items[0].id).toBe("123");
419
419
  expect(items[1].id).toBe("789");
420
420
  });
421
- it("should set appropriate pageSize for API calls", async () => {
421
+ it("should set pageSize in searchParams for API calls", async () => {
422
422
  const sdk = createTestSdk();
423
- await sdk.listAuthentications({ pageSize: 1 });
424
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
423
+ await sdk.listAuthentications({ pageSize: 15 });
424
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
425
425
  searchParams: expect.objectContaining({
426
- limit: "1",
426
+ pageSize: "15",
427
427
  }),
428
428
  }));
429
429
  });
430
+ it("should pass cursor as offset parameter", async () => {
431
+ mockApiClient.get = vi
432
+ .fn()
433
+ .mockResolvedValueOnce({
434
+ data: mockAuthenticationsResponse.data.slice(0, 1),
435
+ nextCursor: "cursor-abc-123",
436
+ })
437
+ .mockResolvedValueOnce({
438
+ data: mockAuthenticationsResponse.data.slice(1, 2),
439
+ nextCursor: undefined,
440
+ });
441
+ const sdk = createTestSdk();
442
+ const pages = [];
443
+ for await (const page of sdk.listAuthentications({ pageSize: 1 })) {
444
+ pages.push(page);
445
+ if (pages.length >= 2)
446
+ break;
447
+ }
448
+ // Second call should include the cursor as offset
449
+ expect(mockApiClient.get).toHaveBeenNthCalledWith(2, "/api/v0/authentications", expect.objectContaining({
450
+ searchParams: expect.objectContaining({
451
+ offset: "cursor-abc-123",
452
+ }),
453
+ }));
454
+ });
455
+ it("should stop pagination when nextCursor is undefined", async () => {
456
+ mockApiClient.get = vi.fn().mockResolvedValue({
457
+ data: mockAuthenticationsResponse.data,
458
+ nextCursor: undefined,
459
+ });
460
+ const sdk = createTestSdk();
461
+ const pages = [];
462
+ for await (const page of sdk.listAuthentications({})) {
463
+ pages.push(page);
464
+ }
465
+ expect(pages).toHaveLength(1);
466
+ expect(mockApiClient.get).toHaveBeenCalledTimes(1);
467
+ });
430
468
  });
431
469
  describe("API integration", () => {
432
470
  it("should call the correct API endpoint", async () => {
433
471
  const sdk = createTestSdk();
434
472
  await sdk.listAuthentications({});
435
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.any(Object));
473
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.any(Object));
436
474
  });
437
- it("should pass pageSize as limit parameter", async () => {
475
+ it("should pass pageSize as string in searchParams", async () => {
438
476
  const sdk = createTestSdk();
439
477
  await sdk.listAuthentications({ pageSize: 25 });
440
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
478
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
441
479
  searchParams: expect.objectContaining({
442
- limit: "25",
480
+ pageSize: "25",
443
481
  }),
444
482
  }));
445
483
  });
446
- it("should handle cursor-based pagination internally", async () => {
484
+ it("should handle cursor-based pagination with nextCursor", async () => {
447
485
  mockApiClient.get = vi
448
486
  .fn()
449
487
  .mockResolvedValueOnce({
450
- results: mockAuthenticationsResponse.results.slice(0, 1),
451
- count: 2,
452
- next: "http://example.com/api/v4/authentications/?offset=1",
488
+ data: mockAuthenticationsResponse.data.slice(0, 1),
489
+ nextCursor: "offset-1",
453
490
  })
454
491
  .mockResolvedValueOnce({
455
- results: mockAuthenticationsResponse.results.slice(1, 2),
456
- count: 2,
457
- next: null,
492
+ data: mockAuthenticationsResponse.data.slice(1, 2),
493
+ nextCursor: undefined,
458
494
  });
459
495
  const sdk = createTestSdk();
460
496
  // Test that the function can handle multiple pages
@@ -466,42 +502,68 @@ describe("listAuthentications plugin", () => {
466
502
  }
467
503
  expect(pageCount).toBe(2);
468
504
  expect(mockApiClient.get).toHaveBeenCalledTimes(2);
469
- // The second call should include the offset from the cursor
470
- expect(mockApiClient.get).toHaveBeenNthCalledWith(2, "/zapier/api/v4/authentications/", expect.objectContaining({
505
+ // The second call should include the offset from the nextCursor
506
+ expect(mockApiClient.get).toHaveBeenNthCalledWith(2, "/api/v0/authentications", expect.objectContaining({
471
507
  searchParams: expect.objectContaining({
472
- offset: "1",
508
+ offset: "offset-1",
473
509
  }),
474
510
  }));
475
511
  });
512
+ it("should set authRequired to true in API call options", async () => {
513
+ const sdk = createTestSdk();
514
+ await sdk.listAuthentications({});
515
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
516
+ authRequired: true,
517
+ }));
518
+ });
519
+ it("should include customErrorHandler in API call options", async () => {
520
+ const sdk = createTestSdk();
521
+ await sdk.listAuthentications({});
522
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
523
+ customErrorHandler: expect.any(Function),
524
+ }));
525
+ });
476
526
  });
477
527
  describe("error handling", () => {
478
528
  it("should throw ZapierAuthenticationError for 401 responses", async () => {
479
- const customErrorHandler = vi
480
- .fn()
481
- .mockReturnValue(new ZapierAuthenticationError("Authentication failed. Your token may not have permission to access authentications or may be expired. (HTTP 401)", { statusCode: 401 }));
482
- mockApiClient.get = vi.fn().mockImplementation(() => {
483
- const error = customErrorHandler({ status: 401 });
484
- throw error;
529
+ mockApiClient.get = vi.fn().mockImplementation((_url, options) => {
530
+ // Simulate the API client calling the customErrorHandler
531
+ const error = options.customErrorHandler({ status: 401 });
532
+ if (error)
533
+ throw error;
485
534
  });
486
535
  const sdk = createTestSdk();
487
536
  await expect(sdk.listAuthentications({})).rejects.toThrow(ZapierAuthenticationError);
537
+ await expect(sdk.listAuthentications({})).rejects.toThrow(/Authentication failed.*HTTP 401/);
488
538
  });
489
539
  it("should throw ZapierAuthenticationError for 403 responses", async () => {
490
- const customErrorHandler = vi
491
- .fn()
492
- .mockReturnValue(new ZapierAuthenticationError("Access forbidden. Your token may not have the required scopes to list authentications. (HTTP 403)", { statusCode: 403 }));
493
- mockApiClient.get = vi.fn().mockImplementation(() => {
494
- const error = customErrorHandler({ status: 403 });
495
- throw error;
540
+ mockApiClient.get = vi.fn().mockImplementation((_url, options) => {
541
+ // Simulate the API client calling the customErrorHandler
542
+ const error = options.customErrorHandler({ status: 403 });
543
+ if (error)
544
+ throw error;
496
545
  });
497
546
  const sdk = createTestSdk();
498
547
  await expect(sdk.listAuthentications({})).rejects.toThrow(ZapierAuthenticationError);
548
+ await expect(sdk.listAuthentications({})).rejects.toThrow(/Access forbidden.*HTTP 403/);
549
+ });
550
+ it("should return undefined from customErrorHandler for non-401/403 errors", async () => {
551
+ let customErrorHandlerResult;
552
+ mockApiClient.get = vi.fn().mockImplementation((_url, options) => {
553
+ // Capture the customErrorHandler result for 500
554
+ customErrorHandlerResult = options.customErrorHandler({ status: 500 });
555
+ // Return valid data since we're just testing the handler
556
+ return mockAuthenticationsResponse;
557
+ });
558
+ const sdk = createTestSdk();
559
+ await sdk.listAuthentications({});
560
+ // The customErrorHandler should return undefined for other status codes
561
+ expect(customErrorHandlerResult).toBeUndefined();
499
562
  });
500
- it("should handle empty results", async () => {
563
+ it("should handle empty data array", async () => {
501
564
  const emptyResponse = {
502
- results: [],
503
- count: 0,
504
- next: null,
565
+ data: [],
566
+ nextCursor: undefined,
505
567
  };
506
568
  mockApiClient.get = vi.fn().mockResolvedValue(emptyResponse);
507
569
  const sdk = createTestSdk();
@@ -509,59 +571,74 @@ describe("listAuthentications plugin", () => {
509
571
  expect(result.data).toHaveLength(0);
510
572
  expect(Array.isArray(result.data)).toBe(true);
511
573
  });
512
- it("should handle missing results field", async () => {
513
- const responseWithoutResults = {
514
- count: 0,
515
- next: null,
516
- };
517
- mockApiClient.get = vi.fn().mockResolvedValue(responseWithoutResults);
574
+ it("should handle API errors gracefully", async () => {
575
+ const networkError = new Error("Network error");
576
+ mockApiClient.get = vi.fn().mockRejectedValue(networkError);
518
577
  const sdk = createTestSdk();
519
- const result = await sdk.listAuthentications({});
520
- expect(result.data).toHaveLength(0);
521
- expect(Array.isArray(result.data)).toBe(true);
578
+ await expect(sdk.listAuthentications({})).rejects.toThrow("Network error");
522
579
  });
523
580
  });
524
581
  describe("app key integration", () => {
525
- it("should handle app without current_implementation_id", async () => {
582
+ it("should not add appKey parameter when getVersionedImplementationId returns null", async () => {
526
583
  mockGetVersionedImplementationId.mockResolvedValue(null);
527
584
  const sdk = createTestSdk();
528
585
  await sdk.listAuthentications({ appKey: "slack" });
529
- // Should not add versionless_selected_api parameter
530
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
586
+ // Should not add appKey parameter when implementation can't be resolved
587
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
531
588
  searchParams: expect.not.objectContaining({
532
- versionless_selected_api: expect.any(String),
589
+ appKey: expect.any(String),
533
590
  }),
534
591
  }));
535
592
  });
536
- it("should extract versionless API from versioned implementation ID", async () => {
593
+ it("should extract versionless app key from versioned implementation ID", async () => {
537
594
  mockGetVersionedImplementationId.mockResolvedValue("SlackCLIAPI@2.1.3");
538
595
  const sdk = createTestSdk();
539
596
  await sdk.listAuthentications({ appKey: "slack" });
540
- expect(mockApiClient.get).toHaveBeenCalledWith("/zapier/api/v4/authentications/", expect.objectContaining({
597
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
598
+ searchParams: expect.objectContaining({
599
+ appKey: "SlackCLIAPI",
600
+ }),
601
+ }));
602
+ });
603
+ it("should handle implementation ID without version", async () => {
604
+ mockGetVersionedImplementationId.mockResolvedValue("SlackCLIAPI");
605
+ const sdk = createTestSdk();
606
+ await sdk.listAuthentications({ appKey: "slack" });
607
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
541
608
  searchParams: expect.objectContaining({
542
- versionless_selected_api: "SlackCLIAPI",
609
+ appKey: "SlackCLIAPI",
543
610
  }),
544
611
  }));
545
612
  });
613
+ it("should call getVersionedImplementationId with the provided appKey", async () => {
614
+ const sdk = createTestSdk();
615
+ await sdk.listAuthentications({ appKey: "my-custom-app" });
616
+ expect(mockGetVersionedImplementationId).toHaveBeenCalledWith("my-custom-app");
617
+ });
618
+ it("should not call getVersionedImplementationId when appKey is not provided", async () => {
619
+ const sdk = createTestSdk();
620
+ await sdk.listAuthentications({});
621
+ expect(mockGetVersionedImplementationId).not.toHaveBeenCalled();
622
+ });
546
623
  });
547
624
  describe("response transformation", () => {
548
- it("should preserve all original authentication fields", async () => {
625
+ it("should preserve all authentication fields from new API response", async () => {
549
626
  const authWithAllFields = {
550
- results: [
627
+ data: [
551
628
  {
552
- id: 123,
629
+ id: "123",
553
630
  date: "2021-01-01",
554
631
  lastchanged: "2021-01-02",
555
- account_id: 456,
556
- customuser_id: 789,
557
- selected_api: "SlackCLIAPI@1.21.1",
632
+ account_id: "456",
633
+ profile_id: "789",
634
+ implementation_id: "SlackCLIAPI@1.21.1",
558
635
  destination_selected_api: "SlackDestAPI@1.0.0",
559
636
  is_invite_only: true,
560
637
  is_private: true,
561
638
  shared_with_all: false,
562
- is_stale: "true",
639
+ is_expired: "true",
563
640
  is_shared: "false",
564
- marked_stale_at: "2021-06-01",
641
+ expired_at: "2021-06-01",
565
642
  label: "Auth Label",
566
643
  title: "Auth Title",
567
644
  identifier: "auth-identifier",
@@ -569,15 +646,17 @@ describe("listAuthentications plugin", () => {
569
646
  groups: "group1,group2",
570
647
  members: "user1,user2",
571
648
  permissions: { read: true, write: false },
649
+ app_key: "SlackCLIAPI",
650
+ app_version: "1.21.1",
572
651
  },
573
652
  ],
574
- count: 1,
653
+ nextCursor: undefined,
575
654
  };
576
655
  mockApiClient.get = vi.fn().mockResolvedValue(authWithAllFields);
577
656
  const sdk = createTestSdk();
578
657
  const result = await sdk.listAuthentications({});
579
658
  const auth = result.data[0];
580
- // Verify original fields are preserved (IDs are converted to strings)
659
+ // Verify all fields are passed through from the new API
581
660
  expect(auth.id).toBe("123");
582
661
  expect(auth.date).toBe("2021-01-01");
583
662
  expect(auth.lastchanged).toBe("2021-01-02");
@@ -596,9 +675,29 @@ describe("listAuthentications plugin", () => {
596
675
  expect(auth.groups).toBe("group1,group2");
597
676
  expect(auth.members).toBe("user1,user2");
598
677
  expect(auth.permissions).toEqual({ read: true, write: false });
599
- // Verify mapped fields
600
678
  expect(auth.is_expired).toBe("true");
601
679
  expect(auth.expired_at).toBe("2021-06-01");
680
+ expect(auth.app_key).toBe("SlackCLIAPI");
681
+ expect(auth.app_version).toBe("1.21.1");
682
+ });
683
+ it("should handle authentications with minimal fields", async () => {
684
+ const minimalAuth = {
685
+ data: [
686
+ {
687
+ id: "100",
688
+ account_id: "200",
689
+ implementation_id: "TestAPI@1.0.0",
690
+ },
691
+ ],
692
+ nextCursor: undefined,
693
+ };
694
+ mockApiClient.get = vi.fn().mockResolvedValue(minimalAuth);
695
+ const sdk = createTestSdk();
696
+ const result = await sdk.listAuthentications({});
697
+ const auth = result.data[0];
698
+ expect(auth.id).toBe("100");
699
+ expect(auth.account_id).toBe("200");
700
+ expect(auth.implementation_id).toBe("TestAPI@1.0.0");
602
701
  });
603
702
  });
604
703
  describe("context and metadata", () => {
@@ -608,5 +707,126 @@ describe("listAuthentications plugin", () => {
608
707
  expect(context.meta.listAuthentications).toBeDefined();
609
708
  expect(context.meta.listAuthentications.inputSchema).toBeDefined();
610
709
  });
710
+ it("should include all required metadata properties", () => {
711
+ const sdk = createTestSdk();
712
+ const context = sdk.getContext();
713
+ const meta = context.meta.listAuthentications;
714
+ expect(meta.categories).toContain("authentication");
715
+ expect(meta.type).toBe("list");
716
+ expect(meta.itemType).toBe("Authentication");
717
+ expect(meta.inputSchema).toBeDefined();
718
+ expect(meta.outputSchema).toBeDefined();
719
+ expect(meta.resolvers).toBeDefined();
720
+ expect(meta.resolvers.appKey).toBeDefined();
721
+ });
722
+ });
723
+ describe("edge cases", () => {
724
+ it("should handle special characters in search parameter", async () => {
725
+ const sdk = createTestSdk();
726
+ await sdk.listAuthentications({ search: "test@email.com" });
727
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
728
+ searchParams: expect.objectContaining({
729
+ search: "test@email.com",
730
+ }),
731
+ }));
732
+ });
733
+ it("should handle special characters in title parameter", async () => {
734
+ const sdk = createTestSdk();
735
+ await sdk.listAuthentications({ title: "My Auth (Test) #1" });
736
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
737
+ searchParams: expect.objectContaining({
738
+ title: "My Auth (Test) #1",
739
+ }),
740
+ }));
741
+ });
742
+ it("should handle single authenticationId in array", async () => {
743
+ const sdk = createTestSdk();
744
+ await sdk.listAuthentications({ authenticationIds: ["single-id"] });
745
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
746
+ searchParams: expect.objectContaining({
747
+ authenticationIds: "single-id",
748
+ }),
749
+ }));
750
+ });
751
+ it("should handle multiple filters combined", async () => {
752
+ const sdk = createTestSdk();
753
+ await sdk.listAuthentications({
754
+ appKey: "slack",
755
+ search: "workspace",
756
+ title: "My Workspace",
757
+ accountId: "acc_123",
758
+ owner: "me",
759
+ authenticationIds: ["id1", "id2"],
760
+ pageSize: 10,
761
+ });
762
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
763
+ searchParams: expect.objectContaining({
764
+ appKey: "SlackCLIAPI",
765
+ search: "workspace",
766
+ title: "My Workspace",
767
+ accountId: "acc_123",
768
+ owner: "me",
769
+ authenticationIds: "id1,id2",
770
+ pageSize: "10",
771
+ }),
772
+ }));
773
+ });
774
+ it("should handle maxItems less than pageSize", async () => {
775
+ mockApiClient.get = vi.fn().mockResolvedValue({
776
+ data: mockAuthenticationsResponse.data,
777
+ nextCursor: "next-page",
778
+ });
779
+ const sdk = createTestSdk();
780
+ const items = [];
781
+ for await (const item of sdk
782
+ .listAuthentications({ pageSize: 10, maxItems: 1 })
783
+ .items()) {
784
+ items.push(item);
785
+ }
786
+ expect(items).toHaveLength(1);
787
+ // Should only make one API call even though there's a next page
788
+ expect(mockApiClient.get).toHaveBeenCalledTimes(1);
789
+ });
790
+ it("should handle owner filter with user ID instead of 'me'", async () => {
791
+ const sdk = createTestSdk();
792
+ await sdk.listAuthentications({ owner: "user_12345" });
793
+ expect(mockApiClient.get).toHaveBeenCalledWith("/api/v0/authentications", expect.objectContaining({
794
+ searchParams: expect.objectContaining({
795
+ owner: "user_12345",
796
+ }),
797
+ }));
798
+ });
799
+ it("should pass through nextCursor in response", async () => {
800
+ mockApiClient.get = vi.fn().mockResolvedValue({
801
+ data: mockAuthenticationsResponse.data,
802
+ nextCursor: "cursor-for-next-page",
803
+ });
804
+ const sdk = createTestSdk();
805
+ const result = await sdk.listAuthentications({});
806
+ expect(result.nextCursor).toBe("cursor-for-next-page");
807
+ });
808
+ it("should handle undefined nextCursor in response", async () => {
809
+ mockApiClient.get = vi.fn().mockResolvedValue({
810
+ data: mockAuthenticationsResponse.data,
811
+ nextCursor: undefined,
812
+ });
813
+ const sdk = createTestSdk();
814
+ const result = await sdk.listAuthentications({});
815
+ expect(result.nextCursor).toBeUndefined();
816
+ });
817
+ });
818
+ describe("concurrent requests", () => {
819
+ it("should handle multiple concurrent listAuthentications calls", async () => {
820
+ const sdk = createTestSdk();
821
+ const [result1, result2, result3] = await Promise.all([
822
+ sdk.listAuthentications({ search: "first" }),
823
+ sdk.listAuthentications({ search: "second" }),
824
+ sdk.listAuthentications({ search: "third" }),
825
+ ]);
826
+ expect(result1.data).toHaveLength(2);
827
+ expect(result2.data).toHaveLength(2);
828
+ expect(result3.data).toHaveLength(2);
829
+ expect(mockApiClient.get).toHaveBeenCalledTimes(3);
830
+ });
611
831
  });
612
832
  });