@zapier/zapier-sdk 0.33.0 → 0.33.2

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 (128) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.cjs +2 -1
  3. package/dist/index.d.mts +9 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.mjs +2 -1
  7. package/dist/plugins/registry/index.d.ts.map +1 -1
  8. package/dist/plugins/registry/index.js +1 -0
  9. package/dist/types/sdk.d.ts +8 -0
  10. package/dist/types/sdk.d.ts.map +1 -1
  11. package/package.json +2 -2
  12. package/dist/api/auth.test.d.ts +0 -2
  13. package/dist/api/auth.test.d.ts.map +0 -1
  14. package/dist/api/auth.test.js +0 -220
  15. package/dist/api/client.test.d.ts +0 -2
  16. package/dist/api/client.test.d.ts.map +0 -1
  17. package/dist/api/client.test.js +0 -611
  18. package/dist/api/debug.test.d.ts +0 -2
  19. package/dist/api/debug.test.d.ts.map +0 -1
  20. package/dist/api/debug.test.js +0 -59
  21. package/dist/api/polling.test.d.ts +0 -2
  22. package/dist/api/polling.test.d.ts.map +0 -1
  23. package/dist/api/polling.test.js +0 -360
  24. package/dist/auth.test.d.ts +0 -2
  25. package/dist/auth.test.d.ts.map +0 -1
  26. package/dist/auth.test.js +0 -480
  27. package/dist/plugins/eventEmission/builders.test.d.ts +0 -2
  28. package/dist/plugins/eventEmission/builders.test.d.ts.map +0 -1
  29. package/dist/plugins/eventEmission/builders.test.js +0 -138
  30. package/dist/plugins/eventEmission/index.test.d.ts +0 -5
  31. package/dist/plugins/eventEmission/index.test.d.ts.map +0 -1
  32. package/dist/plugins/eventEmission/index.test.js +0 -712
  33. package/dist/plugins/eventEmission/transport.test.d.ts +0 -5
  34. package/dist/plugins/eventEmission/transport.test.d.ts.map +0 -1
  35. package/dist/plugins/eventEmission/transport.test.js +0 -164
  36. package/dist/plugins/fetch/index.test.d.ts +0 -2
  37. package/dist/plugins/fetch/index.test.d.ts.map +0 -1
  38. package/dist/plugins/fetch/index.test.js +0 -428
  39. package/dist/plugins/findFirstConnection/index.test.d.ts +0 -2
  40. package/dist/plugins/findFirstConnection/index.test.d.ts.map +0 -1
  41. package/dist/plugins/findFirstConnection/index.test.js +0 -177
  42. package/dist/plugins/findUniqueConnection/index.test.d.ts +0 -2
  43. package/dist/plugins/findUniqueConnection/index.test.d.ts.map +0 -1
  44. package/dist/plugins/findUniqueConnection/index.test.js +0 -159
  45. package/dist/plugins/getAction/index.test.d.ts +0 -2
  46. package/dist/plugins/getAction/index.test.d.ts.map +0 -1
  47. package/dist/plugins/getAction/index.test.js +0 -211
  48. package/dist/plugins/getApp/index.test.d.ts +0 -2
  49. package/dist/plugins/getApp/index.test.d.ts.map +0 -1
  50. package/dist/plugins/getApp/index.test.js +0 -157
  51. package/dist/plugins/getConnection/index.test.d.ts +0 -2
  52. package/dist/plugins/getConnection/index.test.d.ts.map +0 -1
  53. package/dist/plugins/getConnection/index.test.js +0 -124
  54. package/dist/plugins/getInputFieldsSchema/index.test.d.ts +0 -2
  55. package/dist/plugins/getInputFieldsSchema/index.test.d.ts.map +0 -1
  56. package/dist/plugins/getInputFieldsSchema/index.test.js +0 -291
  57. package/dist/plugins/listActions/index.test.d.ts +0 -2
  58. package/dist/plugins/listActions/index.test.d.ts.map +0 -1
  59. package/dist/plugins/listActions/index.test.js +0 -454
  60. package/dist/plugins/listApps/index.test.d.ts +0 -2
  61. package/dist/plugins/listApps/index.test.d.ts.map +0 -1
  62. package/dist/plugins/listApps/index.test.js +0 -124
  63. package/dist/plugins/listConnections/index.test.d.ts +0 -2
  64. package/dist/plugins/listConnections/index.test.d.ts.map +0 -1
  65. package/dist/plugins/listConnections/index.test.js +0 -920
  66. package/dist/plugins/listInputFieldChoices/index.test.d.ts +0 -2
  67. package/dist/plugins/listInputFieldChoices/index.test.d.ts.map +0 -1
  68. package/dist/plugins/listInputFieldChoices/index.test.js +0 -717
  69. package/dist/plugins/listInputFields/index.test.d.ts +0 -2
  70. package/dist/plugins/listInputFields/index.test.d.ts.map +0 -1
  71. package/dist/plugins/listInputFields/index.test.js +0 -359
  72. package/dist/plugins/manifest/index.test.d.ts +0 -2
  73. package/dist/plugins/manifest/index.test.d.ts.map +0 -1
  74. package/dist/plugins/manifest/index.test.js +0 -1179
  75. package/dist/plugins/request/index.test.d.ts +0 -2
  76. package/dist/plugins/request/index.test.d.ts.map +0 -1
  77. package/dist/plugins/request/index.test.js +0 -458
  78. package/dist/plugins/runAction/index.test.d.ts +0 -2
  79. package/dist/plugins/runAction/index.test.d.ts.map +0 -1
  80. package/dist/plugins/runAction/index.test.js +0 -350
  81. package/dist/resolvers/connectionId.test.d.ts +0 -2
  82. package/dist/resolvers/connectionId.test.d.ts.map +0 -1
  83. package/dist/resolvers/connectionId.test.js +0 -61
  84. package/dist/sdk.test.d.ts +0 -2
  85. package/dist/sdk.test.d.ts.map +0 -1
  86. package/dist/sdk.test.js +0 -260
  87. package/dist/types/domain.test.d.ts +0 -2
  88. package/dist/types/domain.test.d.ts.map +0 -1
  89. package/dist/types/domain.test.js +0 -39
  90. package/dist/utils/array-utils.test.d.ts +0 -2
  91. package/dist/utils/array-utils.test.d.ts.map +0 -1
  92. package/dist/utils/array-utils.test.js +0 -107
  93. package/dist/utils/batch-utils.test.d.ts +0 -2
  94. package/dist/utils/batch-utils.test.d.ts.map +0 -1
  95. package/dist/utils/batch-utils.test.js +0 -476
  96. package/dist/utils/domain-utils.test.d.ts +0 -2
  97. package/dist/utils/domain-utils.test.d.ts.map +0 -1
  98. package/dist/utils/domain-utils.test.js +0 -346
  99. package/dist/utils/file-utils.test.d.ts +0 -2
  100. package/dist/utils/file-utils.test.d.ts.map +0 -1
  101. package/dist/utils/file-utils.test.js +0 -51
  102. package/dist/utils/function-utils.test.d.ts +0 -2
  103. package/dist/utils/function-utils.test.d.ts.map +0 -1
  104. package/dist/utils/function-utils.test.js +0 -188
  105. package/dist/utils/id-utils.test.d.ts +0 -2
  106. package/dist/utils/id-utils.test.d.ts.map +0 -1
  107. package/dist/utils/id-utils.test.js +0 -22
  108. package/dist/utils/pagination-utils.test.d.ts +0 -17
  109. package/dist/utils/pagination-utils.test.d.ts.map +0 -1
  110. package/dist/utils/pagination-utils.test.js +0 -461
  111. package/dist/utils/retry-utils.test.d.ts +0 -2
  112. package/dist/utils/retry-utils.test.d.ts.map +0 -1
  113. package/dist/utils/retry-utils.test.js +0 -90
  114. package/dist/utils/string-utils.test.d.ts +0 -2
  115. package/dist/utils/string-utils.test.d.ts.map +0 -1
  116. package/dist/utils/string-utils.test.js +0 -59
  117. package/dist/utils/telemetry-context.test.d.ts +0 -2
  118. package/dist/utils/telemetry-context.test.d.ts.map +0 -1
  119. package/dist/utils/telemetry-context.test.js +0 -154
  120. package/dist/utils/telemetry-utils.test.d.ts +0 -2
  121. package/dist/utils/telemetry-utils.test.d.ts.map +0 -1
  122. package/dist/utils/telemetry-utils.test.js +0 -155
  123. package/dist/utils/url-utils.test.d.ts +0 -2
  124. package/dist/utils/url-utils.test.d.ts.map +0 -1
  125. package/dist/utils/url-utils.test.js +0 -103
  126. package/dist/utils/validation.test.d.ts +0 -2
  127. package/dist/utils/validation.test.d.ts.map +0 -1
  128. package/dist/utils/validation.test.js +0 -44
@@ -1,712 +0,0 @@
1
- /**
2
- * Tests for Event Emission Plugin
3
- */
4
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5
- import { eventEmissionPlugin, cleanupEventListeners } from "./index";
6
- import { createTransport } from "./transport";
7
- import { clearTokenCache } from "../../auth";
8
- const { mockTransport, mockGetToken } = vi.hoisted(() => ({
9
- mockTransport: {
10
- emit: vi.fn().mockResolvedValue(undefined),
11
- close: vi.fn().mockResolvedValue(undefined),
12
- },
13
- mockGetToken: vi.fn().mockResolvedValue(undefined),
14
- }));
15
- vi.mock("./transport", () => ({
16
- createTransport: vi.fn(() => mockTransport),
17
- }));
18
- // Mock both CLI login import paths (SDK tries @zapier/zapier-sdk-cli/login
19
- // first, then falls back to @zapier/zapier-sdk-cli-login)
20
- vi.mock("@zapier/zapier-sdk-cli/login", () => ({
21
- getToken: mockGetToken,
22
- }));
23
- vi.mock("@zapier/zapier-sdk-cli-login", () => ({
24
- getToken: mockGetToken,
25
- }));
26
- describe("eventEmissionPlugin", () => {
27
- beforeEach(() => {
28
- vi.clearAllMocks();
29
- clearTokenCache();
30
- // Reset to default behavior - no token available
31
- mockGetToken.mockResolvedValue(undefined);
32
- delete process.env.ZAPIER_CREDENTIALS;
33
- delete process.env.ZAPIER_CREDENTIALS_CLIENT_ID;
34
- delete process.env.ZAPIER_CREDENTIALS_CLIENT_SECRET;
35
- delete process.env.ZAPIER_CREDENTIALS_BASE_URL;
36
- delete process.env.ZAPIER_CREDENTIALS_SCOPE;
37
- delete process.env.ZAPIER_TOKEN;
38
- delete process.env.ZAPIER_AUTH_CLIENT_ID;
39
- delete process.env.ZAPIER_AUTH_BASE_URL;
40
- });
41
- it("should create plugin with default configuration", () => {
42
- const plugin = eventEmissionPlugin({
43
- sdk: {},
44
- context: {
45
- meta: {},
46
- options: {},
47
- },
48
- });
49
- expect(plugin.context.eventEmission).toBeDefined();
50
- expect(plugin.context.eventEmission.emit).toBeDefined();
51
- expect(plugin.context.eventEmission.transport).toBeDefined();
52
- expect(plugin.context.eventEmission.config).toBeDefined();
53
- });
54
- it("should create noop implementations when disabled", () => {
55
- const config = { enabled: false };
56
- const plugin = eventEmissionPlugin({
57
- sdk: {},
58
- context: {
59
- meta: {},
60
- options: { eventEmission: config },
61
- },
62
- });
63
- // Should not emit any events when disabled
64
- plugin.context.eventEmission.emit("platform.sdk.TestEvent", {
65
- test_event: "data",
66
- });
67
- expect(mockTransport.emit).not.toHaveBeenCalled();
68
- });
69
- it("should emit events using generic emit method", async () => {
70
- const plugin = eventEmissionPlugin({
71
- sdk: {},
72
- context: {
73
- meta: {},
74
- options: {
75
- eventEmission: {
76
- enabled: true,
77
- transport: { type: "console" },
78
- },
79
- },
80
- },
81
- });
82
- await plugin.context.eventEmission.flush();
83
- // Clear startup event calls so we only assert on our test event
84
- mockTransport.emit.mockClear();
85
- const testEvent = {
86
- test_data: "example",
87
- value: 123,
88
- };
89
- const testSubject = "test.event.TestEvent";
90
- plugin.context.eventEmission.emit(testSubject, testEvent);
91
- await plugin.context.eventEmission.flush();
92
- // The event will be enriched with user context (null values)
93
- expect(mockTransport.emit).toHaveBeenCalledWith(testSubject, {
94
- ...testEvent,
95
- customuser_id: null,
96
- account_id: null,
97
- });
98
- });
99
- it("should handle transport creation failures silently", () => {
100
- // Mock createTransport to throw an error
101
- vi.mocked(createTransport).mockImplementationOnce(() => {
102
- throw new Error("Transport creation failed");
103
- });
104
- expect(() => {
105
- eventEmissionPlugin({
106
- sdk: {},
107
- context: {
108
- meta: {},
109
- options: {
110
- eventEmission: {
111
- enabled: true,
112
- transport: { type: "http", endpoint: "invalid-url" },
113
- },
114
- },
115
- },
116
- });
117
- }).not.toThrow();
118
- });
119
- it("should handle event emission failures silently", async () => {
120
- // Mock transport to throw error
121
- const failingTransport = {
122
- emit: vi.fn().mockRejectedValue(new Error("Network error")),
123
- close: vi.fn().mockResolvedValue(undefined),
124
- };
125
- vi.mocked(createTransport).mockReturnValueOnce(failingTransport);
126
- const plugin = eventEmissionPlugin({
127
- sdk: {},
128
- context: {
129
- meta: {},
130
- options: {
131
- eventEmission: {
132
- enabled: true,
133
- transport: {
134
- type: "http",
135
- endpoint: "https://example.com",
136
- },
137
- },
138
- },
139
- },
140
- });
141
- await plugin.context.eventEmission.flush();
142
- // Should not throw even if transport fails
143
- expect(() => {
144
- plugin.context.eventEmission.emit("test.event.TestEvent", {
145
- test_event: "data",
146
- });
147
- }).not.toThrow();
148
- await plugin.context.eventEmission.flush();
149
- expect(failingTransport.emit).toHaveBeenCalled();
150
- });
151
- it("should merge options with defaults", () => {
152
- // Override env var to ensure proper transport is used
153
- vi.stubEnv("ZAPIER_SDK_TELEMETRY_TRANSPORT", undefined);
154
- const plugin = eventEmissionPlugin({
155
- sdk: {},
156
- context: {
157
- meta: {},
158
- options: {
159
- eventEmission: {
160
- transport: {
161
- type: "http",
162
- endpoint: "https://example.com",
163
- },
164
- },
165
- },
166
- },
167
- });
168
- expect(plugin.context.eventEmission.config.enabled).toBe(true);
169
- expect(plugin.context.eventEmission.config.transport).toEqual({
170
- type: "http",
171
- endpoint: "https://example.com",
172
- });
173
- vi.unstubAllEnvs();
174
- });
175
- it("should extract user IDs from JWT token and include in events", async () => {
176
- // Create a test JWT token with user data
177
- // JWT format: header.payload.signature
178
- const header = { alg: "HS256", typ: "JWT" };
179
- const payload = {
180
- "zap:acc": "12345",
181
- sub: "67890",
182
- sub_type: "customuser",
183
- "zap:uname": "test@example.com",
184
- };
185
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
186
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
187
- const testJwt = `${encodedHeader}.${encodedPayload}.fake-signature`;
188
- // Mock getToken to return the JWT
189
- mockGetToken.mockResolvedValue(testJwt);
190
- const plugin = eventEmissionPlugin({
191
- sdk: {},
192
- context: {
193
- meta: {},
194
- options: {
195
- eventEmission: {
196
- enabled: true,
197
- transport: { type: "console" },
198
- },
199
- },
200
- },
201
- });
202
- // Test that createBaseEvent includes the extracted user IDs
203
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
204
- expect(baseEvent.customuser_id).toBe(67890);
205
- expect(baseEvent.account_id).toBe(12345);
206
- });
207
- it("should handle service tokens with nested JWT", async () => {
208
- // Create a nested JWT for service token testing
209
- const nestedHeader = { alg: "HS256", typ: "JWT" };
210
- const nestedPayload = {
211
- "zap:acc": "99999",
212
- sub: "88888",
213
- sub_type: "customuser",
214
- };
215
- const nestedEncodedHeader = Buffer.from(JSON.stringify(nestedHeader)).toString("base64url");
216
- const nestedEncodedPayload = Buffer.from(JSON.stringify(nestedPayload)).toString("base64url");
217
- const nestedJwt = `${nestedEncodedHeader}.${nestedEncodedPayload}.nested-signature`;
218
- // Create the service token that wraps the nested JWT
219
- const serviceHeader = { alg: "HS256", typ: "JWT" };
220
- const servicePayload = {
221
- "zap:acc": "11111",
222
- sub: "22222",
223
- sub_type: "service",
224
- njwt: nestedJwt,
225
- };
226
- const serviceEncodedHeader = Buffer.from(JSON.stringify(serviceHeader)).toString("base64url");
227
- const serviceEncodedPayload = Buffer.from(JSON.stringify(servicePayload)).toString("base64url");
228
- const serviceJwt = `${serviceEncodedHeader}.${serviceEncodedPayload}.service-signature`;
229
- // Mock getToken to return the service JWT
230
- mockGetToken.mockResolvedValue(serviceJwt);
231
- const plugin = eventEmissionPlugin({
232
- sdk: {},
233
- context: {
234
- meta: {},
235
- options: {
236
- eventEmission: {
237
- enabled: true,
238
- transport: { type: "console" },
239
- },
240
- },
241
- },
242
- });
243
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
244
- // Should extract from nested JWT, not the service token
245
- expect(baseEvent.customuser_id).toBe(88888);
246
- expect(baseEvent.account_id).toBe(99999);
247
- });
248
- it("should handle invalid JWT tokens gracefully", async () => {
249
- // Mock getToken to return an invalid JWT
250
- mockGetToken.mockResolvedValue("not-a-valid-jwt-token");
251
- const plugin = eventEmissionPlugin({
252
- sdk: {},
253
- context: {
254
- meta: {},
255
- options: {
256
- eventEmission: {
257
- enabled: true,
258
- transport: { type: "console" },
259
- },
260
- },
261
- },
262
- });
263
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
264
- // Should default to null when JWT is invalid
265
- expect(baseEvent.customuser_id).toBe(null);
266
- expect(baseEvent.account_id).toBe(null);
267
- });
268
- it("should handle missing token gracefully", async () => {
269
- // mockGetToken defaults to returning undefined (no token)
270
- const plugin = eventEmissionPlugin({
271
- sdk: {},
272
- context: {
273
- meta: {},
274
- options: {
275
- eventEmission: {
276
- enabled: true,
277
- transport: { type: "console" },
278
- },
279
- },
280
- },
281
- });
282
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
283
- // Should default to null when no token is provided
284
- expect(baseEvent.customuser_id).toBe(null);
285
- expect(baseEvent.account_id).toBe(null);
286
- });
287
- it("should extract user IDs when getToken returns valid JWT", async () => {
288
- // Create a test JWT token
289
- const header = { alg: "HS256", typ: "JWT" };
290
- const payload = {
291
- "zap:acc": "98765",
292
- sub: "54321",
293
- sub_type: "customuser",
294
- };
295
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
296
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
297
- const testJwt = `${encodedHeader}.${encodedPayload}.test-signature`;
298
- // Mock getToken to return the JWT
299
- mockGetToken.mockResolvedValue(testJwt);
300
- const plugin = eventEmissionPlugin({
301
- sdk: {},
302
- context: {
303
- meta: {},
304
- options: {
305
- eventEmission: {
306
- enabled: true,
307
- transport: { type: "console" },
308
- },
309
- },
310
- },
311
- });
312
- // Test that createBaseEvent includes the extracted user IDs
313
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
314
- expect(baseEvent.customuser_id).toBe(54321);
315
- expect(baseEvent.account_id).toBe(98765);
316
- });
317
- it("should handle getToken failures gracefully", async () => {
318
- // Mock getToken to reject/throw
319
- mockGetToken.mockRejectedValue(new Error("Token fetch failed"));
320
- const plugin = eventEmissionPlugin({
321
- sdk: {},
322
- context: {
323
- meta: {},
324
- options: {
325
- eventEmission: {
326
- enabled: true,
327
- transport: { type: "console" },
328
- },
329
- },
330
- },
331
- });
332
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
333
- // Should gracefully fall back to null context
334
- expect(baseEvent.customuser_id).toBe(null);
335
- expect(baseEvent.account_id).toBe(null);
336
- });
337
- it("should extract user IDs from static token in SDK options", async () => {
338
- // Create a test JWT token
339
- const header = { alg: "HS256", typ: "JWT" };
340
- const payload = {
341
- "zap:acc": "11111",
342
- sub: "22222",
343
- sub_type: "customuser",
344
- };
345
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
346
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
347
- const testJwt = `${encodedHeader}.${encodedPayload}.static-signature`;
348
- const plugin = eventEmissionPlugin({
349
- sdk: {},
350
- context: {
351
- meta: {},
352
- options: {
353
- token: testJwt,
354
- eventEmission: {
355
- enabled: true,
356
- transport: { type: "console" },
357
- },
358
- },
359
- },
360
- });
361
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
362
- // Should extract from static token in options
363
- expect(baseEvent.customuser_id).toBe(22222);
364
- expect(baseEvent.account_id).toBe(11111);
365
- // CLI login package should not be called when token is in options
366
- expect(mockGetToken).not.toHaveBeenCalled();
367
- });
368
- it("should extract user IDs from credentials function in SDK options", async () => {
369
- // Create a test JWT token
370
- const header = { alg: "HS256", typ: "JWT" };
371
- const payload = {
372
- "zap:acc": "33333",
373
- sub: "44444",
374
- sub_type: "customuser",
375
- };
376
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
377
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
378
- const testJwt = `${encodedHeader}.${encodedPayload}.custom-signature`;
379
- const credentialsFn = vi.fn().mockResolvedValue(testJwt);
380
- const plugin = eventEmissionPlugin({
381
- sdk: {},
382
- context: {
383
- meta: {},
384
- options: {
385
- credentials: credentialsFn,
386
- eventEmission: {
387
- enabled: true,
388
- transport: { type: "console" },
389
- },
390
- },
391
- },
392
- });
393
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
394
- // Should extract from custom credentials function
395
- expect(baseEvent.customuser_id).toBe(44444);
396
- expect(baseEvent.account_id).toBe(33333);
397
- expect(credentialsFn).toHaveBeenCalled();
398
- // CLI login package should not be called when credentials is in options
399
- expect(mockGetToken).not.toHaveBeenCalled();
400
- });
401
- it("should prioritize string credentials over credentials function", async () => {
402
- // Create test JWT tokens
403
- const staticHeader = { alg: "HS256", typ: "JWT" };
404
- const staticPayload = {
405
- "zap:acc": "55555",
406
- sub: "66666",
407
- sub_type: "customuser",
408
- };
409
- const staticEncodedHeader = Buffer.from(JSON.stringify(staticHeader)).toString("base64url");
410
- const staticEncodedPayload = Buffer.from(JSON.stringify(staticPayload)).toString("base64url");
411
- const staticJwt = `${staticEncodedHeader}.${staticEncodedPayload}.static-sig`;
412
- const plugin = eventEmissionPlugin({
413
- sdk: {},
414
- context: {
415
- meta: {},
416
- options: {
417
- credentials: staticJwt,
418
- eventEmission: {
419
- enabled: true,
420
- transport: { type: "console" },
421
- },
422
- },
423
- },
424
- });
425
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
426
- // Should use string credentials
427
- expect(baseEvent.customuser_id).toBe(66666);
428
- expect(baseEvent.account_id).toBe(55555);
429
- expect(mockGetToken).not.toHaveBeenCalled();
430
- });
431
- it("should fall back to CLI login when SDK options have no token", async () => {
432
- // Create a test JWT token for CLI login
433
- const header = { alg: "HS256", typ: "JWT" };
434
- const payload = {
435
- "zap:acc": "77777",
436
- sub: "88888",
437
- sub_type: "customuser",
438
- };
439
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
440
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
441
- const testJwt = `${encodedHeader}.${encodedPayload}.cli-signature`;
442
- mockGetToken.mockResolvedValue(testJwt);
443
- const plugin = eventEmissionPlugin({
444
- sdk: {},
445
- context: {
446
- meta: {},
447
- options: {
448
- // No token or getToken in options
449
- eventEmission: {
450
- enabled: true,
451
- transport: { type: "console" },
452
- },
453
- },
454
- },
455
- });
456
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
457
- // Should fall back to CLI login package
458
- expect(baseEvent.customuser_id).toBe(88888);
459
- expect(baseEvent.account_id).toBe(77777);
460
- expect(mockGetToken).toHaveBeenCalled();
461
- });
462
- it("should handle credentials function returning undefined and fall back to CLI login", async () => {
463
- const credentialsFn = vi.fn().mockResolvedValue(undefined);
464
- // Also mock CLI login to return a token
465
- const header = { alg: "HS256", typ: "JWT" };
466
- const payload = {
467
- "zap:acc": "99999",
468
- sub: "10101",
469
- sub_type: "customuser",
470
- };
471
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
472
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
473
- const testJwt = `${encodedHeader}.${encodedPayload}.fallback-signature`;
474
- mockGetToken.mockResolvedValue(testJwt);
475
- const plugin = eventEmissionPlugin({
476
- sdk: {},
477
- context: {
478
- meta: {},
479
- options: {
480
- credentials: credentialsFn,
481
- eventEmission: {
482
- enabled: true,
483
- transport: { type: "console" },
484
- },
485
- },
486
- },
487
- });
488
- const baseEvent = await plugin.context.eventEmission.createBaseEvent();
489
- // Since credentials function returns undefined, SDK falls back to CLI login
490
- expect(baseEvent.customuser_id).toBe(10101);
491
- expect(baseEvent.account_id).toBe(99999);
492
- expect(credentialsFn).toHaveBeenCalled();
493
- expect(mockGetToken).toHaveBeenCalled();
494
- });
495
- });
496
- describe("emitMethodCalled call_context", () => {
497
- beforeEach(() => {
498
- vi.clearAllMocks();
499
- mockGetToken.mockResolvedValue(undefined);
500
- });
501
- it("should default call_context to 'sdk' when callContext is not configured", async () => {
502
- const plugin = eventEmissionPlugin({
503
- sdk: {},
504
- context: {
505
- meta: {},
506
- options: {
507
- eventEmission: {
508
- enabled: true,
509
- transport: { type: "console" },
510
- },
511
- },
512
- },
513
- });
514
- plugin.context.eventEmission.emitMethodCalled({
515
- method_name: "listApps",
516
- execution_duration_ms: 50,
517
- success_flag: true,
518
- argument_count: 0,
519
- });
520
- await plugin.context.eventEmission.flush();
521
- expect(mockTransport.emit).toHaveBeenCalledWith("platform.sdk.MethodCalledEvent", expect.objectContaining({ call_context: "sdk" }));
522
- });
523
- it("should set call_context to 'cli' when callContext is 'cli'", async () => {
524
- const plugin = eventEmissionPlugin({
525
- sdk: {},
526
- context: {
527
- meta: {},
528
- options: {
529
- eventEmission: {
530
- enabled: true,
531
- transport: { type: "console" },
532
- callContext: "cli",
533
- },
534
- },
535
- },
536
- });
537
- plugin.context.eventEmission.emitMethodCalled({
538
- method_name: "listApps",
539
- execution_duration_ms: 50,
540
- success_flag: true,
541
- argument_count: 0,
542
- });
543
- await plugin.context.eventEmission.flush();
544
- expect(mockTransport.emit).toHaveBeenCalledWith("platform.sdk.MethodCalledEvent", expect.objectContaining({ call_context: "cli" }));
545
- });
546
- it("should set call_context to 'mcp' when callContext is 'mcp'", async () => {
547
- const plugin = eventEmissionPlugin({
548
- sdk: {},
549
- context: {
550
- meta: {},
551
- options: {
552
- eventEmission: {
553
- enabled: true,
554
- transport: { type: "console" },
555
- callContext: "mcp",
556
- },
557
- },
558
- },
559
- });
560
- plugin.context.eventEmission.emitMethodCalled({
561
- method_name: "listApps",
562
- execution_duration_ms: 50,
563
- success_flag: true,
564
- argument_count: 0,
565
- });
566
- await plugin.context.eventEmission.flush();
567
- expect(mockTransport.emit).toHaveBeenCalledWith("platform.sdk.MethodCalledEvent", expect.objectContaining({ call_context: "mcp" }));
568
- });
569
- });
570
- describe("close()", () => {
571
- beforeEach(() => {
572
- vi.clearAllMocks();
573
- cleanupEventListeners();
574
- mockGetToken.mockResolvedValue(undefined);
575
- });
576
- afterEach(() => {
577
- cleanupEventListeners();
578
- });
579
- it("should emit exit lifecycle event", async () => {
580
- const plugin = eventEmissionPlugin({
581
- sdk: {},
582
- context: {
583
- meta: {},
584
- options: {
585
- eventEmission: {
586
- enabled: true,
587
- transport: { type: "console" },
588
- },
589
- },
590
- },
591
- });
592
- await plugin.context.eventEmission.close(0);
593
- expect(mockTransport.emit).toHaveBeenCalledWith("platform.sdk.ApplicationLifecycleEvent", expect.objectContaining({
594
- lifecycle_event_type: "exit",
595
- exit_code: 0,
596
- is_graceful_shutdown: true,
597
- }));
598
- });
599
- it("should be idempotent — second call is a no-op", async () => {
600
- const plugin = eventEmissionPlugin({
601
- sdk: {},
602
- context: {
603
- meta: {},
604
- options: {
605
- eventEmission: {
606
- enabled: true,
607
- transport: { type: "console" },
608
- },
609
- },
610
- },
611
- });
612
- await plugin.context.eventEmission.close(0);
613
- const callCountAfterFirst = mockTransport.emit.mock.calls.filter((call) => call[0] === "platform.sdk.ApplicationLifecycleEvent" &&
614
- call[1]?.lifecycle_event_type === "exit").length;
615
- await plugin.context.eventEmission.close(0);
616
- const callCountAfterSecond = mockTransport.emit.mock.calls.filter((call) => call[0] === "platform.sdk.ApplicationLifecycleEvent" &&
617
- call[1]?.lifecycle_event_type === "exit").length;
618
- expect(callCountAfterSecond).toBe(callCountAfterFirst);
619
- });
620
- it("should remove process listeners after close", async () => {
621
- const initialExitCount = process.listenerCount("exit");
622
- const plugin = eventEmissionPlugin({
623
- sdk: {},
624
- context: {
625
- meta: {},
626
- options: {
627
- eventEmission: {
628
- enabled: true,
629
- transport: { type: "console" },
630
- },
631
- },
632
- },
633
- });
634
- expect(process.listenerCount("exit")).toBe(initialExitCount + 1);
635
- await plugin.context.eventEmission.close(0);
636
- expect(process.listenerCount("exit")).toBe(initialExitCount);
637
- });
638
- it("should handle transport failures silently", async () => {
639
- const failingTransport = {
640
- emit: vi.fn().mockRejectedValue(new Error("Network error")),
641
- close: vi.fn().mockResolvedValue(undefined),
642
- };
643
- vi.mocked(createTransport).mockReturnValueOnce(failingTransport);
644
- const plugin = eventEmissionPlugin({
645
- sdk: {},
646
- context: {
647
- meta: {},
648
- options: {
649
- eventEmission: {
650
- enabled: true,
651
- transport: {
652
- type: "http",
653
- endpoint: "https://example.com",
654
- },
655
- },
656
- },
657
- },
658
- });
659
- await expect(plugin.context.eventEmission.close(1)).resolves.toBeUndefined();
660
- });
661
- });
662
- describe("Process Listener Cleanup", () => {
663
- beforeEach(() => {
664
- // Clean up any listeners from previous tests
665
- cleanupEventListeners();
666
- });
667
- afterEach(() => {
668
- cleanupEventListeners();
669
- });
670
- it("should not accumulate process listeners when creating multiple SDK instances", () => {
671
- const initialExitListenerCount = process.listenerCount("exit");
672
- // Create multiple SDK instances
673
- for (let i = 0; i < 5; i++) {
674
- eventEmissionPlugin({
675
- sdk: {},
676
- context: { meta: {}, options: {} },
677
- });
678
- }
679
- // Should only have added 1 listener, not 5
680
- expect(process.listenerCount("exit")).toBe(initialExitListenerCount + 1);
681
- });
682
- it("should remove existing listeners before registering new ones", () => {
683
- const removeListenerSpy = vi.spyOn(process, "removeListener");
684
- // Create first plugin instance
685
- eventEmissionPlugin({
686
- sdk: {},
687
- context: { meta: {}, options: {} },
688
- });
689
- // Create second plugin instance (should remove listeners from first)
690
- eventEmissionPlugin({
691
- sdk: {},
692
- context: { meta: {}, options: {} },
693
- });
694
- // Verify cleanup happened for all event types
695
- expect(removeListenerSpy).toHaveBeenCalledWith("exit", expect.any(Function));
696
- expect(removeListenerSpy).toHaveBeenCalledWith("uncaughtException", expect.any(Function));
697
- expect(removeListenerSpy).toHaveBeenCalledWith("unhandledRejection", expect.any(Function));
698
- expect(removeListenerSpy).toHaveBeenCalledWith("SIGINT", expect.any(Function));
699
- expect(removeListenerSpy).toHaveBeenCalledWith("SIGTERM", expect.any(Function));
700
- removeListenerSpy.mockRestore();
701
- });
702
- it("should allow explicit cleanup via cleanupEventListeners", () => {
703
- const initialExitListenerCount = process.listenerCount("exit");
704
- eventEmissionPlugin({
705
- sdk: {},
706
- context: { meta: {}, options: {} },
707
- });
708
- expect(process.listenerCount("exit")).toBe(initialExitListenerCount + 1);
709
- cleanupEventListeners();
710
- expect(process.listenerCount("exit")).toBe(initialExitListenerCount);
711
- });
712
- });