@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/api/client.test.js
DELETED
|
@@ -1,611 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { createZapierApi } from "./client";
|
|
3
|
-
import * as auth from "../auth";
|
|
4
|
-
import { ZapierRateLimitError } from "../types/errors";
|
|
5
|
-
vi.mock("../auth");
|
|
6
|
-
describe("ApiClient", () => {
|
|
7
|
-
const mockResolveAuthToken = vi.mocked(auth.resolveAuthToken);
|
|
8
|
-
const mockInvalidateCredentialsToken = vi.mocked(auth.invalidateCredentialsToken);
|
|
9
|
-
let mockFetch;
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.clearAllMocks();
|
|
12
|
-
// Prevent any actual HTTP calls
|
|
13
|
-
mockFetch = vi.fn().mockResolvedValue({
|
|
14
|
-
ok: true,
|
|
15
|
-
status: 200,
|
|
16
|
-
text: () => Promise.resolve(JSON.stringify({ data: "test" })),
|
|
17
|
-
});
|
|
18
|
-
global.fetch = mockFetch;
|
|
19
|
-
});
|
|
20
|
-
describe("authentication token resolution", () => {
|
|
21
|
-
it("should pass credentials to resolveAuthToken when provided", async () => {
|
|
22
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
23
|
-
const credentials = {
|
|
24
|
-
type: "client_credentials",
|
|
25
|
-
clientId: "my-client",
|
|
26
|
-
clientSecret: "my-secret",
|
|
27
|
-
};
|
|
28
|
-
const client = createZapierApi({
|
|
29
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
30
|
-
credentials,
|
|
31
|
-
debug: false,
|
|
32
|
-
});
|
|
33
|
-
// Make a request that would trigger token resolution
|
|
34
|
-
await client.get("/test", { authRequired: true });
|
|
35
|
-
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
36
|
-
credentials,
|
|
37
|
-
token: undefined,
|
|
38
|
-
onEvent: undefined,
|
|
39
|
-
fetch: expect.any(Function),
|
|
40
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
41
|
-
requiredScopes: undefined,
|
|
42
|
-
debug: false,
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
it("should pass undefined credentials when not provided", async () => {
|
|
46
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
47
|
-
const client = createZapierApi({
|
|
48
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
49
|
-
debug: false,
|
|
50
|
-
});
|
|
51
|
-
// Make a request that would trigger token resolution
|
|
52
|
-
await client.get("/test", { authRequired: true });
|
|
53
|
-
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
54
|
-
credentials: undefined,
|
|
55
|
-
token: undefined,
|
|
56
|
-
onEvent: undefined,
|
|
57
|
-
fetch: expect.any(Function),
|
|
58
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
59
|
-
requiredScopes: undefined,
|
|
60
|
-
debug: false,
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
it("should pass deprecated token option to resolveAuthToken", async () => {
|
|
64
|
-
mockResolveAuthToken.mockResolvedValue("direct-token");
|
|
65
|
-
const client = createZapierApi({
|
|
66
|
-
token: "direct-token",
|
|
67
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
68
|
-
debug: false,
|
|
69
|
-
});
|
|
70
|
-
// Make a request that would use the direct token
|
|
71
|
-
await client.get("/test", { authRequired: true });
|
|
72
|
-
// resolveAuthToken is still called, but it returns the direct token immediately
|
|
73
|
-
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
74
|
-
credentials: undefined,
|
|
75
|
-
token: "direct-token",
|
|
76
|
-
onEvent: undefined,
|
|
77
|
-
fetch: expect.any(Function),
|
|
78
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
79
|
-
requiredScopes: undefined,
|
|
80
|
-
debug: false,
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
it("should pass string credentials to resolveAuthToken", async () => {
|
|
84
|
-
mockResolveAuthToken.mockResolvedValue("string-token");
|
|
85
|
-
const client = createZapierApi({
|
|
86
|
-
credentials: "string-token",
|
|
87
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
88
|
-
debug: false,
|
|
89
|
-
});
|
|
90
|
-
await client.get("/test", { authRequired: true });
|
|
91
|
-
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
92
|
-
credentials: "string-token",
|
|
93
|
-
token: undefined,
|
|
94
|
-
onEvent: undefined,
|
|
95
|
-
fetch: expect.any(Function),
|
|
96
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
97
|
-
requiredScopes: undefined,
|
|
98
|
-
debug: false,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
it("should pass credentials function to resolveAuthToken", async () => {
|
|
102
|
-
mockResolveAuthToken.mockResolvedValue("function-token");
|
|
103
|
-
const credentialsFn = () => Promise.resolve("dynamic-token");
|
|
104
|
-
const client = createZapierApi({
|
|
105
|
-
credentials: credentialsFn,
|
|
106
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
107
|
-
debug: false,
|
|
108
|
-
});
|
|
109
|
-
await client.get("/test", { authRequired: true });
|
|
110
|
-
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
111
|
-
credentials: credentialsFn,
|
|
112
|
-
token: undefined,
|
|
113
|
-
onEvent: undefined,
|
|
114
|
-
fetch: expect.any(Function),
|
|
115
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
116
|
-
requiredScopes: undefined,
|
|
117
|
-
debug: false,
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
describe("401 cache invalidation", () => {
|
|
122
|
-
it("should invalidate credentials token on 401 response", async () => {
|
|
123
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
124
|
-
mockInvalidateCredentialsToken.mockResolvedValue(undefined);
|
|
125
|
-
const clientCredentials = {
|
|
126
|
-
type: "client_credentials",
|
|
127
|
-
clientId: "my-client-id",
|
|
128
|
-
clientSecret: "my-secret",
|
|
129
|
-
};
|
|
130
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
131
|
-
ok: false,
|
|
132
|
-
status: 401,
|
|
133
|
-
statusText: "Unauthorized",
|
|
134
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Invalid token" })),
|
|
135
|
-
});
|
|
136
|
-
const client = createZapierApi({
|
|
137
|
-
credentials: clientCredentials,
|
|
138
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
139
|
-
debug: false,
|
|
140
|
-
});
|
|
141
|
-
await expect(client.get("/test")).rejects.toThrow();
|
|
142
|
-
expect(mockInvalidateCredentialsToken).toHaveBeenCalledWith({
|
|
143
|
-
credentials: clientCredentials,
|
|
144
|
-
token: undefined,
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
it("should not invalidate cache on 403 response", async () => {
|
|
148
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
149
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
150
|
-
ok: false,
|
|
151
|
-
status: 403,
|
|
152
|
-
statusText: "Forbidden",
|
|
153
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Access denied" })),
|
|
154
|
-
});
|
|
155
|
-
const client = createZapierApi({
|
|
156
|
-
credentials: "test-token",
|
|
157
|
-
baseUrl: "https://api.custom.zapier.dev",
|
|
158
|
-
debug: false,
|
|
159
|
-
});
|
|
160
|
-
await expect(client.get("/test")).rejects.toThrow();
|
|
161
|
-
expect(mockInvalidateCredentialsToken).not.toHaveBeenCalled();
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
describe("URL path configuration", () => {
|
|
165
|
-
beforeEach(() => {
|
|
166
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
167
|
-
});
|
|
168
|
-
it("should apply pathPrefix for /relay path with Zapier base URL", async () => {
|
|
169
|
-
const client = createZapierApi({
|
|
170
|
-
baseUrl: "https://zapier.com",
|
|
171
|
-
credentials: "test-token",
|
|
172
|
-
debug: false,
|
|
173
|
-
});
|
|
174
|
-
await client.get("/relay/some/path");
|
|
175
|
-
expect(mockFetch).toHaveBeenCalledWith("https://sdkapi.zapier.com/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
176
|
-
});
|
|
177
|
-
it("should apply pathPrefix for /zapier path with Zapier base URL", async () => {
|
|
178
|
-
const client = createZapierApi({
|
|
179
|
-
baseUrl: "https://zapier.com",
|
|
180
|
-
credentials: "test-token",
|
|
181
|
-
debug: false,
|
|
182
|
-
});
|
|
183
|
-
await client.get("/zapier/apps");
|
|
184
|
-
expect(mockFetch).toHaveBeenCalledWith("https://sdkapi.zapier.com/api/v0/sdk/zapier/apps", expect.any(Object));
|
|
185
|
-
});
|
|
186
|
-
it("should apply pathPrefix for /relay path with localhost base URL", async () => {
|
|
187
|
-
const client = createZapierApi({
|
|
188
|
-
baseUrl: "http://localhost:3000",
|
|
189
|
-
credentials: "test-token",
|
|
190
|
-
debug: false,
|
|
191
|
-
});
|
|
192
|
-
await client.get("/relay/some/path");
|
|
193
|
-
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
194
|
-
});
|
|
195
|
-
it("should apply pathPrefix for /zapier path with localhost base URL", async () => {
|
|
196
|
-
const client = createZapierApi({
|
|
197
|
-
baseUrl: "http://localhost:3000",
|
|
198
|
-
credentials: "test-token",
|
|
199
|
-
debug: false,
|
|
200
|
-
});
|
|
201
|
-
await client.get("/zapier/apps");
|
|
202
|
-
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/zapier/apps", expect.any(Object));
|
|
203
|
-
});
|
|
204
|
-
it("should not apply pathPrefix for non-configured paths", async () => {
|
|
205
|
-
const client = createZapierApi({
|
|
206
|
-
baseUrl: "http://localhost:3000",
|
|
207
|
-
credentials: "test-token",
|
|
208
|
-
debug: false,
|
|
209
|
-
});
|
|
210
|
-
await client.get("/other/path");
|
|
211
|
-
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/other/path", expect.any(Object));
|
|
212
|
-
});
|
|
213
|
-
it("should handle exact path match for pathPrefix", async () => {
|
|
214
|
-
const client = createZapierApi({
|
|
215
|
-
baseUrl: "http://localhost:3000",
|
|
216
|
-
credentials: "test-token",
|
|
217
|
-
debug: false,
|
|
218
|
-
});
|
|
219
|
-
await client.get("/relay");
|
|
220
|
-
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/api/v0/sdk/relay/", expect.any(Object));
|
|
221
|
-
});
|
|
222
|
-
it("should use sdkapi subdomain when Zapier base URL has trailing slash", async () => {
|
|
223
|
-
const client = createZapierApi({
|
|
224
|
-
baseUrl: "https://zapier-staging.com/",
|
|
225
|
-
credentials: "test-token",
|
|
226
|
-
debug: false,
|
|
227
|
-
});
|
|
228
|
-
await client.get("/zapier/api/v4/profile/");
|
|
229
|
-
expect(mockFetch).toHaveBeenCalledWith("https://sdkapi.zapier-staging.com/api/v0/sdk/zapier/api/v4/profile/", expect.any(Object));
|
|
230
|
-
});
|
|
231
|
-
it("should preserve base URL path for localhost with path component", async () => {
|
|
232
|
-
const client = createZapierApi({
|
|
233
|
-
baseUrl: "http://localhost:3000/a/b/c",
|
|
234
|
-
credentials: "test-token",
|
|
235
|
-
debug: false,
|
|
236
|
-
});
|
|
237
|
-
await client.get("/relay/some/path");
|
|
238
|
-
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/a/b/c/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
describe("fetch body serialization", () => {
|
|
242
|
-
beforeEach(() => {
|
|
243
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
244
|
-
});
|
|
245
|
-
it("should JSON.stringify plain object bodies", async () => {
|
|
246
|
-
const client = createZapierApi({
|
|
247
|
-
baseUrl: "http://localhost:3000",
|
|
248
|
-
credentials: "test-token",
|
|
249
|
-
debug: false,
|
|
250
|
-
});
|
|
251
|
-
const body = { channel: "C123", text: "hello" };
|
|
252
|
-
await client.fetch("/relay/api.example.com/data", {
|
|
253
|
-
method: "POST",
|
|
254
|
-
body: body,
|
|
255
|
-
});
|
|
256
|
-
const [, options] = mockFetch.mock.calls[0];
|
|
257
|
-
expect(options.body).toBe(JSON.stringify(body));
|
|
258
|
-
});
|
|
259
|
-
it("should JSON.stringify array bodies", async () => {
|
|
260
|
-
const client = createZapierApi({
|
|
261
|
-
baseUrl: "http://localhost:3000",
|
|
262
|
-
credentials: "test-token",
|
|
263
|
-
debug: false,
|
|
264
|
-
});
|
|
265
|
-
const body = [{ id: 1 }, { id: 2 }];
|
|
266
|
-
await client.fetch("/relay/api.example.com/data", {
|
|
267
|
-
method: "POST",
|
|
268
|
-
body: body,
|
|
269
|
-
});
|
|
270
|
-
const [, options] = mockFetch.mock.calls[0];
|
|
271
|
-
expect(options.body).toBe(JSON.stringify(body));
|
|
272
|
-
});
|
|
273
|
-
it("should not stringify FormData bodies", async () => {
|
|
274
|
-
const client = createZapierApi({
|
|
275
|
-
baseUrl: "http://localhost:3000",
|
|
276
|
-
credentials: "test-token",
|
|
277
|
-
debug: false,
|
|
278
|
-
});
|
|
279
|
-
const body = new FormData();
|
|
280
|
-
body.append("file", "contents");
|
|
281
|
-
await client.fetch("/relay/api.example.com/upload", {
|
|
282
|
-
method: "POST",
|
|
283
|
-
body,
|
|
284
|
-
});
|
|
285
|
-
const [, options] = mockFetch.mock.calls[0];
|
|
286
|
-
expect(options.body).toBe(body);
|
|
287
|
-
});
|
|
288
|
-
it("should not stringify Blob bodies", async () => {
|
|
289
|
-
const client = createZapierApi({
|
|
290
|
-
baseUrl: "http://localhost:3000",
|
|
291
|
-
credentials: "test-token",
|
|
292
|
-
debug: false,
|
|
293
|
-
});
|
|
294
|
-
const body = new Blob(["binary data"], {
|
|
295
|
-
type: "application/octet-stream",
|
|
296
|
-
});
|
|
297
|
-
await client.fetch("/relay/api.example.com/upload", {
|
|
298
|
-
method: "POST",
|
|
299
|
-
body,
|
|
300
|
-
});
|
|
301
|
-
const [, options] = mockFetch.mock.calls[0];
|
|
302
|
-
expect(options.body).toBe(body);
|
|
303
|
-
});
|
|
304
|
-
it("should pass string bodies through unchanged", async () => {
|
|
305
|
-
const client = createZapierApi({
|
|
306
|
-
baseUrl: "http://localhost:3000",
|
|
307
|
-
credentials: "test-token",
|
|
308
|
-
debug: false,
|
|
309
|
-
});
|
|
310
|
-
const body = '{"already": "stringified"}';
|
|
311
|
-
await client.fetch("/relay/api.example.com/data", {
|
|
312
|
-
method: "POST",
|
|
313
|
-
body,
|
|
314
|
-
});
|
|
315
|
-
const [, options] = mockFetch.mock.calls[0];
|
|
316
|
-
expect(options.body).toBe(body);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
describe("rate limiting (429) retry behavior", () => {
|
|
320
|
-
beforeEach(() => {
|
|
321
|
-
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
322
|
-
vi.useFakeTimers();
|
|
323
|
-
});
|
|
324
|
-
afterEach(() => {
|
|
325
|
-
vi.useRealTimers();
|
|
326
|
-
});
|
|
327
|
-
it("should retry on 429 and succeed on subsequent attempt", async () => {
|
|
328
|
-
let callCount = 0;
|
|
329
|
-
global.fetch = vi.fn().mockImplementation(() => {
|
|
330
|
-
callCount++;
|
|
331
|
-
if (callCount === 1) {
|
|
332
|
-
return Promise.resolve({
|
|
333
|
-
ok: false,
|
|
334
|
-
status: 429,
|
|
335
|
-
statusText: "Too Many Requests",
|
|
336
|
-
headers: new Headers({ "retry-after": "1" }),
|
|
337
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return Promise.resolve({
|
|
341
|
-
ok: true,
|
|
342
|
-
status: 200,
|
|
343
|
-
text: () => Promise.resolve(JSON.stringify({ data: "success" })),
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
const client = createZapierApi({
|
|
347
|
-
baseUrl: "http://localhost:3000",
|
|
348
|
-
credentials: "test-token",
|
|
349
|
-
debug: false,
|
|
350
|
-
});
|
|
351
|
-
const resultPromise = client.get("/test");
|
|
352
|
-
// Fast-forward through the retry delay
|
|
353
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
354
|
-
const result = await resultPromise;
|
|
355
|
-
expect(result).toEqual({ data: "success" });
|
|
356
|
-
expect(callCount).toBe(2);
|
|
357
|
-
});
|
|
358
|
-
it("should throw ZapierRateLimitError after max retries exhausted", async () => {
|
|
359
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
360
|
-
ok: false,
|
|
361
|
-
status: 429,
|
|
362
|
-
statusText: "Too Many Requests",
|
|
363
|
-
headers: new Headers({
|
|
364
|
-
"retry-after": "1",
|
|
365
|
-
"x-ratelimit-limit": "100",
|
|
366
|
-
"x-ratelimit-remaining": "0",
|
|
367
|
-
}),
|
|
368
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
369
|
-
});
|
|
370
|
-
const client = createZapierApi({
|
|
371
|
-
baseUrl: "http://localhost:3000",
|
|
372
|
-
credentials: "test-token",
|
|
373
|
-
debug: false,
|
|
374
|
-
maxNetworkRetries: 2,
|
|
375
|
-
});
|
|
376
|
-
const resultPromise = client.get("/test");
|
|
377
|
-
// Attach error handler immediately to prevent unhandled rejection
|
|
378
|
-
let caughtError;
|
|
379
|
-
resultPromise.catch((err) => {
|
|
380
|
-
caughtError = err;
|
|
381
|
-
});
|
|
382
|
-
// Advance through all retry delays
|
|
383
|
-
await vi.advanceTimersByTimeAsync(1000); // 1st retry
|
|
384
|
-
await vi.advanceTimersByTimeAsync(1000); // 2nd retry
|
|
385
|
-
// Wait for promise to settle
|
|
386
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
387
|
-
expect(caughtError).toBeInstanceOf(ZapierRateLimitError);
|
|
388
|
-
expect(caughtError).toMatchObject({
|
|
389
|
-
rateLimit: {
|
|
390
|
-
retryAfterMs: 1000,
|
|
391
|
-
limit: 100,
|
|
392
|
-
remaining: 0,
|
|
393
|
-
},
|
|
394
|
-
});
|
|
395
|
-
// Should have made 3 calls (initial + 2 retries)
|
|
396
|
-
expect(global.fetch).toHaveBeenCalledTimes(3);
|
|
397
|
-
});
|
|
398
|
-
it("should use exponential backoff when no Retry-After header", async () => {
|
|
399
|
-
let callCount = 0;
|
|
400
|
-
global.fetch = vi.fn().mockImplementation(() => {
|
|
401
|
-
callCount++;
|
|
402
|
-
if (callCount <= 2) {
|
|
403
|
-
return Promise.resolve({
|
|
404
|
-
ok: false,
|
|
405
|
-
status: 429,
|
|
406
|
-
statusText: "Too Many Requests",
|
|
407
|
-
headers: new Headers(),
|
|
408
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
return Promise.resolve({
|
|
412
|
-
ok: true,
|
|
413
|
-
status: 200,
|
|
414
|
-
text: () => Promise.resolve(JSON.stringify({ data: "success" })),
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
const client = createZapierApi({
|
|
418
|
-
baseUrl: "http://localhost:3000",
|
|
419
|
-
credentials: "test-token",
|
|
420
|
-
debug: false,
|
|
421
|
-
});
|
|
422
|
-
const resultPromise = client.get("/test");
|
|
423
|
-
// First retry: ~1000ms base delay
|
|
424
|
-
await vi.advanceTimersByTimeAsync(1500);
|
|
425
|
-
// Second retry: ~2000ms base delay
|
|
426
|
-
await vi.advanceTimersByTimeAsync(3000);
|
|
427
|
-
const result = await resultPromise;
|
|
428
|
-
expect(result).toEqual({ data: "success" });
|
|
429
|
-
expect(callCount).toBe(3);
|
|
430
|
-
});
|
|
431
|
-
it("should respect X-RateLimit-Reset when no Retry-After header", async () => {
|
|
432
|
-
const resetTime = Math.floor(Date.now() / 1000) + 2; // 2 seconds from now
|
|
433
|
-
let callCount = 0;
|
|
434
|
-
global.fetch = vi.fn().mockImplementation(() => {
|
|
435
|
-
callCount++;
|
|
436
|
-
if (callCount === 1) {
|
|
437
|
-
return Promise.resolve({
|
|
438
|
-
ok: false,
|
|
439
|
-
status: 429,
|
|
440
|
-
statusText: "Too Many Requests",
|
|
441
|
-
headers: new Headers({
|
|
442
|
-
"x-ratelimit-reset": String(resetTime),
|
|
443
|
-
}),
|
|
444
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
return Promise.resolve({
|
|
448
|
-
ok: true,
|
|
449
|
-
status: 200,
|
|
450
|
-
text: () => Promise.resolve(JSON.stringify({ data: "success" })),
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
const client = createZapierApi({
|
|
454
|
-
baseUrl: "http://localhost:3000",
|
|
455
|
-
credentials: "test-token",
|
|
456
|
-
debug: false,
|
|
457
|
-
});
|
|
458
|
-
const resultPromise = client.get("/test");
|
|
459
|
-
// Advance past the reset time
|
|
460
|
-
await vi.advanceTimersByTimeAsync(2500);
|
|
461
|
-
const result = await resultPromise;
|
|
462
|
-
expect(result).toEqual({ data: "success" });
|
|
463
|
-
expect(callCount).toBe(2);
|
|
464
|
-
});
|
|
465
|
-
it("should emit retry events via onEvent", async () => {
|
|
466
|
-
const events = [];
|
|
467
|
-
let callCount = 0;
|
|
468
|
-
global.fetch = vi.fn().mockImplementation(() => {
|
|
469
|
-
callCount++;
|
|
470
|
-
if (callCount === 1) {
|
|
471
|
-
return Promise.resolve({
|
|
472
|
-
ok: false,
|
|
473
|
-
status: 429,
|
|
474
|
-
statusText: "Too Many Requests",
|
|
475
|
-
headers: new Headers({ "retry-after": "1" }),
|
|
476
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
return Promise.resolve({
|
|
480
|
-
ok: true,
|
|
481
|
-
status: 200,
|
|
482
|
-
text: () => Promise.resolve(JSON.stringify({ data: "success" })),
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
const client = createZapierApi({
|
|
486
|
-
baseUrl: "http://localhost:3000",
|
|
487
|
-
credentials: "test-token",
|
|
488
|
-
debug: false,
|
|
489
|
-
onEvent: (event) => events.push(event),
|
|
490
|
-
});
|
|
491
|
-
const resultPromise = client.get("/test");
|
|
492
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
493
|
-
await resultPromise;
|
|
494
|
-
expect(events).toContainEqual(expect.objectContaining({
|
|
495
|
-
type: "api:rate_limit_retry",
|
|
496
|
-
payload: expect.objectContaining({
|
|
497
|
-
retry: 1,
|
|
498
|
-
maxNetworkRetries: 3,
|
|
499
|
-
delayMs: 1000,
|
|
500
|
-
}),
|
|
501
|
-
}));
|
|
502
|
-
});
|
|
503
|
-
it("should disable retries when maxNetworkRetries is 0", async () => {
|
|
504
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
505
|
-
ok: false,
|
|
506
|
-
status: 429,
|
|
507
|
-
statusText: "Too Many Requests",
|
|
508
|
-
headers: new Headers({ "retry-after": "1" }),
|
|
509
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
510
|
-
});
|
|
511
|
-
const client = createZapierApi({
|
|
512
|
-
baseUrl: "http://localhost:3000",
|
|
513
|
-
credentials: "test-token",
|
|
514
|
-
debug: false,
|
|
515
|
-
maxNetworkRetries: 0,
|
|
516
|
-
});
|
|
517
|
-
await expect(client.get("/test")).rejects.toThrow(ZapierRateLimitError);
|
|
518
|
-
// Should only make 1 call (no retries)
|
|
519
|
-
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
520
|
-
});
|
|
521
|
-
it("should also retry on raw fetch calls", async () => {
|
|
522
|
-
let callCount = 0;
|
|
523
|
-
global.fetch = vi.fn().mockImplementation(() => {
|
|
524
|
-
callCount++;
|
|
525
|
-
if (callCount === 1) {
|
|
526
|
-
return Promise.resolve({
|
|
527
|
-
ok: false,
|
|
528
|
-
status: 429,
|
|
529
|
-
statusText: "Too Many Requests",
|
|
530
|
-
headers: new Headers({ "retry-after": "1" }),
|
|
531
|
-
text: () => Promise.resolve("Rate limited"),
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
return Promise.resolve({
|
|
535
|
-
ok: true,
|
|
536
|
-
status: 200,
|
|
537
|
-
headers: new Headers(),
|
|
538
|
-
text: () => Promise.resolve("success"),
|
|
539
|
-
});
|
|
540
|
-
});
|
|
541
|
-
const client = createZapierApi({
|
|
542
|
-
baseUrl: "http://localhost:3000",
|
|
543
|
-
credentials: "test-token",
|
|
544
|
-
debug: false,
|
|
545
|
-
});
|
|
546
|
-
const resultPromise = client.fetch("/test");
|
|
547
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
548
|
-
const response = await resultPromise;
|
|
549
|
-
expect(response.status).toBe(200);
|
|
550
|
-
expect(callCount).toBe(2);
|
|
551
|
-
});
|
|
552
|
-
it("should fail immediately when retry delay exceeds maxNetworkRetryDelayMs", async () => {
|
|
553
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
554
|
-
ok: false,
|
|
555
|
-
status: 429,
|
|
556
|
-
statusText: "Too Many Requests",
|
|
557
|
-
headers: new Headers({ "retry-after": "60" }), // Server wants 60 seconds
|
|
558
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limited" })),
|
|
559
|
-
});
|
|
560
|
-
const client = createZapierApi({
|
|
561
|
-
baseUrl: "http://localhost:3000",
|
|
562
|
-
credentials: "test-token",
|
|
563
|
-
debug: false,
|
|
564
|
-
maxNetworkRetryDelayMs: 5000, // But we only want to wait up to 5 seconds
|
|
565
|
-
});
|
|
566
|
-
try {
|
|
567
|
-
await client.get("/test");
|
|
568
|
-
expect.fail("Should have thrown ZapierRateLimitError");
|
|
569
|
-
}
|
|
570
|
-
catch (error) {
|
|
571
|
-
expect(error).toBeInstanceOf(ZapierRateLimitError);
|
|
572
|
-
// Verify retries is 0 (no retries happened), not maxNetworkRetries
|
|
573
|
-
expect(error.retries).toBe(0);
|
|
574
|
-
}
|
|
575
|
-
// Should only make 1 call (no retries because delay exceeds max)
|
|
576
|
-
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
577
|
-
});
|
|
578
|
-
it("should retry when delay is within maxNetworkRetryDelayMs", async () => {
|
|
579
|
-
let callCount = 0;
|
|
580
|
-
global.fetch = vi.fn().mockImplementation(() => {
|
|
581
|
-
callCount++;
|
|
582
|
-
if (callCount === 1) {
|
|
583
|
-
return Promise.resolve({
|
|
584
|
-
ok: false,
|
|
585
|
-
status: 429,
|
|
586
|
-
statusText: "Too Many Requests",
|
|
587
|
-
headers: new Headers({ "retry-after": "1" }), // Server wants 1 second
|
|
588
|
-
text: () => Promise.resolve("Rate limited"),
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
return Promise.resolve({
|
|
592
|
-
ok: true,
|
|
593
|
-
status: 200,
|
|
594
|
-
headers: new Headers(),
|
|
595
|
-
text: () => Promise.resolve(JSON.stringify({ data: "success" })),
|
|
596
|
-
});
|
|
597
|
-
});
|
|
598
|
-
const client = createZapierApi({
|
|
599
|
-
baseUrl: "http://localhost:3000",
|
|
600
|
-
credentials: "test-token",
|
|
601
|
-
debug: false,
|
|
602
|
-
maxNetworkRetryDelayMs: 5000, // 5 seconds is enough for 1 second delay
|
|
603
|
-
});
|
|
604
|
-
const resultPromise = client.get("/test");
|
|
605
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
606
|
-
const result = await resultPromise;
|
|
607
|
-
expect(result).toEqual({ data: "success" });
|
|
608
|
-
expect(callCount).toBe(2);
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
});
|
package/dist/api/debug.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug.test.d.ts","sourceRoot":"","sources":["../../src/api/debug.test.ts"],"names":[],"mappings":""}
|
package/dist/api/debug.test.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { censorHeaders } from "./debug";
|
|
3
|
-
describe("censorHeaders", () => {
|
|
4
|
-
it("should return undefined for undefined headers", () => {
|
|
5
|
-
expect(censorHeaders(undefined)).toBeUndefined();
|
|
6
|
-
});
|
|
7
|
-
it("should return headers unchanged for non-auth headers", () => {
|
|
8
|
-
const headers = {
|
|
9
|
-
"Content-Type": "application/json",
|
|
10
|
-
"User-Agent": "test",
|
|
11
|
-
};
|
|
12
|
-
const result = censorHeaders(headers);
|
|
13
|
-
// Headers API normalizes header names to lowercase
|
|
14
|
-
expect(result["content-type"]).toBe("application/json");
|
|
15
|
-
expect(result["user-agent"]).toBe("test");
|
|
16
|
-
});
|
|
17
|
-
it("should censor authorization header with Bearer prefix - long token", () => {
|
|
18
|
-
const headers = { authorization: "Bearer abcdef1234567890xyz" };
|
|
19
|
-
const result = censorHeaders(headers);
|
|
20
|
-
expect(result.authorization).toBe("Bearer abcd...0xyz");
|
|
21
|
-
});
|
|
22
|
-
it("should censor authorization header with Bearer prefix - short token", () => {
|
|
23
|
-
const headers = { authorization: "Bearer short123" };
|
|
24
|
-
const result = censorHeaders(headers);
|
|
25
|
-
expect(result.authorization).toBe("Bearer s...");
|
|
26
|
-
});
|
|
27
|
-
it("should censor x-api-key header - long token", () => {
|
|
28
|
-
const headers = { "x-api-key": "sk-1234567890abcdefghij" };
|
|
29
|
-
const result = censorHeaders(headers);
|
|
30
|
-
expect(result["x-api-key"]).toBe("sk-1...ghij");
|
|
31
|
-
});
|
|
32
|
-
it("should censor x-api-key header - short token", () => {
|
|
33
|
-
const headers = { "x-api-key": "short" };
|
|
34
|
-
const result = censorHeaders(headers);
|
|
35
|
-
expect(result["x-api-key"]).toBe("s...");
|
|
36
|
-
});
|
|
37
|
-
it("should handle Headers object input", () => {
|
|
38
|
-
const headers = new Headers();
|
|
39
|
-
headers.set("authorization", "Bearer verylongtoken123456789");
|
|
40
|
-
const result = censorHeaders(headers);
|
|
41
|
-
expect(result.authorization).toBe("Bearer very...6789");
|
|
42
|
-
});
|
|
43
|
-
it("should handle mixed case authorization headers", () => {
|
|
44
|
-
const headers = { Authorization: "Bearer mixedcasetoken123" };
|
|
45
|
-
const result = censorHeaders(headers);
|
|
46
|
-
expect(result.authorization).toBe("Bearer mixe...n123");
|
|
47
|
-
});
|
|
48
|
-
it("should preserve non-auth headers while censoring auth headers", () => {
|
|
49
|
-
const headers = {
|
|
50
|
-
"Content-Type": "application/json",
|
|
51
|
-
authorization: "Bearer shouldbecensored123456789",
|
|
52
|
-
"User-Agent": "test-agent",
|
|
53
|
-
};
|
|
54
|
-
const result = censorHeaders(headers);
|
|
55
|
-
expect(result["content-type"]).toBe("application/json");
|
|
56
|
-
expect(result["user-agent"]).toBe("test-agent");
|
|
57
|
-
expect(result.authorization).toBe("Bearer shou...6789");
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"polling.test.d.ts","sourceRoot":"","sources":["../../src/api/polling.test.ts"],"names":[],"mappings":""}
|