@superblocksteam/sabs-client 0.0.1-demo-databricks-deploy
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/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.test.d.ts +2 -0
- package/dist/errors.test.d.ts.map +1 -0
- package/dist/errors.test.js +272 -0
- package/dist/errors.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/sabs.d.ts +177 -0
- package/dist/sabs.d.ts.map +1 -0
- package/dist/sabs.js +372 -0
- package/dist/sabs.js.map +1 -0
- package/dist/sabs.test.d.ts +2 -0
- package/dist/sabs.test.d.ts.map +1 -0
- package/dist/sabs.test.js +953 -0
- package/dist/sabs.test.js.map +1 -0
- package/eslint.config.mjs +261 -0
- package/jest.config.js +13 -0
- package/package.json +60 -0
- package/src/errors.test.ts +341 -0
- package/src/errors.ts +60 -0
- package/src/index.ts +2 -0
- package/src/sabs.test.ts +1107 -0
- package/src/sabs.ts +502 -0
- package/tsconfig.json +23 -0
- package/tsconfig.test.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/sabs.test.ts
ADDED
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
import { ApplicationMetadata, BuildStatus, CreateLiveEditResponse, LiveEditStatus } from '@superblocksteam/sabs-types';
|
|
2
|
+
import axios, { AxiosError, AxiosHeaders } from 'axios';
|
|
3
|
+
|
|
4
|
+
import { SabsClient } from './sabs';
|
|
5
|
+
|
|
6
|
+
describe('sabs service', () => {
|
|
7
|
+
let anyBuildKey: string;
|
|
8
|
+
let anyAccessToken: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
anyBuildKey = 'any-secret-build-key';
|
|
12
|
+
anyAccessToken = 'any-access-token';
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('build', () => {
|
|
20
|
+
test.each([{ accessToken: 'anyScopedJwt', expectedHeaders: { Authorization: 'Bearer anyScopedJwt' } }])(
|
|
21
|
+
'returns expected response with accessToken=$accessToken',
|
|
22
|
+
async ({ accessToken, expectedHeaders }) => {
|
|
23
|
+
const expectedBuildId = 'expectedBuildId';
|
|
24
|
+
const expectedCreated = new Date();
|
|
25
|
+
const expectedUpdated = new Date();
|
|
26
|
+
|
|
27
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
28
|
+
mockAxios.mockResolvedValue({
|
|
29
|
+
data: {
|
|
30
|
+
buildId: expectedBuildId,
|
|
31
|
+
created: expectedCreated,
|
|
32
|
+
updated: expectedUpdated
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const anyDirectoryHash = 'anyDirectoryHash';
|
|
37
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
38
|
+
id: 'anyApplicationId',
|
|
39
|
+
organizationId: 'anyOrganizationId'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
43
|
+
const result = await sabs.build({
|
|
44
|
+
directoryHash: anyDirectoryHash,
|
|
45
|
+
meta: anyApplicationMetadata,
|
|
46
|
+
buildKey: anyBuildKey,
|
|
47
|
+
accessToken
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(result).toEqual({
|
|
51
|
+
buildId: expectedBuildId,
|
|
52
|
+
created: expectedCreated,
|
|
53
|
+
updated: expectedUpdated
|
|
54
|
+
});
|
|
55
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
56
|
+
method: 'POST',
|
|
57
|
+
url: 'http://localhost:3000/v1/builds',
|
|
58
|
+
headers: expectedHeaders,
|
|
59
|
+
data: {
|
|
60
|
+
directoryHash: anyDirectoryHash,
|
|
61
|
+
applicationMetadata: anyApplicationMetadata,
|
|
62
|
+
buildKey: anyBuildKey
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
test('raises error when request fails', async () => {
|
|
69
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
70
|
+
mockAxios.mockRejectedValue(new Error('any error'));
|
|
71
|
+
|
|
72
|
+
const anyDirectoryHash = 'anyDirectoryHash';
|
|
73
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
74
|
+
id: 'anyApplicationId',
|
|
75
|
+
organizationId: 'anyOrganizationId'
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
79
|
+
await expect(
|
|
80
|
+
sabs.build({ directoryHash: anyDirectoryHash, meta: anyApplicationMetadata, buildKey: anyBuildKey, accessToken: anyAccessToken })
|
|
81
|
+
).rejects.toThrow();
|
|
82
|
+
|
|
83
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
84
|
+
method: 'POST',
|
|
85
|
+
url: 'http://localhost:3000/v1/builds',
|
|
86
|
+
headers: { Authorization: `Bearer ${anyAccessToken}` },
|
|
87
|
+
data: {
|
|
88
|
+
directoryHash: anyDirectoryHash,
|
|
89
|
+
applicationMetadata: anyApplicationMetadata,
|
|
90
|
+
buildKey: anyBuildKey
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('raises error when directory hash is empty', async () => {
|
|
96
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
97
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
98
|
+
id: 'anyApplicationId',
|
|
99
|
+
organizationId: 'anyOrganizationId'
|
|
100
|
+
});
|
|
101
|
+
await expect(sabs.build({ directoryHash: '', meta: anyApplicationMetadata, buildKey: anyBuildKey })).rejects.toThrow();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('raises error when application metadata is empty', async () => {
|
|
105
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
106
|
+
await expect(
|
|
107
|
+
sabs.build({
|
|
108
|
+
directoryHash: '',
|
|
109
|
+
meta: undefined as unknown as ApplicationMetadata,
|
|
110
|
+
buildKey: anyBuildKey,
|
|
111
|
+
accessToken: anyAccessToken
|
|
112
|
+
})
|
|
113
|
+
).rejects.toThrow();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('raises error when build key is empty', async () => {
|
|
117
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
118
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
119
|
+
id: 'anyApplicationId',
|
|
120
|
+
organizationId: 'anyOrganizationId'
|
|
121
|
+
});
|
|
122
|
+
await expect(
|
|
123
|
+
sabs.build({ directoryHash: 'anyDirectoryHash', meta: anyApplicationMetadata, buildKey: '', accessToken: anyAccessToken })
|
|
124
|
+
).rejects.toThrow();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('raises error when application id is empty', async () => {
|
|
128
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
129
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
130
|
+
id: '',
|
|
131
|
+
organizationId: 'anyOrganizationId'
|
|
132
|
+
});
|
|
133
|
+
await expect(
|
|
134
|
+
sabs.build({ directoryHash: 'anyDirectoryHash', meta: anyApplicationMetadata, buildKey: anyBuildKey, accessToken: anyAccessToken })
|
|
135
|
+
).rejects.toThrow();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('raises error when organization id is empty', async () => {
|
|
139
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
140
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
141
|
+
id: 'anyApplicationId',
|
|
142
|
+
organizationId: ''
|
|
143
|
+
});
|
|
144
|
+
await expect(
|
|
145
|
+
sabs.build({ directoryHash: 'anyDirectoryHash', meta: anyApplicationMetadata, buildKey: anyBuildKey, accessToken: anyAccessToken })
|
|
146
|
+
).rejects.toThrow();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('raises error when access token is empty', async () => {
|
|
150
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
151
|
+
const anyApplicationMetadata = new ApplicationMetadata({
|
|
152
|
+
id: 'anyApplicationId',
|
|
153
|
+
organizationId: 'anyOrganizationId'
|
|
154
|
+
});
|
|
155
|
+
await expect(
|
|
156
|
+
sabs.build({ directoryHash: 'anyDirectoryHash', meta: anyApplicationMetadata, buildKey: anyBuildKey, accessToken: '' })
|
|
157
|
+
).rejects.toThrow();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('status', () => {
|
|
162
|
+
test.each([{ accessToken: 'anyScopedJwt', expectedHeaders: { Authorization: 'Bearer anyScopedJwt' } }])(
|
|
163
|
+
'returns expected response with accessToken=$accessToken',
|
|
164
|
+
async ({ accessToken, expectedHeaders }) => {
|
|
165
|
+
const expectedBuildId = 'expectedBuildId';
|
|
166
|
+
const expectedStatus = BuildStatus.SUCCESS;
|
|
167
|
+
const expectedCreated = new Date();
|
|
168
|
+
const expectedUpdated = new Date();
|
|
169
|
+
|
|
170
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
171
|
+
mockAxios.mockResolvedValue({
|
|
172
|
+
data: {
|
|
173
|
+
buildId: expectedBuildId,
|
|
174
|
+
status: expectedStatus,
|
|
175
|
+
created: expectedCreated,
|
|
176
|
+
updated: expectedUpdated
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const anyBuildId = 'anyBuildId';
|
|
181
|
+
|
|
182
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
183
|
+
const result = await sabs.status({ buildId: anyBuildId, accessToken });
|
|
184
|
+
|
|
185
|
+
expect(result).toEqual({
|
|
186
|
+
buildId: expectedBuildId,
|
|
187
|
+
status: expectedStatus,
|
|
188
|
+
created: expectedCreated,
|
|
189
|
+
updated: expectedUpdated
|
|
190
|
+
});
|
|
191
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
192
|
+
method: 'GET',
|
|
193
|
+
url: `http://localhost:3000/v1/builds/${anyBuildId}`,
|
|
194
|
+
headers: expectedHeaders
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
test('raises error when request fails', async () => {
|
|
200
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
201
|
+
mockAxios.mockRejectedValue(new Error('any error'));
|
|
202
|
+
|
|
203
|
+
const anyBuildId = 'anyBuildId';
|
|
204
|
+
|
|
205
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
206
|
+
await expect(sabs.status({ buildId: anyBuildId, accessToken: anyAccessToken })).rejects.toThrow();
|
|
207
|
+
|
|
208
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
209
|
+
headers: { Authorization: `Bearer ${anyAccessToken}` },
|
|
210
|
+
method: 'GET',
|
|
211
|
+
url: `http://localhost:3000/v1/builds/${anyBuildId}`
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('raises error when build id is empty', async () => {
|
|
216
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
217
|
+
await expect(sabs.status({ buildId: '', accessToken: anyAccessToken })).rejects.toThrow();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('raises error when access token is empty', async () => {
|
|
221
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
222
|
+
await expect(sabs.status({ buildId: 'anyBuildId', accessToken: '' })).rejects.toThrow();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('list', () => {
|
|
227
|
+
test.each([{ accessToken: 'anyScopedJwt', expectedHeaders: { Authorization: 'Bearer anyScopedJwt' } }])(
|
|
228
|
+
'returns expected response with accessToken=$accessToken',
|
|
229
|
+
async ({ accessToken, expectedHeaders }) => {
|
|
230
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
231
|
+
mockAxios.mockResolvedValue({
|
|
232
|
+
data: {
|
|
233
|
+
builds: [
|
|
234
|
+
{
|
|
235
|
+
buildId: 'id1',
|
|
236
|
+
status: BuildStatus.SUCCESS,
|
|
237
|
+
created: new Date('2023-01-01'),
|
|
238
|
+
updated: new Date('2023-01-02')
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
buildId: 'id2',
|
|
242
|
+
status: BuildStatus.RUNNING,
|
|
243
|
+
created: new Date('2023-01-03'),
|
|
244
|
+
updated: new Date('2023-01-04')
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
buildId: 'id3',
|
|
248
|
+
status: BuildStatus.FAILED,
|
|
249
|
+
error: 'Build failed',
|
|
250
|
+
created: new Date('2023-01-05'),
|
|
251
|
+
updated: new Date('2023-01-06')
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const anyOrganizationId = 'anyOrganizationId';
|
|
258
|
+
const anyApplicationId = 'anyApplicationId';
|
|
259
|
+
const anyDirectoryHash = 'anyDirectoryHash';
|
|
260
|
+
|
|
261
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
262
|
+
const result = await sabs.list({
|
|
263
|
+
organizationId: anyOrganizationId,
|
|
264
|
+
applicationId: anyApplicationId,
|
|
265
|
+
directoryHash: anyDirectoryHash,
|
|
266
|
+
accessToken
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(result).toEqual({
|
|
270
|
+
builds: [
|
|
271
|
+
{
|
|
272
|
+
buildId: 'id1',
|
|
273
|
+
status: BuildStatus.SUCCESS,
|
|
274
|
+
created: new Date('2023-01-01'),
|
|
275
|
+
updated: new Date('2023-01-02')
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
buildId: 'id2',
|
|
279
|
+
status: BuildStatus.RUNNING,
|
|
280
|
+
created: new Date('2023-01-03'),
|
|
281
|
+
updated: new Date('2023-01-04')
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
buildId: 'id3',
|
|
285
|
+
status: BuildStatus.FAILED,
|
|
286
|
+
error: 'Build failed',
|
|
287
|
+
created: new Date('2023-01-05'),
|
|
288
|
+
updated: new Date('2023-01-06')
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
});
|
|
292
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
293
|
+
method: 'GET',
|
|
294
|
+
url: `http://localhost:3000/v1/build`,
|
|
295
|
+
headers: expectedHeaders,
|
|
296
|
+
params: {
|
|
297
|
+
organizationId: anyOrganizationId,
|
|
298
|
+
applicationId: anyApplicationId,
|
|
299
|
+
directoryHash: anyDirectoryHash
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
test('raises error when request fails', async () => {
|
|
306
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
307
|
+
mockAxios.mockRejectedValue(new Error('any error'));
|
|
308
|
+
|
|
309
|
+
const anyOrganizationId = 'anyOrganizationId';
|
|
310
|
+
const anyApplicationId = 'anyApplicationId';
|
|
311
|
+
const anyDirectoryHash = 'anyDirectoryHash';
|
|
312
|
+
|
|
313
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
314
|
+
await expect(
|
|
315
|
+
sabs.list({
|
|
316
|
+
organizationId: anyOrganizationId,
|
|
317
|
+
applicationId: anyApplicationId,
|
|
318
|
+
directoryHash: anyDirectoryHash,
|
|
319
|
+
accessToken: anyAccessToken
|
|
320
|
+
})
|
|
321
|
+
).rejects.toThrow();
|
|
322
|
+
|
|
323
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
324
|
+
method: 'GET',
|
|
325
|
+
url: `http://localhost:3000/v1/build`,
|
|
326
|
+
headers: { Authorization: `Bearer ${anyAccessToken}` },
|
|
327
|
+
params: {
|
|
328
|
+
organizationId: anyOrganizationId,
|
|
329
|
+
applicationId: anyApplicationId,
|
|
330
|
+
directoryHash: anyDirectoryHash
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('raises error when organization id is empty', async () => {
|
|
336
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
337
|
+
await expect(
|
|
338
|
+
sabs.list({ organizationId: '', applicationId: 'anyApplicationId', directoryHash: 'anyDirectoryHash', accessToken: anyAccessToken })
|
|
339
|
+
).rejects.toThrow();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('raises error when application id is empty', async () => {
|
|
343
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
344
|
+
await expect(
|
|
345
|
+
sabs.list({
|
|
346
|
+
organizationId: 'anyOrganizationId',
|
|
347
|
+
applicationId: '',
|
|
348
|
+
directoryHash: 'anyDirectoryHash',
|
|
349
|
+
accessToken: anyAccessToken
|
|
350
|
+
})
|
|
351
|
+
).rejects.toThrow();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('raises error when directory hash is empty', async () => {
|
|
355
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
356
|
+
await expect(
|
|
357
|
+
sabs.list({
|
|
358
|
+
organizationId: 'anyOrganizationId',
|
|
359
|
+
applicationId: 'anyApplicationId',
|
|
360
|
+
directoryHash: '',
|
|
361
|
+
accessToken: anyAccessToken
|
|
362
|
+
})
|
|
363
|
+
).rejects.toThrow();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('raises error when access token is empty', async () => {
|
|
367
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
368
|
+
await expect(
|
|
369
|
+
sabs.list({
|
|
370
|
+
organizationId: 'anyOrganizationId',
|
|
371
|
+
applicationId: 'anyApplicationId',
|
|
372
|
+
directoryHash: 'anyDirectoryHash',
|
|
373
|
+
accessToken: ''
|
|
374
|
+
})
|
|
375
|
+
).rejects.toThrow();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('terminate', () => {
|
|
380
|
+
test.each([{ accessToken: 'anyScopedJwt', expectedHeaders: { Authorization: 'Bearer anyScopedJwt' } }])(
|
|
381
|
+
'returns expected response with accessToken=$accessToken',
|
|
382
|
+
async ({ accessToken, expectedHeaders }) => {
|
|
383
|
+
const expectedBuildId = 'expectedBuildId';
|
|
384
|
+
const expectedStatus = BuildStatus.TIMED_OUT;
|
|
385
|
+
const expectedError = 'build timed out';
|
|
386
|
+
const expectedCreated = new Date();
|
|
387
|
+
const expectedUpdated = new Date();
|
|
388
|
+
|
|
389
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
390
|
+
mockAxios.mockResolvedValue({
|
|
391
|
+
data: {
|
|
392
|
+
buildId: expectedBuildId,
|
|
393
|
+
status: expectedStatus,
|
|
394
|
+
error: expectedError,
|
|
395
|
+
created: expectedCreated,
|
|
396
|
+
updated: expectedUpdated
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const anyBuildId = 'anyBuildId';
|
|
401
|
+
const anyStatus = BuildStatus.TIMED_OUT;
|
|
402
|
+
const anyError = 'build timed out';
|
|
403
|
+
|
|
404
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
405
|
+
const result = await sabs.terminate({
|
|
406
|
+
buildId: anyBuildId,
|
|
407
|
+
status: anyStatus,
|
|
408
|
+
buildKey: anyBuildKey,
|
|
409
|
+
error: anyError,
|
|
410
|
+
accessToken
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(result).toEqual({
|
|
414
|
+
buildId: expectedBuildId,
|
|
415
|
+
status: expectedStatus,
|
|
416
|
+
error: expectedError,
|
|
417
|
+
created: expectedCreated,
|
|
418
|
+
updated: expectedUpdated
|
|
419
|
+
});
|
|
420
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
421
|
+
method: 'POST',
|
|
422
|
+
url: `http://localhost:3000/v1/builds/${anyBuildId}/terminate`,
|
|
423
|
+
headers: expectedHeaders,
|
|
424
|
+
data: {
|
|
425
|
+
buildId: anyBuildId,
|
|
426
|
+
status: anyStatus,
|
|
427
|
+
error: anyError,
|
|
428
|
+
buildKey: anyBuildKey
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
test('raises error when request fails', async () => {
|
|
435
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
436
|
+
mockAxios.mockRejectedValue(new Error('any error'));
|
|
437
|
+
|
|
438
|
+
const anyBuildId = 'anyBuildId';
|
|
439
|
+
const anyStatus = BuildStatus.TIMED_OUT;
|
|
440
|
+
const anyError = 'build timed out';
|
|
441
|
+
|
|
442
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
443
|
+
await expect(
|
|
444
|
+
sabs.terminate({ buildId: anyBuildId, status: anyStatus, buildKey: anyBuildKey, error: anyError, accessToken: anyAccessToken })
|
|
445
|
+
).rejects.toThrow();
|
|
446
|
+
|
|
447
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
448
|
+
method: 'POST',
|
|
449
|
+
headers: { Authorization: `Bearer ${anyAccessToken}` },
|
|
450
|
+
url: `http://localhost:3000/v1/builds/${anyBuildId}/terminate`,
|
|
451
|
+
data: {
|
|
452
|
+
buildId: anyBuildId,
|
|
453
|
+
status: anyStatus,
|
|
454
|
+
error: anyError,
|
|
455
|
+
buildKey: anyBuildKey
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('raises error when build id is empty', async () => {
|
|
461
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
462
|
+
await expect(
|
|
463
|
+
sabs.terminate({
|
|
464
|
+
buildId: '',
|
|
465
|
+
status: BuildStatus.TIMED_OUT,
|
|
466
|
+
buildKey: anyBuildKey,
|
|
467
|
+
error: 'build timed out',
|
|
468
|
+
accessToken: anyAccessToken
|
|
469
|
+
})
|
|
470
|
+
).rejects.toThrow();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('raises error when status is empty', async () => {
|
|
474
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
475
|
+
await expect(
|
|
476
|
+
sabs.terminate({
|
|
477
|
+
buildId: 'anyBuildId',
|
|
478
|
+
status: undefined as unknown as BuildStatus,
|
|
479
|
+
buildKey: anyBuildKey,
|
|
480
|
+
error: 'build timed out',
|
|
481
|
+
accessToken: anyAccessToken
|
|
482
|
+
})
|
|
483
|
+
).rejects.toThrow();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('raises error when build key is empty', async () => {
|
|
487
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
488
|
+
await expect(
|
|
489
|
+
sabs.terminate({
|
|
490
|
+
buildId: 'anyBuildId',
|
|
491
|
+
status: BuildStatus.TIMED_OUT,
|
|
492
|
+
buildKey: '',
|
|
493
|
+
error: 'build timed out',
|
|
494
|
+
accessToken: anyAccessToken
|
|
495
|
+
})
|
|
496
|
+
).rejects.toThrow();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test('raises error when access token is empty', async () => {
|
|
500
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
501
|
+
await expect(
|
|
502
|
+
sabs.terminate({
|
|
503
|
+
buildId: 'anyBuildId',
|
|
504
|
+
status: BuildStatus.TIMED_OUT,
|
|
505
|
+
buildKey: anyBuildKey,
|
|
506
|
+
error: 'build timed out',
|
|
507
|
+
accessToken: ''
|
|
508
|
+
})
|
|
509
|
+
).rejects.toThrow();
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
describe('bulkStatus', () => {
|
|
514
|
+
test('returns expected response', async () => {
|
|
515
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
516
|
+
mockAxios.mockResolvedValue({
|
|
517
|
+
data: {
|
|
518
|
+
builds: [
|
|
519
|
+
{
|
|
520
|
+
buildId: 'build1',
|
|
521
|
+
status: BuildStatus.SUCCESS,
|
|
522
|
+
created: new Date('2023-01-01'),
|
|
523
|
+
updated: new Date('2023-01-02')
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
buildId: 'build2',
|
|
527
|
+
status: BuildStatus.RUNNING,
|
|
528
|
+
created: new Date('2023-01-03'),
|
|
529
|
+
updated: new Date('2023-01-04')
|
|
530
|
+
}
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const anyOrganizationId = 'anyOrganizationId';
|
|
536
|
+
const anyApplicationId = 'anyApplicationId';
|
|
537
|
+
const anyDirectoryHashes = ['hash1', 'hash2'];
|
|
538
|
+
|
|
539
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
540
|
+
const result = await sabs.bulkStatus({
|
|
541
|
+
organizationId: anyOrganizationId,
|
|
542
|
+
applicationId: anyApplicationId,
|
|
543
|
+
directoryHashes: anyDirectoryHashes,
|
|
544
|
+
accessToken: anyAccessToken
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(result).toEqual({
|
|
548
|
+
builds: [
|
|
549
|
+
{
|
|
550
|
+
buildId: 'build1',
|
|
551
|
+
status: BuildStatus.SUCCESS,
|
|
552
|
+
created: new Date('2023-01-01'),
|
|
553
|
+
updated: new Date('2023-01-02')
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
buildId: 'build2',
|
|
557
|
+
status: BuildStatus.RUNNING,
|
|
558
|
+
created: new Date('2023-01-03'),
|
|
559
|
+
updated: new Date('2023-01-04')
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
});
|
|
563
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
564
|
+
method: 'POST',
|
|
565
|
+
headers: { Authorization: `Bearer ${anyAccessToken}` },
|
|
566
|
+
url: `http://localhost:3000/v1/builds/${anyOrganizationId}/${anyApplicationId}/bulk-status`,
|
|
567
|
+
data: {
|
|
568
|
+
organizationId: anyOrganizationId,
|
|
569
|
+
applicationId: anyApplicationId,
|
|
570
|
+
directoryHashes: anyDirectoryHashes
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('raises error when access token is empty', async () => {
|
|
576
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
577
|
+
await expect(
|
|
578
|
+
sabs.bulkStatus({
|
|
579
|
+
organizationId: 'anyOrganizationId',
|
|
580
|
+
applicationId: 'anyApplicationId',
|
|
581
|
+
directoryHashes: ['anyDirectoryHash'],
|
|
582
|
+
accessToken: ''
|
|
583
|
+
})
|
|
584
|
+
).rejects.toThrow();
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test('raises error when organization id is empty', async () => {
|
|
588
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
589
|
+
await expect(
|
|
590
|
+
sabs.bulkStatus({
|
|
591
|
+
organizationId: '',
|
|
592
|
+
applicationId: 'anyApplicationId',
|
|
593
|
+
directoryHashes: ['anyDirectoryHash'],
|
|
594
|
+
accessToken: anyAccessToken
|
|
595
|
+
})
|
|
596
|
+
).rejects.toThrow();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('raises error when application id is empty', async () => {
|
|
600
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
601
|
+
await expect(
|
|
602
|
+
sabs.bulkStatus({
|
|
603
|
+
organizationId: 'anyOrganizationId',
|
|
604
|
+
applicationId: '',
|
|
605
|
+
directoryHashes: ['anyDirectoryHash'],
|
|
606
|
+
accessToken: anyAccessToken
|
|
607
|
+
})
|
|
608
|
+
).rejects.toThrow();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test('raises error when directory hashes is empty', async () => {
|
|
612
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
613
|
+
await expect(
|
|
614
|
+
sabs.bulkStatus({
|
|
615
|
+
organizationId: 'anyOrganizationId',
|
|
616
|
+
applicationId: 'anyApplicationId',
|
|
617
|
+
directoryHashes: [],
|
|
618
|
+
accessToken: anyAccessToken
|
|
619
|
+
})
|
|
620
|
+
).rejects.toThrow();
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
describe('liveEdit', () => {
|
|
625
|
+
const applicationId = 'anyApplicationId';
|
|
626
|
+
const organizationId = 'anyOrganizationId';
|
|
627
|
+
const branch = 'anyBranch';
|
|
628
|
+
|
|
629
|
+
test('raises error when application id is empty', async () => {
|
|
630
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
631
|
+
await expect(
|
|
632
|
+
sabs.createLiveEdit({
|
|
633
|
+
applicationId: '',
|
|
634
|
+
organizationId: 'anyOrganizationId',
|
|
635
|
+
branch: 'anyBranch',
|
|
636
|
+
expiresIn: 1000,
|
|
637
|
+
accessToken: anyAccessToken
|
|
638
|
+
})
|
|
639
|
+
).rejects.toThrow();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test('raises error when organization id is empty', async () => {
|
|
643
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
644
|
+
await expect(
|
|
645
|
+
sabs.createLiveEdit({
|
|
646
|
+
applicationId: 'anyApplicationId',
|
|
647
|
+
organizationId: '',
|
|
648
|
+
branch: 'anyBranch',
|
|
649
|
+
expiresIn: 1000,
|
|
650
|
+
accessToken: anyAccessToken
|
|
651
|
+
})
|
|
652
|
+
).rejects.toThrow();
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test('raises error when branch is empty', async () => {
|
|
656
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
657
|
+
await expect(
|
|
658
|
+
sabs.createLiveEdit({
|
|
659
|
+
applicationId: 'anyApplicationId',
|
|
660
|
+
organizationId: 'anyOrganizationId',
|
|
661
|
+
branch: '',
|
|
662
|
+
expiresIn: 1000,
|
|
663
|
+
accessToken: anyAccessToken
|
|
664
|
+
})
|
|
665
|
+
).rejects.toThrow();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test('raises error when access token is empty', async () => {
|
|
669
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
670
|
+
await expect(
|
|
671
|
+
sabs.createLiveEdit({
|
|
672
|
+
applicationId: 'anyApplicationId',
|
|
673
|
+
organizationId: 'anyOrganizationId',
|
|
674
|
+
branch: 'anyBranch',
|
|
675
|
+
expiresIn: 1000,
|
|
676
|
+
accessToken: ''
|
|
677
|
+
})
|
|
678
|
+
).rejects.toThrow();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test('createLiveEdit', async () => {
|
|
682
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
683
|
+
const expiresInSeconds = 1000;
|
|
684
|
+
const now = Date.now();
|
|
685
|
+
const expectedRequest = {
|
|
686
|
+
application: {
|
|
687
|
+
applicationId,
|
|
688
|
+
organizationId,
|
|
689
|
+
branch
|
|
690
|
+
},
|
|
691
|
+
expiresIn: BigInt(expiresInSeconds),
|
|
692
|
+
sessionJwt: anyAccessToken
|
|
693
|
+
};
|
|
694
|
+
const newLiveEditResponse = new CreateLiveEditResponse({
|
|
695
|
+
liveEditId: 'liveEditId',
|
|
696
|
+
liveEditUrl: 'http://localhost:3000/live-edit/liveEditId',
|
|
697
|
+
application: {
|
|
698
|
+
applicationId,
|
|
699
|
+
organizationId,
|
|
700
|
+
branch
|
|
701
|
+
},
|
|
702
|
+
expiresAt: BigInt(now + expiresInSeconds * 1000)
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
mockAxios.mockResolvedValue({ data: newLiveEditResponse });
|
|
706
|
+
|
|
707
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
708
|
+
const result = await sabs.createLiveEdit({
|
|
709
|
+
applicationId,
|
|
710
|
+
organizationId,
|
|
711
|
+
branch,
|
|
712
|
+
expiresIn: expiresInSeconds,
|
|
713
|
+
accessToken: anyAccessToken
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
expect(result).toEqual(newLiveEditResponse);
|
|
717
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
718
|
+
method: 'POST',
|
|
719
|
+
url: 'http://localhost:3000/v1/live-edit',
|
|
720
|
+
data: expectedRequest,
|
|
721
|
+
headers: {
|
|
722
|
+
Authorization: `Bearer ${anyAccessToken}`
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test('terminateLiveEdit', async () => {
|
|
728
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
729
|
+
const liveEditId = 'liveEditId';
|
|
730
|
+
const expectedRequest = { liveEditId };
|
|
731
|
+
const expectedCreated = new Date();
|
|
732
|
+
const expectedUpdated = new Date();
|
|
733
|
+
const expectedResponse = {
|
|
734
|
+
liveEditId,
|
|
735
|
+
status: LiveEditStatus.TERMINATED,
|
|
736
|
+
application: {
|
|
737
|
+
applicationId,
|
|
738
|
+
organizationId,
|
|
739
|
+
branch
|
|
740
|
+
},
|
|
741
|
+
created: expectedCreated,
|
|
742
|
+
updated: expectedUpdated
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
mockAxios.mockResolvedValue({ data: expectedResponse });
|
|
746
|
+
|
|
747
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
748
|
+
const result = await sabs.terminateLiveEdit({ liveEditId, accessToken: anyAccessToken });
|
|
749
|
+
|
|
750
|
+
expect(result).toEqual(expectedResponse);
|
|
751
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
752
|
+
method: 'POST',
|
|
753
|
+
url: `http://localhost:3000/v1/live-edit/${liveEditId}/terminate`,
|
|
754
|
+
data: expectedRequest,
|
|
755
|
+
headers: {
|
|
756
|
+
Authorization: `Bearer ${anyAccessToken}`
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test('raises error when live edit id is empty', async () => {
|
|
762
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
763
|
+
await expect(sabs.terminateLiveEdit({ liveEditId: '', accessToken: anyAccessToken })).rejects.toThrow();
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test('raises error when access token is empty', async () => {
|
|
767
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
768
|
+
await expect(sabs.terminateLiveEdit({ liveEditId: 'liveEditId', accessToken: '' })).rejects.toThrow();
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test('terminateLiveEdit raises error when request fails', async () => {
|
|
772
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
773
|
+
mockAxios.mockRejectedValue(new Error('any error'));
|
|
774
|
+
|
|
775
|
+
const liveEditId = 'liveEditId';
|
|
776
|
+
|
|
777
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
778
|
+
await expect(sabs.terminateLiveEdit({ liveEditId, accessToken: anyAccessToken })).rejects.toThrow();
|
|
779
|
+
|
|
780
|
+
expect(mockAxios).toHaveBeenCalledWith({
|
|
781
|
+
method: 'POST',
|
|
782
|
+
url: `http://localhost:3000/v1/live-edit/${liveEditId}/terminate`,
|
|
783
|
+
data: {
|
|
784
|
+
liveEditId
|
|
785
|
+
},
|
|
786
|
+
headers: {
|
|
787
|
+
Authorization: `Bearer ${anyAccessToken}`
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
describe('executeRequest', () => {
|
|
794
|
+
test('raies expected error when error is axios error', async () => {
|
|
795
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
796
|
+
mockAxios.mockRejectedValue(
|
|
797
|
+
new AxiosError('request failed', 'internal', undefined, undefined, {
|
|
798
|
+
headers: {},
|
|
799
|
+
config: {
|
|
800
|
+
headers: new AxiosHeaders()
|
|
801
|
+
},
|
|
802
|
+
status: 500,
|
|
803
|
+
statusText: 'internal server error',
|
|
804
|
+
data: 'failed to process request'
|
|
805
|
+
})
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
const anyBuildId = 'anyBuildId';
|
|
809
|
+
|
|
810
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
811
|
+
await expect(sabs.status({ buildId: anyBuildId, accessToken: anyAccessToken })).rejects.toThrow();
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test('re-raises error when error is not axios error', async () => {
|
|
815
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
816
|
+
mockAxios.mockRejectedValue(new Error('unexpected error'));
|
|
817
|
+
|
|
818
|
+
const anyBuildId = 'anyBuildId';
|
|
819
|
+
|
|
820
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
821
|
+
await expect(sabs.status({ buildId: anyBuildId, accessToken: anyAccessToken })).rejects.toThrow();
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
describe('backward compatibility', () => {
|
|
826
|
+
test('errors can be caught as generic Error instances (backward compatibility)', async () => {
|
|
827
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
828
|
+
mockAxios.mockRejectedValue(
|
|
829
|
+
new AxiosError('Service unavailable', 'ECONNREFUSED', undefined, undefined, {
|
|
830
|
+
headers: {},
|
|
831
|
+
config: {
|
|
832
|
+
headers: new AxiosHeaders()
|
|
833
|
+
},
|
|
834
|
+
status: 503,
|
|
835
|
+
statusText: 'Service Unavailable',
|
|
836
|
+
data: { message: 'Server temporarily unavailable' }
|
|
837
|
+
})
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
841
|
+
const applicationId = 'test-app';
|
|
842
|
+
const organizationId = 'test-org';
|
|
843
|
+
const directoryHash = 'abc123';
|
|
844
|
+
|
|
845
|
+
// This simulates how existing applications might handle errors
|
|
846
|
+
try {
|
|
847
|
+
const result = await sabs.build({
|
|
848
|
+
directoryHash,
|
|
849
|
+
meta: new ApplicationMetadata({
|
|
850
|
+
id: applicationId,
|
|
851
|
+
organizationId
|
|
852
|
+
}),
|
|
853
|
+
buildKey: anyBuildKey,
|
|
854
|
+
accessToken: anyAccessToken
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Should not reach here
|
|
858
|
+
expect(result).toBeUndefined();
|
|
859
|
+
} catch (error) {
|
|
860
|
+
// Legacy error handling - should still work with new error types
|
|
861
|
+
expect(error).toBeInstanceOf(Error);
|
|
862
|
+
expect(error.message).toContain('SABS API Error (503)');
|
|
863
|
+
|
|
864
|
+
// Verify we can still access basic Error properties
|
|
865
|
+
expect(typeof error.message).toBe('string');
|
|
866
|
+
expect(error.name).toBeDefined();
|
|
867
|
+
|
|
868
|
+
// This is how existing code might log and re-throw
|
|
869
|
+
const loggedError = {
|
|
870
|
+
error,
|
|
871
|
+
applicationId,
|
|
872
|
+
organizationId,
|
|
873
|
+
directoryHash,
|
|
874
|
+
message: error.message
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
expect(loggedError.error).toBe(error);
|
|
878
|
+
expect(loggedError.message).toBe(error.message);
|
|
879
|
+
|
|
880
|
+
// Re-throwing as generic Error should work
|
|
881
|
+
expect(() => {
|
|
882
|
+
throw new Error('Unable to launch build');
|
|
883
|
+
}).toThrow('Unable to launch build');
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test('new error types provide additional functionality when accessed', async () => {
|
|
888
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
889
|
+
mockAxios.mockRejectedValue(
|
|
890
|
+
new AxiosError('Unauthorized', 'UNAUTHORIZED', undefined, undefined, {
|
|
891
|
+
headers: {},
|
|
892
|
+
config: {
|
|
893
|
+
headers: new AxiosHeaders()
|
|
894
|
+
},
|
|
895
|
+
status: 401,
|
|
896
|
+
statusText: 'Unauthorized',
|
|
897
|
+
data: { message: 'Invalid token' }
|
|
898
|
+
})
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
902
|
+
|
|
903
|
+
try {
|
|
904
|
+
await sabs.status({ buildId: 'test-build', accessToken: 'invalid-token' });
|
|
905
|
+
} catch (error) {
|
|
906
|
+
// Backward compatible - can still catch as Error
|
|
907
|
+
expect(error).toBeInstanceOf(Error);
|
|
908
|
+
|
|
909
|
+
// But applications can now also check for specific error types
|
|
910
|
+
const { UnauthorizedError } = await import('./errors');
|
|
911
|
+
expect(error).toBeInstanceOf(UnauthorizedError);
|
|
912
|
+
|
|
913
|
+
// And access the status code if needed
|
|
914
|
+
if ('status' in error) {
|
|
915
|
+
expect(error.status).toBe(401);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
describe('response data handling', () => {
|
|
921
|
+
test('handles responseData with message property', async () => {
|
|
922
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
923
|
+
mockAxios.mockRejectedValue(
|
|
924
|
+
new AxiosError('Bad Request', 'BAD_REQUEST', undefined, undefined, {
|
|
925
|
+
headers: {},
|
|
926
|
+
config: {
|
|
927
|
+
headers: new AxiosHeaders()
|
|
928
|
+
},
|
|
929
|
+
status: 400,
|
|
930
|
+
statusText: 'Bad Request',
|
|
931
|
+
data: { message: 'Directory hash is required' }
|
|
932
|
+
})
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
936
|
+
|
|
937
|
+
try {
|
|
938
|
+
await sabs.build({
|
|
939
|
+
directoryHash: 'test', // Use non-empty value to avoid client-side validation
|
|
940
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
941
|
+
buildKey: 'test',
|
|
942
|
+
accessToken: 'test'
|
|
943
|
+
});
|
|
944
|
+
} catch (error) {
|
|
945
|
+
expect(error).toBeInstanceOf(Error);
|
|
946
|
+
expect(error.message).toBe('SABS API Error (400): Directory hash is required');
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test('handles responseData as string', async () => {
|
|
951
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
952
|
+
mockAxios.mockRejectedValue(
|
|
953
|
+
new AxiosError('Internal Server Error', 'INTERNAL_SERVER_ERROR', undefined, undefined, {
|
|
954
|
+
headers: {},
|
|
955
|
+
config: {
|
|
956
|
+
headers: new AxiosHeaders()
|
|
957
|
+
},
|
|
958
|
+
status: 500,
|
|
959
|
+
statusText: 'Internal Server Error',
|
|
960
|
+
data: 'Database connection failed'
|
|
961
|
+
})
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
await sabs.build({
|
|
968
|
+
directoryHash: 'test',
|
|
969
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
970
|
+
buildKey: 'test',
|
|
971
|
+
accessToken: 'test'
|
|
972
|
+
});
|
|
973
|
+
} catch (error) {
|
|
974
|
+
expect(error).toBeInstanceOf(Error);
|
|
975
|
+
expect(error.message).toBe('SABS API Error (500): Database connection failed');
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test('handles null responseData', async () => {
|
|
980
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
981
|
+
mockAxios.mockRejectedValue(
|
|
982
|
+
new AxiosError('Service Unavailable', 'SERVICE_UNAVAILABLE', undefined, undefined, {
|
|
983
|
+
headers: {},
|
|
984
|
+
config: {
|
|
985
|
+
headers: new AxiosHeaders()
|
|
986
|
+
},
|
|
987
|
+
status: 503,
|
|
988
|
+
statusText: 'Service Unavailable',
|
|
989
|
+
data: null
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
994
|
+
|
|
995
|
+
try {
|
|
996
|
+
await sabs.build({
|
|
997
|
+
directoryHash: 'test',
|
|
998
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
999
|
+
buildKey: 'test',
|
|
1000
|
+
accessToken: 'test'
|
|
1001
|
+
});
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
expect(error).toBeInstanceOf(Error);
|
|
1004
|
+
expect(error.message).toBe('SABS API Error (503): Service Unavailable (503)');
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
test('handles undefined responseData', async () => {
|
|
1009
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
1010
|
+
mockAxios.mockRejectedValue(
|
|
1011
|
+
new AxiosError('Gateway Timeout', 'GATEWAY_TIMEOUT', undefined, undefined, {
|
|
1012
|
+
headers: {},
|
|
1013
|
+
config: {
|
|
1014
|
+
headers: new AxiosHeaders()
|
|
1015
|
+
},
|
|
1016
|
+
status: 504,
|
|
1017
|
+
statusText: 'Gateway Timeout',
|
|
1018
|
+
data: undefined
|
|
1019
|
+
})
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
1023
|
+
|
|
1024
|
+
try {
|
|
1025
|
+
await sabs.build({
|
|
1026
|
+
directoryHash: 'test',
|
|
1027
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
1028
|
+
buildKey: 'test',
|
|
1029
|
+
accessToken: 'test'
|
|
1030
|
+
});
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
expect(error).toBeInstanceOf(Error);
|
|
1033
|
+
expect(error.message).toBe('SABS API Error (504): Gateway Timeout (504)');
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
test('handles responseData object without message property', async () => {
|
|
1038
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
1039
|
+
mockAxios.mockRejectedValue(
|
|
1040
|
+
new AxiosError('Bad Request', 'BAD_REQUEST', undefined, undefined, {
|
|
1041
|
+
headers: {},
|
|
1042
|
+
config: {
|
|
1043
|
+
headers: new AxiosHeaders()
|
|
1044
|
+
},
|
|
1045
|
+
status: 400,
|
|
1046
|
+
statusText: 'Bad Request',
|
|
1047
|
+
data: { error: 'validation_failed', details: ['field is required'] }
|
|
1048
|
+
})
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
1052
|
+
|
|
1053
|
+
try {
|
|
1054
|
+
await sabs.build({
|
|
1055
|
+
directoryHash: 'test',
|
|
1056
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
1057
|
+
buildKey: 'test',
|
|
1058
|
+
accessToken: 'test'
|
|
1059
|
+
});
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
expect(error).toBeInstanceOf(Error);
|
|
1062
|
+
expect(error.message).toBe('SABS API Error (400): Bad Request (400)');
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
test('all errors have consistent HttpError interface', async () => {
|
|
1067
|
+
const responseData = { code: 'TIMEOUT', retryAfter: 30 };
|
|
1068
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
1069
|
+
mockAxios.mockRejectedValue(
|
|
1070
|
+
new AxiosError('Request timeout', 'TIMEOUT', undefined, undefined, {
|
|
1071
|
+
headers: {},
|
|
1072
|
+
config: {
|
|
1073
|
+
headers: new AxiosHeaders()
|
|
1074
|
+
},
|
|
1075
|
+
status: 500,
|
|
1076
|
+
statusText: 'Internal Server Error',
|
|
1077
|
+
data: responseData
|
|
1078
|
+
})
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
1082
|
+
|
|
1083
|
+
try {
|
|
1084
|
+
await sabs.build({
|
|
1085
|
+
directoryHash: 'test',
|
|
1086
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
1087
|
+
buildKey: 'test',
|
|
1088
|
+
accessToken: 'test'
|
|
1089
|
+
});
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
expect(error).toBeInstanceOf(Error);
|
|
1092
|
+
expect(error.message).toBe('SABS API Error (500): Internal Server Error (500)');
|
|
1093
|
+
|
|
1094
|
+
// All error types now have consistent HttpError interface
|
|
1095
|
+
expect('status' in error).toBe(true);
|
|
1096
|
+
expect('title' in error).toBe(true);
|
|
1097
|
+
if ('status' in error) {
|
|
1098
|
+
expect(error.status).toBe(500);
|
|
1099
|
+
}
|
|
1100
|
+
if ('title' in error) {
|
|
1101
|
+
expect(error.title).toBe('Internal server error');
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
});
|