@zapier/zapier-sdk 0.18.3 → 0.18.4
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 +6 -0
- package/README.md +1 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +0 -18
- package/dist/api/schemas.d.ts +0 -109
- package/dist/api/schemas.d.ts.map +1 -1
- package/dist/api/schemas.js +0 -67
- package/dist/api/types.d.ts +2 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/index.cjs +337 -673
- package/dist/index.d.mts +52 -105
- package/dist/index.mjs +337 -673
- package/dist/plugins/listApps/index.d.ts +2 -8
- package/dist/plugins/listApps/index.d.ts.map +1 -1
- package/dist/plugins/listApps/index.js +4 -6
- package/dist/plugins/listApps/index.test.js +62 -82
- package/dist/plugins/listApps/schemas.d.ts +35 -14
- package/dist/plugins/listApps/schemas.d.ts.map +1 -1
- package/dist/plugins/listApps/schemas.js +44 -14
- package/dist/plugins/listAuthentications/index.test.js +16 -0
- package/dist/schemas/App.d.ts +28 -28
- package/dist/schemas/App.d.ts.map +1 -1
- package/dist/schemas/App.js +3 -8
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.test.js +12 -9
- package/package.json +1 -1
- package/dist/api/client.integration.test.d.ts +0 -5
- package/dist/api/client.integration.test.d.ts.map +0 -1
- package/dist/api/client.integration.test.js +0 -318
- package/dist/api/client.methods.test.d.ts +0 -2
- package/dist/api/client.methods.test.d.ts.map +0 -1
- package/dist/api/client.methods.test.js +0 -158
- package/dist/api/router.d.ts +0 -16
- package/dist/api/router.d.ts.map +0 -1
- package/dist/api/router.js +0 -31
- package/dist/api/router.test.d.ts +0 -2
- package/dist/api/router.test.d.ts.map +0 -1
- package/dist/api/router.test.js +0 -103
- package/dist/temporary-internal-core/handlers/listApps.d.ts +0 -67
- package/dist/temporary-internal-core/handlers/listApps.d.ts.map +0 -1
- package/dist/temporary-internal-core/handlers/listApps.js +0 -134
- package/dist/temporary-internal-core/handlers/listApps.test.d.ts +0 -2
- package/dist/temporary-internal-core/handlers/listApps.test.d.ts.map +0 -1
- package/dist/temporary-internal-core/handlers/listApps.test.js +0 -367
- package/dist/temporary-internal-core/index.d.ts +0 -18
- package/dist/temporary-internal-core/index.d.ts.map +0 -1
- package/dist/temporary-internal-core/index.js +0 -18
- package/dist/temporary-internal-core/schemas/apps/index.d.ts +0 -175
- package/dist/temporary-internal-core/schemas/apps/index.d.ts.map +0 -1
- package/dist/temporary-internal-core/schemas/apps/index.js +0 -97
- package/dist/temporary-internal-core/schemas/errors/index.d.ts +0 -139
- package/dist/temporary-internal-core/schemas/errors/index.d.ts.map +0 -1
- package/dist/temporary-internal-core/schemas/errors/index.js +0 -129
- package/dist/temporary-internal-core/schemas/implementations/index.d.ts +0 -127
- package/dist/temporary-internal-core/schemas/implementations/index.d.ts.map +0 -1
- package/dist/temporary-internal-core/schemas/implementations/index.js +0 -79
- package/dist/temporary-internal-core/types/handler.d.ts +0 -51
- package/dist/temporary-internal-core/types/handler.d.ts.map +0 -1
- package/dist/temporary-internal-core/types/handler.js +0 -8
- package/dist/temporary-internal-core/types/index.d.ts +0 -5
- package/dist/temporary-internal-core/types/index.d.ts.map +0 -1
- package/dist/temporary-internal-core/types/index.js +0 -4
- package/dist/temporary-internal-core/utils/app-locators.d.ts +0 -34
- package/dist/temporary-internal-core/utils/app-locators.d.ts.map +0 -1
- package/dist/temporary-internal-core/utils/app-locators.js +0 -39
- package/dist/temporary-internal-core/utils/string-utils.d.ts +0 -28
- package/dist/temporary-internal-core/utils/string-utils.d.ts.map +0 -1
- package/dist/temporary-internal-core/utils/string-utils.js +0 -52
- package/dist/temporary-internal-core/utils/transformations.d.ts +0 -18
- package/dist/temporary-internal-core/utils/transformations.d.ts.map +0 -1
- package/dist/temporary-internal-core/utils/transformations.js +0 -36
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for API client handler override mechanism
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
5
|
-
import { createZapierApi } from "./client";
|
|
6
|
-
describe("Handler Override Mechanism Integration", () => {
|
|
7
|
-
let mockFetch;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
mockFetch = vi.fn();
|
|
10
|
-
});
|
|
11
|
-
describe("listApps handler override", () => {
|
|
12
|
-
it("should invoke handler instead of making HTTP request to /api/v0/apps", async () => {
|
|
13
|
-
// Mock the nested API call that the handler makes
|
|
14
|
-
mockFetch.mockResolvedValueOnce({
|
|
15
|
-
ok: true,
|
|
16
|
-
status: 200,
|
|
17
|
-
json: async () => ({
|
|
18
|
-
results: [
|
|
19
|
-
{
|
|
20
|
-
api: "slack",
|
|
21
|
-
name: "Slack",
|
|
22
|
-
id: "SlackAPI@1.0.0",
|
|
23
|
-
logo_url: "https://example.com/slack.png",
|
|
24
|
-
categories: ["communication"],
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
next: null,
|
|
28
|
-
}),
|
|
29
|
-
});
|
|
30
|
-
const api = createZapierApi({
|
|
31
|
-
fetch: mockFetch,
|
|
32
|
-
token: "test-token",
|
|
33
|
-
baseUrl: "https://api.zapier.com",
|
|
34
|
-
});
|
|
35
|
-
// Call the path with handler override
|
|
36
|
-
const result = await api.get("/api/v0/apps", {
|
|
37
|
-
searchParams: { implementationIds: "SlackAPI@1.0.0" },
|
|
38
|
-
});
|
|
39
|
-
// Assert: No direct call to /api/v0/apps (handler intercepted it)
|
|
40
|
-
const fetchCalls = mockFetch.mock.calls;
|
|
41
|
-
const appsEndpointCalls = fetchCalls.filter((call) => typeof call[0] === "string" && call[0].includes("/api/v0/apps"));
|
|
42
|
-
expect(appsEndpointCalls).toHaveLength(0);
|
|
43
|
-
// Assert: Handler made nested call to lookup endpoint
|
|
44
|
-
const lookupCalls = fetchCalls.filter((call) => typeof call[0] === "string" &&
|
|
45
|
-
call[0].includes("/zapier/api/v4/implementations-meta/lookup/"));
|
|
46
|
-
expect(lookupCalls.length).toBeGreaterThan(0);
|
|
47
|
-
// Assert: Result has correct structure from handler
|
|
48
|
-
expect(result).toHaveProperty("data");
|
|
49
|
-
expect(result).toHaveProperty("nextCursor");
|
|
50
|
-
expect(Array.isArray(result.data)).toBe(true);
|
|
51
|
-
expect(result.data).toHaveLength(1);
|
|
52
|
-
expect(result.data[0]).toMatchObject({
|
|
53
|
-
key: "SlackAPI",
|
|
54
|
-
title: "Slack",
|
|
55
|
-
implementation_id: "SlackAPI@1.0.0",
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
it("should pass httpClient to handler for nested API calls", async () => {
|
|
59
|
-
// Mock response for the nested lookup call
|
|
60
|
-
mockFetch.mockResolvedValueOnce({
|
|
61
|
-
ok: true,
|
|
62
|
-
status: 200,
|
|
63
|
-
json: async () => ({
|
|
64
|
-
results: [
|
|
65
|
-
{
|
|
66
|
-
api: "github",
|
|
67
|
-
name: "GitHub",
|
|
68
|
-
id: "GitHubAPI@2.1.0",
|
|
69
|
-
logo_url: "https://example.com/github.png",
|
|
70
|
-
categories: ["developer-tools"],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
next: null,
|
|
74
|
-
}),
|
|
75
|
-
});
|
|
76
|
-
const api = createZapierApi({
|
|
77
|
-
fetch: mockFetch,
|
|
78
|
-
token: "test-token",
|
|
79
|
-
baseUrl: "https://api.zapier.com",
|
|
80
|
-
});
|
|
81
|
-
// Request with multiple implementation IDs
|
|
82
|
-
await api.get("/api/v0/apps", {
|
|
83
|
-
searchParams: {
|
|
84
|
-
implementationIds: "GitHubAPI@2.1.0,SlackAPI@1.0.0",
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
// Verify that nested API call was made
|
|
88
|
-
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/zapier/api/v4/implementations-meta/lookup/"), expect.any(Object));
|
|
89
|
-
// Verify the request includes proper configuration
|
|
90
|
-
const fetchCall = mockFetch.mock.calls[0];
|
|
91
|
-
expect(fetchCall[0]).toContain("selected_apis=GitHubAPI%402.1.0%2CSlackAPI%401.0.0");
|
|
92
|
-
expect(fetchCall[1]).toHaveProperty("method", "GET");
|
|
93
|
-
});
|
|
94
|
-
it("should handle search augmentation through handler", async () => {
|
|
95
|
-
// Mock search endpoint response
|
|
96
|
-
mockFetch
|
|
97
|
-
.mockResolvedValueOnce({
|
|
98
|
-
ok: true,
|
|
99
|
-
status: 200,
|
|
100
|
-
json: async () => ({
|
|
101
|
-
results: [
|
|
102
|
-
{
|
|
103
|
-
api: "gmail",
|
|
104
|
-
name: "Gmail",
|
|
105
|
-
id: "GmailAPI@1.5.0",
|
|
106
|
-
logo_url: "https://example.com/gmail.png",
|
|
107
|
-
categories: ["email"],
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
next: null,
|
|
111
|
-
}),
|
|
112
|
-
})
|
|
113
|
-
// Mock lookup endpoint response
|
|
114
|
-
.mockResolvedValueOnce({
|
|
115
|
-
ok: true,
|
|
116
|
-
status: 200,
|
|
117
|
-
json: async () => ({
|
|
118
|
-
results: [
|
|
119
|
-
{
|
|
120
|
-
api: "slack",
|
|
121
|
-
name: "Slack",
|
|
122
|
-
id: "SlackAPI@1.0.0",
|
|
123
|
-
logo_url: "https://example.com/slack.png",
|
|
124
|
-
categories: ["communication"],
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
api: "gmail",
|
|
128
|
-
name: "Gmail",
|
|
129
|
-
id: "GmailAPI@1.5.0",
|
|
130
|
-
logo_url: "https://example.com/gmail.png",
|
|
131
|
-
categories: ["email"],
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
next: null,
|
|
135
|
-
}),
|
|
136
|
-
});
|
|
137
|
-
const api = createZapierApi({
|
|
138
|
-
fetch: mockFetch,
|
|
139
|
-
token: "test-token",
|
|
140
|
-
baseUrl: "https://api.zapier.com",
|
|
141
|
-
});
|
|
142
|
-
// Request with search term and specific implementation
|
|
143
|
-
const result = await api.get("/api/v0/apps", {
|
|
144
|
-
searchParams: {
|
|
145
|
-
implementationIds: "SlackAPI@1.0.0",
|
|
146
|
-
search: "email",
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
// Verify search endpoint was called
|
|
150
|
-
const searchCalls = mockFetch.mock.calls.filter((call) => typeof call[0] === "string" &&
|
|
151
|
-
call[0].includes("/zapier/api/v4/implementations-meta/search/"));
|
|
152
|
-
expect(searchCalls.length).toBeGreaterThan(0);
|
|
153
|
-
// Verify lookup endpoint was called with augmented IDs
|
|
154
|
-
const lookupCalls = mockFetch.mock.calls.filter((call) => typeof call[0] === "string" &&
|
|
155
|
-
call[0].includes("/zapier/api/v4/implementations-meta/lookup/"));
|
|
156
|
-
expect(lookupCalls.length).toBeGreaterThan(0);
|
|
157
|
-
// Verify result contains both original and search results
|
|
158
|
-
expect(result.data).toHaveLength(2);
|
|
159
|
-
});
|
|
160
|
-
it("should handle pagination through handler", async () => {
|
|
161
|
-
// Mock first page response
|
|
162
|
-
mockFetch.mockResolvedValueOnce({
|
|
163
|
-
ok: true,
|
|
164
|
-
status: 200,
|
|
165
|
-
json: async () => ({
|
|
166
|
-
results: [
|
|
167
|
-
{
|
|
168
|
-
api: "app1",
|
|
169
|
-
name: "App 1",
|
|
170
|
-
id: "App1API@1.0.0",
|
|
171
|
-
logo_url: "https://example.com/app1.png",
|
|
172
|
-
categories: ["productivity"],
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
next: "https://example.com/api?offset=20",
|
|
176
|
-
}),
|
|
177
|
-
});
|
|
178
|
-
const api = createZapierApi({
|
|
179
|
-
fetch: mockFetch,
|
|
180
|
-
token: "test-token",
|
|
181
|
-
baseUrl: "https://api.zapier.com",
|
|
182
|
-
});
|
|
183
|
-
const result = await api.get("/api/v0/apps", {
|
|
184
|
-
searchParams: { pageSize: "10" },
|
|
185
|
-
});
|
|
186
|
-
// Verify pagination cursor is extracted
|
|
187
|
-
expect(result.nextCursor).toBe("20");
|
|
188
|
-
// Mock second page response
|
|
189
|
-
mockFetch.mockResolvedValueOnce({
|
|
190
|
-
ok: true,
|
|
191
|
-
status: 200,
|
|
192
|
-
json: async () => ({
|
|
193
|
-
results: [
|
|
194
|
-
{
|
|
195
|
-
api: "app2",
|
|
196
|
-
name: "App 2",
|
|
197
|
-
id: "App2API@1.0.0",
|
|
198
|
-
logo_url: "https://example.com/app2.png",
|
|
199
|
-
categories: ["productivity"],
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
next: null,
|
|
203
|
-
}),
|
|
204
|
-
});
|
|
205
|
-
// Request second page
|
|
206
|
-
const secondPageResult = await api.get("/api/v0/apps", {
|
|
207
|
-
searchParams: { cursor: "20" },
|
|
208
|
-
});
|
|
209
|
-
expect(secondPageResult.nextCursor).toBeUndefined();
|
|
210
|
-
});
|
|
211
|
-
it("should handle errors from handler correctly", async () => {
|
|
212
|
-
// Mock an error response from nested API call
|
|
213
|
-
mockFetch.mockResolvedValueOnce({
|
|
214
|
-
ok: false,
|
|
215
|
-
status: 404,
|
|
216
|
-
statusText: "Not Found",
|
|
217
|
-
json: async () => ({
|
|
218
|
-
errors: [
|
|
219
|
-
{
|
|
220
|
-
message: "Implementation not found",
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
}),
|
|
224
|
-
});
|
|
225
|
-
const api = createZapierApi({
|
|
226
|
-
fetch: mockFetch,
|
|
227
|
-
token: "test-token",
|
|
228
|
-
baseUrl: "https://api.zapier.com",
|
|
229
|
-
});
|
|
230
|
-
// Request should propagate the error from handler
|
|
231
|
-
await expect(api.get("/api/v0/apps", {
|
|
232
|
-
searchParams: { implementationIds: "NonExistentAPI" },
|
|
233
|
-
})).rejects.toThrow();
|
|
234
|
-
});
|
|
235
|
-
it("should handle empty search results correctly", async () => {
|
|
236
|
-
// Mock empty search response
|
|
237
|
-
mockFetch.mockResolvedValueOnce({
|
|
238
|
-
ok: true,
|
|
239
|
-
status: 200,
|
|
240
|
-
json: async () => ({
|
|
241
|
-
results: [],
|
|
242
|
-
next: null,
|
|
243
|
-
}),
|
|
244
|
-
});
|
|
245
|
-
const api = createZapierApi({
|
|
246
|
-
fetch: mockFetch,
|
|
247
|
-
token: "test-token",
|
|
248
|
-
baseUrl: "https://api.zapier.com",
|
|
249
|
-
});
|
|
250
|
-
const result = await api.get("/api/v0/apps", {
|
|
251
|
-
searchParams: {
|
|
252
|
-
implementationIds: "",
|
|
253
|
-
search: "nonexistentapp",
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
// Should return empty results without calling lookup endpoint
|
|
257
|
-
expect(result.data).toEqual([]);
|
|
258
|
-
expect(result.nextCursor).toBeUndefined();
|
|
259
|
-
// Verify only search endpoint was called, not lookup
|
|
260
|
-
const lookupCalls = mockFetch.mock.calls.filter((call) => typeof call[0] === "string" &&
|
|
261
|
-
call[0].includes("/zapier/api/v4/implementations-meta/lookup/"));
|
|
262
|
-
expect(lookupCalls).toHaveLength(0);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
describe("standard path handling (non-override)", () => {
|
|
266
|
-
it("should make normal HTTP requests for paths without handler override", async () => {
|
|
267
|
-
mockFetch.mockResolvedValueOnce({
|
|
268
|
-
ok: true,
|
|
269
|
-
status: 200,
|
|
270
|
-
json: async () => ({ result: "direct HTTP response" }),
|
|
271
|
-
});
|
|
272
|
-
const api = createZapierApi({
|
|
273
|
-
fetch: mockFetch,
|
|
274
|
-
token: "test-token",
|
|
275
|
-
baseUrl: "https://api.zapier.com",
|
|
276
|
-
});
|
|
277
|
-
// Request to a path without handler override
|
|
278
|
-
await api.get("/zapier/some-other-endpoint");
|
|
279
|
-
// Verify HTTP request was made directly
|
|
280
|
-
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/zapier/some-other-endpoint"), expect.any(Object));
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
describe("handler override type safety", () => {
|
|
284
|
-
it("should handle handler request transformation correctly", async () => {
|
|
285
|
-
// Mock response for lookup
|
|
286
|
-
mockFetch.mockResolvedValueOnce({
|
|
287
|
-
ok: true,
|
|
288
|
-
status: 200,
|
|
289
|
-
json: async () => ({
|
|
290
|
-
results: [],
|
|
291
|
-
next: null,
|
|
292
|
-
}),
|
|
293
|
-
});
|
|
294
|
-
const api = createZapierApi({
|
|
295
|
-
fetch: mockFetch,
|
|
296
|
-
token: "test-token",
|
|
297
|
-
baseUrl: "https://api.zapier.com",
|
|
298
|
-
});
|
|
299
|
-
// Test that string implementationIds are properly handled
|
|
300
|
-
const result = await api.get("/api/v0/apps", {
|
|
301
|
-
searchParams: {
|
|
302
|
-
implementationIds: "App1,App2,App3",
|
|
303
|
-
pageSize: "15",
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
// Verify the handler received and processed the request
|
|
307
|
-
expect(result).toHaveProperty("data");
|
|
308
|
-
expect(Array.isArray(result.data)).toBe(true);
|
|
309
|
-
// Check that lookup endpoint was called with proper params
|
|
310
|
-
const lookupCall = mockFetch.mock.calls.find((call) => typeof call[0] === "string" &&
|
|
311
|
-
call[0].includes("/zapier/api/v4/implementations-meta/lookup/"));
|
|
312
|
-
expect(lookupCall).toBeDefined();
|
|
313
|
-
// Commas are URL-encoded as %2C
|
|
314
|
-
expect(lookupCall[0]).toContain("selected_apis=App1%2CApp2%2CApp3");
|
|
315
|
-
expect(lookupCall[0]).toContain("limit=15");
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.methods.test.d.ts","sourceRoot":"","sources":["../../src/api/client.methods.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, } from "vitest";
|
|
2
|
-
import { createZapierApi } from "./client";
|
|
3
|
-
import * as router from "./router";
|
|
4
|
-
vi.mock("./router");
|
|
5
|
-
describe("ZapierApiClient Methods & Routing", () => {
|
|
6
|
-
let mockFetch;
|
|
7
|
-
let mockMatchRoute;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
vi.clearAllMocks();
|
|
10
|
-
mockFetch = vi.fn();
|
|
11
|
-
mockMatchRoute = vi.mocked(router.matchRoute);
|
|
12
|
-
// Default fetch implementation
|
|
13
|
-
mockFetch.mockResolvedValue({
|
|
14
|
-
ok: true,
|
|
15
|
-
status: 200,
|
|
16
|
-
json: async () => ({ data: "default-response" }),
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
const createClient = () => createZapierApi({
|
|
20
|
-
baseUrl: "https://api.zapier.com",
|
|
21
|
-
token: "test-token",
|
|
22
|
-
fetch: mockFetch,
|
|
23
|
-
});
|
|
24
|
-
describe("GET requests", () => {
|
|
25
|
-
it("should route GET requests to handler when matched", async () => {
|
|
26
|
-
const client = createClient();
|
|
27
|
-
const mockHandler = vi.fn().mockResolvedValue({ id: 1, name: "Test" });
|
|
28
|
-
mockMatchRoute.mockReturnValue({
|
|
29
|
-
handler: mockHandler,
|
|
30
|
-
params: { id: "123" },
|
|
31
|
-
});
|
|
32
|
-
const result = await client.get("/api/resource/123", {
|
|
33
|
-
searchParams: { include: "details" },
|
|
34
|
-
});
|
|
35
|
-
expect(mockMatchRoute).toHaveBeenCalledWith("GET", "/api/resource/123");
|
|
36
|
-
expect(mockHandler).toHaveBeenCalledWith({
|
|
37
|
-
request: {
|
|
38
|
-
id: "123",
|
|
39
|
-
include: "details",
|
|
40
|
-
},
|
|
41
|
-
deps: expect.objectContaining({
|
|
42
|
-
httpClient: expect.any(Object),
|
|
43
|
-
}),
|
|
44
|
-
});
|
|
45
|
-
expect(result).toEqual({ id: 1, name: "Test" });
|
|
46
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
47
|
-
});
|
|
48
|
-
it("should fallback to plain fetch when no route matches", async () => {
|
|
49
|
-
const client = createClient();
|
|
50
|
-
mockMatchRoute.mockReturnValue(null);
|
|
51
|
-
await client.get("/api/unknown");
|
|
52
|
-
expect(mockMatchRoute).toHaveBeenCalledWith("GET", "/api/unknown");
|
|
53
|
-
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/unknown"), expect.objectContaining({
|
|
54
|
-
method: "GET",
|
|
55
|
-
}));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
describe("POST requests", () => {
|
|
59
|
-
it("should route POST requests to handler with body", async () => {
|
|
60
|
-
const client = createClient();
|
|
61
|
-
const mockHandler = vi.fn().mockResolvedValue({ success: true });
|
|
62
|
-
const body = { name: "New Item" };
|
|
63
|
-
mockMatchRoute.mockReturnValue({
|
|
64
|
-
handler: mockHandler,
|
|
65
|
-
params: {},
|
|
66
|
-
});
|
|
67
|
-
await client.post("/api/resource", body);
|
|
68
|
-
expect(mockMatchRoute).toHaveBeenCalledWith("POST", "/api/resource");
|
|
69
|
-
expect(mockHandler).toHaveBeenCalledWith({
|
|
70
|
-
request: {
|
|
71
|
-
name: "New Item",
|
|
72
|
-
},
|
|
73
|
-
deps: expect.objectContaining({
|
|
74
|
-
httpClient: expect.any(Object),
|
|
75
|
-
}),
|
|
76
|
-
});
|
|
77
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
78
|
-
});
|
|
79
|
-
it("should merge body, params, and query params for handler", async () => {
|
|
80
|
-
const client = createClient();
|
|
81
|
-
const mockHandler = vi.fn().mockResolvedValue({ success: true });
|
|
82
|
-
const body = { name: "Updated Item" };
|
|
83
|
-
mockMatchRoute.mockReturnValue({
|
|
84
|
-
handler: mockHandler,
|
|
85
|
-
params: { id: "456" },
|
|
86
|
-
});
|
|
87
|
-
await client.post("/api/resource/456", body, {
|
|
88
|
-
searchParams: { version: "2" },
|
|
89
|
-
});
|
|
90
|
-
expect(mockHandler).toHaveBeenCalledWith({
|
|
91
|
-
request: {
|
|
92
|
-
name: "Updated Item", // from body
|
|
93
|
-
id: "456", // from route params
|
|
94
|
-
version: "2", // from query params
|
|
95
|
-
},
|
|
96
|
-
deps: expect.any(Object),
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe("PUT requests", () => {
|
|
101
|
-
it("should route PUT requests to handler", async () => {
|
|
102
|
-
const client = createClient();
|
|
103
|
-
const mockHandler = vi.fn().mockResolvedValue({ updated: true });
|
|
104
|
-
const body = { status: "active" };
|
|
105
|
-
mockMatchRoute.mockReturnValue({
|
|
106
|
-
handler: mockHandler,
|
|
107
|
-
params: { id: "789" },
|
|
108
|
-
});
|
|
109
|
-
await client.put("/api/resource/789", body);
|
|
110
|
-
expect(mockMatchRoute).toHaveBeenCalledWith("PUT", "/api/resource/789");
|
|
111
|
-
expect(mockHandler).toHaveBeenCalledWith({
|
|
112
|
-
request: {
|
|
113
|
-
status: "active",
|
|
114
|
-
id: "789",
|
|
115
|
-
},
|
|
116
|
-
deps: expect.any(Object),
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe("DELETE requests", () => {
|
|
121
|
-
it("should route DELETE requests to handler", async () => {
|
|
122
|
-
const client = createClient();
|
|
123
|
-
const mockHandler = vi.fn().mockResolvedValue({ deleted: true });
|
|
124
|
-
mockMatchRoute.mockReturnValue({
|
|
125
|
-
handler: mockHandler,
|
|
126
|
-
params: { id: "999" },
|
|
127
|
-
});
|
|
128
|
-
await client.delete("/api/resource/999");
|
|
129
|
-
expect(mockMatchRoute).toHaveBeenCalledWith("DELETE", "/api/resource/999");
|
|
130
|
-
expect(mockHandler).toHaveBeenCalledWith({
|
|
131
|
-
request: {
|
|
132
|
-
id: "999",
|
|
133
|
-
},
|
|
134
|
-
deps: expect.any(Object),
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
describe("Header handling", () => {
|
|
139
|
-
it("should pass headers to plain fetch but NOT to handler request object (by default)", async () => {
|
|
140
|
-
const client = createClient();
|
|
141
|
-
const mockHandler = vi.fn().mockResolvedValue({});
|
|
142
|
-
mockMatchRoute.mockReturnValue({
|
|
143
|
-
handler: mockHandler,
|
|
144
|
-
params: {},
|
|
145
|
-
});
|
|
146
|
-
await client.get("/api/test", {
|
|
147
|
-
headers: { "X-Custom": "Value" },
|
|
148
|
-
});
|
|
149
|
-
// The handler request object only includes params/body, not headers
|
|
150
|
-
// based on client.ts implementation:
|
|
151
|
-
// const handlerRequest = { ...data, ...options.searchParams, ...routeMatch.params };
|
|
152
|
-
expect(mockHandler).toHaveBeenCalledWith({
|
|
153
|
-
request: {},
|
|
154
|
-
deps: expect.any(Object),
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
});
|
package/dist/api/router.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type Handler } from "../temporary-internal-core";
|
|
2
|
-
export interface Route {
|
|
3
|
-
method: string;
|
|
4
|
-
pattern: RegExp;
|
|
5
|
-
handler: Handler<any, any, any>;
|
|
6
|
-
paramMap: string[];
|
|
7
|
-
}
|
|
8
|
-
export declare function findMatchingRoute(routeList: Route[], method: string, path: string): {
|
|
9
|
-
handler: Handler<any, any, any>;
|
|
10
|
-
params: Record<string, string>;
|
|
11
|
-
} | null;
|
|
12
|
-
export declare function matchRoute(method: string, path: string): {
|
|
13
|
-
handler: Handler<any, any, any>;
|
|
14
|
-
params: Record<string, string>;
|
|
15
|
-
} | null;
|
|
16
|
-
//# sourceMappingURL=router.d.ts.map
|
package/dist/api/router.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAWD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,KAAK,EAAE,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;;;SAsBb;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;SAEtD"}
|
package/dist/api/router.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { handleListApps } from "../temporary-internal-core";
|
|
2
|
-
const routes = [
|
|
3
|
-
{
|
|
4
|
-
method: "GET",
|
|
5
|
-
pattern: /^\/api\/v0\/apps$/,
|
|
6
|
-
handler: handleListApps,
|
|
7
|
-
paramMap: [],
|
|
8
|
-
},
|
|
9
|
-
];
|
|
10
|
-
export function findMatchingRoute(routeList, method, path) {
|
|
11
|
-
for (const route of routeList) {
|
|
12
|
-
if (route.method !== method) {
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
const match = path.match(route.pattern);
|
|
16
|
-
if (match) {
|
|
17
|
-
const params = {};
|
|
18
|
-
route.paramMap.forEach((name, index) => {
|
|
19
|
-
params[name] = match[index + 1];
|
|
20
|
-
});
|
|
21
|
-
return {
|
|
22
|
-
handler: route.handler,
|
|
23
|
-
params,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
export function matchRoute(method, path) {
|
|
30
|
-
return findMatchingRoute(routes, method, path);
|
|
31
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"router.test.d.ts","sourceRoot":"","sources":["../../src/api/router.test.ts"],"names":[],"mappings":""}
|
package/dist/api/router.test.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { matchRoute, findMatchingRoute } from "./router";
|
|
3
|
-
import { handleListApps } from "../temporary-internal-core";
|
|
4
|
-
describe("Router", () => {
|
|
5
|
-
describe("matchRoute (Integration)", () => {
|
|
6
|
-
it("should match GET /api/v0/apps", () => {
|
|
7
|
-
const match = matchRoute("GET", "/api/v0/apps");
|
|
8
|
-
expect(match).not.toBeNull();
|
|
9
|
-
expect(match?.handler).toBe(handleListApps);
|
|
10
|
-
expect(match?.params).toEqual({});
|
|
11
|
-
});
|
|
12
|
-
it("should not match POST /api/v0/apps", () => {
|
|
13
|
-
const match = matchRoute("POST", "/api/v0/apps");
|
|
14
|
-
expect(match).toBeNull();
|
|
15
|
-
});
|
|
16
|
-
it("should not match unknown paths", () => {
|
|
17
|
-
expect(matchRoute("GET", "/api/v0/unknown")).toBeNull();
|
|
18
|
-
expect(matchRoute("GET", "/")).toBeNull();
|
|
19
|
-
});
|
|
20
|
-
it("should not match partial paths if regex is anchored", () => {
|
|
21
|
-
// The regex is /^\/api\/v0\/apps$/ so it shouldn't match suffix
|
|
22
|
-
expect(matchRoute("GET", "/api/v0/apps/something")).toBeNull();
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
describe("findMatchingRoute (Unit)", () => {
|
|
26
|
-
const mockHandler = vi.fn();
|
|
27
|
-
const testRoutes = [
|
|
28
|
-
{
|
|
29
|
-
method: "GET",
|
|
30
|
-
pattern: /^\/items$/,
|
|
31
|
-
handler: mockHandler,
|
|
32
|
-
paramMap: [],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
method: "GET",
|
|
36
|
-
pattern: /^\/items\/(\d+)$/,
|
|
37
|
-
handler: mockHandler,
|
|
38
|
-
paramMap: ["id"],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
method: "POST",
|
|
42
|
-
pattern: /^\/items$/,
|
|
43
|
-
handler: mockHandler,
|
|
44
|
-
paramMap: [],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
method: "GET",
|
|
48
|
-
pattern: /^\/users\/(\w+)\/posts\/(\d+)$/,
|
|
49
|
-
handler: mockHandler,
|
|
50
|
-
paramMap: ["userId", "postId"],
|
|
51
|
-
},
|
|
52
|
-
];
|
|
53
|
-
it("should match exact path", () => {
|
|
54
|
-
const match = findMatchingRoute(testRoutes, "GET", "/items");
|
|
55
|
-
expect(match).not.toBeNull();
|
|
56
|
-
expect(match?.handler).toBe(mockHandler);
|
|
57
|
-
expect(match?.params).toEqual({});
|
|
58
|
-
});
|
|
59
|
-
it("should extract single parameter", () => {
|
|
60
|
-
const match = findMatchingRoute(testRoutes, "GET", "/items/123");
|
|
61
|
-
expect(match).not.toBeNull();
|
|
62
|
-
expect(match?.params).toEqual({ id: "123" });
|
|
63
|
-
});
|
|
64
|
-
it("should extract multiple parameters", () => {
|
|
65
|
-
const match = findMatchingRoute(testRoutes, "GET", "/users/alice/posts/456");
|
|
66
|
-
expect(match).not.toBeNull();
|
|
67
|
-
expect(match?.params).toEqual({ userId: "alice", postId: "456" });
|
|
68
|
-
});
|
|
69
|
-
it("should respect HTTP method", () => {
|
|
70
|
-
// GET /items exists
|
|
71
|
-
expect(findMatchingRoute(testRoutes, "GET", "/items")).not.toBeNull();
|
|
72
|
-
// POST /items exists
|
|
73
|
-
expect(findMatchingRoute(testRoutes, "POST", "/items")).not.toBeNull();
|
|
74
|
-
// PUT /items does not exist
|
|
75
|
-
expect(findMatchingRoute(testRoutes, "PUT", "/items")).toBeNull();
|
|
76
|
-
});
|
|
77
|
-
it("should not match if regex does not match", () => {
|
|
78
|
-
// /items/abc does not match /^\/items\/(\d+)$/ (\d+ is digits only)
|
|
79
|
-
expect(findMatchingRoute(testRoutes, "GET", "/items/abc")).toBeNull();
|
|
80
|
-
});
|
|
81
|
-
it("should handle empty route list", () => {
|
|
82
|
-
expect(findMatchingRoute([], "GET", "/anything")).toBeNull();
|
|
83
|
-
});
|
|
84
|
-
it("should return first matching route", () => {
|
|
85
|
-
const overlappingRoutes = [
|
|
86
|
-
{
|
|
87
|
-
method: "GET",
|
|
88
|
-
pattern: /^\/overlapping$/,
|
|
89
|
-
handler: mockHandler,
|
|
90
|
-
paramMap: [],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
method: "GET",
|
|
94
|
-
pattern: /^\/overlapping$/, // duplicate pattern
|
|
95
|
-
handler: () => Promise.resolve("second"),
|
|
96
|
-
paramMap: [],
|
|
97
|
-
},
|
|
98
|
-
];
|
|
99
|
-
const match = findMatchingRoute(overlappingRoutes, "GET", "/overlapping");
|
|
100
|
-
expect(match?.handler).toBe(mockHandler); // Should match the first one
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|