@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.
@@ -0,0 +1,108 @@
1
+ import type { AxiosInstance } from 'axios';
2
+ /**
3
+ * Tests for axios-default-instance.ts
4
+ *
5
+ * This file manages the default Axios instance for GROWI SDK,
6
+ * providing functionality to configure base URL and authentication headers.
7
+ */
8
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
9
+ import { AXIOS_DEFAULT } from './axios-default-instance.js';
10
+
11
+ describe('AXIOS_DEFAULT', () => {
12
+ beforeEach(() => {
13
+ // Reset the instance before each test
14
+ AXIOS_DEFAULT.instance.defaults.baseURL = 'http://localhost';
15
+ AXIOS_DEFAULT.instance.defaults.headers.common.Authorization = undefined;
16
+ });
17
+
18
+ describe('setBaseURL', () => {
19
+ it('should update base URL when setting a valid URL', () => {
20
+ // Arrange
21
+ const newBaseURL = 'https://api.example.com';
22
+
23
+ // Act
24
+ AXIOS_DEFAULT.setBaseURL(newBaseURL);
25
+
26
+ // Assert
27
+ expect(AXIOS_DEFAULT.instance.defaults.baseURL).toBe(newBaseURL);
28
+ });
29
+
30
+ it('should set base URL to empty string when empty string is provided', () => {
31
+ // Arrange
32
+ const emptyURL = '';
33
+
34
+ // Act
35
+ AXIOS_DEFAULT.setBaseURL(emptyURL);
36
+
37
+ // Assert
38
+ expect(AXIOS_DEFAULT.instance.defaults.baseURL).toBe(emptyURL);
39
+ });
40
+
41
+ it('should use the last value when called multiple times', () => {
42
+ // Arrange
43
+ const firstURL = 'https://first.example.com';
44
+ const secondURL = 'https://second.example.com';
45
+
46
+ // Act
47
+ AXIOS_DEFAULT.setBaseURL(firstURL);
48
+ AXIOS_DEFAULT.setBaseURL(secondURL);
49
+
50
+ // Assert
51
+ expect(AXIOS_DEFAULT.instance.defaults.baseURL).toBe(secondURL);
52
+ });
53
+ });
54
+
55
+ describe('setAuthorizationHeader', () => {
56
+ it('should set authorization header in Bearer token format when valid token is provided', () => {
57
+ // Arrange
58
+ const token = 'test-token-123';
59
+ const expectedHeader = `Bearer ${token}`;
60
+
61
+ // Act
62
+ AXIOS_DEFAULT.setAuthorizationHeader(token);
63
+
64
+ // Assert
65
+ expect(AXIOS_DEFAULT.instance.defaults.headers.common.Authorization).toBe(expectedHeader);
66
+ });
67
+
68
+ it('should set Bearer header with empty string when empty token is provided', () => {
69
+ // Arrange
70
+ const emptyToken = '';
71
+ const expectedHeader = 'Bearer ';
72
+
73
+ // Act
74
+ AXIOS_DEFAULT.setAuthorizationHeader(emptyToken);
75
+
76
+ // Assert
77
+ expect(AXIOS_DEFAULT.instance.defaults.headers.common.Authorization).toBe(expectedHeader);
78
+ });
79
+
80
+ it('should use the last token when called multiple times', () => {
81
+ // Arrange
82
+ const firstToken = 'first-token';
83
+ const secondToken = 'second-token';
84
+ const expectedHeader = `Bearer ${secondToken}`;
85
+
86
+ // Act
87
+ AXIOS_DEFAULT.setAuthorizationHeader(firstToken);
88
+ AXIOS_DEFAULT.setAuthorizationHeader(secondToken);
89
+
90
+ // Assert
91
+ expect(AXIOS_DEFAULT.instance.defaults.headers.common.Authorization).toBe(expectedHeader);
92
+ });
93
+ });
94
+
95
+ describe('instance', () => {
96
+ it('should have default base URL set to localhost', () => {
97
+ // Assert
98
+ expect(AXIOS_DEFAULT.instance.defaults.baseURL).toBe('http://localhost');
99
+ });
100
+
101
+ it('should have Axios instance properly created', () => {
102
+ // Assert
103
+ expect(AXIOS_DEFAULT.instance).toBeDefined();
104
+ expect(typeof AXIOS_DEFAULT.instance).toBe('function');
105
+ expect(AXIOS_DEFAULT.instance.defaults).toBeDefined();
106
+ });
107
+ });
108
+ });
@@ -0,0 +1,250 @@
1
+ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2
+ import Axios from 'axios';
3
+ /**
4
+ * Tests for v1/axios-instance.ts
5
+ *
6
+ * This file provides custom Axios instance creation functionality for GROWI API v1,
7
+ * including automatic base URL configuration, 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('v1 customInstance', () => {
26
+ let mockCancelTokenSource: {
27
+ token: string;
28
+ cancel: MockedFunction<() => void>;
29
+ };
30
+ let mockAxiosInstance: MockedFunction<(config: AxiosRequestConfig) => Promise<AxiosResponse<unknown>>>;
31
+
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+
35
+ // Mock for CancelTokenSource
36
+ mockCancelTokenSource = {
37
+ token: 'mock-cancel-token',
38
+ cancel: vi.fn(),
39
+ };
40
+
41
+ // Mock for Axios.CancelToken.source
42
+ mockedAxios.CancelToken = {
43
+ source: vi.fn().mockReturnValue(mockCancelTokenSource),
44
+ } as unknown as typeof Axios.CancelToken;
45
+
46
+ // Mock for Axios instance
47
+ mockAxiosInstance = vi.fn().mockResolvedValue({
48
+ data: { result: 'success' },
49
+ status: 200,
50
+ statusText: 'OK',
51
+ headers: {},
52
+ config: {},
53
+ } as AxiosResponse<{ result: string }>);
54
+
55
+ // Mock for AXIOS_DEFAULT
56
+ mockedAXIOS_DEFAULT.instance = mockAxiosInstance as unknown as typeof AXIOS_DEFAULT.instance;
57
+ Object.defineProperty(mockedAXIOS_DEFAULT.instance, 'defaults', {
58
+ value: {
59
+ baseURL: 'http://localhost',
60
+ headers: {
61
+ common: {},
62
+ delete: {},
63
+ get: {},
64
+ head: {},
65
+ post: {},
66
+ put: {},
67
+ patch: {},
68
+ },
69
+ },
70
+ writable: true,
71
+ configurable: true,
72
+ });
73
+ });
74
+
75
+ afterEach(() => {
76
+ vi.resetAllMocks();
77
+ });
78
+
79
+ describe('Base URL configuration', () => {
80
+ it('should append _api suffix to default base URL', async () => {
81
+ // Arrange
82
+ const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
83
+
84
+ // Act
85
+ await customInstance(config);
86
+
87
+ // Assert
88
+ expect(mockAxiosInstance).toHaveBeenCalledWith(
89
+ expect.objectContaining({
90
+ baseURL: 'http://localhost/_api',
91
+ cancelToken: 'mock-cancel-token',
92
+ }),
93
+ );
94
+ });
95
+
96
+ it('should prioritize baseURL specified in options', async () => {
97
+ // Arrange
98
+ const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
99
+ const options: AxiosRequestConfig = { baseURL: 'https://custom.example.com' };
100
+
101
+ // Act
102
+ await customInstance(config, options);
103
+
104
+ // Assert
105
+ expect(mockAxiosInstance).toHaveBeenCalledWith(
106
+ expect.objectContaining({
107
+ baseURL: 'https://custom.example.com/_api',
108
+ }),
109
+ );
110
+ });
111
+
112
+ it('should use baseURL specified in config when provided', async () => {
113
+ // Arrange
114
+ const config: AxiosRequestConfig = {
115
+ method: 'GET',
116
+ url: '/test',
117
+ baseURL: 'https://config.example.com',
118
+ };
119
+
120
+ // Act
121
+ await customInstance(config);
122
+
123
+ // Assert
124
+ expect(mockAxiosInstance).toHaveBeenCalledWith(
125
+ expect.objectContaining({
126
+ baseURL: 'https://config.example.com/_api',
127
+ }),
128
+ );
129
+ });
130
+ });
131
+
132
+ describe('Configuration merging', () => {
133
+ it('should correctly merge config and options', async () => {
134
+ // Arrange
135
+ const config: AxiosRequestConfig = {
136
+ method: 'POST',
137
+ url: '/test',
138
+ data: { name: 'test' },
139
+ };
140
+ const options: AxiosRequestConfig = {
141
+ headers: { 'Custom-Header': 'value' },
142
+ timeout: 5000,
143
+ };
144
+
145
+ // Act
146
+ await customInstance(config, options);
147
+
148
+ // Assert
149
+ expect(mockAxiosInstance).toHaveBeenCalledWith(
150
+ expect.objectContaining({
151
+ method: 'POST',
152
+ url: '/test',
153
+ data: { name: 'test' },
154
+ headers: { 'Custom-Header': 'value' },
155
+ timeout: 5000,
156
+ baseURL: 'http://localhost/_api',
157
+ cancelToken: 'mock-cancel-token',
158
+ }),
159
+ );
160
+ });
161
+
162
+ it('should override config values with options values', async () => {
163
+ // Arrange
164
+ const config: AxiosRequestConfig = {
165
+ method: 'GET',
166
+ timeout: 1000,
167
+ };
168
+ const options: AxiosRequestConfig = {
169
+ timeout: 3000,
170
+ };
171
+
172
+ // Act
173
+ await customInstance(config, options);
174
+
175
+ // Assert
176
+ expect(mockAxiosInstance).toHaveBeenCalledWith(
177
+ expect.objectContaining({
178
+ timeout: 3000,
179
+ }),
180
+ );
181
+ });
182
+ });
183
+
184
+ describe('Response processing', () => {
185
+ it('should return the data property from response', async () => {
186
+ // Arrange
187
+ const expectedData = { users: [{ id: 1, name: 'John' }] };
188
+ mockAxiosInstance.mockResolvedValue({
189
+ data: expectedData,
190
+ status: 200,
191
+ statusText: 'OK',
192
+ headers: {},
193
+ config: {},
194
+ } as AxiosResponse<{ users: Array<{ id: number; name: string }> }>);
195
+
196
+ const config: AxiosRequestConfig = { method: 'GET', url: '/users' };
197
+
198
+ // Act
199
+ const result = await customInstance(config);
200
+
201
+ // Assert
202
+ expect(result).toEqual(expectedData);
203
+ });
204
+ });
205
+
206
+ describe('Cancellation functionality', () => {
207
+ it('should add cancel function to returned Promise', async () => {
208
+ // Arrange
209
+ const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
210
+
211
+ // Act
212
+ const promise = customInstance(config) as CancellablePromise<unknown>;
213
+
214
+ // Assert
215
+ expect(promise).toHaveProperty('cancel');
216
+ expect(typeof promise.cancel).toBe('function');
217
+
218
+ // cleanup
219
+ await promise;
220
+ });
221
+
222
+ it('should call CancelTokenSource cancel when cancel function is invoked', async () => {
223
+ // Arrange
224
+ const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
225
+
226
+ // Act
227
+ const promise = customInstance(config) as CancellablePromise<unknown>;
228
+ promise.cancel();
229
+
230
+ // Assert
231
+ expect(mockCancelTokenSource.cancel).toHaveBeenCalledWith('Query cancelled');
232
+
233
+ // cleanup
234
+ await promise;
235
+ });
236
+ });
237
+
238
+ describe('Error handling', () => {
239
+ it('should reject Promise when Axios error occurs', async () => {
240
+ // Arrange
241
+ const error = new Error('Network error');
242
+ mockAxiosInstance.mockRejectedValue(error);
243
+
244
+ const config: AxiosRequestConfig = { method: 'GET', url: '/test' };
245
+
246
+ // Act & Assert
247
+ await expect(customInstance(config)).rejects.toThrow('Network error');
248
+ });
249
+ });
250
+ });