@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.
@@ -1,220 +1,176 @@
1
- import axios from 'axios';
2
- import { Rooguys } from '../../index';
3
- import { createMockAxiosInstance, mockErrorResponse } from '../utils/mockClient';
1
+ import { Rooguys, RooguysError, ValidationError, AuthenticationError, NotFoundError, RateLimitError, ServerError } from '../../index';
2
+ import {
3
+ createMockRooguysClient,
4
+ setupMockRequest,
5
+ setupMockRequestError,
6
+ mockErrorResponse,
7
+ MockAxiosInstance,
8
+ } from '../utils/mockClient';
4
9
  import { mockErrors } from '../fixtures/responses';
5
10
 
6
- jest.mock('axios');
7
- const mockedAxios = axios as jest.Mocked<typeof axios>;
8
-
9
11
  describe('Error Handling', () => {
10
12
  let client: Rooguys;
11
- let mockAxiosInstance: ReturnType<typeof createMockAxiosInstance>;
12
- const apiKey = 'test-api-key';
13
+ let mockAxios: MockAxiosInstance;
13
14
 
14
15
  beforeEach(() => {
15
- mockAxiosInstance = createMockAxiosInstance();
16
- mockedAxios.create.mockReturnValue(mockAxiosInstance as any);
17
- client = new Rooguys(apiKey);
18
- jest.clearAllMocks();
16
+ const mock = createMockRooguysClient();
17
+ client = mock.client;
18
+ mockAxios = mock.mockAxios;
19
19
  });
20
20
 
21
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
- );
22
+ it('should throw ValidationError for 400 Bad Request', async () => {
23
+ setupMockRequestError(mockAxios, 400, 'Bad Request', 'VALIDATION_ERROR');
26
24
 
27
- await expect(client.events.track('test', 'user1')).rejects.toThrow(
28
- 'Bad Request'
29
- );
25
+ await expect(client.events.track('test', 'user1')).rejects.toThrow(ValidationError);
30
26
  });
31
27
 
32
- it('should throw error with message for 401 Unauthorized', async () => {
33
- mockAxiosInstance.get.mockRejectedValue(
34
- mockErrorResponse(401, mockErrors.unauthorizedError.message)
35
- );
28
+ it('should throw AuthenticationError for 401 Unauthorized', async () => {
29
+ setupMockRequestError(mockAxios, 401, 'Invalid or missing API key', 'UNAUTHORIZED');
36
30
 
37
- await expect(client.users.get('user1')).rejects.toThrow(
38
- 'Invalid or missing API key'
39
- );
31
+ await expect(client.users.get('user1')).rejects.toThrow(AuthenticationError);
40
32
  });
41
33
 
42
- it('should throw error with message for 404 Not Found', async () => {
43
- mockAxiosInstance.get.mockRejectedValue(
44
- mockErrorResponse(404, mockErrors.notFoundError.message)
45
- );
34
+ it('should throw NotFoundError for 404 Not Found', async () => {
35
+ setupMockRequestError(mockAxios, 404, "User 'user123' does not exist in this project", 'NOT_FOUND');
46
36
 
47
- await expect(client.users.get('nonexistent')).rejects.toThrow(
48
- "User 'user123' does not exist in this project"
49
- );
37
+ await expect(client.users.get('nonexistent')).rejects.toThrow(NotFoundError);
50
38
  });
51
39
 
52
40
  it('should include validation details in error', async () => {
53
- mockAxiosInstance.post.mockRejectedValue(
54
- mockErrorResponse(400, 'Validation failed', mockErrors.validationError.details)
55
- );
41
+ setupMockRequestError(mockAxios, 400, 'Validation failed', 'VALIDATION_ERROR', [
42
+ { field: 'user_id', message: 'User ID is required' },
43
+ ]);
56
44
 
57
- await expect(client.events.track('', 'user1')).rejects.toThrow(
58
- 'Validation failed'
59
- );
45
+ try {
46
+ await client.events.track('', 'user1');
47
+ fail('Should have thrown an error');
48
+ } catch (error) {
49
+ expect(error).toBeInstanceOf(ValidationError);
50
+ expect((error as ValidationError).fieldErrors).toBeDefined();
51
+ }
60
52
  });
61
53
 
62
- it('should throw error for 429 Too Many Requests', async () => {
63
- mockAxiosInstance.get.mockRejectedValue(
64
- mockErrorResponse(429, 'Rate limit exceeded')
65
- );
54
+ it('should throw RateLimitError for 429 Too Many Requests', async () => {
55
+ const error = mockErrorResponse(429, 'Rate limit exceeded', 'RATE_LIMIT_EXCEEDED');
56
+ error.response.headers['retry-after'] = '60';
57
+ mockAxios.request.mockRejectedValue(error);
66
58
 
67
- await expect(client.users.get('user1')).rejects.toThrow(
68
- 'Rate limit exceeded'
69
- );
59
+ await expect(client.users.get('user1')).rejects.toThrow(RateLimitError);
70
60
  });
71
61
  });
72
62
 
73
63
  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
- );
64
+ it('should throw ServerError for 500 Internal Server Error', async () => {
65
+ setupMockRequestError(mockAxios, 500, 'Internal server error', 'SERVER_ERROR');
78
66
 
79
- await expect(client.events.track('test', 'user1')).rejects.toThrow(
80
- 'Internal server error'
81
- );
67
+ await expect(client.events.track('test', 'user1')).rejects.toThrow(ServerError);
82
68
  });
83
69
 
84
- it('should throw error with message for 503 Service Unavailable', async () => {
85
- mockAxiosInstance.post.mockRejectedValue(
86
- mockErrorResponse(503, mockErrors.queueFullError.message)
87
- );
70
+ it('should throw ServerError for 503 Service Unavailable', async () => {
71
+ setupMockRequestError(mockAxios, 503, 'Event queue is full. Please retry later.', 'SERVICE_UNAVAILABLE');
88
72
 
89
- await expect(client.events.track('test', 'user1')).rejects.toThrow(
90
- 'Event queue is full'
91
- );
73
+ await expect(client.events.track('test', 'user1')).rejects.toThrow(ServerError);
92
74
  });
93
75
 
94
- it('should throw error for 502 Bad Gateway', async () => {
95
- mockAxiosInstance.get.mockRejectedValue(
96
- mockErrorResponse(502, 'Bad Gateway')
97
- );
76
+ it('should throw ServerError for 502 Bad Gateway', async () => {
77
+ setupMockRequestError(mockAxios, 502, 'Bad Gateway', 'BAD_GATEWAY');
98
78
 
99
- await expect(client.users.get('user1')).rejects.toThrow(
100
- 'Bad Gateway'
101
- );
79
+ await expect(client.users.get('user1')).rejects.toThrow(ServerError);
102
80
  });
103
81
  });
104
82
 
105
83
  describe('network errors', () => {
106
- it('should throw error for network timeout', async () => {
84
+ it('should throw RooguysError for network timeout', async () => {
107
85
  const timeoutError = new Error('timeout of 10000ms exceeded');
108
- (timeoutError as any).code = 'ECONNABORTED';
109
- mockAxiosInstance.post.mockRejectedValue(timeoutError);
86
+ mockAxios.request.mockRejectedValue(timeoutError);
110
87
 
111
- await expect(client.events.track('test', 'user1')).rejects.toThrow(
112
- 'timeout'
113
- );
88
+ await expect(client.events.track('test', 'user1')).rejects.toThrow(RooguysError);
114
89
  });
115
90
 
116
- it('should throw error for connection refused', async () => {
91
+ it('should throw RooguysError for connection refused', async () => {
117
92
  const connectionError = new Error('connect ECONNREFUSED');
118
- (connectionError as any).code = 'ECONNREFUSED';
119
- mockAxiosInstance.get.mockRejectedValue(connectionError);
93
+ mockAxios.request.mockRejectedValue(connectionError);
120
94
 
121
- await expect(client.users.get('user1')).rejects.toThrow(
122
- 'ECONNREFUSED'
123
- );
95
+ await expect(client.users.get('user1')).rejects.toThrow(RooguysError);
124
96
  });
125
97
 
126
- it('should throw error for DNS lookup failure', async () => {
98
+ it('should throw RooguysError for DNS lookup failure', async () => {
127
99
  const dnsError = new Error('getaddrinfo ENOTFOUND');
128
- (dnsError as any).code = 'ENOTFOUND';
129
- mockAxiosInstance.get.mockRejectedValue(dnsError);
100
+ mockAxios.request.mockRejectedValue(dnsError);
130
101
 
131
- await expect(client.users.get('user1')).rejects.toThrow(
132
- 'ENOTFOUND'
133
- );
102
+ await expect(client.users.get('user1')).rejects.toThrow(RooguysError);
134
103
  });
135
104
  });
136
105
 
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
- );
106
+ describe('error properties', () => {
107
+ it('should include status code in error', async () => {
108
+ setupMockRequestError(mockAxios, 404, 'Not found', 'NOT_FOUND');
109
+
110
+ try {
111
+ await client.users.get('user1');
112
+ fail('Should have thrown an error');
113
+ } catch (error) {
114
+ expect(error).toBeInstanceOf(NotFoundError);
115
+ expect((error as NotFoundError).statusCode).toBe(404);
116
+ }
164
117
  });
165
- });
166
118
 
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
- );
119
+ it('should include error code in error', async () => {
120
+ setupMockRequestError(mockAxios, 400, 'Validation failed', 'VALIDATION_ERROR');
175
121
 
176
122
  try {
177
- await client.events.track('', '');
123
+ await client.events.track('test', 'user1');
178
124
  fail('Should have thrown an error');
179
- } catch (error: any) {
180
- expect(error.message).toContain('Validation failed');
125
+ } catch (error) {
126
+ expect(error).toBeInstanceOf(ValidationError);
127
+ expect((error as ValidationError).code).toBe('VALIDATION_ERROR');
181
128
  }
182
129
  });
183
130
 
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
- );
131
+ it('should include request ID in error when available', async () => {
132
+ setupMockRequestError(mockAxios, 500, 'Server error', 'SERVER_ERROR');
194
133
 
195
- await expect(client.events.track('test', 'user1')).rejects.toThrow(
196
- 'Complex validation error'
197
- );
134
+ try {
135
+ await client.users.get('user1');
136
+ fail('Should have thrown an error');
137
+ } catch (error) {
138
+ expect(error).toBeInstanceOf(ServerError);
139
+ expect((error as ServerError).requestId).toBeDefined();
140
+ }
198
141
  });
199
142
  });
200
143
 
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);
144
+ describe('error class hierarchy', () => {
145
+ it('ValidationError should be instance of RooguysError', () => {
146
+ const error = new ValidationError('Test error');
147
+ expect(error).toBeInstanceOf(RooguysError);
148
+ expect(error).toBeInstanceOf(Error);
149
+ });
150
+
151
+ it('AuthenticationError should be instance of RooguysError', () => {
152
+ const error = new AuthenticationError('Test error');
153
+ expect(error).toBeInstanceOf(RooguysError);
154
+ expect(error).toBeInstanceOf(Error);
155
+ });
205
156
 
206
- await expect(client.events.track('test', 'user1')).rejects.toThrow(
207
- 'Custom error'
208
- );
157
+ it('NotFoundError should be instance of RooguysError', () => {
158
+ const error = new NotFoundError('Test error');
159
+ expect(error).toBeInstanceOf(RooguysError);
160
+ expect(error).toBeInstanceOf(Error);
209
161
  });
210
162
 
211
- it('should handle TypeError', async () => {
212
- const typeError = new TypeError('Cannot read property');
213
- mockAxiosInstance.get.mockRejectedValue(typeError);
163
+ it('RateLimitError should be instance of RooguysError', () => {
164
+ const error = new RateLimitError('Test error', { retryAfter: 60 });
165
+ expect(error).toBeInstanceOf(RooguysError);
166
+ expect(error).toBeInstanceOf(Error);
167
+ expect(error.retryAfter).toBe(60);
168
+ });
214
169
 
215
- await expect(client.users.get('user1')).rejects.toThrow(
216
- 'Cannot read property'
217
- );
170
+ it('ServerError should be instance of RooguysError', () => {
171
+ const error = new ServerError('Test error');
172
+ expect(error).toBeInstanceOf(RooguysError);
173
+ expect(error).toBeInstanceOf(Error);
218
174
  });
219
175
  });
220
176
  });