@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.
- package/CHANGELOG.md +12 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/plugins/registry/index.d.ts.map +1 -1
- package/dist/plugins/registry/index.js +1 -0
- package/dist/types/sdk.d.ts +8 -0
- package/dist/types/sdk.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/api/auth.test.d.ts +0 -2
- package/dist/api/auth.test.d.ts.map +0 -1
- package/dist/api/auth.test.js +0 -220
- package/dist/api/client.test.d.ts +0 -2
- package/dist/api/client.test.d.ts.map +0 -1
- package/dist/api/client.test.js +0 -611
- package/dist/api/debug.test.d.ts +0 -2
- package/dist/api/debug.test.d.ts.map +0 -1
- package/dist/api/debug.test.js +0 -59
- package/dist/api/polling.test.d.ts +0 -2
- package/dist/api/polling.test.d.ts.map +0 -1
- package/dist/api/polling.test.js +0 -360
- package/dist/auth.test.d.ts +0 -2
- package/dist/auth.test.d.ts.map +0 -1
- package/dist/auth.test.js +0 -480
- package/dist/plugins/eventEmission/builders.test.d.ts +0 -2
- package/dist/plugins/eventEmission/builders.test.d.ts.map +0 -1
- package/dist/plugins/eventEmission/builders.test.js +0 -138
- package/dist/plugins/eventEmission/index.test.d.ts +0 -5
- package/dist/plugins/eventEmission/index.test.d.ts.map +0 -1
- package/dist/plugins/eventEmission/index.test.js +0 -712
- package/dist/plugins/eventEmission/transport.test.d.ts +0 -5
- package/dist/plugins/eventEmission/transport.test.d.ts.map +0 -1
- package/dist/plugins/eventEmission/transport.test.js +0 -164
- package/dist/plugins/fetch/index.test.d.ts +0 -2
- package/dist/plugins/fetch/index.test.d.ts.map +0 -1
- package/dist/plugins/fetch/index.test.js +0 -428
- package/dist/plugins/findFirstConnection/index.test.d.ts +0 -2
- package/dist/plugins/findFirstConnection/index.test.d.ts.map +0 -1
- package/dist/plugins/findFirstConnection/index.test.js +0 -177
- package/dist/plugins/findUniqueConnection/index.test.d.ts +0 -2
- package/dist/plugins/findUniqueConnection/index.test.d.ts.map +0 -1
- package/dist/plugins/findUniqueConnection/index.test.js +0 -159
- package/dist/plugins/getAction/index.test.d.ts +0 -2
- package/dist/plugins/getAction/index.test.d.ts.map +0 -1
- package/dist/plugins/getAction/index.test.js +0 -211
- package/dist/plugins/getApp/index.test.d.ts +0 -2
- package/dist/plugins/getApp/index.test.d.ts.map +0 -1
- package/dist/plugins/getApp/index.test.js +0 -157
- package/dist/plugins/getConnection/index.test.d.ts +0 -2
- package/dist/plugins/getConnection/index.test.d.ts.map +0 -1
- package/dist/plugins/getConnection/index.test.js +0 -124
- package/dist/plugins/getInputFieldsSchema/index.test.d.ts +0 -2
- package/dist/plugins/getInputFieldsSchema/index.test.d.ts.map +0 -1
- package/dist/plugins/getInputFieldsSchema/index.test.js +0 -291
- package/dist/plugins/listActions/index.test.d.ts +0 -2
- package/dist/plugins/listActions/index.test.d.ts.map +0 -1
- package/dist/plugins/listActions/index.test.js +0 -454
- package/dist/plugins/listApps/index.test.d.ts +0 -2
- package/dist/plugins/listApps/index.test.d.ts.map +0 -1
- package/dist/plugins/listApps/index.test.js +0 -124
- package/dist/plugins/listConnections/index.test.d.ts +0 -2
- package/dist/plugins/listConnections/index.test.d.ts.map +0 -1
- package/dist/plugins/listConnections/index.test.js +0 -920
- package/dist/plugins/listInputFieldChoices/index.test.d.ts +0 -2
- package/dist/plugins/listInputFieldChoices/index.test.d.ts.map +0 -1
- package/dist/plugins/listInputFieldChoices/index.test.js +0 -717
- package/dist/plugins/listInputFields/index.test.d.ts +0 -2
- package/dist/plugins/listInputFields/index.test.d.ts.map +0 -1
- package/dist/plugins/listInputFields/index.test.js +0 -359
- package/dist/plugins/manifest/index.test.d.ts +0 -2
- package/dist/plugins/manifest/index.test.d.ts.map +0 -1
- package/dist/plugins/manifest/index.test.js +0 -1179
- package/dist/plugins/request/index.test.d.ts +0 -2
- package/dist/plugins/request/index.test.d.ts.map +0 -1
- package/dist/plugins/request/index.test.js +0 -458
- package/dist/plugins/runAction/index.test.d.ts +0 -2
- package/dist/plugins/runAction/index.test.d.ts.map +0 -1
- package/dist/plugins/runAction/index.test.js +0 -350
- package/dist/resolvers/connectionId.test.d.ts +0 -2
- package/dist/resolvers/connectionId.test.d.ts.map +0 -1
- package/dist/resolvers/connectionId.test.js +0 -61
- package/dist/sdk.test.d.ts +0 -2
- package/dist/sdk.test.d.ts.map +0 -1
- package/dist/sdk.test.js +0 -260
- package/dist/types/domain.test.d.ts +0 -2
- package/dist/types/domain.test.d.ts.map +0 -1
- package/dist/types/domain.test.js +0 -39
- package/dist/utils/array-utils.test.d.ts +0 -2
- package/dist/utils/array-utils.test.d.ts.map +0 -1
- package/dist/utils/array-utils.test.js +0 -107
- package/dist/utils/batch-utils.test.d.ts +0 -2
- package/dist/utils/batch-utils.test.d.ts.map +0 -1
- package/dist/utils/batch-utils.test.js +0 -476
- package/dist/utils/domain-utils.test.d.ts +0 -2
- package/dist/utils/domain-utils.test.d.ts.map +0 -1
- package/dist/utils/domain-utils.test.js +0 -346
- package/dist/utils/file-utils.test.d.ts +0 -2
- package/dist/utils/file-utils.test.d.ts.map +0 -1
- package/dist/utils/file-utils.test.js +0 -51
- package/dist/utils/function-utils.test.d.ts +0 -2
- package/dist/utils/function-utils.test.d.ts.map +0 -1
- package/dist/utils/function-utils.test.js +0 -188
- package/dist/utils/id-utils.test.d.ts +0 -2
- package/dist/utils/id-utils.test.d.ts.map +0 -1
- package/dist/utils/id-utils.test.js +0 -22
- package/dist/utils/pagination-utils.test.d.ts +0 -17
- package/dist/utils/pagination-utils.test.d.ts.map +0 -1
- package/dist/utils/pagination-utils.test.js +0 -461
- package/dist/utils/retry-utils.test.d.ts +0 -2
- package/dist/utils/retry-utils.test.d.ts.map +0 -1
- package/dist/utils/retry-utils.test.js +0 -90
- package/dist/utils/string-utils.test.d.ts +0 -2
- package/dist/utils/string-utils.test.d.ts.map +0 -1
- package/dist/utils/string-utils.test.js +0 -59
- package/dist/utils/telemetry-context.test.d.ts +0 -2
- package/dist/utils/telemetry-context.test.d.ts.map +0 -1
- package/dist/utils/telemetry-context.test.js +0 -154
- package/dist/utils/telemetry-utils.test.d.ts +0 -2
- package/dist/utils/telemetry-utils.test.d.ts.map +0 -1
- package/dist/utils/telemetry-utils.test.js +0 -155
- package/dist/utils/url-utils.test.d.ts +0 -2
- package/dist/utils/url-utils.test.d.ts.map +0 -1
- package/dist/utils/url-utils.test.js +0 -103
- package/dist/utils/validation.test.d.ts +0 -2
- package/dist/utils/validation.test.d.ts.map +0 -1
- 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
|
-
});
|