Skip to content

Commit 33e4b78

Browse files
committed
Extract repetable contents
1 parent a678f50 commit 33e4b78

File tree

3 files changed

+184
-133
lines changed

3 files changed

+184
-133
lines changed

packages/api/tests/api-client.integration.test.ts

Lines changed: 63 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,50 @@
1-
import { MockAgent } from 'undici';
2-
import { ApiClient } from '../lib/api-client';
3-
import { MONDAY_API_ENDPOINT } from '../lib/constants';
4-
5-
const MONDAY_API_SUFFIX = '/v2';
6-
const MONDAY_API_ORIGIN = MONDAY_API_ENDPOINT.replace(MONDAY_API_SUFFIX, '');
1+
import {
2+
createMockAgentContext,
3+
setupMockAgent,
4+
teardownMockAgent,
5+
createMockedApiClient,
6+
mockGraphQLResponse,
7+
MockAgentContext,
8+
MONDAY_API_ORIGIN,
9+
MONDAY_API_SUFFIX,
10+
JSON_HEADERS,
11+
} from './test-utils';
712

813
describe('ApiClient timeout integration', () => {
9-
let mockAgent: MockAgent;
14+
const ctx = createMockAgentContext();
1015

1116
beforeEach(() => {
12-
mockAgent = new MockAgent();
13-
mockAgent.disableNetConnect();
17+
setupMockAgent(ctx);
1418
});
1519

1620
afterEach(async () => {
17-
await mockAgent.close();
21+
await teardownMockAgent(ctx);
1822
});
1923

20-
// Helper to create ApiClient with mocked fetch
21-
const createMockedApiClient = () => {
22-
const mockedFetch = ((input: any, init: any) => {
23-
return fetch(input, { ...init, dispatcher: mockAgent });
24-
}) as typeof fetch;
25-
return new ApiClient({
26-
token: 'test-token',
27-
requestConfig: { fetch: mockedFetch },
28-
});
29-
};
30-
31-
const jsonHeaders = { 'content-type': 'application/json' };
32-
3324
describe('request method', () => {
3425
it('should abort request when timeout is exceeded', async () => {
35-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
36-
mockPool
37-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
38-
.reply(200, { data: { users: [] } }, { headers: jsonHeaders })
39-
.delay(2000);
26+
mockGraphQLResponse(ctx.mockAgent, { users: [] }, { delay: 2000 });
4027

41-
const apiClient = createMockedApiClient();
28+
const apiClient = createMockedApiClient(ctx.mockAgent);
4229
const query = '{ users { id } }';
4330

4431
await expect(apiClient.request(query, undefined, { timeout: 100 })).rejects.toThrow('This operation was aborted');
4532
});
4633

4734
it('should complete successfully when response arrives before timeout', async () => {
48-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
49-
mockPool
50-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
51-
.reply(200, { data: { users: [{ id: '1' }] } }, { headers: jsonHeaders });
35+
mockGraphQLResponse(ctx.mockAgent, { users: [{ id: '1' }] });
5236

53-
const apiClient = createMockedApiClient();
37+
const apiClient = createMockedApiClient(ctx.mockAgent);
5438
const query = '{ users { id } }';
5539

5640
const result = await apiClient.request(query, undefined, { timeout: 500 });
5741
expect(result).toEqual({ users: [{ id: '1' }] });
5842
});
5943

6044
it('should work without timeout option', async () => {
61-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
62-
mockPool
63-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
64-
.reply(200, { data: { users: [{ id: '1', name: 'John' }] } }, { headers: jsonHeaders });
45+
mockGraphQLResponse(ctx.mockAgent, { users: [{ id: '1', name: 'John' }] });
6546

66-
const apiClient = createMockedApiClient();
47+
const apiClient = createMockedApiClient(ctx.mockAgent);
6748
const query = '{ users { id name } }';
6849

6950
const result = await apiClient.request(query);
@@ -73,25 +54,18 @@ describe('ApiClient timeout integration', () => {
7354

7455
describe('rawRequest method', () => {
7556
it('should abort rawRequest when timeout is exceeded', async () => {
76-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
77-
mockPool
78-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
79-
.reply(200, { data: { users: [] } }, { headers: jsonHeaders })
80-
.delay(2000);
57+
mockGraphQLResponse(ctx.mockAgent, { users: [] }, { delay: 2000 });
8158

82-
const apiClient = createMockedApiClient();
59+
const apiClient = createMockedApiClient(ctx.mockAgent);
8360
const query = '{ users { id } }';
8461

8562
await expect(apiClient.rawRequest(query, undefined, { timeout: 100 })).rejects.toThrow('This operation was aborted');
8663
});
8764

8865
it('should complete rawRequest successfully when response arrives before timeout', async () => {
89-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
90-
mockPool
91-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
92-
.reply(200, { data: { users: [{ id: '1' }] } }, { headers: jsonHeaders });
66+
mockGraphQLResponse(ctx.mockAgent, { users: [{ id: '1' }] });
9367

94-
const apiClient = createMockedApiClient();
68+
const apiClient = createMockedApiClient(ctx.mockAgent);
9569
const query = '{ users { id } }';
9670

9771
const result = await apiClient.rawRequest(query, undefined, { timeout: 500 });
@@ -101,56 +75,38 @@ describe('ApiClient timeout integration', () => {
10175
});
10276

10377
describe('ApiClient file upload integration', () => {
104-
let mockAgent: MockAgent;
105-
let capturedRequest: { body: any; headers: Record<string, string> } | null = null;
78+
const ctx = createMockAgentContext();
10679

10780
beforeEach(() => {
108-
mockAgent = new MockAgent();
109-
mockAgent.disableNetConnect();
110-
capturedRequest = null;
81+
setupMockAgent(ctx);
11182
});
11283

11384
afterEach(async () => {
114-
await mockAgent.close();
85+
await teardownMockAgent(ctx);
11586
});
11687

117-
// Helper to create ApiClient with mocked fetch that captures the request
118-
const createMockedApiClient = () => {
119-
const mockedFetch = (async (input: any, init: any) => {
120-
// Capture the request body and headers for assertions
121-
capturedRequest = {
122-
body: init?.body,
123-
headers: init?.headers || {},
124-
};
125-
return fetch(input, { ...init, dispatcher: mockAgent });
126-
}) as typeof fetch;
127-
128-
return new ApiClient({
129-
token: 'test-token',
130-
requestConfig: { fetch: mockedFetch },
88+
// Helper to create ApiClient with request capture for file upload tests
89+
const createApiClientWithCapture = () => {
90+
return createMockedApiClient(ctx.mockAgent, {
91+
captureRequest: ctx.setCapturedRequest,
13192
});
13293
};
13394

134-
const jsonHeaders = { 'content-type': 'application/json' };
135-
13695
describe('multipart form data conversion', () => {
13796
it('should convert request with single file to multipart/form-data', async () => {
138-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
139-
mockPool
140-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
141-
.reply(200, { data: { add_file_to_column: { id: '123' } } }, { headers: jsonHeaders });
97+
mockGraphQLResponse(ctx.mockAgent, { add_file_to_column: { id: '123' } });
14298

143-
const apiClient = createMockedApiClient();
99+
const apiClient = createApiClientWithCapture();
144100
const query = `mutation ($file: File!) { add_file_to_column(file: $file, item_id: 123, column_id: "files") { id } }`;
145101
const file = new Blob(['test file content'], { type: 'text/plain' });
146102

147103
await apiClient.request(query, { file });
148104

149105
// Verify request was converted to FormData
150-
expect(capturedRequest).not.toBeNull();
151-
expect(capturedRequest!.body).toBeInstanceOf(FormData);
106+
expect(ctx.capturedRequest).not.toBeNull();
107+
expect(ctx.capturedRequest!.body).toBeInstanceOf(FormData);
152108

153-
const formData = capturedRequest!.body as FormData;
109+
const formData = ctx.capturedRequest!.body as FormData;
154110

155111
// Verify FormData structure
156112
expect(formData.get('query')).toBe(query);
@@ -165,22 +121,19 @@ describe('ApiClient file upload integration', () => {
165121
});
166122

167123
it('should convert request with multiple files in array to multipart/form-data', async () => {
168-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
169-
mockPool
170-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
171-
.reply(200, { data: { upload_files: { success: true } } }, { headers: jsonHeaders });
124+
mockGraphQLResponse(ctx.mockAgent, { upload_files: { success: true } });
172125

173-
const apiClient = createMockedApiClient();
126+
const apiClient = createApiClientWithCapture();
174127
const query = `mutation ($files: [File!]!) { upload_files(files: $files) { success } }`;
175128
const file1 = new Blob(['file 1 content'], { type: 'text/plain' });
176129
const file2 = new Blob(['file 2 content'], { type: 'text/plain' });
177130

178131
await apiClient.request(query, { files: [file1, file2] });
179132

180-
expect(capturedRequest).not.toBeNull();
181-
expect(capturedRequest!.body).toBeInstanceOf(FormData);
133+
expect(ctx.capturedRequest).not.toBeNull();
134+
expect(ctx.capturedRequest!.body).toBeInstanceOf(FormData);
182135

183-
const formData = capturedRequest!.body as FormData;
136+
const formData = ctx.capturedRequest!.body as FormData;
184137

185138
const variables = JSON.parse(formData.get('variables') as string);
186139
expect(variables).toEqual({ files: [null, null] }); // Files replaced with null
@@ -196,12 +149,9 @@ describe('ApiClient file upload integration', () => {
196149
});
197150

198151
it('should handle nested files in objects', async () => {
199-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
200-
mockPool
201-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
202-
.reply(200, { data: { upload: { id: '456' } } }, { headers: jsonHeaders });
152+
mockGraphQLResponse(ctx.mockAgent, { upload: { id: '456' } });
203153

204-
const apiClient = createMockedApiClient();
154+
const apiClient = createApiClientWithCapture();
205155
const query = `mutation ($input: UploadInput!) { upload(input: $input) { id } }`;
206156
const file = new Blob(['nested file'], { type: 'application/pdf' });
207157

@@ -215,10 +165,10 @@ describe('ApiClient file upload integration', () => {
215165
},
216166
});
217167

218-
expect(capturedRequest).not.toBeNull();
219-
expect(capturedRequest!.body).toBeInstanceOf(FormData);
168+
expect(ctx.capturedRequest).not.toBeNull();
169+
expect(ctx.capturedRequest!.body).toBeInstanceOf(FormData);
220170

221-
const formData = capturedRequest!.body as FormData;
171+
const formData = ctx.capturedRequest!.body as FormData;
222172

223173
const variables = JSON.parse(formData.get('variables') as string);
224174
expect(variables).toEqual({
@@ -236,63 +186,51 @@ describe('ApiClient file upload integration', () => {
236186
});
237187

238188
it('should remove Content-Type header for multipart requests', async () => {
239-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
240-
mockPool
241-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
242-
.reply(200, { data: { upload: { id: '789' } } }, { headers: jsonHeaders });
189+
mockGraphQLResponse(ctx.mockAgent, { upload: { id: '789' } });
243190

244-
const apiClient = createMockedApiClient();
191+
const apiClient = createApiClientWithCapture();
245192
const query = `mutation ($file: File!) { upload(file: $file) { id } }`;
246193
const file = new Blob(['content'], { type: 'text/plain' });
247194

248195
await apiClient.request(query, { file });
249196

250-
expect(capturedRequest).not.toBeNull();
197+
expect(ctx.capturedRequest).not.toBeNull();
251198
// Content-Type should be removed to let browser set it with boundary
252-
expect(capturedRequest!.headers['Content-Type']).toBeUndefined();
253-
expect(capturedRequest!.headers['content-type']).toBeUndefined();
199+
expect(ctx.capturedRequest!.headers['Content-Type']).toBeUndefined();
200+
expect(ctx.capturedRequest!.headers['content-type']).toBeUndefined();
254201
});
255202

256203
it('should keep request as JSON when no files are present', async () => {
257-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
258-
mockPool
259-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
260-
.reply(200, { data: { users: [{ id: '1' }] } }, { headers: jsonHeaders });
204+
mockGraphQLResponse(ctx.mockAgent, { users: [{ id: '1' }] });
261205

262-
const apiClient = createMockedApiClient();
206+
const apiClient = createApiClientWithCapture();
263207
const query = '{ users { id } }';
264208

265209
await apiClient.request(query, { limit: 10 });
266210

267-
expect(capturedRequest).not.toBeNull();
211+
expect(ctx.capturedRequest).not.toBeNull();
268212
// Should remain as string (JSON), not FormData
269-
expect(typeof capturedRequest!.body).toBe('string');
270-
expect(capturedRequest!.headers['Content-Type']).toBe('application/json');
213+
expect(typeof ctx.capturedRequest!.body).toBe('string');
214+
expect(ctx.capturedRequest!.headers['Content-Type']).toBe('application/json');
271215
});
272216

273217
it('should work with rawRequest method for file uploads', async () => {
274-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
275-
mockPool
276-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
277-
.reply(200, { data: { add_file_to_column: { id: '999' } } }, { headers: jsonHeaders });
218+
mockGraphQLResponse(ctx.mockAgent, { add_file_to_column: { id: '999' } });
278219

279-
const apiClient = createMockedApiClient();
220+
const apiClient = createApiClientWithCapture();
280221
const query = `mutation ($file: File!) { add_file_to_column(file: $file, item_id: 456, column_id: "files") { id } }`;
281222
const file = new Blob(['raw request file'], { type: 'image/png' });
282223

283224
const result = await apiClient.rawRequest(query, { file });
284225

285226
expect(result.data).toEqual({ add_file_to_column: { id: '999' } });
286-
expect(capturedRequest!.body).toBeInstanceOf(FormData);
227+
expect(ctx.capturedRequest!.body).toBeInstanceOf(FormData);
287228
});
288229

289230
it('should handle mixed files and regular variables', async () => {
290-
const mockPool = mockAgent.get(MONDAY_API_ORIGIN);
291-
mockPool
292-
.intercept({ path: MONDAY_API_SUFFIX, method: 'POST' })
293-
.reply(200, { data: { create_item: { id: '111' } } }, { headers: jsonHeaders });
231+
mockGraphQLResponse(ctx.mockAgent, { create_item: { id: '111' } });
294232

295-
const apiClient = createMockedApiClient();
233+
const apiClient = createApiClientWithCapture();
296234
const query = `mutation ($name: String!, $file: File!, $tags: [String!]) { create_item(name: $name, file: $file, tags: $tags) { id } }`;
297235
const file = new Blob(['content'], { type: 'text/plain' });
298236

@@ -302,9 +240,9 @@ describe('ApiClient file upload integration', () => {
302240
tags: ['important', 'urgent'],
303241
});
304242

305-
expect(capturedRequest!.body).toBeInstanceOf(FormData);
243+
expect(ctx.capturedRequest!.body).toBeInstanceOf(FormData);
306244

307-
const formData = capturedRequest!.body as FormData;
245+
const formData = ctx.capturedRequest!.body as FormData;
308246
const variables = JSON.parse(formData.get('variables') as string);
309247

310248
expect(variables).toEqual({

0 commit comments

Comments
 (0)