@growi/sdk-typescript 1.0.0 → 1.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/CHANGELOG.md +6 -0
- package/dist/apiv1/index.d.ts +8 -5
- package/dist/apiv1/index.d.ts.map +1 -1
- package/dist/generated/v1/index.d.ts +14 -8
- package/dist/generated/v1/index.d.ts.map +1 -1
- package/dist/generated/v1/index.js +30 -6
- package/dist/generated/v1/index.js.map +1 -1
- package/dist/generated/v1/index.schemas.d.ts +132 -73
- package/dist/generated/v1/index.schemas.d.ts.map +1 -1
- package/dist/generated/v1/index.schemas.js +0 -4
- package/dist/generated/v1/index.schemas.js.map +1 -1
- package/dist/generated/v3/index.d.ts +1 -1
- package/dist/generated/v3/index.schemas.d.ts +45 -11
- package/dist/generated/v3/index.schemas.d.ts.map +1 -1
- package/dist/generated/v3/index.schemas.js.map +1 -1
- package/package.json +5 -3
- package/src/generated/v1/index.schemas.ts +153 -75
- package/src/generated/v1/index.ts +63 -20
- package/src/generated/v3/index.schemas.ts +51 -11
- package/src/generated/v3/index.ts +1 -1
- package/src/utils/axios-default-instance.test.ts +108 -0
- package/src/utils/v1/axios-instance.test.ts +250 -0
- package/src/utils/v3/axios-instance.test.ts +369 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
import Axios from 'axios';
|
|
3
|
+
/**
|
|
4
|
+
* Tests for v3/axios-instance.ts
|
|
5
|
+
*
|
|
6
|
+
* This file provides custom Axios instance creation functionality for GROWI API v3,
|
|
7
|
+
* including automatic base URL configuration (with v3 suffix), cancellation capability, and request configuration merging.
|
|
8
|
+
*/
|
|
9
|
+
import { type MockedFunction, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
+
import { AXIOS_DEFAULT } from '../axios-default-instance.js';
|
|
11
|
+
import { customInstance } from './axios-instance.js';
|
|
12
|
+
|
|
13
|
+
// Mock Axios
|
|
14
|
+
vi.mock('axios');
|
|
15
|
+
vi.mock('../axios-default-instance.js');
|
|
16
|
+
|
|
17
|
+
const mockedAxios = vi.mocked(Axios);
|
|
18
|
+
const mockedAXIOS_DEFAULT = vi.mocked(AXIOS_DEFAULT);
|
|
19
|
+
|
|
20
|
+
// Type for the promise returned by customInstance with cancel functionality
|
|
21
|
+
type CancellablePromise<T> = Promise<T> & {
|
|
22
|
+
cancel: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('v3 customInstance', () => {
|
|
26
|
+
let mockCancelTokenSource: {
|
|
27
|
+
token: string;
|
|
28
|
+
cancel: MockedFunction<() => void>;
|
|
29
|
+
};
|
|
30
|
+
let mockAxiosInstance: MockedFunction<(config: AxiosRequestConfig) => Promise<AxiosResponse<unknown>>>;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
|
|
34
|
+
// Mock for CancelTokenSource
|
|
35
|
+
mockCancelTokenSource = {
|
|
36
|
+
token: 'mock-cancel-token',
|
|
37
|
+
cancel: vi.fn(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Mock for Axios.CancelToken.source
|
|
41
|
+
mockedAxios.CancelToken = {
|
|
42
|
+
source: vi.fn().mockReturnValue(mockCancelTokenSource),
|
|
43
|
+
} as unknown as typeof Axios.CancelToken;
|
|
44
|
+
|
|
45
|
+
// Mock for Axios instance
|
|
46
|
+
mockAxiosInstance = vi.fn().mockResolvedValue({
|
|
47
|
+
data: { result: 'success' },
|
|
48
|
+
status: 200,
|
|
49
|
+
statusText: 'OK',
|
|
50
|
+
headers: {},
|
|
51
|
+
config: {},
|
|
52
|
+
} as AxiosResponse<{ result: string }>);
|
|
53
|
+
|
|
54
|
+
// Mock for AXIOS_DEFAULT
|
|
55
|
+
mockedAXIOS_DEFAULT.instance = mockAxiosInstance as unknown as typeof AXIOS_DEFAULT.instance;
|
|
56
|
+
Object.defineProperty(mockedAXIOS_DEFAULT.instance, 'defaults', {
|
|
57
|
+
value: {
|
|
58
|
+
baseURL: 'http://localhost',
|
|
59
|
+
headers: {
|
|
60
|
+
common: {},
|
|
61
|
+
delete: {},
|
|
62
|
+
get: {},
|
|
63
|
+
head: {},
|
|
64
|
+
post: {},
|
|
65
|
+
put: {},
|
|
66
|
+
patch: {},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
writable: true,
|
|
70
|
+
configurable: true,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
vi.resetAllMocks();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('Base URL configuration', () => {
|
|
79
|
+
it('should append _api/v3 suffix to default base URL', async () => {
|
|
80
|
+
// Arrange
|
|
81
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
|
|
82
|
+
|
|
83
|
+
// Act
|
|
84
|
+
await customInstance(config);
|
|
85
|
+
|
|
86
|
+
// Assert
|
|
87
|
+
expect(mockAxiosInstance).toHaveBeenCalledWith(
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
baseURL: 'http://localhost/_api/v3',
|
|
90
|
+
cancelToken: 'mock-cancel-token',
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should append _api/v3 suffix to baseURL specified in options', async () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
|
|
98
|
+
const options: AxiosRequestConfig = { baseURL: 'https://custom.example.com' };
|
|
99
|
+
|
|
100
|
+
// Act
|
|
101
|
+
await customInstance(config, options);
|
|
102
|
+
|
|
103
|
+
// Assert
|
|
104
|
+
expect(mockAxiosInstance).toHaveBeenCalledWith(
|
|
105
|
+
expect.objectContaining({
|
|
106
|
+
baseURL: 'https://custom.example.com/_api/v3',
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should append _api/v3 suffix to baseURL specified in config', async () => {
|
|
112
|
+
// Arrange
|
|
113
|
+
const config: AxiosRequestConfig = {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
url: '/test',
|
|
116
|
+
baseURL: 'https://config.example.com',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Act
|
|
120
|
+
await customInstance(config);
|
|
121
|
+
|
|
122
|
+
// Assert
|
|
123
|
+
expect(mockAxiosInstance).toHaveBeenCalledWith(
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
baseURL: 'https://config.example.com/_api/v3',
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should set only _api/v3 when baseURL is empty string', async () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
mockedAXIOS_DEFAULT.instance.defaults.baseURL = '';
|
|
133
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
|
|
134
|
+
|
|
135
|
+
// Act
|
|
136
|
+
await customInstance(config);
|
|
137
|
+
|
|
138
|
+
// Assert
|
|
139
|
+
expect(mockAxiosInstance).toHaveBeenCalledWith(
|
|
140
|
+
expect.objectContaining({
|
|
141
|
+
baseURL: '/_api/v3',
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('Configuration merging', () => {
|
|
148
|
+
it('should correctly merge config and options', async () => {
|
|
149
|
+
// Arrange
|
|
150
|
+
const config: AxiosRequestConfig = {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
url: '/pages',
|
|
153
|
+
data: { title: 'Test Page', body: 'Content' },
|
|
154
|
+
};
|
|
155
|
+
const options: AxiosRequestConfig = {
|
|
156
|
+
headers: { 'X-API-Version': 'v3' },
|
|
157
|
+
timeout: 10000,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Act
|
|
161
|
+
await customInstance(config, options);
|
|
162
|
+
|
|
163
|
+
// Assert
|
|
164
|
+
expect(mockAxiosInstance).toHaveBeenCalledWith(
|
|
165
|
+
expect.objectContaining({
|
|
166
|
+
method: 'POST',
|
|
167
|
+
url: '/pages',
|
|
168
|
+
data: { title: 'Test Page', body: 'Content' },
|
|
169
|
+
headers: { 'X-API-Version': 'v3' },
|
|
170
|
+
timeout: 10000,
|
|
171
|
+
baseURL: 'http://localhost/_api/v3',
|
|
172
|
+
cancelToken: 'mock-cancel-token',
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should override config values with options values', async () => {
|
|
178
|
+
// Arrange
|
|
179
|
+
const config: AxiosRequestConfig = {
|
|
180
|
+
method: 'GET',
|
|
181
|
+
timeout: 1000,
|
|
182
|
+
headers: { 'Content-Type': 'application/json' },
|
|
183
|
+
};
|
|
184
|
+
const options: AxiosRequestConfig = {
|
|
185
|
+
timeout: 5000,
|
|
186
|
+
headers: { 'Content-Type': 'application/xml' },
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Act
|
|
190
|
+
await customInstance(config, options);
|
|
191
|
+
|
|
192
|
+
// Assert
|
|
193
|
+
expect(mockAxiosInstance).toHaveBeenCalledWith(
|
|
194
|
+
expect.objectContaining({
|
|
195
|
+
timeout: 5000,
|
|
196
|
+
headers: { 'Content-Type': 'application/xml' },
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('Response processing', () => {
|
|
203
|
+
it('should return the data property from response', async () => {
|
|
204
|
+
// Arrange
|
|
205
|
+
const expectedData = {
|
|
206
|
+
pages: [
|
|
207
|
+
{ id: '1', title: 'Page 1', body: 'Content 1' },
|
|
208
|
+
{ id: '2', title: 'Page 2', body: 'Content 2' },
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
mockAxiosInstance.mockResolvedValue({
|
|
212
|
+
data: expectedData,
|
|
213
|
+
status: 200,
|
|
214
|
+
statusText: 'OK',
|
|
215
|
+
headers: {},
|
|
216
|
+
config: {},
|
|
217
|
+
} as AxiosResponse<{
|
|
218
|
+
pages: Array<{ id: string; title: string; body: string }>;
|
|
219
|
+
}>);
|
|
220
|
+
|
|
221
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/pages' };
|
|
222
|
+
|
|
223
|
+
// Act
|
|
224
|
+
const result = await customInstance(config);
|
|
225
|
+
|
|
226
|
+
// Assert
|
|
227
|
+
expect(result).toEqual(expectedData);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should handle empty response data correctly', async () => {
|
|
231
|
+
// Arrange
|
|
232
|
+
const expectedData = null;
|
|
233
|
+
mockAxiosInstance.mockResolvedValue({
|
|
234
|
+
data: expectedData,
|
|
235
|
+
status: 204,
|
|
236
|
+
statusText: 'No Content',
|
|
237
|
+
headers: {},
|
|
238
|
+
config: {},
|
|
239
|
+
} as AxiosResponse<null>);
|
|
240
|
+
|
|
241
|
+
const config: AxiosRequestConfig = { method: 'DELETE', url: '/pages/1' };
|
|
242
|
+
|
|
243
|
+
// Act
|
|
244
|
+
const result = await customInstance(config);
|
|
245
|
+
|
|
246
|
+
// Assert
|
|
247
|
+
expect(result).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('Cancellation functionality', () => {
|
|
252
|
+
it('should add cancel function to returned Promise', async () => {
|
|
253
|
+
// Arrange
|
|
254
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
|
|
255
|
+
|
|
256
|
+
// Act
|
|
257
|
+
const promise = customInstance(config) as CancellablePromise<unknown>;
|
|
258
|
+
|
|
259
|
+
// Assert
|
|
260
|
+
expect(promise).toHaveProperty('cancel');
|
|
261
|
+
expect(typeof promise.cancel).toBe('function');
|
|
262
|
+
|
|
263
|
+
// cleanup
|
|
264
|
+
await promise;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should call CancelTokenSource cancel when cancel function is invoked', async () => {
|
|
268
|
+
// Arrange
|
|
269
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
|
|
270
|
+
|
|
271
|
+
// Act
|
|
272
|
+
const promise = customInstance(config) as CancellablePromise<unknown>;
|
|
273
|
+
promise.cancel();
|
|
274
|
+
|
|
275
|
+
// Assert
|
|
276
|
+
expect(mockCancelTokenSource.cancel).toHaveBeenCalledWith('Query cancelled');
|
|
277
|
+
|
|
278
|
+
// cleanup
|
|
279
|
+
await promise;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should allow cancellation during request execution', async () => {
|
|
283
|
+
// Arrange
|
|
284
|
+
let resolveRequest: (value: unknown) => void = () => {};
|
|
285
|
+
const requestPromise = new Promise((resolve) => {
|
|
286
|
+
resolveRequest = resolve;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
mockAxiosInstance.mockImplementation(() => requestPromise);
|
|
290
|
+
|
|
291
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/long-request' };
|
|
292
|
+
|
|
293
|
+
// Act
|
|
294
|
+
const promise = customInstance(config) as CancellablePromise<unknown>;
|
|
295
|
+
promise.cancel();
|
|
296
|
+
|
|
297
|
+
// Assert
|
|
298
|
+
expect(mockCancelTokenSource.cancel).toHaveBeenCalledWith('Query cancelled');
|
|
299
|
+
|
|
300
|
+
// cleanup
|
|
301
|
+
if (resolveRequest) {
|
|
302
|
+
resolveRequest({ data: 'test' });
|
|
303
|
+
}
|
|
304
|
+
await promise;
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('Error handling', () => {
|
|
309
|
+
it('should reject Promise when Axios error occurs', async () => {
|
|
310
|
+
// Arrange
|
|
311
|
+
const error = new Error('API v3 endpoint not found');
|
|
312
|
+
mockAxiosInstance.mockRejectedValue(error);
|
|
313
|
+
|
|
314
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/invalid' };
|
|
315
|
+
|
|
316
|
+
// Act & Assert
|
|
317
|
+
await expect(customInstance(config)).rejects.toThrow('API v3 endpoint not found');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should handle network errors appropriately', async () => {
|
|
321
|
+
// Arrange
|
|
322
|
+
const networkError = new Error('Network Error');
|
|
323
|
+
networkError.name = 'NetworkError';
|
|
324
|
+
mockAxiosInstance.mockRejectedValue(networkError);
|
|
325
|
+
|
|
326
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
|
|
327
|
+
|
|
328
|
+
// Act & Assert
|
|
329
|
+
await expect(customInstance(config)).rejects.toThrow('Network Error');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('Type safety', () => {
|
|
334
|
+
it('should apply generic types correctly', async () => {
|
|
335
|
+
// Arrange
|
|
336
|
+
interface User {
|
|
337
|
+
id: string;
|
|
338
|
+
name: string;
|
|
339
|
+
email: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const expectedUser: User = {
|
|
343
|
+
id: '1',
|
|
344
|
+
name: 'John Doe',
|
|
345
|
+
email: 'john@example.com',
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
mockAxiosInstance.mockResolvedValue({
|
|
349
|
+
data: expectedUser,
|
|
350
|
+
status: 200,
|
|
351
|
+
statusText: 'OK',
|
|
352
|
+
headers: {},
|
|
353
|
+
config: {},
|
|
354
|
+
} as AxiosResponse<User>);
|
|
355
|
+
|
|
356
|
+
const config: AxiosRequestConfig = { method: 'GET', url: '/user/1' };
|
|
357
|
+
|
|
358
|
+
// Act
|
|
359
|
+
const result = await customInstance<User>(config);
|
|
360
|
+
|
|
361
|
+
// Assert
|
|
362
|
+
expect(result).toEqual(expectedUser);
|
|
363
|
+
// TypeScript type checking ensures result is treated as User type
|
|
364
|
+
expect(result.id).toBe('1');
|
|
365
|
+
expect(result.name).toBe('John Doe');
|
|
366
|
+
expect(result.email).toBe('john@example.com');
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|