@rooguys/sdk 0.1.0 → 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/README.md +478 -113
- package/dist/__tests__/utils/mockClient.d.ts +65 -3
- package/dist/__tests__/utils/mockClient.js +144 -5
- package/dist/errors.d.ts +123 -0
- package/dist/errors.js +163 -0
- package/dist/http-client.d.ts +167 -0
- package/dist/http-client.js +250 -0
- package/dist/index.d.ts +160 -10
- package/dist/index.js +585 -146
- package/dist/types.d.ts +372 -50
- package/dist/types.js +21 -0
- package/package.json +1 -1
- package/src/__tests__/property/request-construction.property.test.ts +142 -91
- package/src/__tests__/property/response-parsing.property.test.ts +118 -67
- package/src/__tests__/property/sdk-modules.property.test.ts +450 -0
- package/src/__tests__/unit/aha.test.ts +61 -50
- package/src/__tests__/unit/badges.test.ts +27 -33
- package/src/__tests__/unit/config.test.ts +94 -126
- package/src/__tests__/unit/errors.test.ts +106 -150
- package/src/__tests__/unit/events.test.ts +119 -144
- package/src/__tests__/unit/leaderboards.test.ts +173 -40
- package/src/__tests__/unit/levels.test.ts +25 -33
- package/src/__tests__/unit/questionnaires.test.ts +33 -42
- package/src/__tests__/unit/users.test.ts +214 -99
- package/src/__tests__/utils/mockClient.ts +193 -6
- package/src/errors.ts +255 -0
- package/src/http-client.ts +433 -0
- package/src/index.ts +742 -150
- package/src/types.ts +429 -51
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { Rooguys, ValidationError } from '../../index';
|
|
2
|
+
import {
|
|
3
|
+
createMockRooguysClient,
|
|
4
|
+
setupMockRequest,
|
|
5
|
+
setupMockRequestError,
|
|
6
|
+
expectRequestWith,
|
|
7
|
+
MockAxiosInstance,
|
|
8
|
+
} from '../utils/mockClient';
|
|
4
9
|
import { mockResponses, mockErrors } from '../fixtures/responses';
|
|
5
10
|
|
|
6
|
-
jest.mock('axios');
|
|
7
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
8
|
-
|
|
9
11
|
describe('Users Resource', () => {
|
|
10
12
|
let client: Rooguys;
|
|
11
|
-
let
|
|
12
|
-
const apiKey = 'test-api-key';
|
|
13
|
+
let mockAxios: MockAxiosInstance;
|
|
13
14
|
|
|
14
15
|
beforeEach(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
jest.clearAllMocks();
|
|
16
|
+
const mock = createMockRooguysClient();
|
|
17
|
+
client = mock.client;
|
|
18
|
+
mockAxios = mock.mockAxios;
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
describe('get', () => {
|
|
22
22
|
it('should get a user profile', async () => {
|
|
23
|
-
|
|
24
|
-
mockSuccessResponse(mockResponses.userProfile)
|
|
25
|
-
);
|
|
23
|
+
setupMockRequest(mockAxios, mockResponses.userProfile);
|
|
26
24
|
|
|
27
|
-
const result = await client.users.get('
|
|
25
|
+
const result = await client.users.get('user123');
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
expectRequestWith(mockAxios, {
|
|
28
|
+
method: 'GET',
|
|
29
|
+
url: '/users/user123',
|
|
30
|
+
});
|
|
31
31
|
expect(result.user_id).toBe('user123');
|
|
32
32
|
expect(result.points).toBe(100);
|
|
33
33
|
});
|
|
@@ -38,7 +38,7 @@ describe('Users Resource', () => {
|
|
|
38
38
|
level: null,
|
|
39
39
|
next_level: null,
|
|
40
40
|
};
|
|
41
|
-
|
|
41
|
+
setupMockRequest(mockAxios, userWithoutLevel);
|
|
42
42
|
|
|
43
43
|
const result = await client.users.get('user_new');
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ describe('Users Resource', () => {
|
|
|
51
51
|
...mockResponses.userProfile,
|
|
52
52
|
badges: [],
|
|
53
53
|
};
|
|
54
|
-
|
|
54
|
+
setupMockRequest(mockAxios, userWithoutBadges);
|
|
55
55
|
|
|
56
56
|
const result = await client.users.get('user_123');
|
|
57
57
|
|
|
@@ -59,9 +59,7 @@ describe('Users Resource', () => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('should throw 404 error when user not found', async () => {
|
|
62
|
-
|
|
63
|
-
mockErrorResponse(404, mockErrors.notFoundError.message)
|
|
64
|
-
);
|
|
62
|
+
setupMockRequestError(mockAxios, 404, "User 'user123' does not exist in this project");
|
|
65
63
|
|
|
66
64
|
await expect(client.users.get('nonexistent_user')).rejects.toThrow(
|
|
67
65
|
"User 'user123' does not exist in this project"
|
|
@@ -69,35 +67,154 @@ describe('Users Resource', () => {
|
|
|
69
67
|
});
|
|
70
68
|
|
|
71
69
|
it('should handle special characters in user ID', async () => {
|
|
72
|
-
|
|
73
|
-
mockSuccessResponse(mockResponses.userProfile)
|
|
74
|
-
);
|
|
70
|
+
setupMockRequest(mockAxios, mockResponses.userProfile);
|
|
75
71
|
|
|
76
72
|
await client.users.get('user@example.com');
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
expectRequestWith(mockAxios, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
url: '/users/user%40example.com',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should support field selection', async () => {
|
|
81
|
+
setupMockRequest(mockAxios, mockResponses.userProfile);
|
|
82
|
+
|
|
83
|
+
await client.users.get('user123', { fields: ['points', 'level'] });
|
|
84
|
+
|
|
85
|
+
expectRequestWith(mockAxios, {
|
|
86
|
+
method: 'GET',
|
|
87
|
+
url: '/users/user123',
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('create', () => {
|
|
93
|
+
it('should create a new user', async () => {
|
|
94
|
+
setupMockRequest(mockAxios, mockResponses.userProfile);
|
|
95
|
+
|
|
96
|
+
const result = await client.users.create({
|
|
97
|
+
userId: 'new_user',
|
|
98
|
+
displayName: 'New User',
|
|
99
|
+
email: 'new@example.com',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expectRequestWith(mockAxios, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
url: '/users',
|
|
105
|
+
data: {
|
|
106
|
+
user_id: 'new_user',
|
|
107
|
+
display_name: 'New User',
|
|
108
|
+
email: 'new@example.com',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
expect(result).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should throw error for missing user ID', async () => {
|
|
115
|
+
await expect(client.users.create({} as any)).rejects.toThrow(ValidationError);
|
|
116
|
+
await expect(client.users.create({} as any)).rejects.toThrow('User ID is required');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should throw error for invalid email format', async () => {
|
|
120
|
+
await expect(
|
|
121
|
+
client.users.create({ userId: 'user1', email: 'invalid-email' })
|
|
122
|
+
).rejects.toThrow(ValidationError);
|
|
123
|
+
await expect(
|
|
124
|
+
client.users.create({ userId: 'user1', email: 'invalid-email' })
|
|
125
|
+
).rejects.toThrow('Invalid email format');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('update', () => {
|
|
130
|
+
it('should update an existing user', async () => {
|
|
131
|
+
setupMockRequest(mockAxios, mockResponses.userProfile);
|
|
132
|
+
|
|
133
|
+
const result = await client.users.update('user123', {
|
|
134
|
+
displayName: 'Updated Name',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expectRequestWith(mockAxios, {
|
|
138
|
+
method: 'PATCH',
|
|
139
|
+
url: '/users/user123',
|
|
140
|
+
data: {
|
|
141
|
+
display_name: 'Updated Name',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
expect(result).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should throw error for missing user ID', async () => {
|
|
148
|
+
await expect(client.users.update('', { displayName: 'Test' })).rejects.toThrow(ValidationError);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should throw error for invalid email format', async () => {
|
|
152
|
+
await expect(
|
|
153
|
+
client.users.update('user123', { email: 'invalid-email' })
|
|
154
|
+
).rejects.toThrow(ValidationError);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('createBatch', () => {
|
|
159
|
+
it('should create multiple users', async () => {
|
|
160
|
+
const batchResponse = { created: 2, failed: 0 };
|
|
161
|
+
setupMockRequest(mockAxios, batchResponse);
|
|
162
|
+
|
|
163
|
+
const users = [
|
|
164
|
+
{ userId: 'user1', displayName: 'User 1' },
|
|
165
|
+
{ userId: 'user2', displayName: 'User 2' },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const result = await client.users.createBatch(users);
|
|
169
|
+
|
|
170
|
+
expectRequestWith(mockAxios, {
|
|
171
|
+
method: 'POST',
|
|
172
|
+
url: '/users/batch',
|
|
173
|
+
});
|
|
174
|
+
expect(result).toEqual(batchResponse);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should throw error for empty users array', async () => {
|
|
178
|
+
await expect(client.users.createBatch([])).rejects.toThrow(ValidationError);
|
|
179
|
+
await expect(client.users.createBatch([])).rejects.toThrow('cannot be empty');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should throw error for more than 100 users', async () => {
|
|
183
|
+
const manyUsers = Array.from({ length: 101 }, (_, i) => ({
|
|
184
|
+
userId: `user_${i}`,
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
await expect(client.users.createBatch(manyUsers)).rejects.toThrow(ValidationError);
|
|
188
|
+
await expect(client.users.createBatch(manyUsers)).rejects.toThrow('maximum of 100');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should throw error for user without userId', async () => {
|
|
192
|
+
const users = [{ displayName: 'User 1' }] as any;
|
|
193
|
+
|
|
194
|
+
await expect(client.users.createBatch(users)).rejects.toThrow(ValidationError);
|
|
195
|
+
await expect(client.users.createBatch(users)).rejects.toThrow('User ID is required');
|
|
79
196
|
});
|
|
80
197
|
});
|
|
81
198
|
|
|
82
199
|
describe('getBulk', () => {
|
|
83
200
|
it('should get multiple user profiles', async () => {
|
|
84
|
-
|
|
85
|
-
mockSuccessResponse(mockResponses.bulkUsersResponse)
|
|
86
|
-
);
|
|
201
|
+
setupMockRequest(mockAxios, mockResponses.bulkUsersResponse);
|
|
87
202
|
|
|
88
203
|
const result = await client.users.getBulk(['user1', 'user2']);
|
|
89
204
|
|
|
90
|
-
|
|
91
|
-
|
|
205
|
+
expectRequestWith(mockAxios, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
url: '/users/bulk',
|
|
208
|
+
data: {
|
|
209
|
+
user_ids: ['user1', 'user2'],
|
|
210
|
+
},
|
|
92
211
|
});
|
|
93
212
|
expect(result).toEqual(mockResponses.bulkUsersResponse);
|
|
94
213
|
expect(result.users).toHaveLength(2);
|
|
95
214
|
});
|
|
96
215
|
|
|
97
216
|
it('should handle single user in bulk request', async () => {
|
|
98
|
-
|
|
99
|
-
mockSuccessResponse({ users: [mockResponses.userProfile] })
|
|
100
|
-
);
|
|
217
|
+
setupMockRequest(mockAxios, { users: [mockResponses.userProfile] });
|
|
101
218
|
|
|
102
219
|
const result = await client.users.getBulk(['user_123']);
|
|
103
220
|
|
|
@@ -105,45 +222,30 @@ describe('Users Resource', () => {
|
|
|
105
222
|
});
|
|
106
223
|
|
|
107
224
|
it('should handle empty results', async () => {
|
|
108
|
-
|
|
109
|
-
mockSuccessResponse({ users: [] })
|
|
110
|
-
);
|
|
225
|
+
setupMockRequest(mockAxios, { users: [] });
|
|
111
226
|
|
|
112
227
|
const result = await client.users.getBulk(['nonexistent1', 'nonexistent2']);
|
|
113
228
|
|
|
114
229
|
expect(result.users).toEqual([]);
|
|
115
230
|
});
|
|
116
|
-
|
|
117
|
-
it('should throw error for more than 100 users', async () => {
|
|
118
|
-
mockAxiosInstance.post.mockRejectedValue(
|
|
119
|
-
mockErrorResponse(400, 'Maximum 100 user IDs allowed')
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
const manyUsers = Array.from({ length: 101 }, (_, i) => `user_${i}`);
|
|
123
|
-
|
|
124
|
-
await expect(client.users.getBulk(manyUsers)).rejects.toThrow(
|
|
125
|
-
'Maximum 100 user IDs allowed'
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
231
|
});
|
|
129
232
|
|
|
130
233
|
describe('getBadges', () => {
|
|
131
234
|
it('should get user badges', async () => {
|
|
132
|
-
|
|
133
|
-
mockSuccessResponse({ badges: mockResponses.userProfile.badges })
|
|
134
|
-
);
|
|
235
|
+
setupMockRequest(mockAxios, { badges: mockResponses.userProfile.badges });
|
|
135
236
|
|
|
136
237
|
const result = await client.users.getBadges('user_123');
|
|
137
238
|
|
|
138
|
-
|
|
239
|
+
expectRequestWith(mockAxios, {
|
|
240
|
+
method: 'GET',
|
|
241
|
+
url: '/users/user_123/badges',
|
|
242
|
+
});
|
|
139
243
|
expect(result.badges).toHaveLength(1);
|
|
140
244
|
expect(result.badges[0].name).toBe('First Steps');
|
|
141
245
|
});
|
|
142
246
|
|
|
143
247
|
it('should handle user with no badges', async () => {
|
|
144
|
-
|
|
145
|
-
mockSuccessResponse({ badges: [] })
|
|
146
|
-
);
|
|
248
|
+
setupMockRequest(mockAxios, { badges: [] });
|
|
147
249
|
|
|
148
250
|
const result = await client.users.getBadges('user_new');
|
|
149
251
|
|
|
@@ -151,9 +253,7 @@ describe('Users Resource', () => {
|
|
|
151
253
|
});
|
|
152
254
|
|
|
153
255
|
it('should throw 404 error when user not found', async () => {
|
|
154
|
-
|
|
155
|
-
mockErrorResponse(404, mockErrors.notFoundError.message)
|
|
156
|
-
);
|
|
256
|
+
setupMockRequestError(mockAxios, 404, "User 'user123' does not exist in this project");
|
|
157
257
|
|
|
158
258
|
await expect(client.users.getBadges('nonexistent_user')).rejects.toThrow(
|
|
159
259
|
"User 'user123' does not exist in this project"
|
|
@@ -163,47 +263,41 @@ describe('Users Resource', () => {
|
|
|
163
263
|
|
|
164
264
|
describe('getRank', () => {
|
|
165
265
|
it('should get user rank with default timeframe', async () => {
|
|
166
|
-
|
|
167
|
-
mockSuccessResponse(mockResponses.userRankResponse)
|
|
168
|
-
);
|
|
266
|
+
setupMockRequest(mockAxios, mockResponses.userRankResponse);
|
|
169
267
|
|
|
170
268
|
const result = await client.users.getRank('user_123');
|
|
171
269
|
|
|
172
|
-
|
|
173
|
-
|
|
270
|
+
expectRequestWith(mockAxios, {
|
|
271
|
+
method: 'GET',
|
|
272
|
+
url: '/users/user_123/rank',
|
|
174
273
|
});
|
|
175
|
-
expect(result).toEqual(mockResponses.userRankResponse);
|
|
176
274
|
expect(result.rank).toBe(42);
|
|
177
275
|
});
|
|
178
276
|
|
|
179
277
|
it('should get user rank with weekly timeframe', async () => {
|
|
180
|
-
|
|
181
|
-
mockSuccessResponse(mockResponses.userRankResponse)
|
|
182
|
-
);
|
|
278
|
+
setupMockRequest(mockAxios, mockResponses.userRankResponse);
|
|
183
279
|
|
|
184
280
|
await client.users.getRank('user_123', 'weekly');
|
|
185
281
|
|
|
186
|
-
|
|
187
|
-
|
|
282
|
+
expectRequestWith(mockAxios, {
|
|
283
|
+
method: 'GET',
|
|
284
|
+
url: '/users/user_123/rank',
|
|
188
285
|
});
|
|
189
286
|
});
|
|
190
287
|
|
|
191
288
|
it('should get user rank with monthly timeframe', async () => {
|
|
192
|
-
|
|
193
|
-
mockSuccessResponse(mockResponses.userRankResponse)
|
|
194
|
-
);
|
|
289
|
+
setupMockRequest(mockAxios, mockResponses.userRankResponse);
|
|
195
290
|
|
|
196
291
|
await client.users.getRank('user_123', 'monthly');
|
|
197
292
|
|
|
198
|
-
|
|
199
|
-
|
|
293
|
+
expectRequestWith(mockAxios, {
|
|
294
|
+
method: 'GET',
|
|
295
|
+
url: '/users/user_123/rank',
|
|
200
296
|
});
|
|
201
297
|
});
|
|
202
298
|
|
|
203
299
|
it('should throw 404 error when user not found', async () => {
|
|
204
|
-
|
|
205
|
-
mockErrorResponse(404, mockErrors.notFoundError.message)
|
|
206
|
-
);
|
|
300
|
+
setupMockRequestError(mockAxios, 404, "User 'user123' does not exist in this project");
|
|
207
301
|
|
|
208
302
|
await expect(client.users.getRank('nonexistent_user')).rejects.toThrow(
|
|
209
303
|
"User 'user123' does not exist in this project"
|
|
@@ -213,9 +307,7 @@ describe('Users Resource', () => {
|
|
|
213
307
|
|
|
214
308
|
describe('submitAnswers', () => {
|
|
215
309
|
it('should submit questionnaire answers', async () => {
|
|
216
|
-
|
|
217
|
-
mockSuccessResponse(mockResponses.answerSubmissionResponse)
|
|
218
|
-
);
|
|
310
|
+
setupMockRequest(mockAxios, mockResponses.answerSubmissionResponse);
|
|
219
311
|
|
|
220
312
|
const answers = [
|
|
221
313
|
{ question_id: 'q1', answer_option_id: 'a1' },
|
|
@@ -228,30 +320,30 @@ describe('Users Resource', () => {
|
|
|
228
320
|
answers
|
|
229
321
|
);
|
|
230
322
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
answers,
|
|
323
|
+
expectRequestWith(mockAxios, {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
url: '/users/user_123/answers',
|
|
326
|
+
data: {
|
|
327
|
+
questionnaire_id: 'questionnaire_id',
|
|
328
|
+
answers,
|
|
329
|
+
},
|
|
234
330
|
});
|
|
235
331
|
expect(result).toEqual(mockResponses.answerSubmissionResponse);
|
|
236
332
|
expect(result.status).toBe('accepted');
|
|
237
333
|
});
|
|
238
334
|
|
|
239
335
|
it('should handle single answer submission', async () => {
|
|
240
|
-
|
|
241
|
-
mockSuccessResponse(mockResponses.answerSubmissionResponse)
|
|
242
|
-
);
|
|
336
|
+
setupMockRequest(mockAxios, mockResponses.answerSubmissionResponse);
|
|
243
337
|
|
|
244
338
|
const answers = [{ question_id: 'q1', answer_option_id: 'a1' }];
|
|
245
339
|
|
|
246
340
|
await client.users.submitAnswers('user_123', 'questionnaire_id', answers);
|
|
247
341
|
|
|
248
|
-
expect(
|
|
342
|
+
expect(mockAxios.request).toHaveBeenCalled();
|
|
249
343
|
});
|
|
250
344
|
|
|
251
345
|
it('should throw error for invalid questionnaire ID', async () => {
|
|
252
|
-
|
|
253
|
-
mockErrorResponse(400, 'Invalid questionnaire ID')
|
|
254
|
-
);
|
|
346
|
+
setupMockRequestError(mockAxios, 400, 'Invalid questionnaire ID');
|
|
255
347
|
|
|
256
348
|
const answers = [{ question_id: 'q1', answer_option_id: 'a1' }];
|
|
257
349
|
|
|
@@ -259,15 +351,38 @@ describe('Users Resource', () => {
|
|
|
259
351
|
client.users.submitAnswers('user_123', 'invalid_id', answers)
|
|
260
352
|
).rejects.toThrow('Invalid questionnaire ID');
|
|
261
353
|
});
|
|
354
|
+
});
|
|
262
355
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
356
|
+
describe('search', () => {
|
|
357
|
+
it('should search users', async () => {
|
|
358
|
+
const searchResponse = {
|
|
359
|
+
users: [mockResponses.userProfile],
|
|
360
|
+
pagination: { page: 1, limit: 50, total: 1, totalPages: 1 },
|
|
361
|
+
};
|
|
362
|
+
setupMockRequest(mockAxios, searchResponse);
|
|
267
363
|
|
|
268
|
-
await
|
|
269
|
-
|
|
270
|
-
|
|
364
|
+
const result = await client.users.search('john');
|
|
365
|
+
|
|
366
|
+
expectRequestWith(mockAxios, {
|
|
367
|
+
method: 'GET',
|
|
368
|
+
url: '/users/search',
|
|
369
|
+
});
|
|
370
|
+
expect(result.users).toHaveLength(1);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should search users with pagination', async () => {
|
|
374
|
+
const searchResponse = {
|
|
375
|
+
users: [],
|
|
376
|
+
pagination: { page: 2, limit: 25, total: 0, totalPages: 0 },
|
|
377
|
+
};
|
|
378
|
+
setupMockRequest(mockAxios, searchResponse);
|
|
379
|
+
|
|
380
|
+
await client.users.search('john', { page: 2, limit: 25 });
|
|
381
|
+
|
|
382
|
+
expectRequestWith(mockAxios, {
|
|
383
|
+
method: 'GET',
|
|
384
|
+
url: '/users/search',
|
|
385
|
+
});
|
|
271
386
|
});
|
|
272
387
|
});
|
|
273
388
|
});
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AxiosResponse } from 'axios';
|
|
2
|
+
import { Rooguys } from '../../index';
|
|
3
|
+
import { HttpClient, ApiResponse, RateLimitInfo } from '../../http-client';
|
|
2
4
|
|
|
3
5
|
export interface MockAxiosInstance {
|
|
4
6
|
get: jest.Mock;
|
|
5
7
|
post: jest.Mock;
|
|
8
|
+
put: jest.Mock;
|
|
9
|
+
patch: jest.Mock;
|
|
10
|
+
delete: jest.Mock;
|
|
11
|
+
request: jest.Mock;
|
|
6
12
|
defaults: {
|
|
7
13
|
headers: {
|
|
8
14
|
common: Record<string, string>;
|
|
@@ -18,6 +24,10 @@ export function createMockAxiosInstance(): MockAxiosInstance {
|
|
|
18
24
|
return {
|
|
19
25
|
get: jest.fn(),
|
|
20
26
|
post: jest.fn(),
|
|
27
|
+
put: jest.fn(),
|
|
28
|
+
patch: jest.fn(),
|
|
29
|
+
delete: jest.fn(),
|
|
30
|
+
request: jest.fn(),
|
|
21
31
|
defaults: {
|
|
22
32
|
headers: {
|
|
23
33
|
common: {},
|
|
@@ -30,19 +40,196 @@ export function createMockAxiosInstance(): MockAxiosInstance {
|
|
|
30
40
|
};
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Create a mock Rooguys client with mocked HTTP client
|
|
45
|
+
* Returns both the client and the mock axios instance for setting up expectations
|
|
46
|
+
*/
|
|
47
|
+
export function createMockRooguysClient(apiKey = 'test-api-key'): {
|
|
48
|
+
client: Rooguys;
|
|
49
|
+
mockAxios: MockAxiosInstance;
|
|
50
|
+
} {
|
|
51
|
+
const client = new Rooguys(apiKey);
|
|
52
|
+
const mockAxios = createMockAxiosInstance();
|
|
53
|
+
|
|
54
|
+
// Access the internal HttpClient and replace its axios instance
|
|
55
|
+
const httpClient = (client as any)._httpClient as HttpClient;
|
|
56
|
+
(httpClient as any).client = mockAxios;
|
|
57
|
+
|
|
58
|
+
return { client, mockAxios };
|
|
35
59
|
}
|
|
36
60
|
|
|
37
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Create a mock AxiosResponse with rate limit headers
|
|
63
|
+
*/
|
|
64
|
+
export function mockAxiosResponse<T>(
|
|
65
|
+
data: T,
|
|
66
|
+
status = 200,
|
|
67
|
+
headers: Record<string, string> = {}
|
|
68
|
+
): AxiosResponse<T> {
|
|
69
|
+
return {
|
|
70
|
+
data,
|
|
71
|
+
status,
|
|
72
|
+
statusText: status === 200 ? 'OK' : 'Error',
|
|
73
|
+
headers: {
|
|
74
|
+
'x-ratelimit-limit': '1000',
|
|
75
|
+
'x-ratelimit-remaining': '999',
|
|
76
|
+
'x-ratelimit-reset': String(Math.floor(Date.now() / 1000) + 3600),
|
|
77
|
+
...headers,
|
|
78
|
+
},
|
|
79
|
+
config: {} as any,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a mock AxiosResponse for standardized API format
|
|
85
|
+
*/
|
|
86
|
+
export function mockStandardizedResponse<T>(
|
|
87
|
+
data: T,
|
|
88
|
+
requestId = 'req-123',
|
|
89
|
+
headers: Record<string, string> = {}
|
|
90
|
+
): AxiosResponse<{ success: true; data: T; request_id: string }> {
|
|
91
|
+
return mockAxiosResponse(
|
|
92
|
+
{
|
|
93
|
+
success: true as const,
|
|
94
|
+
data,
|
|
95
|
+
request_id: requestId,
|
|
96
|
+
},
|
|
97
|
+
200,
|
|
98
|
+
{
|
|
99
|
+
'x-request-id': requestId,
|
|
100
|
+
...headers,
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a mock success response (legacy format - data directly in response)
|
|
107
|
+
*/
|
|
108
|
+
export function mockSuccessResponse<T>(
|
|
109
|
+
data: T,
|
|
110
|
+
headers: Record<string, string> = {}
|
|
111
|
+
): AxiosResponse<T> {
|
|
112
|
+
return mockAxiosResponse(data, 200, headers);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a mock error that mimics an Axios error
|
|
117
|
+
*/
|
|
118
|
+
export function mockErrorResponse(
|
|
119
|
+
status: number,
|
|
120
|
+
message: string,
|
|
121
|
+
code?: string,
|
|
122
|
+
details?: Array<{ field: string; message: string }>
|
|
123
|
+
): any {
|
|
38
124
|
const error: any = new Error(message);
|
|
39
125
|
error.response = {
|
|
40
126
|
status,
|
|
41
127
|
data: {
|
|
42
|
-
|
|
43
|
-
|
|
128
|
+
success: false,
|
|
129
|
+
error: {
|
|
130
|
+
message,
|
|
131
|
+
code: code || 'ERROR',
|
|
132
|
+
details,
|
|
133
|
+
},
|
|
134
|
+
request_id: 'req-error-123',
|
|
135
|
+
},
|
|
136
|
+
headers: {
|
|
137
|
+
'x-ratelimit-limit': '1000',
|
|
138
|
+
'x-ratelimit-remaining': '999',
|
|
139
|
+
'x-ratelimit-reset': String(Math.floor(Date.now() / 1000) + 3600),
|
|
140
|
+
'x-request-id': 'req-error-123',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
error.isAxiosError = true;
|
|
144
|
+
return error;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Create a mock rate limit error
|
|
149
|
+
*/
|
|
150
|
+
export function mockRateLimitError(retryAfter = 60): any {
|
|
151
|
+
const error: any = new Error('Rate limit exceeded');
|
|
152
|
+
error.response = {
|
|
153
|
+
status: 429,
|
|
154
|
+
data: {
|
|
155
|
+
success: false,
|
|
156
|
+
error: {
|
|
157
|
+
message: 'Rate limit exceeded',
|
|
158
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
159
|
+
},
|
|
160
|
+
request_id: 'req-ratelimit-123',
|
|
161
|
+
},
|
|
162
|
+
headers: {
|
|
163
|
+
'x-ratelimit-limit': '1000',
|
|
164
|
+
'x-ratelimit-remaining': '0',
|
|
165
|
+
'x-ratelimit-reset': String(Math.floor(Date.now() / 1000) + retryAfter),
|
|
166
|
+
'retry-after': String(retryAfter),
|
|
167
|
+
'x-request-id': 'req-ratelimit-123',
|
|
44
168
|
},
|
|
45
169
|
};
|
|
46
170
|
error.isAxiosError = true;
|
|
47
171
|
return error;
|
|
48
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Helper to set up mock for a successful request
|
|
176
|
+
* The mock axios instance uses request() method internally
|
|
177
|
+
*/
|
|
178
|
+
export function setupMockRequest<T>(
|
|
179
|
+
mockAxios: MockAxiosInstance,
|
|
180
|
+
responseData: T,
|
|
181
|
+
headers: Record<string, string> = {}
|
|
182
|
+
): void {
|
|
183
|
+
mockAxios.request.mockResolvedValue(mockAxiosResponse(responseData, 200, headers));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Helper to set up mock for a failed request
|
|
188
|
+
*/
|
|
189
|
+
export function setupMockRequestError(
|
|
190
|
+
mockAxios: MockAxiosInstance,
|
|
191
|
+
status: number,
|
|
192
|
+
message: string,
|
|
193
|
+
code?: string,
|
|
194
|
+
details?: Array<{ field: string; message: string }>
|
|
195
|
+
): void {
|
|
196
|
+
mockAxios.request.mockRejectedValue(mockErrorResponse(status, message, code, details));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get the last request config from mock axios
|
|
201
|
+
*/
|
|
202
|
+
export function getLastRequestConfig(mockAxios: MockAxiosInstance): any {
|
|
203
|
+
const calls = mockAxios.request.mock.calls;
|
|
204
|
+
if (calls.length === 0) return null;
|
|
205
|
+
return calls[calls.length - 1][0];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Assert that a request was made with specific config
|
|
210
|
+
*/
|
|
211
|
+
export function expectRequestWith(
|
|
212
|
+
mockAxios: MockAxiosInstance,
|
|
213
|
+
expected: {
|
|
214
|
+
method?: string;
|
|
215
|
+
url?: string;
|
|
216
|
+
data?: any;
|
|
217
|
+
params?: any;
|
|
218
|
+
}
|
|
219
|
+
): void {
|
|
220
|
+
const lastConfig = getLastRequestConfig(mockAxios);
|
|
221
|
+
expect(lastConfig).toBeTruthy();
|
|
222
|
+
|
|
223
|
+
if (expected.method) {
|
|
224
|
+
expect(lastConfig.method).toBe(expected.method);
|
|
225
|
+
}
|
|
226
|
+
if (expected.url) {
|
|
227
|
+
expect(lastConfig.url).toBe(expected.url);
|
|
228
|
+
}
|
|
229
|
+
if (expected.data) {
|
|
230
|
+
expect(lastConfig.data).toEqual(expected.data);
|
|
231
|
+
}
|
|
232
|
+
if (expected.params) {
|
|
233
|
+
expect(lastConfig.params).toEqual(expected.params);
|
|
234
|
+
}
|
|
235
|
+
}
|