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
813describe ( '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
10377describe ( '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