@rooguys/sdk 0.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/README.md +217 -0
- package/dist/__tests__/fixtures/responses.d.ts +272 -0
- package/dist/__tests__/fixtures/responses.js +311 -0
- package/dist/__tests__/utils/generators.d.ts +28 -0
- package/dist/__tests__/utils/generators.js +52 -0
- package/dist/__tests__/utils/mockClient.d.ts +22 -0
- package/dist/__tests__/utils/mockClient.js +35 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +222 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +2 -0
- package/jest.config.js +31 -0
- package/package.json +41 -0
- package/src/__tests__/fixtures/responses.ts +330 -0
- package/src/__tests__/property/request-construction.property.test.ts +203 -0
- package/src/__tests__/property/response-parsing.property.test.ts +200 -0
- package/src/__tests__/unit/aha.test.ts +164 -0
- package/src/__tests__/unit/badges.test.ts +112 -0
- package/src/__tests__/unit/config.test.ts +187 -0
- package/src/__tests__/unit/errors.test.ts +220 -0
- package/src/__tests__/unit/events.test.ts +282 -0
- package/src/__tests__/unit/leaderboards.test.ts +124 -0
- package/src/__tests__/unit/levels.test.ts +138 -0
- package/src/__tests__/unit/questionnaires.test.ts +138 -0
- package/src/__tests__/unit/users.test.ts +273 -0
- package/src/__tests__/utils/generators.ts +80 -0
- package/src/__tests__/utils/mockClient.ts +48 -0
- package/src/index.test.ts +69 -0
- package/src/index.ts +236 -0
- package/src/types.ts +165 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { Rooguys } from '../../index';
|
|
3
|
+
import { createMockAxiosInstance, mockErrorResponse } from '../utils/mockClient';
|
|
4
|
+
import { mockErrors } from '../fixtures/responses';
|
|
5
|
+
|
|
6
|
+
jest.mock('axios');
|
|
7
|
+
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
8
|
+
|
|
9
|
+
describe('Error Handling', () => {
|
|
10
|
+
let client: Rooguys;
|
|
11
|
+
let mockAxiosInstance: ReturnType<typeof createMockAxiosInstance>;
|
|
12
|
+
const apiKey = 'test-api-key';
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockAxiosInstance = createMockAxiosInstance();
|
|
16
|
+
mockedAxios.create.mockReturnValue(mockAxiosInstance as any);
|
|
17
|
+
client = new Rooguys(apiKey);
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('4xx client errors', () => {
|
|
22
|
+
it('should throw error with message for 400 Bad Request', async () => {
|
|
23
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
24
|
+
mockErrorResponse(400, 'Bad Request')
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
28
|
+
'Bad Request'
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should throw error with message for 401 Unauthorized', async () => {
|
|
33
|
+
mockAxiosInstance.get.mockRejectedValue(
|
|
34
|
+
mockErrorResponse(401, mockErrors.unauthorizedError.message)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
38
|
+
'Invalid or missing API key'
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should throw error with message for 404 Not Found', async () => {
|
|
43
|
+
mockAxiosInstance.get.mockRejectedValue(
|
|
44
|
+
mockErrorResponse(404, mockErrors.notFoundError.message)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await expect(client.users.get('nonexistent')).rejects.toThrow(
|
|
48
|
+
"User 'user123' does not exist in this project"
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should include validation details in error', async () => {
|
|
53
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
54
|
+
mockErrorResponse(400, 'Validation failed', mockErrors.validationError.details)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
await expect(client.events.track('', 'user1')).rejects.toThrow(
|
|
58
|
+
'Validation failed'
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should throw error for 429 Too Many Requests', async () => {
|
|
63
|
+
mockAxiosInstance.get.mockRejectedValue(
|
|
64
|
+
mockErrorResponse(429, 'Rate limit exceeded')
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
68
|
+
'Rate limit exceeded'
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('5xx server errors', () => {
|
|
74
|
+
it('should throw error with message for 500 Internal Server Error', async () => {
|
|
75
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
76
|
+
mockErrorResponse(500, 'Internal server error')
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
80
|
+
'Internal server error'
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should throw error with message for 503 Service Unavailable', async () => {
|
|
85
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
86
|
+
mockErrorResponse(503, mockErrors.queueFullError.message)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
90
|
+
'Event queue is full'
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should throw error for 502 Bad Gateway', async () => {
|
|
95
|
+
mockAxiosInstance.get.mockRejectedValue(
|
|
96
|
+
mockErrorResponse(502, 'Bad Gateway')
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
100
|
+
'Bad Gateway'
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('network errors', () => {
|
|
106
|
+
it('should throw error for network timeout', async () => {
|
|
107
|
+
const timeoutError = new Error('timeout of 10000ms exceeded');
|
|
108
|
+
(timeoutError as any).code = 'ECONNABORTED';
|
|
109
|
+
mockAxiosInstance.post.mockRejectedValue(timeoutError);
|
|
110
|
+
|
|
111
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
112
|
+
'timeout'
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should throw error for connection refused', async () => {
|
|
117
|
+
const connectionError = new Error('connect ECONNREFUSED');
|
|
118
|
+
(connectionError as any).code = 'ECONNREFUSED';
|
|
119
|
+
mockAxiosInstance.get.mockRejectedValue(connectionError);
|
|
120
|
+
|
|
121
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
122
|
+
'ECONNREFUSED'
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should throw error for DNS lookup failure', async () => {
|
|
127
|
+
const dnsError = new Error('getaddrinfo ENOTFOUND');
|
|
128
|
+
(dnsError as any).code = 'ENOTFOUND';
|
|
129
|
+
mockAxiosInstance.get.mockRejectedValue(dnsError);
|
|
130
|
+
|
|
131
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
132
|
+
'ENOTFOUND'
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('malformed responses', () => {
|
|
138
|
+
it('should handle response without error message', async () => {
|
|
139
|
+
const error: any = new Error('Request failed');
|
|
140
|
+
error.response = {
|
|
141
|
+
status: 500,
|
|
142
|
+
data: {},
|
|
143
|
+
};
|
|
144
|
+
error.isAxiosError = true;
|
|
145
|
+
mockAxiosInstance.post.mockRejectedValue(error);
|
|
146
|
+
|
|
147
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
148
|
+
'Request failed'
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should handle response with null data', async () => {
|
|
153
|
+
const error: any = new Error('Request failed');
|
|
154
|
+
error.response = {
|
|
155
|
+
status: 500,
|
|
156
|
+
data: null,
|
|
157
|
+
};
|
|
158
|
+
error.isAxiosError = true;
|
|
159
|
+
mockAxiosInstance.get.mockRejectedValue(error);
|
|
160
|
+
|
|
161
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
162
|
+
'Request failed'
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('error detail preservation', () => {
|
|
168
|
+
it('should preserve error details from API response', async () => {
|
|
169
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
170
|
+
mockErrorResponse(400, 'Validation failed', [
|
|
171
|
+
{ field: 'user_id', message: 'User ID is required' },
|
|
172
|
+
{ field: 'event_name', message: 'Event name is required' },
|
|
173
|
+
])
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await client.events.track('', '');
|
|
178
|
+
fail('Should have thrown an error');
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
expect(error.message).toContain('Validation failed');
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle errors with nested details', async () => {
|
|
185
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
186
|
+
mockErrorResponse(400, 'Complex validation error', {
|
|
187
|
+
errors: {
|
|
188
|
+
properties: {
|
|
189
|
+
amount: 'Must be a positive number',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
196
|
+
'Complex validation error'
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('non-axios errors', () => {
|
|
202
|
+
it('should rethrow non-axios errors', async () => {
|
|
203
|
+
const customError = new Error('Custom error');
|
|
204
|
+
mockAxiosInstance.post.mockRejectedValue(customError);
|
|
205
|
+
|
|
206
|
+
await expect(client.events.track('test', 'user1')).rejects.toThrow(
|
|
207
|
+
'Custom error'
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should handle TypeError', async () => {
|
|
212
|
+
const typeError = new TypeError('Cannot read property');
|
|
213
|
+
mockAxiosInstance.get.mockRejectedValue(typeError);
|
|
214
|
+
|
|
215
|
+
await expect(client.users.get('user1')).rejects.toThrow(
|
|
216
|
+
'Cannot read property'
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { Rooguys } from '../../index';
|
|
3
|
+
import { createMockAxiosInstance, mockSuccessResponse, mockErrorResponse } from '../utils/mockClient';
|
|
4
|
+
import { mockResponses, mockErrors } from '../fixtures/responses';
|
|
5
|
+
|
|
6
|
+
jest.mock('axios');
|
|
7
|
+
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
8
|
+
|
|
9
|
+
describe('Events Resource', () => {
|
|
10
|
+
let client: Rooguys;
|
|
11
|
+
let mockAxiosInstance: ReturnType<typeof createMockAxiosInstance>;
|
|
12
|
+
const apiKey = 'test-api-key';
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockAxiosInstance = createMockAxiosInstance();
|
|
16
|
+
mockedAxios.create.mockReturnValue(mockAxiosInstance as any);
|
|
17
|
+
client = new Rooguys(apiKey);
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('track', () => {
|
|
22
|
+
it('should track an event with valid inputs', async () => {
|
|
23
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
24
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const result = await client.events.track('purchase-completed', 'user_123', {
|
|
28
|
+
amount: 50.0,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
32
|
+
'/event',
|
|
33
|
+
{
|
|
34
|
+
event_name: 'purchase-completed',
|
|
35
|
+
user_id: 'user_123',
|
|
36
|
+
properties: { amount: 50.0 },
|
|
37
|
+
},
|
|
38
|
+
{ params: { include_profile: undefined } }
|
|
39
|
+
);
|
|
40
|
+
expect(result).toEqual(mockResponses.trackEventResponse);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should track an event with empty properties', async () => {
|
|
44
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
45
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const result = await client.events.track('user-login', 'user_456');
|
|
49
|
+
|
|
50
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
51
|
+
'/event',
|
|
52
|
+
{
|
|
53
|
+
event_name: 'user-login',
|
|
54
|
+
user_id: 'user_456',
|
|
55
|
+
properties: {},
|
|
56
|
+
},
|
|
57
|
+
{ params: { include_profile: undefined } }
|
|
58
|
+
);
|
|
59
|
+
expect(result).toEqual(mockResponses.trackEventResponse);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should include profile when includeProfile is true', async () => {
|
|
63
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
64
|
+
mockSuccessResponse(mockResponses.trackEventWithProfileResponse)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const result = await client.events.track(
|
|
68
|
+
'purchase-completed',
|
|
69
|
+
'user_123',
|
|
70
|
+
{ amount: 50.0 },
|
|
71
|
+
{ includeProfile: true }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
75
|
+
'/event',
|
|
76
|
+
{
|
|
77
|
+
event_name: 'purchase-completed',
|
|
78
|
+
user_id: 'user_123',
|
|
79
|
+
properties: { amount: 50.0 },
|
|
80
|
+
},
|
|
81
|
+
{ params: { include_profile: true } }
|
|
82
|
+
);
|
|
83
|
+
expect(result).toEqual(mockResponses.trackEventWithProfileResponse);
|
|
84
|
+
expect(result.profile).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle special characters in event name', async () => {
|
|
88
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
89
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
await client.events.track('user-signup_v2', 'user_123');
|
|
93
|
+
|
|
94
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
95
|
+
'/event',
|
|
96
|
+
expect.objectContaining({
|
|
97
|
+
event_name: 'user-signup_v2',
|
|
98
|
+
}),
|
|
99
|
+
expect.any(Object)
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should handle special characters in user ID', async () => {
|
|
104
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
105
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
await client.events.track('user-login', 'user@example.com');
|
|
109
|
+
|
|
110
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
111
|
+
'/event',
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
user_id: 'user@example.com',
|
|
114
|
+
}),
|
|
115
|
+
expect.any(Object)
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle complex nested properties', async () => {
|
|
120
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
121
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const complexProperties = {
|
|
125
|
+
order: {
|
|
126
|
+
id: 'order_123',
|
|
127
|
+
items: [
|
|
128
|
+
{ sku: 'ITEM1', quantity: 2 },
|
|
129
|
+
{ sku: 'ITEM2', quantity: 1 },
|
|
130
|
+
],
|
|
131
|
+
total: 150.0,
|
|
132
|
+
},
|
|
133
|
+
metadata: {
|
|
134
|
+
source: 'mobile_app',
|
|
135
|
+
version: '2.1.0',
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
await client.events.track('order_placed', 'user_123', complexProperties);
|
|
140
|
+
|
|
141
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
142
|
+
'/event',
|
|
143
|
+
expect.objectContaining({
|
|
144
|
+
properties: complexProperties,
|
|
145
|
+
}),
|
|
146
|
+
expect.any(Object)
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should throw error when API returns 400', async () => {
|
|
151
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
152
|
+
mockErrorResponse(400, 'Validation failed', mockErrors.validationError.details)
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
await expect(
|
|
156
|
+
client.events.track('', 'user_123')
|
|
157
|
+
).rejects.toThrow('Validation failed');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should throw error when API returns 500', async () => {
|
|
161
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
162
|
+
mockErrorResponse(500, 'Internal server error')
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
await expect(
|
|
166
|
+
client.events.track('user-login', 'user_123')
|
|
167
|
+
).rejects.toThrow('Internal server error');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should throw error when API returns 503 (queue full)', async () => {
|
|
171
|
+
mockAxiosInstance.post.mockRejectedValue(
|
|
172
|
+
mockErrorResponse(503, mockErrors.queueFullError.message)
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await expect(
|
|
176
|
+
client.events.track('user-login', 'user_123')
|
|
177
|
+
).rejects.toThrow('Event queue is full');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle network timeout', async () => {
|
|
181
|
+
const timeoutError = new Error('timeout of 10000ms exceeded');
|
|
182
|
+
(timeoutError as any).code = 'ECONNABORTED';
|
|
183
|
+
mockAxiosInstance.post.mockRejectedValue(timeoutError);
|
|
184
|
+
|
|
185
|
+
await expect(
|
|
186
|
+
client.events.track('user-login', 'user_123')
|
|
187
|
+
).rejects.toThrow('timeout');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should handle properties with null values', async () => {
|
|
191
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
192
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await client.events.track('user_updated', 'user_123', {
|
|
196
|
+
email: 'user@example.com',
|
|
197
|
+
phone: null,
|
|
198
|
+
address: null,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
202
|
+
'/event',
|
|
203
|
+
expect.objectContaining({
|
|
204
|
+
properties: {
|
|
205
|
+
email: 'user@example.com',
|
|
206
|
+
phone: null,
|
|
207
|
+
address: null,
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
expect.any(Object)
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle properties with boolean values', async () => {
|
|
215
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
216
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await client.events.track('feature-toggled', 'user_123', {
|
|
220
|
+
feature_name: 'dark_mode',
|
|
221
|
+
enabled: true,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
225
|
+
'/event',
|
|
226
|
+
expect.objectContaining({
|
|
227
|
+
properties: {
|
|
228
|
+
feature_name: 'dark_mode',
|
|
229
|
+
enabled: true,
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
expect.any(Object)
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle properties with numeric values', async () => {
|
|
237
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
238
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await client.events.track('score-updated', 'user_123', {
|
|
242
|
+
score: 1500,
|
|
243
|
+
multiplier: 1.5,
|
|
244
|
+
rank: 42,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
248
|
+
'/event',
|
|
249
|
+
expect.objectContaining({
|
|
250
|
+
properties: {
|
|
251
|
+
score: 1500,
|
|
252
|
+
multiplier: 1.5,
|
|
253
|
+
rank: 42,
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
expect.any(Object)
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle empty string properties', async () => {
|
|
261
|
+
mockAxiosInstance.post.mockResolvedValue(
|
|
262
|
+
mockSuccessResponse(mockResponses.trackEventResponse)
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
await client.events.track('form-submitted', 'user_123', {
|
|
266
|
+
name: 'John Doe',
|
|
267
|
+
comment: '',
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
|
271
|
+
'/event',
|
|
272
|
+
expect.objectContaining({
|
|
273
|
+
properties: {
|
|
274
|
+
name: 'John Doe',
|
|
275
|
+
comment: '',
|
|
276
|
+
},
|
|
277
|
+
}),
|
|
278
|
+
expect.any(Object)
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { Rooguys } from '../../index';
|
|
3
|
+
import { createMockAxiosInstance, mockSuccessResponse, mockErrorResponse } from '../utils/mockClient';
|
|
4
|
+
import { mockResponses, mockErrors } from '../fixtures/responses';
|
|
5
|
+
|
|
6
|
+
jest.mock('axios');
|
|
7
|
+
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
8
|
+
|
|
9
|
+
describe('Leaderboards Resource', () => {
|
|
10
|
+
let client: Rooguys;
|
|
11
|
+
let mockAxiosInstance: ReturnType<typeof createMockAxiosInstance>;
|
|
12
|
+
const apiKey = 'test-api-key';
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockAxiosInstance = createMockAxiosInstance();
|
|
16
|
+
mockedAxios.create.mockReturnValue(mockAxiosInstance as any);
|
|
17
|
+
client = new Rooguys(apiKey);
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('getGlobal', () => {
|
|
22
|
+
it('should get global leaderboard with default parameters', async () => {
|
|
23
|
+
mockAxiosInstance.get.mockResolvedValue(
|
|
24
|
+
mockSuccessResponse(mockResponses.leaderboardResponse)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const result = await client.leaderboards.getGlobal();
|
|
28
|
+
|
|
29
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/leaderboard', {
|
|
30
|
+
params: { timeframe: 'all-time', page: 1, limit: 50 },
|
|
31
|
+
});
|
|
32
|
+
expect(result).toEqual(mockResponses.leaderboardResponse);
|
|
33
|
+
expect(result.rankings).toHaveLength(2);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should get global leaderboard with weekly timeframe', async () => {
|
|
37
|
+
mockAxiosInstance.get.mockResolvedValue(
|
|
38
|
+
mockSuccessResponse(mockResponses.leaderboardResponse)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
await client.leaderboards.getGlobal('weekly');
|
|
42
|
+
|
|
43
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/leaderboard', {
|
|
44
|
+
params: { timeframe: 'weekly', page: 1, limit: 50 },
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should get global leaderboard with monthly timeframe', async () => {
|
|
49
|
+
mockAxiosInstance.get.mockResolvedValue(
|
|
50
|
+
mockSuccessResponse(mockResponses.leaderboardResponse)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
await client.leaderboards.getGlobal('monthly');
|
|
54
|
+
|
|
55
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/leaderboard', {
|
|
56
|
+
params: { timeframe: 'monthly', page: 1, limit: 50 },
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should get global leaderboard with custom pagination', async () => {
|
|
61
|
+
mockAxiosInstance.get.mockResolvedValue(
|
|
62
|
+
mockSuccessResponse(mockResponses.leaderboardResponse)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
await client.leaderboards.getGlobal('all-time', 2, 25);
|
|
66
|
+
|
|
67
|
+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/leaderboard', {
|
|
68
|
+
params: { timeframe: 'all-time', page: 2, limit: 25 },
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle empty leaderboard', async () => {
|
|
73
|
+
const emptyLeaderboard = {
|
|
74
|
+
...mockResponses.leaderboardResponse,
|
|
75
|
+
rankings: [],
|
|
76
|
+
total: 0,
|
|
77
|
+
};
|
|
78
|
+
mockAxiosInstance.get.mockResolvedValue(mockSuccessResponse(emptyLeaderboard));
|
|
79
|
+
|
|
80
|
+
const result = await client.leaderboards.getGlobal();
|
|
81
|
+
|
|
82
|
+
expect(result.rankings).toEqual([]);
|
|
83
|
+
expect(result.total).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should throw error for invalid timeframe', async () => {
|
|
87
|
+
mockAxiosInstance.get.mockRejectedValue(
|
|
88
|
+
mockErrorResponse(400, mockErrors.invalidTimeframeError.message)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
await expect(
|
|
92
|
+
client.leaderboards.getGlobal('invalid' as any)
|
|
93
|
+
).rejects.toThrow('Timeframe must be one of');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should throw error for invalid pagination', async () => {
|
|
97
|
+
mockAxiosInstance.get.mockRejectedValue(
|
|
98
|
+
mockErrorResponse(400, mockErrors.invalidPaginationError.message)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await expect(
|
|
102
|
+
client.leaderboards.getGlobal('all-time', 1, 150)
|
|
103
|
+
).rejects.toThrow('Limit must be between 1 and 100');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle leaderboard with users at same rank', async () => {
|
|
107
|
+
const leaderboardWithTies = {
|
|
108
|
+
...mockResponses.leaderboardResponse,
|
|
109
|
+
rankings: [
|
|
110
|
+
{ rank: 1, user_id: 'user1', points: 1000, level: null },
|
|
111
|
+
{ rank: 1, user_id: 'user2', points: 1000, level: null },
|
|
112
|
+
{ rank: 3, user_id: 'user3', points: 900, level: null },
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
mockAxiosInstance.get.mockResolvedValue(mockSuccessResponse(leaderboardWithTies));
|
|
116
|
+
|
|
117
|
+
const result = await client.leaderboards.getGlobal();
|
|
118
|
+
|
|
119
|
+
expect(result.rankings[0].rank).toBe(1);
|
|
120
|
+
expect(result.rankings[1].rank).toBe(1);
|
|
121
|
+
expect(result.rankings[2].rank).toBe(3);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|