@ranwhenparked/trustap-sdk 0.1.0
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/README.md +251 -0
- package/deno.json +9 -0
- package/eslint.config.js +21 -0
- package/package.json +47 -0
- package/scripts/build-node.mjs +75 -0
- package/scripts/generate-operations-map.mjs +57 -0
- package/scripts/generate-security-map.mjs +92 -0
- package/src/__tests__/auth-middleware.test.ts +171 -0
- package/src/__tests__/client-factory.test.ts +105 -0
- package/src/__tests__/error-handling.test.ts +302 -0
- package/src/__tests__/helpers/mock-http-client.ts +193 -0
- package/src/__tests__/helpers/run-guard.ts +24 -0
- package/src/__tests__/helpers/test-fixtures.ts +82 -0
- package/src/__tests__/node-client.test.ts +244 -0
- package/src/__tests__/operation-proxy.test.ts +268 -0
- package/src/__tests__/query-params.test.ts +232 -0
- package/src/__tests__/setup.ts +44 -0
- package/src/__tests__/types.test.ts +169 -0
- package/src/client-deno.ts +219 -0
- package/src/client-factory.ts +45 -0
- package/src/core.ts +619 -0
- package/src/index.deno.ts +28 -0
- package/src/index.ts +36 -0
- package/src/operations-map.ts +667 -0
- package/src/schema.d.ts +12046 -0
- package/src/security-map.ts +770 -0
- package/src/state-machine.ts +79 -0
- package/src/webhook-schemas.ts +400 -0
- package/tsconfig.build.json +27 -0
- package/tsconfig.json +22 -0
- package/vitest.config.ts +31 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { createTrustapClientWithDeps } from "../client-factory.ts";
|
|
3
|
+
import type { TrustapClientDependencies } from "../client-factory.ts";
|
|
4
|
+
import { MockHttpClient } from "./helpers/mock-http-client.ts";
|
|
5
|
+
import type { MockMiddleware } from "./helpers/mock-http-client.ts";
|
|
6
|
+
import {
|
|
7
|
+
createTestOptions,
|
|
8
|
+
sampleChargeQuery,
|
|
9
|
+
sampleTransactionBody,
|
|
10
|
+
} from "./helpers/test-fixtures.ts";
|
|
11
|
+
import { runTrustapSuite } from "./helpers/run-guard.ts";
|
|
12
|
+
|
|
13
|
+
const CHARGE_ENDPOINT = "/api/v4/charge";
|
|
14
|
+
const TRANSACTIONS_ENDPOINT = "/api/v4/transactions";
|
|
15
|
+
|
|
16
|
+
runTrustapSuite(import.meta.url, "Operation Proxy", () => {
|
|
17
|
+
describe("Operation Proxy", () => {
|
|
18
|
+
let mockClient: MockHttpClient;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockClient = new MockHttpClient();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const createDeps = (): TrustapClientDependencies<
|
|
25
|
+
MockMiddleware,
|
|
26
|
+
MockHttpClient,
|
|
27
|
+
Record<string, unknown>
|
|
28
|
+
> => ({
|
|
29
|
+
createClient: () => mockClient,
|
|
30
|
+
wrapAsPathBasedClient: (client) => ({ pathBased: client }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("Operation ID to HTTP Method Mapping", () => {
|
|
34
|
+
it("should map operation ID to GET request", async () => {
|
|
35
|
+
const client = createTrustapClientWithDeps(
|
|
36
|
+
createDeps(),
|
|
37
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
41
|
+
data: { success: true },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await client["basic.getCharge"]({
|
|
45
|
+
params: { query: sampleChargeQuery },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
49
|
+
expect(mockClient.requests[0]?.method).toBe("GET");
|
|
50
|
+
expect(mockClient.requests[0]?.path).toBe(CHARGE_ENDPOINT);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should map operation ID to POST request", async () => {
|
|
54
|
+
const client = createTrustapClientWithDeps(
|
|
55
|
+
createDeps(),
|
|
56
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "POST", {
|
|
60
|
+
data: { success: true },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await client["basic.createTransaction"]({
|
|
64
|
+
body: sampleTransactionBody,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
68
|
+
expect(mockClient.requests[0]?.method).toBe("POST");
|
|
69
|
+
expect(mockClient.requests[0]?.path).toBe(TRANSACTIONS_ENDPOINT);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should map operation ID to PUT request", async () => {
|
|
73
|
+
const client = createTrustapClientWithDeps(
|
|
74
|
+
createDeps(),
|
|
75
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "PUT", {
|
|
79
|
+
data: { success: true },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await client["basic.joinTransaction"]({
|
|
83
|
+
params: { query: { join_code: "JOIN123" } },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
87
|
+
expect(mockClient.requests[0]?.method).toBe("PUT");
|
|
88
|
+
expect(mockClient.requests[0]?.path).toBe(TRANSACTIONS_ENDPOINT);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("HTTP Method Support", () => {
|
|
93
|
+
it("should support GET method", async () => {
|
|
94
|
+
const client = createTrustapClientWithDeps(
|
|
95
|
+
createDeps(),
|
|
96
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
100
|
+
data: { success: true },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await client["basic.getCharge"]({
|
|
104
|
+
params: { query: sampleChargeQuery },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(mockClient.requests[0]?.method).toBe("GET");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should support POST method", async () => {
|
|
111
|
+
const client = createTrustapClientWithDeps(
|
|
112
|
+
createDeps(),
|
|
113
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "POST", {
|
|
117
|
+
data: { success: true },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await client["basic.createTransaction"]({
|
|
121
|
+
body: sampleTransactionBody,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(mockClient.requests[0]?.method).toBe("POST");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should support PUT method", async () => {
|
|
128
|
+
const client = createTrustapClientWithDeps(
|
|
129
|
+
createDeps(),
|
|
130
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "PUT", {
|
|
134
|
+
data: { success: true },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await client["basic.joinTransaction"]({
|
|
138
|
+
params: { query: { join_code: "JOIN123" } },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(mockClient.requests[0]?.method).toBe("PUT");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should support PATCH method", async () => {
|
|
145
|
+
const client = createTrustapClientWithDeps(
|
|
146
|
+
createDeps(),
|
|
147
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
148
|
+
) as unknown;
|
|
149
|
+
|
|
150
|
+
const rawClient = (client as { raw: MockHttpClient }).raw;
|
|
151
|
+
await rawClient.PATCH("/test", {});
|
|
152
|
+
|
|
153
|
+
const request = mockClient.requests.find((r) => r.method === "PATCH");
|
|
154
|
+
expect(request).toBeDefined();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should support DELETE method", async () => {
|
|
158
|
+
const client = createTrustapClientWithDeps(
|
|
159
|
+
createDeps(),
|
|
160
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
161
|
+
) as unknown;
|
|
162
|
+
|
|
163
|
+
const rawClient = (client as { raw: MockHttpClient }).raw;
|
|
164
|
+
await rawClient.DELETE("/test", {});
|
|
165
|
+
|
|
166
|
+
const request = mockClient.requests.find((r) => r.method === "DELETE");
|
|
167
|
+
expect(request).toBeDefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should support HEAD method", async () => {
|
|
171
|
+
const client = createTrustapClientWithDeps(
|
|
172
|
+
createDeps(),
|
|
173
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
174
|
+
) as unknown;
|
|
175
|
+
|
|
176
|
+
const rawClient = (client as { raw: MockHttpClient }).raw;
|
|
177
|
+
await rawClient.HEAD("/test", {});
|
|
178
|
+
|
|
179
|
+
const request = mockClient.requests.find((r) => r.method === "HEAD");
|
|
180
|
+
expect(request).toBeDefined();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should support OPTIONS method", async () => {
|
|
184
|
+
const client = createTrustapClientWithDeps(
|
|
185
|
+
createDeps(),
|
|
186
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
187
|
+
) as unknown;
|
|
188
|
+
|
|
189
|
+
const rawClient = (client as { raw: MockHttpClient }).raw;
|
|
190
|
+
await rawClient.OPTIONS("/test", {});
|
|
191
|
+
|
|
192
|
+
const request = mockClient.requests.find((r) => r.method === "OPTIONS");
|
|
193
|
+
expect(request).toBeDefined();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("Undefined Operation Handling", () => {
|
|
198
|
+
it("should return undefined for non-existent operation ID", () => {
|
|
199
|
+
const client = createTrustapClientWithDeps(
|
|
200
|
+
createDeps(),
|
|
201
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
202
|
+
) as Record<string, unknown>;
|
|
203
|
+
|
|
204
|
+
const nonExistent = client["nonexistent.operation"];
|
|
205
|
+
|
|
206
|
+
expect(nonExistent).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should not throw when accessing non-existent operation", () => {
|
|
210
|
+
const client = createTrustapClientWithDeps(
|
|
211
|
+
createDeps(),
|
|
212
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
213
|
+
) as Record<string, unknown>;
|
|
214
|
+
|
|
215
|
+
expect(() => {
|
|
216
|
+
const _ = client["another.fake.operation"];
|
|
217
|
+
return _;
|
|
218
|
+
}).not.toThrow();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("Proxy Behavior", () => {
|
|
223
|
+
it("should create operation functions dynamically", () => {
|
|
224
|
+
const client = createTrustapClientWithDeps(
|
|
225
|
+
createDeps(),
|
|
226
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const op1 = client["basic.getCharge"];
|
|
230
|
+
const op2 = client["basic.getCharge"];
|
|
231
|
+
|
|
232
|
+
// Should return the same function when accessed multiple times
|
|
233
|
+
expect(typeof op1).toBe("function");
|
|
234
|
+
expect(typeof op2).toBe("function");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should pass options through to HTTP client", async () => {
|
|
238
|
+
const client = createTrustapClientWithDeps(
|
|
239
|
+
createDeps(),
|
|
240
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "POST", {
|
|
244
|
+
data: { success: true },
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const testOptions = {
|
|
248
|
+
body: sampleTransactionBody,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
await client["basic.createTransaction"](testOptions);
|
|
252
|
+
|
|
253
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
254
|
+
expect(mockClient.requests[0]?.options).toBeDefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should work with path-based client methods", () => {
|
|
258
|
+
const client = createTrustapClientWithDeps(
|
|
259
|
+
createDeps(),
|
|
260
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
261
|
+
) as unknown as { pathBased: unknown };
|
|
262
|
+
|
|
263
|
+
// Path-based client should be available
|
|
264
|
+
expect(client.pathBased).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { createTrustapClientWithDeps } from "../client-factory.ts";
|
|
3
|
+
import type { TrustapClientDependencies } from "../client-factory.ts";
|
|
4
|
+
import { MockHttpClient } from "./helpers/mock-http-client.ts";
|
|
5
|
+
import type { MockMiddleware } from "./helpers/mock-http-client.ts";
|
|
6
|
+
import {
|
|
7
|
+
createTestOptions,
|
|
8
|
+
sampleChargeQuery,
|
|
9
|
+
sampleTransactionBody,
|
|
10
|
+
} from "./helpers/test-fixtures.ts";
|
|
11
|
+
import { runTrustapSuite } from "./helpers/run-guard.ts";
|
|
12
|
+
|
|
13
|
+
const CHARGE_ENDPOINT = "/api/v4/charge";
|
|
14
|
+
const TRANSACTIONS_ENDPOINT = "/api/v4/transactions";
|
|
15
|
+
|
|
16
|
+
runTrustapSuite(import.meta.url, "Query Parameter Handling", () => {
|
|
17
|
+
describe("Query Parameter Handling", () => {
|
|
18
|
+
let mockClient: MockHttpClient;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockClient = new MockHttpClient();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const createDeps = (): TrustapClientDependencies<
|
|
25
|
+
MockMiddleware,
|
|
26
|
+
MockHttpClient,
|
|
27
|
+
Record<string, unknown>
|
|
28
|
+
> => ({
|
|
29
|
+
createClient: () => mockClient,
|
|
30
|
+
wrapAsPathBasedClient: () => ({}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("Legacy Format", () => {
|
|
34
|
+
it("should convert legacy { query: ... } format to new format", async () => {
|
|
35
|
+
const client = createTrustapClientWithDeps(
|
|
36
|
+
createDeps(),
|
|
37
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
41
|
+
data: { success: true },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await client["basic.getCharge"]({
|
|
45
|
+
query: sampleChargeQuery,
|
|
46
|
+
} as Parameters<typeof client["basic.getCharge"]>[0]);
|
|
47
|
+
|
|
48
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
49
|
+
const request = mockClient.requests[0];
|
|
50
|
+
expect(request?.path).toBe(CHARGE_ENDPOINT);
|
|
51
|
+
expect(request?.options).toBeDefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle legacy format with body", async () => {
|
|
55
|
+
const client = createTrustapClientWithDeps(
|
|
56
|
+
createDeps(),
|
|
57
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "POST", {
|
|
61
|
+
data: { success: true },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await client["basic.createTransaction"]({
|
|
65
|
+
query: sampleChargeQuery,
|
|
66
|
+
body: sampleTransactionBody,
|
|
67
|
+
} as unknown as Parameters<typeof client["basic.createTransaction"]>[0]);
|
|
68
|
+
|
|
69
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
70
|
+
expect(mockClient.requests[0]?.options).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("New Format", () => {
|
|
75
|
+
it("should handle { params: { query: ... } } format", async () => {
|
|
76
|
+
const client = createTrustapClientWithDeps(
|
|
77
|
+
createDeps(),
|
|
78
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
82
|
+
data: { success: true },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await client["basic.getCharge"]({
|
|
86
|
+
params: {
|
|
87
|
+
query: sampleChargeQuery,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
92
|
+
const request = mockClient.requests[0];
|
|
93
|
+
expect(request?.options).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should handle path parameters in params", async () => {
|
|
97
|
+
const client = createTrustapClientWithDeps(
|
|
98
|
+
createDeps(),
|
|
99
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
mockClient.setResponse("/api/v4/users/user-123", "GET", {
|
|
103
|
+
data: { id: "user-123" },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await client["oauth.getUser"]({
|
|
107
|
+
params: {
|
|
108
|
+
path: { userId: "user-123" },
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should handle mixed query and path parameters", async () => {
|
|
116
|
+
const client = createTrustapClientWithDeps(
|
|
117
|
+
createDeps(),
|
|
118
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
mockClient.setResponse("/api/v4/users/user-456", "GET", {
|
|
122
|
+
data: { id: "user-456" },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await client["oauth.getUser"]({
|
|
126
|
+
params: {
|
|
127
|
+
path: { userId: "user-456" },
|
|
128
|
+
query: sampleChargeQuery,
|
|
129
|
+
},
|
|
130
|
+
} as Parameters<typeof client["oauth.getUser"]>[0]);
|
|
131
|
+
|
|
132
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should handle body with new format", async () => {
|
|
136
|
+
const client = createTrustapClientWithDeps(
|
|
137
|
+
createDeps(),
|
|
138
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
mockClient.setResponse("/api/v4/users/user-789", "PUT", {
|
|
142
|
+
data: { success: true },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await client["oauth.updateUser"]({
|
|
146
|
+
params: {
|
|
147
|
+
path: { userId: "user-789" },
|
|
148
|
+
},
|
|
149
|
+
body: {
|
|
150
|
+
email: "updated@example.com",
|
|
151
|
+
},
|
|
152
|
+
} as Parameters<typeof client["oauth.updateUser"]>[0]);
|
|
153
|
+
|
|
154
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("Edge Cases", () => {
|
|
159
|
+
it("should handle empty query parameters", async () => {
|
|
160
|
+
const client = createTrustapClientWithDeps(
|
|
161
|
+
createDeps(),
|
|
162
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
166
|
+
data: { success: true },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await client["basic.getCharge"]({
|
|
170
|
+
params: {
|
|
171
|
+
query: {},
|
|
172
|
+
},
|
|
173
|
+
} as Parameters<typeof client["basic.getCharge"]>[0]);
|
|
174
|
+
|
|
175
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should handle undefined options", async () => {
|
|
179
|
+
const client = createTrustapClientWithDeps(
|
|
180
|
+
createDeps(),
|
|
181
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
185
|
+
data: { success: true },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await client["basic.getCharge"]();
|
|
189
|
+
|
|
190
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should handle options without query or params", async () => {
|
|
194
|
+
const client = createTrustapClientWithDeps(
|
|
195
|
+
createDeps(),
|
|
196
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
mockClient.setResponse(TRANSACTIONS_ENDPOINT, "POST", {
|
|
200
|
+
data: { success: true },
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await client["basic.createTransaction"]({
|
|
204
|
+
body: sampleTransactionBody,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should not duplicate query params when converting", async () => {
|
|
211
|
+
const client = createTrustapClientWithDeps(
|
|
212
|
+
createDeps(),
|
|
213
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
217
|
+
data: { success: true },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Legacy format should be converted cleanly
|
|
221
|
+
await client["basic.getCharge"]({
|
|
222
|
+
query: sampleChargeQuery,
|
|
223
|
+
} as Parameters<typeof client["basic.getCharge"]>[0]);
|
|
224
|
+
|
|
225
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
226
|
+
// Verify only one set of params exists (no duplication)
|
|
227
|
+
const options = mockClient.requests[0]?.options as Record<string, unknown> | undefined;
|
|
228
|
+
expect(options).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
beforeAll(() => {
|
|
4
|
+
vi.stubGlobal(
|
|
5
|
+
"fetch",
|
|
6
|
+
vi.fn(() => Promise.resolve(new Response("{}", {
|
|
7
|
+
status: 200,
|
|
8
|
+
headers: { "content-type": "application/json" },
|
|
9
|
+
}))),
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Per-test setup - clear mocks after each test to prevent state leakage
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
vi.unstubAllGlobals();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Test utilities
|
|
23
|
+
export function createMockRequest(
|
|
24
|
+
url: string,
|
|
25
|
+
options?: RequestInit,
|
|
26
|
+
): Request {
|
|
27
|
+
return new Request(url, options);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createMockResponse(
|
|
31
|
+
body?: unknown,
|
|
32
|
+
options?: ResponseInit,
|
|
33
|
+
): Response {
|
|
34
|
+
const responseBody = body
|
|
35
|
+
? JSON.stringify(body)
|
|
36
|
+
: undefined;
|
|
37
|
+
return new Response(responseBody, {
|
|
38
|
+
status: 200,
|
|
39
|
+
headers: {
|
|
40
|
+
"content-type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
...options,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
2
|
+
import { createTrustapClient } from "../index.ts";
|
|
3
|
+
import type { operations } from "../schema.d.ts";
|
|
4
|
+
import {
|
|
5
|
+
sampleChargeQuery,
|
|
6
|
+
sampleTransactionBody,
|
|
7
|
+
} from "./helpers/test-fixtures.ts";
|
|
8
|
+
import { runTrustapSuite } from "./helpers/run-guard.ts";
|
|
9
|
+
|
|
10
|
+
const TEST_API_URL = "https://test.api";
|
|
11
|
+
const TEST_CREDENTIALS_USERNAME = "test";
|
|
12
|
+
const TEST_CREDENTIALS_PASSWORD = Buffer.from("test").toString("base64");
|
|
13
|
+
|
|
14
|
+
runTrustapSuite(import.meta.url, "TypeScript Type Integration", () => {
|
|
15
|
+
describe("TypeScript Type Integration", () => {
|
|
16
|
+
describe("Operation Parameter Types", () => {
|
|
17
|
+
it("should extract proper parameter types from operations", () => {
|
|
18
|
+
const client = createTrustapClient({
|
|
19
|
+
apiUrl: TEST_API_URL,
|
|
20
|
+
basicAuth: { username: TEST_CREDENTIALS_USERNAME, password: TEST_CREDENTIALS_PASSWORD },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Test that basic.getCharge has proper types
|
|
24
|
+
const chargeRequest = client["basic.getCharge"]({
|
|
25
|
+
params: {
|
|
26
|
+
query: {
|
|
27
|
+
price: 1000,
|
|
28
|
+
currency: "usd",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(chargeRequest).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should support backward compatible legacy format", () => {
|
|
37
|
+
const client = createTrustapClient({
|
|
38
|
+
apiUrl: TEST_API_URL,
|
|
39
|
+
basicAuth: { username: TEST_CREDENTIALS_USERNAME, password: TEST_CREDENTIALS_PASSWORD },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Test backward compatibility with legacy format
|
|
43
|
+
const chargeRequestLegacy = client["basic.getCharge"]({
|
|
44
|
+
query: sampleChargeQuery,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(chargeRequestLegacy).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("Operation Type Assertions", () => {
|
|
52
|
+
it("should correctly type operation query parameters", () => {
|
|
53
|
+
// Type assertion tests
|
|
54
|
+
type BasicGetChargeOperation = operations["basic.getCharge"];
|
|
55
|
+
type QueryParams = BasicGetChargeOperation["parameters"]["query"];
|
|
56
|
+
|
|
57
|
+
// Ensure the types are correct at compile time
|
|
58
|
+
const validParams: QueryParams = {
|
|
59
|
+
price: 1000,
|
|
60
|
+
currency: "usd",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
expect(validParams).toEqual({
|
|
64
|
+
price: 1000,
|
|
65
|
+
currency: "usd",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should enforce required parameters", () => {
|
|
70
|
+
type BasicGetChargeOperation = operations["basic.getCharge"];
|
|
71
|
+
type QueryParams = BasicGetChargeOperation["parameters"]["query"];
|
|
72
|
+
|
|
73
|
+
// This should compile if price and currency are required
|
|
74
|
+
const params: QueryParams = {
|
|
75
|
+
price: 2500,
|
|
76
|
+
currency: "gbp",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
expect(params.price).toBe(2500);
|
|
80
|
+
expect(params.currency).toBe("gbp");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("Response Type Inference", () => {
|
|
85
|
+
it("should infer proper response types", () => {
|
|
86
|
+
const client = createTrustapClient({
|
|
87
|
+
apiUrl: TEST_API_URL,
|
|
88
|
+
basicAuth: { username: TEST_CREDENTIALS_USERNAME, password: TEST_CREDENTIALS_PASSWORD },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Type should be inferred as Promise<{ data?: ..., error?: ..., response: Response }>
|
|
92
|
+
type ResponseType = ReturnType<(typeof client)["basic.getCharge"]>;
|
|
93
|
+
expectTypeOf<ResponseType>().toExtend<
|
|
94
|
+
Promise<{
|
|
95
|
+
data?: unknown;
|
|
96
|
+
error?: unknown;
|
|
97
|
+
response: Response;
|
|
98
|
+
}>
|
|
99
|
+
>();
|
|
100
|
+
|
|
101
|
+
// Verify it's a Promise
|
|
102
|
+
const isPromise = (value: unknown): value is Promise<unknown> =>
|
|
103
|
+
value instanceof Promise ||
|
|
104
|
+
(typeof value === "object" && value !== null && "then" in value);
|
|
105
|
+
|
|
106
|
+
const result = client["basic.getCharge"]();
|
|
107
|
+
expect(isPromise(result)).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("Path Parameter Types", () => {
|
|
112
|
+
it("should enforce path parameter types", () => {
|
|
113
|
+
const client = createTrustapClient({
|
|
114
|
+
apiUrl: TEST_API_URL,
|
|
115
|
+
getAccessToken: () => Promise.resolve("token"),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// userId should be properly typed
|
|
119
|
+
const userRequest = client["oauth.getUser"]({
|
|
120
|
+
params: {
|
|
121
|
+
path: {
|
|
122
|
+
userId: "user-123",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(userRequest).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("Body Parameter Types", () => {
|
|
132
|
+
it("should enforce request body types", () => {
|
|
133
|
+
const client = createTrustapClient({
|
|
134
|
+
apiUrl: TEST_API_URL,
|
|
135
|
+
basicAuth: { username: TEST_CREDENTIALS_USERNAME, password: TEST_CREDENTIALS_PASSWORD },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Body should be properly typed
|
|
139
|
+
const transactionRequest = client["basic.createTransaction"]({
|
|
140
|
+
body: sampleTransactionBody,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(transactionRequest).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("Mixed Parameter Types", () => {
|
|
148
|
+
it("should handle operations with path, query, and body parameters", () => {
|
|
149
|
+
const client = createTrustapClient({
|
|
150
|
+
apiUrl: TEST_API_URL,
|
|
151
|
+
getAccessToken: () => Promise.resolve("token"),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Should accept all parameter types
|
|
155
|
+
const updateRequest = client["oauth.updateUser"]({
|
|
156
|
+
params: {
|
|
157
|
+
path: {
|
|
158
|
+
userId: "user-456",
|
|
159
|
+
},
|
|
160
|
+
query: sampleChargeQuery,
|
|
161
|
+
},
|
|
162
|
+
body: sampleTransactionBody,
|
|
163
|
+
} as Parameters<(typeof client)["oauth.updateUser"]>[0]);
|
|
164
|
+
|
|
165
|
+
expect(updateRequest).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|