Skip to content

Commit eb20d08

Browse files
committed
feat: rate limiting information for all responses (#562)
1 parent f96fbe6 commit eb20d08

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+693
-716
lines changed

src/api-keys/api-keys.spec.ts

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { enableFetchMocks } from 'jest-fetch-mock';
22
import type { ErrorResponse } from '../interfaces';
33
import { Resend } from '../resend';
4+
import {
5+
mockErrorResponse,
6+
mockSuccessResponse,
7+
} from '../test-utils/mock-fetch';
48
import type {
59
CreateApiKeyOptions,
610
CreateApiKeyResponseSuccess,
@@ -23,12 +27,8 @@ describe('API Keys', () => {
2327
id: '430eed87-632a-4ea6-90db-0aace67ec228',
2428
};
2529

26-
fetchMock.mockOnce(JSON.stringify(response), {
27-
status: 201,
28-
headers: {
29-
'content-type': 'application/json',
30-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
31-
},
30+
mockSuccessResponse(response, {
31+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
3232
});
3333

3434
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -42,6 +42,11 @@ describe('API Keys', () => {
4242
"token": "re_PKr4RCko_Lhm9ost2YjNCctnPjbLw8Nqk",
4343
},
4444
"error": null,
45+
"rateLimiting": {
46+
"limit": 2,
47+
"remainingRequests": 2,
48+
"shouldResetAfter": 1,
49+
},
4550
}
4651
`);
4752
});
@@ -55,12 +60,8 @@ describe('API Keys', () => {
5560
name: 'validation_error',
5661
};
5762

58-
fetchMock.mockOnce(JSON.stringify(response), {
59-
status: 422,
60-
headers: {
61-
'content-type': 'application/json',
62-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
63-
},
63+
mockErrorResponse(response, {
64+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
6465
});
6566

6667
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -74,6 +75,11 @@ describe('API Keys', () => {
7475
"message": "String must contain at least 1 character(s)",
7576
"name": "validation_error",
7677
},
78+
"rateLimiting": {
79+
"limit": 2,
80+
"remainingRequests": 2,
81+
"shouldResetAfter": 1,
82+
},
7783
}
7884
`);
7985
});
@@ -90,12 +96,8 @@ describe('API Keys', () => {
9096
id: '430eed87-632a-4ea6-90db-0aace67ec228',
9197
};
9298

93-
fetchMock.mockOnce(JSON.stringify(response), {
94-
status: 201,
95-
headers: {
96-
'content-type': 'application/json',
97-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
98-
},
99+
mockSuccessResponse(response, {
100+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
99101
});
100102

101103
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -109,6 +111,11 @@ describe('API Keys', () => {
109111
"token": "re_PKr4RCko_Lhm9ost2YjNCctnPjbLw8Nqk",
110112
},
111113
"error": null,
114+
"rateLimiting": {
115+
"limit": 2,
116+
"remainingRequests": 2,
117+
"shouldResetAfter": 1,
118+
},
112119
}
113120
`);
114121
});
@@ -123,12 +130,8 @@ describe('API Keys', () => {
123130
id: '430eed87-632a-4ea6-90db-0aace67ec228',
124131
};
125132

126-
fetchMock.mockOnce(JSON.stringify(response), {
127-
status: 201,
128-
headers: {
129-
'content-type': 'application/json',
130-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
131-
},
133+
mockSuccessResponse(response, {
134+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
132135
});
133136

134137
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -142,6 +145,11 @@ describe('API Keys', () => {
142145
"token": "re_PKr4RCko_Lhm9ost2YjNCctnPjbLw8Nqk",
143146
},
144147
"error": null,
148+
"rateLimiting": {
149+
"limit": 2,
150+
"remainingRequests": 2,
151+
"shouldResetAfter": 1,
152+
},
145153
}
146154
`);
147155
});
@@ -152,12 +160,8 @@ describe('API Keys', () => {
152160
message: 'Access must be "full_access" | "sending_access"',
153161
};
154162

155-
fetchMock.mockOnce(JSON.stringify(response), {
156-
status: 422,
157-
headers: {
158-
'content-type': 'application/json',
159-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
160-
},
163+
mockErrorResponse(response, {
164+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
161165
});
162166

163167
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -176,6 +180,11 @@ describe('API Keys', () => {
176180
"message": "Access must be "full_access" | "sending_access"",
177181
"name": "invalid_access",
178182
},
183+
"rateLimiting": {
184+
"limit": 2,
185+
"remainingRequests": 2,
186+
"shouldResetAfter": 1,
187+
},
179188
}
180189
`);
181190
});
@@ -271,12 +280,8 @@ describe('API Keys', () => {
271280
created_at: '2023-04-06T23:09:49.093947+00:00',
272281
},
273282
];
274-
fetchMock.mockOnce(JSON.stringify(response), {
275-
status: 200,
276-
headers: {
277-
'content-type': 'application/json',
278-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
279-
},
283+
mockSuccessResponse(response, {
284+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
280285
});
281286

282287
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -296,6 +301,11 @@ describe('API Keys', () => {
296301
},
297302
],
298303
"error": null,
304+
"rateLimiting": {
305+
"limit": 2,
306+
"remainingRequests": 2,
307+
"shouldResetAfter": 1,
308+
},
299309
}
300310
`);
301311
});
@@ -306,12 +316,8 @@ describe('API Keys', () => {
306316
const response: RemoveApiKeyResponseSuccess = {};
307317

308318
it('removes an api key', async () => {
309-
fetchMock.mockOnce(JSON.stringify(response), {
310-
status: 200,
311-
headers: {
312-
'content-type': 'application/json',
313-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
314-
},
319+
mockSuccessResponse(response, {
320+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
315321
});
316322

317323
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -320,6 +326,11 @@ describe('API Keys', () => {
320326
{
321327
"data": {},
322328
"error": null,
329+
"rateLimiting": {
330+
"limit": 2,
331+
"remainingRequests": 2,
332+
"shouldResetAfter": 1,
333+
},
323334
}
324335
`);
325336
});
@@ -330,12 +341,8 @@ describe('API Keys', () => {
330341
message: 'Something went wrong',
331342
};
332343

333-
fetchMock.mockOnce(JSON.stringify(response), {
334-
status: 500,
335-
headers: {
336-
'content-type': 'application/json',
337-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
338-
},
344+
mockErrorResponse(response, {
345+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
339346
});
340347

341348
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -349,6 +356,11 @@ describe('API Keys', () => {
349356
"message": "Something went wrong",
350357
"name": "application_error",
351358
},
359+
"rateLimiting": {
360+
"limit": 2,
361+
"remainingRequests": 2,
362+
"shouldResetAfter": 1,
363+
},
352364
}
353365
`);
354366
});
@@ -359,12 +371,8 @@ describe('API Keys', () => {
359371
message: 'API key not found',
360372
};
361373

362-
fetchMock.mockOnce(JSON.stringify(response), {
363-
status: 404,
364-
headers: {
365-
'content-type': 'application/json',
366-
Authorization: 'Bearer re_924b3rjh2387fbewf823',
367-
},
374+
mockErrorResponse(response, {
375+
headers: { Authorization: 'Bearer re_924b3rjh2387fbewf823' },
368376
});
369377

370378
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
@@ -380,6 +388,11 @@ describe('API Keys', () => {
380388
"message": "API key not found",
381389
"name": "not_found",
382390
},
391+
"rateLimiting": {
392+
"limit": 2,
393+
"remainingRequests": 2,
394+
"shouldResetAfter": 1,
395+
},
383396
}
384397
`);
385398
});
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PostOptions } from '../../common/interfaces';
2-
import type { ErrorResponse } from '../../interfaces';
2+
import type { Response } from '../../interfaces';
33

44
export interface CreateApiKeyOptions {
55
name: string;
@@ -14,12 +14,4 @@ export interface CreateApiKeyResponseSuccess {
1414
id: string;
1515
}
1616

17-
export type CreateApiKeyResponse =
18-
| {
19-
data: CreateApiKeyResponseSuccess;
20-
error: null;
21-
}
22-
| {
23-
data: null;
24-
error: ErrorResponse;
25-
};
17+
export type CreateApiKeyResponse = Response<CreateApiKeyResponseSuccess>;
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
import type { ErrorResponse } from '../../interfaces';
1+
import type { Response } from '../../interfaces';
22
import type { ApiKey } from './api-key';
33

44
export type ListApiKeysResponseSuccess = Pick<
55
ApiKey,
66
'name' | 'id' | 'created_at'
77
>[];
88

9-
export type ListApiKeysResponse =
10-
| {
11-
data: ListApiKeysResponseSuccess;
12-
error: null;
13-
}
14-
| {
15-
data: null;
16-
error: ErrorResponse;
17-
};
9+
export type ListApiKeysResponse = Response<ListApiKeysResponseSuccess>;
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1-
import type { ErrorResponse } from '../../interfaces';
1+
import type { Response } from '../../interfaces';
22

33
// biome-ignore lint/complexity/noBannedTypes: <explanation>
44
export type RemoveApiKeyResponseSuccess = {};
55

6-
export type RemoveApiKeyResponse =
7-
| {
8-
data: RemoveApiKeyResponseSuccess;
9-
error: null;
10-
}
11-
| {
12-
data: null;
13-
error: ErrorResponse;
14-
};
6+
export type RemoveApiKeyResponse = Response<RemoveApiKeyResponseSuccess>;

0 commit comments

Comments
 (0)