@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,171 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createTrustapClientWithDeps } from "../client-factory.ts";
|
|
4
|
+
import type { TrustapClientDependencies } from "../client-factory.ts";
|
|
5
|
+
import { MockHttpClient } from "./helpers/mock-http-client.ts";
|
|
6
|
+
import type { MockMiddleware } from "./helpers/mock-http-client.ts";
|
|
7
|
+
import {
|
|
8
|
+
createTestOptions,
|
|
9
|
+
encodeBasicAuth,
|
|
10
|
+
mockBasicAuth,
|
|
11
|
+
mockOAuthToken,
|
|
12
|
+
sampleCarrierFacilityRequest,
|
|
13
|
+
sampleChargeQuery,
|
|
14
|
+
sampleTransactionBody,
|
|
15
|
+
} from "./helpers/test-fixtures.ts";
|
|
16
|
+
import { runTrustapSuite } from "./helpers/run-guard.ts";
|
|
17
|
+
|
|
18
|
+
runTrustapSuite(import.meta.url, "Auth Middleware", () => {
|
|
19
|
+
describe("Auth Middleware", () => {
|
|
20
|
+
let mockClient: MockHttpClient;
|
|
21
|
+
|
|
22
|
+
const createDeps = (): TrustapClientDependencies<
|
|
23
|
+
MockMiddleware,
|
|
24
|
+
MockHttpClient,
|
|
25
|
+
Record<string, unknown>
|
|
26
|
+
> => ({
|
|
27
|
+
createClient: () => mockClient,
|
|
28
|
+
wrapAsPathBasedClient: () => ({}),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const createClient = (
|
|
32
|
+
overrides?: Parameters<typeof createTestOptions>[0],
|
|
33
|
+
) =>
|
|
34
|
+
createTrustapClientWithDeps(
|
|
35
|
+
createDeps(),
|
|
36
|
+
createTestOptions(overrides ?? {}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
mockClient = new MockHttpClient();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("adds a Basic Authorization header when APIKey security is declared", async () => {
|
|
44
|
+
const client = createClient({ getAccessToken: undefined });
|
|
45
|
+
|
|
46
|
+
await client["basic.getCharge"]({
|
|
47
|
+
params: { query: sampleChargeQuery },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
51
|
+
"Authorization",
|
|
52
|
+
);
|
|
53
|
+
expect(header).toBe(
|
|
54
|
+
`Basic ${encodeBasicAuth(
|
|
55
|
+
mockBasicAuth.username,
|
|
56
|
+
mockBasicAuth.password,
|
|
57
|
+
)}`,
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("reuses the encoded Basic credentials for repeated calls", async () => {
|
|
62
|
+
const client = createClient({ getAccessToken: undefined });
|
|
63
|
+
|
|
64
|
+
await client["basic.getCharge"]({
|
|
65
|
+
params: { query: sampleChargeQuery },
|
|
66
|
+
});
|
|
67
|
+
await client["basic.getCharge"]({
|
|
68
|
+
params: { query: sampleChargeQuery },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const first = mockClient.requests[0]?.request.headers.get(
|
|
72
|
+
"Authorization",
|
|
73
|
+
);
|
|
74
|
+
const second = mockClient.requests[1]?.request.headers.get(
|
|
75
|
+
"Authorization",
|
|
76
|
+
);
|
|
77
|
+
expect(first).toBe(second);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("requests bearer tokens for OAuth-protected endpoints", async () => {
|
|
81
|
+
const getAccessToken = vi.fn(() => Promise.resolve(mockOAuthToken));
|
|
82
|
+
const client = createClient({
|
|
83
|
+
basicAuth: undefined,
|
|
84
|
+
getAccessToken,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await client["oauth.getUser"]({
|
|
88
|
+
params: {
|
|
89
|
+
path: { userId: "user_123" },
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
94
|
+
"Authorization",
|
|
95
|
+
);
|
|
96
|
+
expect(header).toBe(`Bearer ${mockOAuthToken}`);
|
|
97
|
+
expect(getAccessToken).toHaveBeenCalledTimes(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("honors authOverrides using exact path matches", async () => {
|
|
101
|
+
const client = createClient({
|
|
102
|
+
authOverrides: {
|
|
103
|
+
"/transactions": "basic",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await client["basic.createTransaction"]({
|
|
108
|
+
body: sampleTransactionBody,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
112
|
+
"Authorization",
|
|
113
|
+
);
|
|
114
|
+
expect(header).toBe(
|
|
115
|
+
`Basic ${encodeBasicAuth(
|
|
116
|
+
mockBasicAuth.username,
|
|
117
|
+
mockBasicAuth.password,
|
|
118
|
+
)}`,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("uses explicit OAuth overrides when provided", async () => {
|
|
123
|
+
const client = createClient({
|
|
124
|
+
authOverrides: {
|
|
125
|
+
"/transactions": "oauth2",
|
|
126
|
+
},
|
|
127
|
+
getAccessToken: () => Promise.resolve(mockOAuthToken),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await client["basic.createTransaction"]({
|
|
131
|
+
body: sampleTransactionBody,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
135
|
+
"Authorization",
|
|
136
|
+
);
|
|
137
|
+
expect(header).toBe(`Bearer ${mockOAuthToken}`);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("does not override an Authorization header that is already present", async () => {
|
|
141
|
+
const client = createClient({ getAccessToken: undefined });
|
|
142
|
+
|
|
143
|
+
await client["basic.getCharge"]({
|
|
144
|
+
headers: { Authorization: "Custom token" },
|
|
145
|
+
params: { query: sampleChargeQuery },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
149
|
+
"Authorization",
|
|
150
|
+
);
|
|
151
|
+
expect(header).toBe("Custom token");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("skips auth headers when no credentials are configured", async () => {
|
|
155
|
+
const client = createClient({
|
|
156
|
+
basicAuth: undefined,
|
|
157
|
+
getAccessToken: undefined,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await client["basic.getCarrierFacilityOptions"]({
|
|
161
|
+
params: { path: { carrier_id: "carrier_1" } },
|
|
162
|
+
body: sampleCarrierFacilityRequest,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
166
|
+
"Authorization",
|
|
167
|
+
);
|
|
168
|
+
expect(header).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createTrustapClientWithDeps } from "../client-factory.ts";
|
|
4
|
+
import type { TrustapClientDependencies } from "../client-factory.ts";
|
|
5
|
+
import { MockHttpClient } from "./helpers/mock-http-client.ts";
|
|
6
|
+
import type { MockMiddleware } from "./helpers/mock-http-client.ts";
|
|
7
|
+
import {
|
|
8
|
+
createTestOptions,
|
|
9
|
+
encodeBasicAuth,
|
|
10
|
+
mockBasicAuth,
|
|
11
|
+
sampleChargeQuery,
|
|
12
|
+
} from "./helpers/test-fixtures.ts";
|
|
13
|
+
import { runTrustapSuite } from "./helpers/run-guard.ts";
|
|
14
|
+
|
|
15
|
+
runTrustapSuite(import.meta.url, "Client Factory", () => {
|
|
16
|
+
describe("Client Factory", () => {
|
|
17
|
+
let mockClient: MockHttpClient;
|
|
18
|
+
|
|
19
|
+
const createDeps = (): TrustapClientDependencies<
|
|
20
|
+
MockMiddleware,
|
|
21
|
+
MockHttpClient,
|
|
22
|
+
{ pathBased: MockHttpClient }
|
|
23
|
+
> => ({
|
|
24
|
+
createClient: () => mockClient,
|
|
25
|
+
wrapAsPathBasedClient: (client) => ({ pathBased: client }),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
mockClient = new MockHttpClient();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("creates a client that exposes operation-id functions", () => {
|
|
33
|
+
const client = createTrustapClientWithDeps(
|
|
34
|
+
createDeps(),
|
|
35
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(client).toBeDefined();
|
|
39
|
+
expect(typeof client["basic.getCharge"]).toBe("function");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("routes requests using the generated operations map", async () => {
|
|
43
|
+
const client = createTrustapClientWithDeps(
|
|
44
|
+
createDeps(),
|
|
45
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
await client["basic.getCharge"]({
|
|
49
|
+
params: { query: sampleChargeQuery },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(mockClient.requests).toHaveLength(1);
|
|
53
|
+
expect(mockClient.requests[0]?.path).toBe("/api/v4/charge");
|
|
54
|
+
expect(mockClient.requests[0]?.method).toBe("GET");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("applies auth middleware sourced from the security map", async () => {
|
|
58
|
+
const client = createTrustapClientWithDeps(
|
|
59
|
+
createDeps(),
|
|
60
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
await client["basic.getCharge"]({
|
|
64
|
+
params: { query: sampleChargeQuery },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const header = mockClient.requests[0]?.request.headers.get(
|
|
68
|
+
"Authorization",
|
|
69
|
+
);
|
|
70
|
+
expect(header).toBe(
|
|
71
|
+
`Basic ${encodeBasicAuth(
|
|
72
|
+
mockBasicAuth.username,
|
|
73
|
+
mockBasicAuth.password,
|
|
74
|
+
)}`,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("exposes the underlying HTTP client via the raw property", () => {
|
|
79
|
+
const client = createTrustapClientWithDeps(
|
|
80
|
+
createDeps(),
|
|
81
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
82
|
+
) as { raw: MockHttpClient };
|
|
83
|
+
|
|
84
|
+
expect(client.raw).toBe(mockClient);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("merges the path-based wrapper onto the returned client", () => {
|
|
88
|
+
const deps: TrustapClientDependencies<
|
|
89
|
+
MockMiddleware,
|
|
90
|
+
MockHttpClient,
|
|
91
|
+
{ byPath: MockHttpClient }
|
|
92
|
+
> = {
|
|
93
|
+
createClient: () => mockClient,
|
|
94
|
+
wrapAsPathBasedClient: (client) => ({ byPath: client }),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const client = createTrustapClientWithDeps(
|
|
98
|
+
deps,
|
|
99
|
+
createTestOptions({ getAccessToken: undefined }),
|
|
100
|
+
) as { byPath: MockHttpClient };
|
|
101
|
+
|
|
102
|
+
expect(client.byPath).toBe(mockClient);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createTrustapClientWithDeps } from "../client-factory.ts";
|
|
4
|
+
import type { TrustapClientDependencies } from "../client-factory.ts";
|
|
5
|
+
import { MockHttpClient } from "./helpers/mock-http-client.ts";
|
|
6
|
+
import type { MockMiddleware } from "./helpers/mock-http-client.ts";
|
|
7
|
+
import { createTestOptions } from "./helpers/test-fixtures.ts";
|
|
8
|
+
import { runTrustapSuite } from "./helpers/run-guard.ts";
|
|
9
|
+
|
|
10
|
+
const CHARGE_ENDPOINT = "/api/v4/charge";
|
|
11
|
+
|
|
12
|
+
runTrustapSuite(import.meta.url, "Error Handling", () => {
|
|
13
|
+
describe("Error Handling", () => {
|
|
14
|
+
let mockClient: MockHttpClient;
|
|
15
|
+
|
|
16
|
+
const createDeps = (): TrustapClientDependencies<
|
|
17
|
+
MockMiddleware,
|
|
18
|
+
MockHttpClient,
|
|
19
|
+
Record<string, unknown>
|
|
20
|
+
> => ({
|
|
21
|
+
createClient: () => mockClient,
|
|
22
|
+
wrapAsPathBasedClient: () => ({}),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const createClient = (
|
|
26
|
+
overrides?: Parameters<typeof createTestOptions>[0],
|
|
27
|
+
) =>
|
|
28
|
+
createTrustapClientWithDeps(
|
|
29
|
+
createDeps(),
|
|
30
|
+
createTestOptions(overrides ?? {}),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
mockClient = new MockHttpClient();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("Network Failures", () => {
|
|
38
|
+
it("handles network errors when middleware throws", async () => {
|
|
39
|
+
const client = createClient({ getAccessToken: undefined });
|
|
40
|
+
|
|
41
|
+
// Simulate network failure by adding middleware that throws
|
|
42
|
+
mockClient.use({
|
|
43
|
+
onRequest: () => {
|
|
44
|
+
throw new Error("Network connection failed");
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await expect(client["basic.getCharge"]()).rejects.toThrow(
|
|
49
|
+
"Network connection failed",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("handles errors in response middleware", async () => {
|
|
54
|
+
const client = createClient({ getAccessToken: undefined });
|
|
55
|
+
|
|
56
|
+
mockClient.use({
|
|
57
|
+
onResponse: () => {
|
|
58
|
+
throw new Error("Response processing failed");
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await expect(client["basic.getCharge"]()).rejects.toThrow(
|
|
63
|
+
"Response processing failed",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("HTTP Error Responses", () => {
|
|
69
|
+
it("returns error for 400 Bad Request", async () => {
|
|
70
|
+
const client = createClient({ getAccessToken: undefined });
|
|
71
|
+
|
|
72
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
73
|
+
error: { message: "Invalid parameters" },
|
|
74
|
+
status: 400,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = await client["basic.getCharge"]();
|
|
78
|
+
|
|
79
|
+
expect(result.error).toEqual({ message: "Invalid parameters" });
|
|
80
|
+
expect(result.data).toBeUndefined();
|
|
81
|
+
expect(result.response.status).toBe(400);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns error for 401 Unauthorized", async () => {
|
|
85
|
+
const client = createClient({ getAccessToken: undefined });
|
|
86
|
+
|
|
87
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
88
|
+
error: { message: "Unauthorized" },
|
|
89
|
+
status: 401,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = await client["basic.getCharge"]();
|
|
93
|
+
|
|
94
|
+
expect(result.error).toEqual({ message: "Unauthorized" });
|
|
95
|
+
expect(result.data).toBeUndefined();
|
|
96
|
+
expect(result.response.status).toBe(401);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns error for 403 Forbidden", async () => {
|
|
100
|
+
const client = createClient({ getAccessToken: undefined });
|
|
101
|
+
|
|
102
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
103
|
+
error: { message: "Forbidden" },
|
|
104
|
+
status: 403,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const result = await client["basic.getCharge"]();
|
|
108
|
+
|
|
109
|
+
expect(result.error).toEqual({ message: "Forbidden" });
|
|
110
|
+
expect(result.data).toBeUndefined();
|
|
111
|
+
expect(result.response.status).toBe(403);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns error for 404 Not Found", async () => {
|
|
115
|
+
const client = createClient({ getAccessToken: undefined });
|
|
116
|
+
|
|
117
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
118
|
+
error: { message: "Not found" },
|
|
119
|
+
status: 404,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const result = await client["basic.getCharge"]();
|
|
123
|
+
|
|
124
|
+
expect(result.error).toEqual({ message: "Not found" });
|
|
125
|
+
expect(result.data).toBeUndefined();
|
|
126
|
+
expect(result.response.status).toBe(404);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns error for 500 Internal Server Error", async () => {
|
|
130
|
+
const client = createClient({ getAccessToken: undefined });
|
|
131
|
+
|
|
132
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
133
|
+
error: { message: "Internal server error" },
|
|
134
|
+
status: 500,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await client["basic.getCharge"]();
|
|
138
|
+
|
|
139
|
+
expect(result.error).toEqual({ message: "Internal server error" });
|
|
140
|
+
expect(result.data).toBeUndefined();
|
|
141
|
+
expect(result.response.status).toBe(500);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("returns error for 503 Service Unavailable", async () => {
|
|
145
|
+
const client = createClient({ getAccessToken: undefined });
|
|
146
|
+
|
|
147
|
+
mockClient.setResponse(CHARGE_ENDPOINT, "GET", {
|
|
148
|
+
error: { message: "Service unavailable" },
|
|
149
|
+
status: 503,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const result = await client["basic.getCharge"]();
|
|
153
|
+
|
|
154
|
+
expect(result.error).toEqual({ message: "Service unavailable" });
|
|
155
|
+
expect(result.data).toBeUndefined();
|
|
156
|
+
expect(result.response.status).toBe(503);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("Malformed Responses", () => {
|
|
161
|
+
it("handles responses with invalid JSON", async () => {
|
|
162
|
+
const client = createClient({ getAccessToken: undefined });
|
|
163
|
+
|
|
164
|
+
// Mock a response that will fail JSON parsing
|
|
165
|
+
mockClient.use({
|
|
166
|
+
onResponse: () => {
|
|
167
|
+
return new Response("Invalid JSON {{{", {
|
|
168
|
+
status: 200,
|
|
169
|
+
headers: { "content-type": "application/json" },
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await client["basic.getCharge"]();
|
|
175
|
+
|
|
176
|
+
// Response should still be returned even if body parsing might fail later
|
|
177
|
+
expect(result.response).toBeInstanceOf(Response);
|
|
178
|
+
expect(result.response.status).toBe(200);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("handles responses with missing content-type", async () => {
|
|
182
|
+
const client = createClient({ getAccessToken: undefined });
|
|
183
|
+
|
|
184
|
+
mockClient.use({
|
|
185
|
+
onResponse: () => {
|
|
186
|
+
return Response.json({ data: "test" }, {
|
|
187
|
+
status: 200,
|
|
188
|
+
// No content-type header
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = await client["basic.getCharge"]();
|
|
194
|
+
|
|
195
|
+
expect(result.response).toBeInstanceOf(Response);
|
|
196
|
+
expect(result.response.status).toBe(200);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("handles empty response bodies", async () => {
|
|
200
|
+
const client = createClient({ getAccessToken: undefined });
|
|
201
|
+
|
|
202
|
+
mockClient.use({
|
|
203
|
+
onResponse: () => {
|
|
204
|
+
return new Response(null, {
|
|
205
|
+
status: 204,
|
|
206
|
+
headers: { "content-type": "application/json" },
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const result = await client["basic.getCharge"]();
|
|
212
|
+
|
|
213
|
+
expect(result.response).toBeInstanceOf(Response);
|
|
214
|
+
expect(result.response.status).toBe(204);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("Authentication Errors", () => {
|
|
219
|
+
it("handles OAuth token retrieval failures", async () => {
|
|
220
|
+
const client = createClient({
|
|
221
|
+
basicAuth: undefined,
|
|
222
|
+
getAccessToken: () =>
|
|
223
|
+
Promise.reject(new Error("Failed to get access token")),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await expect(
|
|
227
|
+
client["oauth.getUser"]({
|
|
228
|
+
params: { path: { userId: "user_123" } },
|
|
229
|
+
}),
|
|
230
|
+
).rejects.toThrow("Failed to get access token");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("continues when OAuth token is null or empty", async () => {
|
|
234
|
+
const client = createClient({
|
|
235
|
+
basicAuth: undefined,
|
|
236
|
+
getAccessToken: () => Promise.resolve(""),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Should not throw, just proceed without auth header
|
|
240
|
+
const result = await client["oauth.getUser"]({
|
|
241
|
+
params: { path: { userId: "user_123" } },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(result.response).toBeInstanceOf(Response);
|
|
245
|
+
|
|
246
|
+
// Verify no Authorization header was set
|
|
247
|
+
const authHeader = mockClient.requests[0]?.request.headers.get(
|
|
248
|
+
"Authorization",
|
|
249
|
+
);
|
|
250
|
+
expect(authHeader).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("Path Parameter Errors", () => {
|
|
255
|
+
it("handles missing required path parameters", async () => {
|
|
256
|
+
const client = createClient({
|
|
257
|
+
basicAuth: undefined,
|
|
258
|
+
getAccessToken: () => Promise.resolve("token"),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Call with missing userId parameter
|
|
262
|
+
await client["oauth.getUser"]({
|
|
263
|
+
params: { path: {} },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Path should contain the unreplaced placeholder
|
|
267
|
+
const requestPath = mockClient.requests[0]?.path;
|
|
268
|
+
expect(requestPath).toContain("{userId}");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("handles null path parameters", async () => {
|
|
272
|
+
const client = createClient({
|
|
273
|
+
basicAuth: undefined,
|
|
274
|
+
getAccessToken: () => Promise.resolve("token"),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await client["oauth.getUser"]({
|
|
278
|
+
params: { path: { userId: null } },
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Path should contain the unreplaced placeholder
|
|
282
|
+
const requestPath = mockClient.requests[0]?.path;
|
|
283
|
+
expect(requestPath).toContain("{userId}");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("handles undefined path parameters", async () => {
|
|
287
|
+
const client = createClient({
|
|
288
|
+
basicAuth: undefined,
|
|
289
|
+
getAccessToken: () => Promise.resolve("token"),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await client["oauth.getUser"]({
|
|
293
|
+
params: { path: { userId: undefined } },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Path should contain the unreplaced placeholder
|
|
297
|
+
const requestPath = mockClient.requests[0]?.path;
|
|
298
|
+
expect(requestPath).toContain("{userId}");
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|