Skip to content

Commit 9654979

Browse files
committed
feat: add pagination for inbound email attachments
1 parent c220601 commit 9654979

File tree

4 files changed

+121
-38
lines changed

4 files changed

+121
-38
lines changed

src/attachments/receiving/interfaces/list-attachments.interface.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import type { PaginationOptions } from '../../../common/interfaces';
12
import type { ErrorResponse } from '../../../interfaces';
23
import type { InboundAttachment } from './attachment';
34

4-
export interface ListAttachmentsOptions {
5+
export type ListAttachmentsOptions = PaginationOptions & {
56
emailId: string;
6-
}
7+
};
78

89
export interface ListAttachmentsResponseSuccess {
9-
object: 'attachment';
10+
object: 'list';
11+
has_more: boolean;
1012
data: InboundAttachment[];
1113
}
1214

src/attachments/receiving/receiving.spec.ts

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { ErrorResponse } from '../../interfaces';
22
import { Resend } from '../../resend';
3+
import { mockSuccessResponse } from '../../test-utils/mock-fetch';
4+
import type { GetAttachmentResponseSuccess } from './interfaces';
5+
import type { ListAttachmentsResponseSuccess } from './interfaces/list-attachments.interface';
36

47
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
58

@@ -41,7 +44,7 @@ describe('Receiving', () => {
4144

4245
describe('when attachment found', () => {
4346
it('returns attachment with transformed fields', async () => {
44-
const apiResponse = {
47+
const apiResponse: GetAttachmentResponseSuccess = {
4548
object: 'attachment' as const,
4649
data: {
4750
id: 'att_123',
@@ -174,6 +177,33 @@ describe('Receiving', () => {
174177
});
175178

176179
describe('list', () => {
180+
const apiResponse: ListAttachmentsResponseSuccess = {
181+
object: 'list' as const,
182+
has_more: false,
183+
data: [
184+
{
185+
id: 'att_123',
186+
filename: 'document.pdf',
187+
content_type: 'application/pdf',
188+
content_id: 'cid_123',
189+
content_disposition: 'attachment' as const,
190+
content: 'base64encodedcontent==',
191+
},
192+
{
193+
id: 'att_456',
194+
filename: 'image.png',
195+
content_type: 'image/png',
196+
content_id: 'cid_456',
197+
content_disposition: 'inline' as const,
198+
content: 'imagebase64==',
199+
},
200+
],
201+
};
202+
203+
const headers = {
204+
Authorization: 'Bearer re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop',
205+
};
206+
177207
describe('when inbound email not found', () => {
178208
it('returns error', async () => {
179209
const response: ErrorResponse = {
@@ -199,28 +229,6 @@ describe('Receiving', () => {
199229

200230
describe('when attachments found', () => {
201231
it('returns multiple attachments', async () => {
202-
const apiResponse = {
203-
object: 'attachment' as const,
204-
data: [
205-
{
206-
id: 'att_123',
207-
filename: 'document.pdf',
208-
content_type: 'application/pdf',
209-
content_id: 'cid_123',
210-
content_disposition: 'attachment' as const,
211-
content: 'base64encodedcontent==',
212-
},
213-
{
214-
id: 'att_456',
215-
filename: 'image.png',
216-
content_type: 'image/png',
217-
content_id: 'cid_456',
218-
content_disposition: 'inline' as const,
219-
content: 'imagebase64==',
220-
},
221-
],
222-
};
223-
224232
fetchMock.mockOnce(JSON.stringify(apiResponse), {
225233
status: 200,
226234
headers: {
@@ -237,12 +245,12 @@ describe('Receiving', () => {
237245
});
238246

239247
it('returns empty array when no attachments', async () => {
240-
const apiResponse = {
248+
const emptyResponse = {
241249
object: 'attachment' as const,
242250
data: [],
243251
};
244252

245-
fetchMock.mockOnce(JSON.stringify(apiResponse), {
253+
fetchMock.mockOnce(JSON.stringify(emptyResponse), {
246254
status: 200,
247255
headers: {
248256
'content-type': 'application/json',
@@ -254,7 +262,74 @@ describe('Receiving', () => {
254262
emailId: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
255263
});
256264

257-
expect(result).toEqual({ data: apiResponse, error: null });
265+
expect(result).toEqual({ data: emptyResponse, error: null });
266+
});
267+
});
268+
269+
describe('when no pagination options provided', () => {
270+
it('calls endpoint without query params and return the response', async () => {
271+
mockSuccessResponse(apiResponse, {
272+
headers,
273+
});
274+
275+
const result = await resend.attachments.receiving.list({
276+
emailId: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
277+
});
278+
279+
expect(result).toEqual({
280+
data: apiResponse,
281+
error: null,
282+
});
283+
expect(fetchMock.mock.calls[0][0]).toBe(
284+
'https://api.resend.com/emails/receiving/67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
285+
);
286+
});
287+
});
288+
289+
describe('when pagination options are provided', () => {
290+
it('calls endpoint passing limit param and return the response', async () => {
291+
mockSuccessResponse(apiResponse, { headers });
292+
const result = await resend.attachments.receiving.list({
293+
emailId: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
294+
limit: 10,
295+
});
296+
expect(result).toEqual({
297+
data: apiResponse,
298+
error: null,
299+
});
300+
expect(fetchMock.mock.calls[0][0]).toBe(
301+
'https://api.resend.com/emails/receiving/67d9bcdb-5a02-42d7-8da9-0d6feea18cff?limit=10',
302+
);
303+
});
304+
305+
it('calls endpoint passing after param and return the response', async () => {
306+
mockSuccessResponse(apiResponse, { headers });
307+
const result = await resend.attachments.receiving.list({
308+
emailId: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
309+
after: 'cursor123',
310+
});
311+
expect(result).toEqual({
312+
data: apiResponse,
313+
error: null,
314+
});
315+
expect(fetchMock.mock.calls[0][0]).toBe(
316+
'https://api.resend.com/emails/receiving/67d9bcdb-5a02-42d7-8da9-0d6feea18cff?after=cursor123',
317+
);
318+
});
319+
320+
it('calls endpoint passing before param and return the response', async () => {
321+
mockSuccessResponse(apiResponse, { headers });
322+
const result = await resend.attachments.receiving.list({
323+
emailId: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
324+
before: 'cursor123',
325+
});
326+
expect(result).toEqual({
327+
data: apiResponse,
328+
error: null,
329+
});
330+
expect(fetchMock.mock.calls[0][0]).toBe(
331+
'https://api.resend.com/emails/receiving/67d9bcdb-5a02-42d7-8da9-0d6feea18cff?before=cursor123',
332+
);
258333
});
259334
});
260335
});

src/attachments/receiving/receiving.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { buildPaginationQuery } from '../../common/utils/build-pagination-query';
12
import type { Resend } from '../../resend';
23
import type {
34
GetAttachmentOptions,
@@ -17,7 +18,7 @@ export class Receiving {
1718
const { emailId, id } = options;
1819

1920
const data = await this.resend.get<GetAttachmentResponseSuccess>(
20-
`/emails/inbound/${emailId}/attachments/${id}`,
21+
`/emails/receiving/${emailId}/attachments/${id}`,
2122
);
2223

2324
return data;
@@ -28,9 +29,12 @@ export class Receiving {
2829
): Promise<ListAttachmentsResponse> {
2930
const { emailId } = options;
3031

31-
const data = await this.resend.get<ListAttachmentsResponseSuccess>(
32-
`/emails/inbound/${emailId}/attachments`,
33-
);
32+
const queryString = buildPaginationQuery(options);
33+
const url = queryString
34+
? `/emails/receiving/${emailId}/attachments?${queryString}`
35+
: `/emails/receiving/${emailId}/attachments`;
36+
37+
const data = await this.resend.get<ListAttachmentsResponseSuccess>(url);
3438

3539
return data;
3640
}

src/emails/receiving/receiving.spec.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { ErrorResponse } from '../../interfaces';
22
import { Resend } from '../../resend';
3+
import type {
4+
GetInboundEmailResponseSuccess,
5+
ListInboundEmailsResponseSuccess,
6+
} from './interfaces';
37

48
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
59

@@ -40,7 +44,7 @@ describe('Receiving', () => {
4044

4145
describe('when inbound email found', () => {
4246
it('returns inbound email', async () => {
43-
const apiResponse = {
47+
const apiResponse: GetInboundEmailResponseSuccess = {
4448
object: 'inbound' as const,
4549
id: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
4650
@@ -117,7 +121,7 @@ describe('Receiving', () => {
117121
});
118122

119123
it('returns inbound email with no attachments', async () => {
120-
const apiResponse = {
124+
const apiResponse: GetInboundEmailResponseSuccess = {
121125
object: 'inbound' as const,
122126
id: '67d9bcdb-5a02-42d7-8da9-0d6feea18cff',
123127
@@ -205,7 +209,7 @@ describe('Receiving', () => {
205209

206210
describe('when inbound emails found', () => {
207211
it('returns list of inbound emails with transformed fields', async () => {
208-
const apiResponse = {
212+
const apiResponse: ListInboundEmailsResponseSuccess = {
209213
object: 'list' as const,
210214
has_more: true,
211215
data: [
@@ -225,7 +229,6 @@ describe('Receiving', () => {
225229
content_type: 'application/pdf',
226230
content_id: 'cid_123',
227231
content_disposition: 'attachment' as const,
228-
size: 12345,
229232
},
230233
],
231234
},
@@ -265,7 +268,6 @@ describe('Receiving', () => {
265268
"content_type": "application/pdf",
266269
"filename": "document.pdf",
267270
"id": "att_123",
268-
"size": 12345,
269271
},
270272
],
271273
"bcc": null,

0 commit comments

Comments
 (0)