@safeurl/sdk 1.0.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/CHANGELOG.md +20 -0
- package/CLAUDE.md +111 -0
- package/README.md +15 -0
- package/bun.lock +140 -0
- package/index.ts +1 -0
- package/package.json +29 -0
- package/src/client/client/client.gen.ts +290 -0
- package/src/client/client/index.ts +25 -0
- package/src/client/client/types.gen.ts +214 -0
- package/src/client/client/utils.gen.ts +316 -0
- package/src/client/client.gen.ts +16 -0
- package/src/client/core/auth.gen.ts +41 -0
- package/src/client/core/bodySerializer.gen.ts +82 -0
- package/src/client/core/params.gen.ts +169 -0
- package/src/client/core/pathSerializer.gen.ts +171 -0
- package/src/client/core/queryKeySerializer.gen.ts +117 -0
- package/src/client/core/serverSentEvents.gen.ts +243 -0
- package/src/client/core/types.gen.ts +104 -0
- package/src/client/core/utils.gen.ts +140 -0
- package/src/client/index.ts +4 -0
- package/src/client/sdk.gen.ts +152 -0
- package/src/client/types.gen.ts +323 -0
- package/tests/client.unit.test.ts +118 -0
- package/tests/sdk-smoke.test.ts +239 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// This file is auto-generated by @hey-api/openapi-ts
|
|
2
|
+
|
|
3
|
+
export type ClientOptions = {
|
|
4
|
+
baseUrl: `${string}://${string}` | (string & {});
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type GetHealthData = {
|
|
8
|
+
body?: never;
|
|
9
|
+
path?: never;
|
|
10
|
+
query?: never;
|
|
11
|
+
url: '/health';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type GetV1ApiKeysData = {
|
|
15
|
+
body?: never;
|
|
16
|
+
path?: never;
|
|
17
|
+
query?: never;
|
|
18
|
+
url: '/v1/api-keys/';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type GetV1ApiKeysErrors = {
|
|
22
|
+
/**
|
|
23
|
+
* Response for status 500
|
|
24
|
+
*/
|
|
25
|
+
500: unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type GetV1ApiKeysResponses = {
|
|
29
|
+
/**
|
|
30
|
+
* Response for status 200
|
|
31
|
+
*/
|
|
32
|
+
200: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type PostV1ApiKeysData = {
|
|
36
|
+
body: unknown;
|
|
37
|
+
path?: never;
|
|
38
|
+
query?: never;
|
|
39
|
+
url: '/v1/api-keys/';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PostV1ApiKeysErrors = {
|
|
43
|
+
/**
|
|
44
|
+
* Response for status 500
|
|
45
|
+
*/
|
|
46
|
+
500: unknown;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type PostV1ApiKeysResponses = {
|
|
50
|
+
/**
|
|
51
|
+
* Response for status 201
|
|
52
|
+
*/
|
|
53
|
+
201: unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type DeleteV1ApiKeysByIdData = {
|
|
57
|
+
body?: never;
|
|
58
|
+
path: {
|
|
59
|
+
id: string;
|
|
60
|
+
};
|
|
61
|
+
query?: never;
|
|
62
|
+
url: '/v1/api-keys/{id}';
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type DeleteV1ApiKeysByIdErrors = {
|
|
66
|
+
/**
|
|
67
|
+
* Response for status 400
|
|
68
|
+
*/
|
|
69
|
+
400: unknown;
|
|
70
|
+
/**
|
|
71
|
+
* Response for status 404
|
|
72
|
+
*/
|
|
73
|
+
404: unknown;
|
|
74
|
+
/**
|
|
75
|
+
* Response for status 500
|
|
76
|
+
*/
|
|
77
|
+
500: unknown;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type DeleteV1ApiKeysByIdResponses = {
|
|
81
|
+
/**
|
|
82
|
+
* Response for status 200
|
|
83
|
+
*/
|
|
84
|
+
200: unknown;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type GetV1ScansData = {
|
|
88
|
+
body?: never;
|
|
89
|
+
path?: never;
|
|
90
|
+
query?: never;
|
|
91
|
+
url: '/v1/scans/';
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type GetV1ScansErrors = {
|
|
95
|
+
/**
|
|
96
|
+
* Insufficient scope (requires scan:read)
|
|
97
|
+
*/
|
|
98
|
+
403: unknown;
|
|
99
|
+
/**
|
|
100
|
+
* Server error
|
|
101
|
+
*/
|
|
102
|
+
500: unknown;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type GetV1ScansResponses = {
|
|
106
|
+
/**
|
|
107
|
+
* List of scan jobs
|
|
108
|
+
*/
|
|
109
|
+
200: unknown;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type PostV1ScansData = {
|
|
113
|
+
body: unknown;
|
|
114
|
+
path?: never;
|
|
115
|
+
query?: never;
|
|
116
|
+
url: '/v1/scans/';
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type PostV1ScansResponses = {
|
|
120
|
+
/**
|
|
121
|
+
* Response for status 201
|
|
122
|
+
*/
|
|
123
|
+
201: unknown;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type PostV1ScansBatchData = {
|
|
127
|
+
body: unknown;
|
|
128
|
+
path?: never;
|
|
129
|
+
query?: never;
|
|
130
|
+
url: '/v1/scans/batch';
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type PostV1ScansBatchResponses = {
|
|
134
|
+
/**
|
|
135
|
+
* Response for status 201
|
|
136
|
+
*/
|
|
137
|
+
201: unknown;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type GetV1ScansByIdAnalyticsData = {
|
|
141
|
+
body?: never;
|
|
142
|
+
path: {
|
|
143
|
+
id: string;
|
|
144
|
+
};
|
|
145
|
+
query?: never;
|
|
146
|
+
url: '/v1/scans/{id}/analytics';
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export type GetV1ScansByIdAnalyticsErrors = {
|
|
150
|
+
/**
|
|
151
|
+
* Insufficient scope (requires scan:read)
|
|
152
|
+
*/
|
|
153
|
+
403: unknown;
|
|
154
|
+
/**
|
|
155
|
+
* Scan job not found
|
|
156
|
+
*/
|
|
157
|
+
404: unknown;
|
|
158
|
+
/**
|
|
159
|
+
* Server error
|
|
160
|
+
*/
|
|
161
|
+
500: unknown;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export type GetV1ScansByIdAnalyticsResponses = {
|
|
165
|
+
/**
|
|
166
|
+
* Scan analytics
|
|
167
|
+
*/
|
|
168
|
+
200: unknown;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export type GetV1ScansByIdEventsData = {
|
|
172
|
+
body?: never;
|
|
173
|
+
path: {
|
|
174
|
+
id: string;
|
|
175
|
+
};
|
|
176
|
+
query?: never;
|
|
177
|
+
url: '/v1/scans/{id}/events';
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export type GetV1ScansByIdEventsErrors = {
|
|
181
|
+
/**
|
|
182
|
+
* Insufficient scope (requires scan:read)
|
|
183
|
+
*/
|
|
184
|
+
403: unknown;
|
|
185
|
+
/**
|
|
186
|
+
* Scan job not found
|
|
187
|
+
*/
|
|
188
|
+
404: unknown;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export type GetV1ScansByIdEventsResponses = {
|
|
192
|
+
/**
|
|
193
|
+
* SSE stream
|
|
194
|
+
*/
|
|
195
|
+
200: unknown;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export type GetV1ScansByIdData = {
|
|
199
|
+
body?: never;
|
|
200
|
+
path: {
|
|
201
|
+
id: string;
|
|
202
|
+
};
|
|
203
|
+
query?: never;
|
|
204
|
+
url: '/v1/scans/{id}';
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export type GetV1ScansByIdErrors = {
|
|
208
|
+
/**
|
|
209
|
+
* Response for status 404
|
|
210
|
+
*/
|
|
211
|
+
404: unknown;
|
|
212
|
+
/**
|
|
213
|
+
* Response for status 500
|
|
214
|
+
*/
|
|
215
|
+
500: unknown;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export type GetV1ScansByIdResponses = {
|
|
219
|
+
/**
|
|
220
|
+
* Response for status 200
|
|
221
|
+
*/
|
|
222
|
+
200: unknown;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export type GetV1CreditsData = {
|
|
226
|
+
body?: never;
|
|
227
|
+
path?: never;
|
|
228
|
+
query?: never;
|
|
229
|
+
url: '/v1/credits/';
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export type GetV1CreditsErrors = {
|
|
233
|
+
/**
|
|
234
|
+
* Response for status 500
|
|
235
|
+
*/
|
|
236
|
+
500: unknown;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type GetV1CreditsResponses = {
|
|
240
|
+
/**
|
|
241
|
+
* Response for status 200
|
|
242
|
+
*/
|
|
243
|
+
200: unknown;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export type PostV1CreditsPurchaseData = {
|
|
247
|
+
body: unknown;
|
|
248
|
+
path?: never;
|
|
249
|
+
query?: never;
|
|
250
|
+
url: '/v1/credits/purchase';
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export type PostV1CreditsPurchaseErrors = {
|
|
254
|
+
/**
|
|
255
|
+
* Response for status 400
|
|
256
|
+
*/
|
|
257
|
+
400: unknown;
|
|
258
|
+
/**
|
|
259
|
+
* Response for status 402
|
|
260
|
+
*/
|
|
261
|
+
402: unknown;
|
|
262
|
+
/**
|
|
263
|
+
* Response for status 500
|
|
264
|
+
*/
|
|
265
|
+
500: unknown;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export type PostV1CreditsPurchaseResponses = {
|
|
269
|
+
/**
|
|
270
|
+
* Response for status 201
|
|
271
|
+
*/
|
|
272
|
+
201: unknown;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export type GetV1SettingsData = {
|
|
276
|
+
body?: never;
|
|
277
|
+
path?: never;
|
|
278
|
+
query?: never;
|
|
279
|
+
url: '/v1/settings/';
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export type GetV1SettingsErrors = {
|
|
283
|
+
/**
|
|
284
|
+
* Response for status 404
|
|
285
|
+
*/
|
|
286
|
+
404: unknown;
|
|
287
|
+
/**
|
|
288
|
+
* Response for status 500
|
|
289
|
+
*/
|
|
290
|
+
500: unknown;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type GetV1SettingsResponses = {
|
|
294
|
+
/**
|
|
295
|
+
* Response for status 200
|
|
296
|
+
*/
|
|
297
|
+
200: unknown;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export type PutV1SettingsData = {
|
|
301
|
+
body: unknown;
|
|
302
|
+
path?: never;
|
|
303
|
+
query?: never;
|
|
304
|
+
url: '/v1/settings/';
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export type PutV1SettingsErrors = {
|
|
308
|
+
/**
|
|
309
|
+
* Response for status 404
|
|
310
|
+
*/
|
|
311
|
+
404: unknown;
|
|
312
|
+
/**
|
|
313
|
+
* Response for status 500
|
|
314
|
+
*/
|
|
315
|
+
500: unknown;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export type PutV1SettingsResponses = {
|
|
319
|
+
/**
|
|
320
|
+
* Response for status 200
|
|
321
|
+
*/
|
|
322
|
+
200: unknown;
|
|
323
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { createClient } from "../src/client/client";
|
|
4
|
+
import { getHealth, postV1Scans } from "../src/client";
|
|
5
|
+
|
|
6
|
+
function lastRequest(mockFetch: ReturnType<typeof mock>) {
|
|
7
|
+
const calls = mockFetch.mock.calls as unknown as [
|
|
8
|
+
input: RequestInfo | URL,
|
|
9
|
+
init?: RequestInit,
|
|
10
|
+
][];
|
|
11
|
+
const [input, init] = calls[calls.length - 1]!;
|
|
12
|
+
if (input instanceof Request) {
|
|
13
|
+
return input;
|
|
14
|
+
}
|
|
15
|
+
return new Request(input, init);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("getHealth issues GET to {baseUrl}/health with Authorization", async () => {
|
|
19
|
+
const fetchMock = mock((_input: RequestInfo | URL, _init?: RequestInit) =>
|
|
20
|
+
Promise.resolve(
|
|
21
|
+
new Response(
|
|
22
|
+
JSON.stringify({
|
|
23
|
+
status: "healthy",
|
|
24
|
+
service: "safeurl-api",
|
|
25
|
+
checks: {
|
|
26
|
+
database: { status: "healthy" },
|
|
27
|
+
queue: { status: "healthy" },
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
{
|
|
31
|
+
status: 200,
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
},
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const client = createClient({
|
|
39
|
+
baseUrl: "https://api.example.com",
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: "Bearer sk_live_unit",
|
|
42
|
+
},
|
|
43
|
+
fetch: fetchMock as typeof fetch,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = await getHealth({ client });
|
|
47
|
+
expect(result.response.status).toBe(200);
|
|
48
|
+
|
|
49
|
+
const req = lastRequest(fetchMock);
|
|
50
|
+
expect(req.method).toBe("GET");
|
|
51
|
+
expect(req.url).toBe("https://api.example.com/health");
|
|
52
|
+
expect(req.headers.get("Authorization")).toBe("Bearer sk_live_unit");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("createClient normalizes baseUrl trailing slash before requests", async () => {
|
|
56
|
+
const fetchMock = mock((_input: RequestInfo | URL, _init?: RequestInit) =>
|
|
57
|
+
Promise.resolve(
|
|
58
|
+
new Response(
|
|
59
|
+
JSON.stringify({
|
|
60
|
+
status: "healthy",
|
|
61
|
+
service: "safeurl-api",
|
|
62
|
+
checks: {
|
|
63
|
+
database: { status: "healthy" },
|
|
64
|
+
queue: { status: "healthy" },
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
{
|
|
68
|
+
status: 200,
|
|
69
|
+
headers: { "Content-Type": "application/json" },
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const client = createClient({
|
|
76
|
+
baseUrl: "https://api.example.com/",
|
|
77
|
+
fetch: fetchMock as typeof fetch,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await getHealth({ client });
|
|
81
|
+
const req = lastRequest(fetchMock);
|
|
82
|
+
expect(req.url).toBe("https://api.example.com/health");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("postV1Scans sends JSON body and Content-Type application/json", async () => {
|
|
86
|
+
const fetchMock = mock((_input: RequestInfo | URL, _init?: RequestInit) =>
|
|
87
|
+
Promise.resolve(
|
|
88
|
+
new Response(
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
id: "00000000-0000-0000-0000-000000000001",
|
|
91
|
+
state: "QUEUED",
|
|
92
|
+
}),
|
|
93
|
+
{
|
|
94
|
+
status: 201,
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
},
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const client = createClient({
|
|
102
|
+
baseUrl: "https://api.example.com",
|
|
103
|
+
headers: { Authorization: "Bearer sk_live_unit" },
|
|
104
|
+
fetch: fetchMock as typeof fetch,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const result = await postV1Scans({
|
|
108
|
+
client,
|
|
109
|
+
body: { url: "https://example.com" },
|
|
110
|
+
});
|
|
111
|
+
expect(result.response.status).toBe(201);
|
|
112
|
+
|
|
113
|
+
const req = lastRequest(fetchMock);
|
|
114
|
+
expect(req.method).toBe("POST");
|
|
115
|
+
expect(req.url).toBe("https://api.example.com/v1/scans/");
|
|
116
|
+
expect(req.headers.get("Content-Type")).toBe("application/json");
|
|
117
|
+
expect(await req.text()).toBe(JSON.stringify({ url: "https://example.com" }));
|
|
118
|
+
});
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { createClient } from "../src/client/client";
|
|
4
|
+
import {
|
|
5
|
+
deleteV1ApiKeysById,
|
|
6
|
+
getHealth,
|
|
7
|
+
getV1ApiKeys,
|
|
8
|
+
getV1Credits,
|
|
9
|
+
getV1ScansById,
|
|
10
|
+
postV1ApiKeys,
|
|
11
|
+
postV1CreditsPurchase,
|
|
12
|
+
postV1Scans,
|
|
13
|
+
} from "../src/client";
|
|
14
|
+
|
|
15
|
+
type CreditsResponse = {
|
|
16
|
+
balance: number;
|
|
17
|
+
userId: string;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type PurchaseCreditsResponse = {
|
|
22
|
+
id: string;
|
|
23
|
+
userId: string;
|
|
24
|
+
amount: number;
|
|
25
|
+
status: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
completedAt: string;
|
|
28
|
+
newBalance: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ScanResponse = {
|
|
32
|
+
id: string;
|
|
33
|
+
url: string;
|
|
34
|
+
state: "QUEUED" | "FETCHING" | "ANALYZING" | "COMPLETED" | "FAILED" | "TIMED_OUT";
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type CreateApiKeyResponse = {
|
|
40
|
+
id: string;
|
|
41
|
+
key: string;
|
|
42
|
+
name: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ApiKeyListItem = {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
scopes: string[];
|
|
50
|
+
expiresAt: string | null;
|
|
51
|
+
lastUsedAt: string | null;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
revokedAt: string | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const baseUrl = process.env["SAFEURL_SDK_TEST_BASE_URL"] ?? "http://localhost:8081";
|
|
57
|
+
const serviceSecret =
|
|
58
|
+
process.env["SAFEURL_SDK_TEST_SERVICE_SECRET"] ??
|
|
59
|
+
process.env["SAFEURL_SERVICE_SECRET"];
|
|
60
|
+
const smoke = serviceSecret ? test : test.skip;
|
|
61
|
+
|
|
62
|
+
smoke("bootstraps and uses a real API key", async () => {
|
|
63
|
+
if (!serviceSecret) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const serviceClient = createClient({
|
|
68
|
+
baseUrl,
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `Bearer ${serviceSecret}`,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const health = await getHealth({
|
|
75
|
+
client: serviceClient,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(health.response.status).toBe(200);
|
|
79
|
+
expect(health.data).toMatchObject({
|
|
80
|
+
status: "healthy",
|
|
81
|
+
service: "safeurl-api",
|
|
82
|
+
checks: {
|
|
83
|
+
database: { status: "healthy" },
|
|
84
|
+
queue: { status: "healthy" },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let createdKey: CreateApiKeyResponse | undefined;
|
|
89
|
+
let revokeKey: CreateApiKeyResponse | undefined;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const keyName = `ts-sdk-smoke-${Date.now()}`;
|
|
93
|
+
const createApiKeyResult = await postV1ApiKeys({
|
|
94
|
+
client: serviceClient,
|
|
95
|
+
body: {
|
|
96
|
+
name: keyName,
|
|
97
|
+
scopes: ["scan:read", "scan:write", "credits:read", "credits:write"],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
expect(createApiKeyResult.response.status).toBe(201);
|
|
101
|
+
createdKey = createApiKeyResult.data as CreateApiKeyResponse;
|
|
102
|
+
expect(createdKey.id).toBeTruthy();
|
|
103
|
+
expect(createdKey.name).toBe(keyName);
|
|
104
|
+
expect(createdKey.key).toMatch(/^sk_live_/);
|
|
105
|
+
|
|
106
|
+
const apiKeyClient = createClient({
|
|
107
|
+
baseUrl,
|
|
108
|
+
headers: {
|
|
109
|
+
Authorization: `Bearer ${createdKey.key}`,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const apiKeyListResult = await getV1ApiKeys({
|
|
114
|
+
client: apiKeyClient,
|
|
115
|
+
});
|
|
116
|
+
expect(apiKeyListResult.response.status).toBe(200);
|
|
117
|
+
const apiKeyList = apiKeyListResult.data as ApiKeyListItem[];
|
|
118
|
+
expect(
|
|
119
|
+
apiKeyList.some((key) => key.id === createdKey?.id && key.name === keyName),
|
|
120
|
+
).toBe(true);
|
|
121
|
+
|
|
122
|
+
const purchaseResult = await postV1CreditsPurchase({
|
|
123
|
+
client: serviceClient,
|
|
124
|
+
body: {
|
|
125
|
+
amount: 1,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
expect(purchaseResult.response.status).toBe(201);
|
|
129
|
+
const purchase = purchaseResult.data as PurchaseCreditsResponse;
|
|
130
|
+
expect(purchase.amount).toBe(1);
|
|
131
|
+
|
|
132
|
+
const apiKeyCreditsResult = await getV1Credits({
|
|
133
|
+
client: apiKeyClient,
|
|
134
|
+
});
|
|
135
|
+
expect(apiKeyCreditsResult.response.status).toBe(200);
|
|
136
|
+
const apiKeyCredits = apiKeyCreditsResult.data as CreditsResponse;
|
|
137
|
+
expect(apiKeyCredits.balance).toBe(purchase.newBalance);
|
|
138
|
+
|
|
139
|
+
const revokeKeyName = `ts-sdk-revoke-${Date.now()}`;
|
|
140
|
+
const createRevokeKeyResult = await postV1ApiKeys({
|
|
141
|
+
client: serviceClient,
|
|
142
|
+
body: {
|
|
143
|
+
name: revokeKeyName,
|
|
144
|
+
scopes: ["scan:read", "credits:read"],
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
expect(createRevokeKeyResult.response.status).toBe(201);
|
|
148
|
+
revokeKey = createRevokeKeyResult.data as CreateApiKeyResponse;
|
|
149
|
+
expect(revokeKey.id).toBeTruthy();
|
|
150
|
+
expect(revokeKey.key).toMatch(/^sk_live_/);
|
|
151
|
+
|
|
152
|
+
const revokeClient = createClient({
|
|
153
|
+
baseUrl,
|
|
154
|
+
headers: {
|
|
155
|
+
Authorization: `Bearer ${revokeKey.key}`,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const revokePrecheck = await getV1Credits({
|
|
160
|
+
client: revokeClient,
|
|
161
|
+
});
|
|
162
|
+
expect(revokePrecheck.response.status).toBe(200);
|
|
163
|
+
|
|
164
|
+
const revokeResult = await deleteV1ApiKeysById({
|
|
165
|
+
client: apiKeyClient,
|
|
166
|
+
path: { id: revokeKey.id },
|
|
167
|
+
});
|
|
168
|
+
expect(revokeResult.response.status).toBe(200);
|
|
169
|
+
revokeKey = undefined;
|
|
170
|
+
|
|
171
|
+
const revokedCreditsResult = await getV1Credits({
|
|
172
|
+
client: revokeClient,
|
|
173
|
+
});
|
|
174
|
+
expect(revokedCreditsResult.response.status).toBe(401);
|
|
175
|
+
|
|
176
|
+
const unauthenticatedClient = createClient({ baseUrl });
|
|
177
|
+
const missingAuthResult = await getV1Credits({
|
|
178
|
+
client: unauthenticatedClient,
|
|
179
|
+
});
|
|
180
|
+
expect(missingAuthResult.response.status).toBe(401);
|
|
181
|
+
|
|
182
|
+
const invalidAuthClient = createClient({
|
|
183
|
+
baseUrl,
|
|
184
|
+
headers: {
|
|
185
|
+
Authorization: "Bearer sk_live_invalid_api_key",
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
const invalidAuthResult = await getV1Credits({
|
|
189
|
+
client: invalidAuthClient,
|
|
190
|
+
});
|
|
191
|
+
expect(invalidAuthResult.response.status).toBe(401);
|
|
192
|
+
|
|
193
|
+
const scanUrl = "https://example.com";
|
|
194
|
+
const createScanResult = await postV1Scans({
|
|
195
|
+
client: apiKeyClient,
|
|
196
|
+
body: {
|
|
197
|
+
url: scanUrl,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
expect(createScanResult.response.status).toBe(201);
|
|
201
|
+
|
|
202
|
+
const createdScan = createScanResult.data as { id: string; state: "QUEUED" };
|
|
203
|
+
expect(createdScan.id).toBeTruthy();
|
|
204
|
+
expect(createdScan.state).toBe("QUEUED");
|
|
205
|
+
|
|
206
|
+
const scanResult = await getV1ScansById({
|
|
207
|
+
client: apiKeyClient,
|
|
208
|
+
path: { id: createdScan.id },
|
|
209
|
+
});
|
|
210
|
+
expect(scanResult.response.status).toBe(200);
|
|
211
|
+
|
|
212
|
+
const scan = scanResult.data as ScanResponse;
|
|
213
|
+
expect(scan.id).toBe(createdScan.id);
|
|
214
|
+
expect(scan.url).toBe(scanUrl);
|
|
215
|
+
expect([
|
|
216
|
+
"QUEUED",
|
|
217
|
+
"FETCHING",
|
|
218
|
+
"ANALYZING",
|
|
219
|
+
"COMPLETED",
|
|
220
|
+
"FAILED",
|
|
221
|
+
"TIMED_OUT",
|
|
222
|
+
]).toContain(scan.state);
|
|
223
|
+
expect(scan.createdAt).toBeDefined();
|
|
224
|
+
expect(scan.updatedAt).toBeDefined();
|
|
225
|
+
} finally {
|
|
226
|
+
if (revokeKey?.id) {
|
|
227
|
+
await deleteV1ApiKeysById({
|
|
228
|
+
client: serviceClient,
|
|
229
|
+
path: { id: revokeKey.id },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (createdKey?.id) {
|
|
233
|
+
await deleteV1ApiKeysById({
|
|
234
|
+
client: serviceClient,
|
|
235
|
+
path: { id: createdKey.id },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}, 30_000);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|