@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
package/dist/auth.test.js
DELETED
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import * as auth from "./auth";
|
|
3
|
-
import { resetDeprecationWarnings } from "./utils/logging";
|
|
4
|
-
// Mock both CLI login import paths (SDK tries @zapier/zapier-sdk-cli/login
|
|
5
|
-
// first, then falls back to @zapier/zapier-sdk-cli-login)
|
|
6
|
-
const mockGetToken = vi.fn();
|
|
7
|
-
vi.mock("@zapier/zapier-sdk-cli/login", () => ({
|
|
8
|
-
getToken: mockGetToken,
|
|
9
|
-
}));
|
|
10
|
-
vi.mock("@zapier/zapier-sdk-cli-login", () => ({
|
|
11
|
-
getToken: mockGetToken,
|
|
12
|
-
}));
|
|
13
|
-
// Mock fetch for client credentials tests
|
|
14
|
-
const mockFetch = vi.fn();
|
|
15
|
-
describe("auth", () => {
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
vi.clearAllMocks();
|
|
18
|
-
auth.clearTokenCache();
|
|
19
|
-
resetDeprecationWarnings();
|
|
20
|
-
// Clear all ZAPIER_* env vars
|
|
21
|
-
delete process.env.ZAPIER_CREDENTIALS;
|
|
22
|
-
delete process.env.ZAPIER_CREDENTIALS_CLIENT_ID;
|
|
23
|
-
delete process.env.ZAPIER_CREDENTIALS_CLIENT_SECRET;
|
|
24
|
-
delete process.env.ZAPIER_CREDENTIALS_BASE_URL;
|
|
25
|
-
delete process.env.ZAPIER_TOKEN;
|
|
26
|
-
delete process.env.ZAPIER_AUTH_CLIENT_ID;
|
|
27
|
-
delete process.env.ZAPIER_AUTH_BASE_URL;
|
|
28
|
-
});
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
auth.clearTokenCache();
|
|
31
|
-
});
|
|
32
|
-
describe("getTokenFromCliLogin", () => {
|
|
33
|
-
it("should pass options to CLI login package getToken", async () => {
|
|
34
|
-
mockGetToken.mockResolvedValue("cli-token");
|
|
35
|
-
const options = {
|
|
36
|
-
baseUrl: "https://api.custom.zapier.com",
|
|
37
|
-
onEvent: vi.fn(),
|
|
38
|
-
fetch: vi.fn(),
|
|
39
|
-
};
|
|
40
|
-
const result = await auth.getTokenFromCliLogin(options);
|
|
41
|
-
expect(result).toBe("cli-token");
|
|
42
|
-
expect(mockGetToken).toHaveBeenCalled();
|
|
43
|
-
});
|
|
44
|
-
it("should propagate errors from CLI login package getToken", async () => {
|
|
45
|
-
mockGetToken.mockRejectedValue(new Error("Token refresh failed"));
|
|
46
|
-
await expect(auth.getTokenFromCliLogin({})).rejects.toThrow("Token refresh failed");
|
|
47
|
-
});
|
|
48
|
-
it("should set isCliLoginAvailable to true when import succeeds", async () => {
|
|
49
|
-
mockGetToken.mockResolvedValue("cli-token");
|
|
50
|
-
await auth.getTokenFromCliLogin({});
|
|
51
|
-
expect(auth.isCliLoginAvailable()).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("resolveAuthToken", () => {
|
|
55
|
-
it("should return string credentials directly", async () => {
|
|
56
|
-
const result = await auth.resolveAuthToken({
|
|
57
|
-
credentials: "my-token",
|
|
58
|
-
});
|
|
59
|
-
expect(result).toBe("my-token");
|
|
60
|
-
});
|
|
61
|
-
it("should use deprecated token option with warning", async () => {
|
|
62
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
63
|
-
const result = await auth.resolveAuthToken({
|
|
64
|
-
token: "deprecated-token",
|
|
65
|
-
});
|
|
66
|
-
expect(result).toBe("deprecated-token");
|
|
67
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
|
|
68
|
-
warnSpy.mockRestore();
|
|
69
|
-
});
|
|
70
|
-
it("should resolve credentials from env var (string token)", async () => {
|
|
71
|
-
// Set env var and re-import to pick up the new value
|
|
72
|
-
process.env.ZAPIER_CREDENTIALS = "env-token";
|
|
73
|
-
vi.resetModules();
|
|
74
|
-
const freshAuth = await import("./auth");
|
|
75
|
-
const result = await freshAuth.resolveAuthToken({});
|
|
76
|
-
expect(result).toBe("env-token");
|
|
77
|
-
});
|
|
78
|
-
it("should resolve credentials from ZAPIER_TOKEN with deprecation warning", async () => {
|
|
79
|
-
// Set env var and re-import to pick up the new value
|
|
80
|
-
process.env.ZAPIER_TOKEN = "legacy-token";
|
|
81
|
-
vi.resetModules();
|
|
82
|
-
const freshAuth = await import("./auth");
|
|
83
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
84
|
-
const result = await freshAuth.resolveAuthToken({});
|
|
85
|
-
expect(result).toBe("legacy-token");
|
|
86
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("ZAPIER_TOKEN is deprecated"));
|
|
87
|
-
warnSpy.mockRestore();
|
|
88
|
-
});
|
|
89
|
-
it("should fall back to CLI login when no credentials provided", async () => {
|
|
90
|
-
mockGetToken.mockResolvedValue("cli-stored-token");
|
|
91
|
-
const result = await auth.resolveAuthToken({});
|
|
92
|
-
expect(result).toBe("cli-stored-token");
|
|
93
|
-
expect(mockGetToken).toHaveBeenCalled();
|
|
94
|
-
});
|
|
95
|
-
it("should return undefined when no credentials found", async () => {
|
|
96
|
-
mockGetToken.mockResolvedValue(undefined);
|
|
97
|
-
const result = await auth.resolveAuthToken({});
|
|
98
|
-
expect(result).toBeUndefined();
|
|
99
|
-
});
|
|
100
|
-
it("should call credentials function and use result", async () => {
|
|
101
|
-
const credentialsFn = vi.fn().mockResolvedValue("dynamic-token");
|
|
102
|
-
const result = await auth.resolveAuthToken({
|
|
103
|
-
credentials: credentialsFn,
|
|
104
|
-
});
|
|
105
|
-
expect(result).toBe("dynamic-token");
|
|
106
|
-
expect(credentialsFn).toHaveBeenCalled();
|
|
107
|
-
});
|
|
108
|
-
it("should throw if credentials function returns another function", async () => {
|
|
109
|
-
const credentialsFn = vi.fn().mockResolvedValue(() => "nested");
|
|
110
|
-
await expect(auth.resolveAuthToken({
|
|
111
|
-
credentials: credentialsFn,
|
|
112
|
-
})).rejects.toThrow("Credentials function returned another function");
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
describe("client credentials flow", () => {
|
|
116
|
-
it("should exchange client credentials for token with scope and audience", async () => {
|
|
117
|
-
mockFetch.mockResolvedValue({
|
|
118
|
-
ok: true,
|
|
119
|
-
json: () => Promise.resolve({
|
|
120
|
-
access_token: "exchanged-token",
|
|
121
|
-
expires_in: 3600,
|
|
122
|
-
}),
|
|
123
|
-
});
|
|
124
|
-
const result = await auth.resolveAuthToken({
|
|
125
|
-
credentials: {
|
|
126
|
-
type: "client_credentials",
|
|
127
|
-
clientId: "test-client-id",
|
|
128
|
-
clientSecret: "test-client-secret",
|
|
129
|
-
},
|
|
130
|
-
fetch: mockFetch,
|
|
131
|
-
});
|
|
132
|
-
expect(result).toBe("exchanged-token");
|
|
133
|
-
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/oauth/token/"), expect.objectContaining({
|
|
134
|
-
method: "POST",
|
|
135
|
-
body: expect.any(URLSearchParams),
|
|
136
|
-
}));
|
|
137
|
-
// Verify the body includes required parameters
|
|
138
|
-
const callArgs = mockFetch.mock.calls[0];
|
|
139
|
-
const body = callArgs[1].body;
|
|
140
|
-
expect(body.get("grant_type")).toBe("client_credentials");
|
|
141
|
-
expect(body.get("client_id")).toBe("test-client-id");
|
|
142
|
-
expect(body.get("client_secret")).toBe("test-client-secret");
|
|
143
|
-
expect(body.get("scope")).toBe("external");
|
|
144
|
-
expect(body.get("audience")).toBe("zapier.com");
|
|
145
|
-
});
|
|
146
|
-
it("should cache token from client credentials exchange", async () => {
|
|
147
|
-
mockFetch.mockResolvedValue({
|
|
148
|
-
ok: true,
|
|
149
|
-
json: () => Promise.resolve({
|
|
150
|
-
access_token: "cached-token",
|
|
151
|
-
expires_in: 3600,
|
|
152
|
-
}),
|
|
153
|
-
});
|
|
154
|
-
// First call - should exchange
|
|
155
|
-
const result1 = await auth.resolveAuthToken({
|
|
156
|
-
credentials: {
|
|
157
|
-
type: "client_credentials",
|
|
158
|
-
clientId: "cache-test-client",
|
|
159
|
-
clientSecret: "secret",
|
|
160
|
-
},
|
|
161
|
-
fetch: mockFetch,
|
|
162
|
-
});
|
|
163
|
-
// Second call - should use cache
|
|
164
|
-
const result2 = await auth.resolveAuthToken({
|
|
165
|
-
credentials: {
|
|
166
|
-
type: "client_credentials",
|
|
167
|
-
clientId: "cache-test-client",
|
|
168
|
-
clientSecret: "secret",
|
|
169
|
-
},
|
|
170
|
-
fetch: mockFetch,
|
|
171
|
-
});
|
|
172
|
-
expect(result1).toBe("cached-token");
|
|
173
|
-
expect(result2).toBe("cached-token");
|
|
174
|
-
expect(mockFetch).toHaveBeenCalledTimes(1); // Only called once due to cache
|
|
175
|
-
});
|
|
176
|
-
it("should deduplicate concurrent client credentials exchanges", async () => {
|
|
177
|
-
let resolveExchange;
|
|
178
|
-
const exchangePromise = new Promise((resolve) => {
|
|
179
|
-
resolveExchange = resolve;
|
|
180
|
-
});
|
|
181
|
-
mockFetch.mockReturnValue(exchangePromise);
|
|
182
|
-
// Start two concurrent requests - they will both start executing
|
|
183
|
-
const promise1 = auth.resolveAuthToken({
|
|
184
|
-
credentials: {
|
|
185
|
-
type: "client_credentials",
|
|
186
|
-
clientId: "concurrent-test-client",
|
|
187
|
-
clientSecret: "secret",
|
|
188
|
-
},
|
|
189
|
-
fetch: mockFetch,
|
|
190
|
-
});
|
|
191
|
-
const promise2 = auth.resolveAuthToken({
|
|
192
|
-
credentials: {
|
|
193
|
-
type: "client_credentials",
|
|
194
|
-
clientId: "concurrent-test-client",
|
|
195
|
-
clientSecret: "secret",
|
|
196
|
-
},
|
|
197
|
-
fetch: mockFetch,
|
|
198
|
-
});
|
|
199
|
-
// Let microtasks run so both promises start
|
|
200
|
-
await new Promise((r) => setTimeout(r, 0));
|
|
201
|
-
// Now resolve the exchange
|
|
202
|
-
resolveExchange({
|
|
203
|
-
ok: true,
|
|
204
|
-
json: () => Promise.resolve({
|
|
205
|
-
access_token: "concurrent-token",
|
|
206
|
-
expires_in: 3600,
|
|
207
|
-
}),
|
|
208
|
-
});
|
|
209
|
-
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
210
|
-
expect(result1).toBe("concurrent-token");
|
|
211
|
-
expect(result2).toBe("concurrent-token");
|
|
212
|
-
expect(mockFetch).toHaveBeenCalledTimes(1); // Only one exchange despite two concurrent calls
|
|
213
|
-
});
|
|
214
|
-
it("should throw on client credentials exchange failure", async () => {
|
|
215
|
-
mockFetch.mockResolvedValue({
|
|
216
|
-
ok: false,
|
|
217
|
-
status: 401,
|
|
218
|
-
statusText: "Unauthorized",
|
|
219
|
-
text: () => Promise.resolve("Invalid credentials"),
|
|
220
|
-
});
|
|
221
|
-
await expect(auth.resolveAuthToken({
|
|
222
|
-
credentials: {
|
|
223
|
-
type: "client_credentials",
|
|
224
|
-
clientId: "bad-client",
|
|
225
|
-
clientSecret: "bad-secret",
|
|
226
|
-
},
|
|
227
|
-
fetch: mockFetch,
|
|
228
|
-
})).rejects.toThrow("Client credentials exchange failed");
|
|
229
|
-
});
|
|
230
|
-
it("should merge requiredScopes with credentials scope", async () => {
|
|
231
|
-
mockFetch.mockResolvedValue({
|
|
232
|
-
ok: true,
|
|
233
|
-
json: () => Promise.resolve({
|
|
234
|
-
access_token: "merged-scope-token",
|
|
235
|
-
expires_in: 3600,
|
|
236
|
-
}),
|
|
237
|
-
});
|
|
238
|
-
await auth.resolveAuthToken({
|
|
239
|
-
credentials: {
|
|
240
|
-
type: "client_credentials",
|
|
241
|
-
clientId: "scope-test-client",
|
|
242
|
-
clientSecret: "secret",
|
|
243
|
-
scope: "external",
|
|
244
|
-
},
|
|
245
|
-
requiredScopes: ["credentials"],
|
|
246
|
-
fetch: mockFetch,
|
|
247
|
-
});
|
|
248
|
-
const callArgs = mockFetch.mock.calls[0];
|
|
249
|
-
const body = callArgs[1].body;
|
|
250
|
-
// Scopes should be merged and sorted alphabetically
|
|
251
|
-
expect(body.get("scope")).toBe("credentials external");
|
|
252
|
-
});
|
|
253
|
-
it("should always include external scope with requiredScopes", async () => {
|
|
254
|
-
mockFetch.mockResolvedValue({
|
|
255
|
-
ok: true,
|
|
256
|
-
json: () => Promise.resolve({
|
|
257
|
-
access_token: "required-scope-token",
|
|
258
|
-
expires_in: 3600,
|
|
259
|
-
}),
|
|
260
|
-
});
|
|
261
|
-
await auth.resolveAuthToken({
|
|
262
|
-
credentials: {
|
|
263
|
-
type: "client_credentials",
|
|
264
|
-
clientId: "required-scope-client",
|
|
265
|
-
clientSecret: "secret",
|
|
266
|
-
},
|
|
267
|
-
requiredScopes: ["credentials"],
|
|
268
|
-
fetch: mockFetch,
|
|
269
|
-
});
|
|
270
|
-
const callArgs = mockFetch.mock.calls[0];
|
|
271
|
-
const body = callArgs[1].body;
|
|
272
|
-
// Should include both external (always present) and credentials (required)
|
|
273
|
-
expect(body.get("scope")).toBe("credentials external");
|
|
274
|
-
});
|
|
275
|
-
it("should cache tokens separately for different scope combinations", async () => {
|
|
276
|
-
mockFetch.mockResolvedValue({
|
|
277
|
-
ok: true,
|
|
278
|
-
json: () => Promise.resolve({
|
|
279
|
-
access_token: "first-scope-token",
|
|
280
|
-
expires_in: 3600,
|
|
281
|
-
}),
|
|
282
|
-
});
|
|
283
|
-
// First call with no requiredScopes
|
|
284
|
-
const result1 = await auth.resolveAuthToken({
|
|
285
|
-
credentials: {
|
|
286
|
-
type: "client_credentials",
|
|
287
|
-
clientId: "scope-cache-client",
|
|
288
|
-
clientSecret: "secret",
|
|
289
|
-
},
|
|
290
|
-
fetch: mockFetch,
|
|
291
|
-
});
|
|
292
|
-
expect(result1).toBe("first-scope-token");
|
|
293
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
294
|
-
// Second call with different requiredScopes - should NOT use cache
|
|
295
|
-
mockFetch.mockResolvedValue({
|
|
296
|
-
ok: true,
|
|
297
|
-
json: () => Promise.resolve({
|
|
298
|
-
access_token: "second-scope-token",
|
|
299
|
-
expires_in: 3600,
|
|
300
|
-
}),
|
|
301
|
-
});
|
|
302
|
-
const result2 = await auth.resolveAuthToken({
|
|
303
|
-
credentials: {
|
|
304
|
-
type: "client_credentials",
|
|
305
|
-
clientId: "scope-cache-client",
|
|
306
|
-
clientSecret: "secret",
|
|
307
|
-
},
|
|
308
|
-
requiredScopes: ["credentials"],
|
|
309
|
-
fetch: mockFetch,
|
|
310
|
-
});
|
|
311
|
-
expect(result2).toBe("second-scope-token");
|
|
312
|
-
expect(mockFetch).toHaveBeenCalledTimes(2); // Should have made a new request
|
|
313
|
-
// Third call with same requiredScopes as second - should use cache
|
|
314
|
-
const result3 = await auth.resolveAuthToken({
|
|
315
|
-
credentials: {
|
|
316
|
-
type: "client_credentials",
|
|
317
|
-
clientId: "scope-cache-client",
|
|
318
|
-
clientSecret: "secret",
|
|
319
|
-
},
|
|
320
|
-
requiredScopes: ["credentials"],
|
|
321
|
-
fetch: mockFetch,
|
|
322
|
-
});
|
|
323
|
-
expect(result3).toBe("second-scope-token");
|
|
324
|
-
expect(mockFetch).toHaveBeenCalledTimes(2); // No new request
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
describe("PKCE credentials", () => {
|
|
328
|
-
it("should delegate PKCE credentials to CLI login", async () => {
|
|
329
|
-
mockGetToken.mockResolvedValue("pkce-stored-token");
|
|
330
|
-
const result = await auth.resolveAuthToken({
|
|
331
|
-
credentials: {
|
|
332
|
-
type: "pkce",
|
|
333
|
-
clientId: "pkce-client",
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
expect(result).toBe("pkce-stored-token");
|
|
337
|
-
expect(mockGetToken).toHaveBeenCalled();
|
|
338
|
-
});
|
|
339
|
-
it("should throw when PKCE credentials have no stored token", async () => {
|
|
340
|
-
mockGetToken.mockResolvedValue(undefined);
|
|
341
|
-
await expect(auth.resolveAuthToken({
|
|
342
|
-
credentials: {
|
|
343
|
-
type: "pkce",
|
|
344
|
-
clientId: "pkce-client",
|
|
345
|
-
},
|
|
346
|
-
})).rejects.toThrow("PKCE credentials require interactive login");
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
describe("token cache management", () => {
|
|
350
|
-
it("should invalidate cached token", async () => {
|
|
351
|
-
mockFetch.mockResolvedValue({
|
|
352
|
-
ok: true,
|
|
353
|
-
json: () => Promise.resolve({
|
|
354
|
-
access_token: "will-be-invalidated",
|
|
355
|
-
expires_in: 3600,
|
|
356
|
-
}),
|
|
357
|
-
});
|
|
358
|
-
// First call - cache the token
|
|
359
|
-
await auth.resolveAuthToken({
|
|
360
|
-
credentials: {
|
|
361
|
-
type: "client_credentials",
|
|
362
|
-
clientId: "invalidate-test",
|
|
363
|
-
clientSecret: "secret",
|
|
364
|
-
},
|
|
365
|
-
fetch: mockFetch,
|
|
366
|
-
});
|
|
367
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
368
|
-
// Invalidate the cache (default scope is "external")
|
|
369
|
-
auth.invalidateCachedToken("invalidate-test", ["external"]);
|
|
370
|
-
// Next call should fetch again
|
|
371
|
-
mockFetch.mockResolvedValue({
|
|
372
|
-
ok: true,
|
|
373
|
-
json: () => Promise.resolve({
|
|
374
|
-
access_token: "new-token-after-invalidation",
|
|
375
|
-
expires_in: 3600,
|
|
376
|
-
}),
|
|
377
|
-
});
|
|
378
|
-
const result = await auth.resolveAuthToken({
|
|
379
|
-
credentials: {
|
|
380
|
-
type: "client_credentials",
|
|
381
|
-
clientId: "invalidate-test",
|
|
382
|
-
clientSecret: "secret",
|
|
383
|
-
},
|
|
384
|
-
fetch: mockFetch,
|
|
385
|
-
});
|
|
386
|
-
expect(result).toBe("new-token-after-invalidation");
|
|
387
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
388
|
-
});
|
|
389
|
-
it("should clear all cached tokens", async () => {
|
|
390
|
-
mockFetch.mockResolvedValue({
|
|
391
|
-
ok: true,
|
|
392
|
-
json: () => Promise.resolve({
|
|
393
|
-
access_token: "token",
|
|
394
|
-
expires_in: 3600,
|
|
395
|
-
}),
|
|
396
|
-
});
|
|
397
|
-
// Cache a token
|
|
398
|
-
await auth.resolveAuthToken({
|
|
399
|
-
credentials: {
|
|
400
|
-
type: "client_credentials",
|
|
401
|
-
clientId: "clear-test",
|
|
402
|
-
clientSecret: "secret",
|
|
403
|
-
},
|
|
404
|
-
fetch: mockFetch,
|
|
405
|
-
});
|
|
406
|
-
// Clear all caches
|
|
407
|
-
auth.clearTokenCache();
|
|
408
|
-
// Should fetch again
|
|
409
|
-
await auth.resolveAuthToken({
|
|
410
|
-
credentials: {
|
|
411
|
-
type: "client_credentials",
|
|
412
|
-
clientId: "clear-test",
|
|
413
|
-
clientSecret: "secret",
|
|
414
|
-
},
|
|
415
|
-
fetch: mockFetch,
|
|
416
|
-
});
|
|
417
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
describe("invalidateCredentialsToken", () => {
|
|
421
|
-
it("should invalidate cached token for client credentials", async () => {
|
|
422
|
-
const mockFetch = vi.fn().mockResolvedValue({
|
|
423
|
-
ok: true,
|
|
424
|
-
json: () => Promise.resolve({
|
|
425
|
-
access_token: "cached-token",
|
|
426
|
-
expires_in: 3600,
|
|
427
|
-
}),
|
|
428
|
-
});
|
|
429
|
-
// First, get a token to populate the cache
|
|
430
|
-
await auth.resolveAuthToken({
|
|
431
|
-
credentials: {
|
|
432
|
-
type: "client_credentials",
|
|
433
|
-
clientId: "invalidate-test-client",
|
|
434
|
-
clientSecret: "test-secret",
|
|
435
|
-
},
|
|
436
|
-
fetch: mockFetch,
|
|
437
|
-
});
|
|
438
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
439
|
-
// Verify token is cached (no new fetch)
|
|
440
|
-
const cachedToken = await auth.resolveAuthToken({
|
|
441
|
-
credentials: {
|
|
442
|
-
type: "client_credentials",
|
|
443
|
-
clientId: "invalidate-test-client",
|
|
444
|
-
clientSecret: "test-secret",
|
|
445
|
-
},
|
|
446
|
-
fetch: mockFetch,
|
|
447
|
-
});
|
|
448
|
-
expect(cachedToken).toBe("cached-token");
|
|
449
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
450
|
-
// Invalidate the token
|
|
451
|
-
await auth.invalidateCredentialsToken({
|
|
452
|
-
credentials: {
|
|
453
|
-
type: "client_credentials",
|
|
454
|
-
clientId: "invalidate-test-client",
|
|
455
|
-
clientSecret: "test-secret",
|
|
456
|
-
},
|
|
457
|
-
});
|
|
458
|
-
// Next request should fetch a new token
|
|
459
|
-
await auth.resolveAuthToken({
|
|
460
|
-
credentials: {
|
|
461
|
-
type: "client_credentials",
|
|
462
|
-
clientId: "invalidate-test-client",
|
|
463
|
-
clientSecret: "test-secret",
|
|
464
|
-
},
|
|
465
|
-
fetch: mockFetch,
|
|
466
|
-
});
|
|
467
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
468
|
-
});
|
|
469
|
-
it("should do nothing for string credentials", async () => {
|
|
470
|
-
// Should not throw
|
|
471
|
-
await auth.invalidateCredentialsToken({
|
|
472
|
-
credentials: "string-token",
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
it("should do nothing when no credentials provided", async () => {
|
|
476
|
-
// Should not throw
|
|
477
|
-
await auth.invalidateCredentialsToken({});
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"builders.test.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/builders.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { buildMethodCalledEvent, buildErrorEvent, buildApplicationLifecycleEvent, buildErrorEventWithContext, } from "./builders";
|
|
3
|
-
describe("buildMethodCalledEvent", () => {
|
|
4
|
-
it("should use data.selected_api when provided", () => {
|
|
5
|
-
const event = buildMethodCalledEvent({
|
|
6
|
-
method_name: "runAction",
|
|
7
|
-
execution_duration_ms: 100,
|
|
8
|
-
success_flag: true,
|
|
9
|
-
argument_count: 1,
|
|
10
|
-
selected_api: "SlackCLIAPI@1.0.0",
|
|
11
|
-
});
|
|
12
|
-
expect(event.selected_api).toBe("SlackCLIAPI@1.0.0");
|
|
13
|
-
});
|
|
14
|
-
it("should use data.operation_type and data.operation_key when provided", () => {
|
|
15
|
-
const event = buildMethodCalledEvent({
|
|
16
|
-
method_name: "runAction",
|
|
17
|
-
execution_duration_ms: 100,
|
|
18
|
-
success_flag: true,
|
|
19
|
-
argument_count: 1,
|
|
20
|
-
operation_type: "write",
|
|
21
|
-
operation_key: "send_message",
|
|
22
|
-
});
|
|
23
|
-
expect(event.operation_type).toBe("write");
|
|
24
|
-
expect(event.operation_key).toBe("send_message");
|
|
25
|
-
});
|
|
26
|
-
it("should fall back to null when data fields are not provided", () => {
|
|
27
|
-
const event = buildMethodCalledEvent({
|
|
28
|
-
method_name: "listApps",
|
|
29
|
-
execution_duration_ms: 50,
|
|
30
|
-
success_flag: true,
|
|
31
|
-
argument_count: 0,
|
|
32
|
-
});
|
|
33
|
-
expect(event.selected_api).toBeNull();
|
|
34
|
-
expect(event.operation_type).toBeNull();
|
|
35
|
-
expect(event.operation_key).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
it("should prefer data.selected_api over context.selected_api", () => {
|
|
38
|
-
const event = buildMethodCalledEvent({
|
|
39
|
-
method_name: "runAction",
|
|
40
|
-
execution_duration_ms: 100,
|
|
41
|
-
success_flag: true,
|
|
42
|
-
argument_count: 1,
|
|
43
|
-
selected_api: "SlackCLIAPI@2.0.0",
|
|
44
|
-
}, { selected_api: "SlackCLIAPI@1.0.0" });
|
|
45
|
-
expect(event.selected_api).toBe("SlackCLIAPI@2.0.0");
|
|
46
|
-
});
|
|
47
|
-
it("should fall back to context.selected_api when data.selected_api is not provided", () => {
|
|
48
|
-
const event = buildMethodCalledEvent({
|
|
49
|
-
method_name: "runAction",
|
|
50
|
-
execution_duration_ms: 100,
|
|
51
|
-
success_flag: true,
|
|
52
|
-
argument_count: 1,
|
|
53
|
-
}, { selected_api: "SlackCLIAPI@1.0.0" });
|
|
54
|
-
expect(event.selected_api).toBe("SlackCLIAPI@1.0.0");
|
|
55
|
-
});
|
|
56
|
-
it("should default call_context to null (plugin layer overrides this)", () => {
|
|
57
|
-
const event = buildMethodCalledEvent({
|
|
58
|
-
method_name: "listApps",
|
|
59
|
-
execution_duration_ms: 50,
|
|
60
|
-
success_flag: true,
|
|
61
|
-
argument_count: 0,
|
|
62
|
-
});
|
|
63
|
-
expect(event.call_context).toBeNull();
|
|
64
|
-
});
|
|
65
|
-
it("should populate sdk_version", () => {
|
|
66
|
-
const event = buildMethodCalledEvent({
|
|
67
|
-
method_name: "listApps",
|
|
68
|
-
execution_duration_ms: 50,
|
|
69
|
-
success_flag: true,
|
|
70
|
-
argument_count: 0,
|
|
71
|
-
});
|
|
72
|
-
expect(event.sdk_version).toBe("unknown");
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe("buildErrorEvent", () => {
|
|
76
|
-
it("should build an error event with correct fields", () => {
|
|
77
|
-
const event = buildErrorEvent({
|
|
78
|
-
error_message: "Something went wrong",
|
|
79
|
-
error_type: "RuntimeError",
|
|
80
|
-
is_user_facing: false,
|
|
81
|
-
}, { selected_api: "SlackCLIAPI@1.0.0" });
|
|
82
|
-
expect(event.error_message).toBe("Something went wrong");
|
|
83
|
-
expect(event.error_type).toBe("RuntimeError");
|
|
84
|
-
expect(event.selected_api).toBe("SlackCLIAPI@1.0.0");
|
|
85
|
-
expect(event.sdk_version).toBe("unknown");
|
|
86
|
-
});
|
|
87
|
-
it("should not allow sdk_version to be overwritten via data spread", () => {
|
|
88
|
-
const event = buildErrorEvent({
|
|
89
|
-
error_message: "test",
|
|
90
|
-
error_type: "Error",
|
|
91
|
-
is_user_facing: false,
|
|
92
|
-
sdk_version: "evil-version",
|
|
93
|
-
}, {});
|
|
94
|
-
expect(event.sdk_version).not.toBe("evil-version");
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
describe("buildApplicationLifecycleEvent", () => {
|
|
98
|
-
it("should build a lifecycle event with correct fields", () => {
|
|
99
|
-
const event = buildApplicationLifecycleEvent({
|
|
100
|
-
lifecycle_event_type: "startup",
|
|
101
|
-
}, { app_id: 123 });
|
|
102
|
-
expect(event.lifecycle_event_type).toBe("startup");
|
|
103
|
-
expect(event.app_id).toBe(123);
|
|
104
|
-
expect(event.sdk_version).toBe("unknown");
|
|
105
|
-
});
|
|
106
|
-
it("should not allow sdk_version to be overwritten via data spread", () => {
|
|
107
|
-
const event = buildApplicationLifecycleEvent({
|
|
108
|
-
lifecycle_event_type: "startup",
|
|
109
|
-
sdk_version: "evil-version",
|
|
110
|
-
}, {});
|
|
111
|
-
expect(event.sdk_version).not.toBe("evil-version");
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
describe("buildErrorEventWithContext", () => {
|
|
115
|
-
it("should build an error event with execution time", () => {
|
|
116
|
-
const startTime = Date.now() - 500;
|
|
117
|
-
const event = buildErrorEventWithContext({
|
|
118
|
-
error_message: "Timeout",
|
|
119
|
-
error_type: "TimeoutError",
|
|
120
|
-
is_user_facing: false,
|
|
121
|
-
execution_start_time: startTime,
|
|
122
|
-
}, { selected_api: "SlackCLIAPI@1.0.0" });
|
|
123
|
-
expect(event.error_message).toBe("Timeout");
|
|
124
|
-
expect(event.error_type).toBe("TimeoutError");
|
|
125
|
-
expect(event.selected_api).toBe("SlackCLIAPI@1.0.0");
|
|
126
|
-
expect(event.sdk_version).toBe("unknown");
|
|
127
|
-
expect(event.execution_time_before_error_ms).toBeGreaterThanOrEqual(400);
|
|
128
|
-
});
|
|
129
|
-
it("should not allow sdk_version to be overwritten via data spread", () => {
|
|
130
|
-
const event = buildErrorEventWithContext({
|
|
131
|
-
error_message: "test",
|
|
132
|
-
error_type: "Error",
|
|
133
|
-
is_user_facing: false,
|
|
134
|
-
sdk_version: "evil-version",
|
|
135
|
-
}, {});
|
|
136
|
-
expect(event.sdk_version).not.toBe("evil-version");
|
|
137
|
-
});
|
|
138
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|