1+ import { describe , test , expect , beforeEach , afterEach } from 'vitest' ;
2+ import nock from 'nock' ;
3+ import { createClient } from '../../src/index.ts' ;
4+
5+ describe ( 'Functions Module' , ( ) => {
6+ let base44 ;
7+ let scope ;
8+ const appId = 'test-app-id' ;
9+ const serverUrl = 'https://api.base44.com' ;
10+
11+ beforeEach ( ( ) => {
12+ // Create a new client for each test
13+ base44 = createClient ( {
14+ serverUrl,
15+ appId,
16+ } ) ;
17+
18+ // Create a nock scope for mocking API calls
19+ scope = nock ( serverUrl ) ;
20+
21+ // Enable request debugging for Nock
22+ nock . disableNetConnect ( ) ;
23+ nock . emitter . on ( 'no match' , ( req ) => {
24+ console . log ( `Nock: No match for ${ req . method } ${ req . path } ` ) ;
25+ console . log ( 'Headers:' , req . getHeaders ( ) ) ;
26+ } ) ;
27+ } ) ;
28+
29+ afterEach ( ( ) => {
30+ // Clean up any pending mocks
31+ nock . cleanAll ( ) ;
32+ nock . emitter . removeAllListeners ( 'no match' ) ;
33+ nock . enableNetConnect ( ) ;
34+ } ) ;
35+
36+ test ( 'should call a function with JSON data' , async ( ) => {
37+ const functionName = 'sendNotification' ;
38+ const functionData = {
39+ userId : '123' ,
40+ message : 'Hello World' ,
41+ priority : 'high'
42+ } ;
43+
44+ // Mock the API response
45+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
46+ . matchHeader ( 'Content-Type' , 'application/json' )
47+ . reply ( 200 , {
48+ success : true ,
49+ messageId : 'msg-456'
50+ } ) ;
51+
52+ // Call the function
53+ const result = await base44 . functions [ functionName ] ( functionData ) ;
54+
55+ // Verify the response
56+ expect ( result . data . success ) . toBe ( true ) ;
57+ expect ( result . data . messageId ) . toBe ( 'msg-456' ) ;
58+
59+ // Verify all mocks were called
60+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
61+ } ) ;
62+
63+ test ( 'should handle function with empty object parameters' , async ( ) => {
64+ const functionName = 'getStatus' ;
65+
66+ // Mock the API response
67+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , { } )
68+ . matchHeader ( 'Content-Type' , 'application/json' )
69+ . reply ( 200 , {
70+ status : 'healthy' ,
71+ timestamp : '2024-01-01T00:00:00Z'
72+ } ) ;
73+
74+ // Call the function
75+ const result = await base44 . functions [ functionName ] ( { } ) ;
76+
77+ // Verify the response
78+ expect ( result . data . status ) . toBe ( 'healthy' ) ;
79+
80+ // Verify all mocks were called
81+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
82+ } ) ;
83+
84+ test ( 'should handle function with complex nested objects' , async ( ) => {
85+ const functionName = 'processData' ;
86+ const functionData = {
87+ user : {
88+ id : '123' ,
89+ profile : {
90+ name : 'John Doe' ,
91+ preferences : {
92+ theme : 'dark' ,
93+ notifications : true
94+ }
95+ }
96+ } ,
97+ settings : {
98+ timeout : 5000 ,
99+ retries : 3
100+ }
101+ } ;
102+
103+ // Mock the API response
104+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
105+ . matchHeader ( 'Content-Type' , 'application/json' )
106+ . reply ( 200 , {
107+ processed : true ,
108+ userId : '123'
109+ } ) ;
110+
111+ // Call the function
112+ const result = await base44 . functions [ functionName ] ( functionData ) ;
113+
114+ // Verify the response
115+ expect ( result . data . processed ) . toBe ( true ) ;
116+
117+ // Verify all mocks were called
118+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
119+ } ) ;
120+
121+ test ( 'should handle file uploads with FormData' , async ( ) => {
122+ const functionName = 'uploadFile' ;
123+ const file = new File ( [ 'test content' ] , 'test.txt' , { type : 'text/plain' } ) ;
124+ const functionData = {
125+ file : file ,
126+ description : 'Test file upload 2' ,
127+ category : 'documents'
128+ } ;
129+
130+ // Mock the API response
131+ // TODO: Add validation to the request body
132+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` )
133+ . matchHeader ( 'Content-Type' , / ^ m u l t i p a r t \/ f o r m - d a t a / )
134+ . reply ( ( ) => {
135+ return [ 200 , {
136+ fileId : 'file-789' ,
137+ filename : 'test.txt' ,
138+ size : 12
139+ } ] ;
140+ } ) ;
141+
142+ // Call the function
143+ const result = await base44 . functions [ functionName ] ( functionData ) ;
144+
145+ // Verify the response
146+ expect ( result . data . fileId ) . toBe ( 'file-789' ) ;
147+ expect ( result . data . filename ) . toBe ( 'test.txt' ) ;
148+
149+ // Verify all mocks were called
150+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
151+ } ) ;
152+
153+ test ( 'should handle mixed data with files and regular data' , async ( ) => {
154+ const functionName = 'processDocument' ;
155+ const file = new File ( [ 'document content' ] , 'document.pdf' , { type : 'application/pdf' } ) ;
156+ const functionData = {
157+ file : file ,
158+ metadata : {
159+ title : 'Important Document' ,
160+ author : 'Jane Smith' ,
161+ tags : [ 'important' , 'confidential' ]
162+ } ,
163+ priority : 'high'
164+ } ;
165+
166+ // Mock the API response
167+ // TODO: Add validation to the request body
168+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` )
169+ . matchHeader ( 'Content-Type' , / ^ m u l t i p a r t \/ f o r m - d a t a / )
170+ . reply ( 200 , {
171+ documentId : 'doc-123' ,
172+ processed : true ,
173+ extractedText : 'document content'
174+ } ) ;
175+
176+ // Call the function
177+ const result = await base44 . functions [ functionName ] ( functionData ) ;
178+
179+ // Verify the response
180+ expect ( result . data . documentId ) . toBe ( 'doc-123' ) ;
181+ expect ( result . data . processed ) . toBe ( true ) ;
182+
183+ // Verify all mocks were called
184+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
185+ } ) ;
186+
187+ test ( 'should handle FormData input directly' , async ( ) => {
188+ const functionName = 'submitForm' ;
189+ const formData = new FormData ( ) ;
190+ formData . append ( 'name' , 'John Doe' ) ;
191+ formData . append ( 'email' , 'john@example.com' ) ;
192+ formData . append ( 'message' , 'Hello there' ) ;
193+
194+ // Mock the API response
195+ // TODO: Add validation to the request body
196+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` )
197+ . matchHeader ( 'Content-Type' , / ^ m u l t i p a r t \/ f o r m - d a t a / )
198+ . reply ( 200 , {
199+ formId : 'form-456' ,
200+ submitted : true
201+ } ) ;
202+
203+ // Call the function
204+ const result = await base44 . functions [ functionName ] ( formData ) ;
205+
206+ // Verify the response
207+ expect ( result . data . formId ) . toBe ( 'form-456' ) ;
208+ expect ( result . data . submitted ) . toBe ( true ) ;
209+
210+ // Verify all mocks were called
211+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
212+ } ) ;
213+
214+ test ( 'should throw error for string input instead of object' , async ( ) => {
215+ const functionName = 'processData' ;
216+
217+ // Call the function with string input (should throw)
218+ await expect ( base44 . functions [ functionName ] ( 'invalid string input' ) )
219+ . rejects
220+ . toThrow ( `Function ${ functionName } must receive an object with named parameters, received: invalid string input` ) ;
221+ } ) ;
222+
223+ test ( 'should handle function names with special characters' , async ( ) => {
224+ const functionName = 'process-data_v2' ;
225+ const functionData = {
226+ input : 'test data'
227+ } ;
228+
229+ // Mock the API response
230+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
231+ . matchHeader ( 'Content-Type' , 'application/json' )
232+ . reply ( 200 , {
233+ processed : true
234+ } ) ;
235+
236+ // Call the function
237+ const result = await base44 . functions [ functionName ] ( functionData ) ;
238+
239+ // Verify the response
240+ expect ( result . data . processed ) . toBe ( true ) ;
241+
242+ // Verify all mocks were called
243+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
244+ } ) ;
245+
246+ test ( 'should handle API errors gracefully' , async ( ) => {
247+ const functionName = 'failingFunction' ;
248+ const functionData = {
249+ param : 'value'
250+ } ;
251+
252+ // Mock the API error response
253+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
254+ . matchHeader ( 'Content-Type' , 'application/json' )
255+ . reply ( 500 , {
256+ error : 'Internal server error' ,
257+ code : 'INTERNAL_ERROR'
258+ } ) ;
259+
260+ // Call the function and expect it to throw
261+ await expect ( base44 . functions [ functionName ] ( functionData ) )
262+ . rejects
263+ . toThrow ( ) ;
264+
265+ // Verify all mocks were called
266+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
267+ } ) ;
268+
269+ test ( 'should handle 404 errors for non-existent functions' , async ( ) => {
270+ const functionName = 'nonExistentFunction' ;
271+ const functionData = {
272+ param : 'value'
273+ } ;
274+
275+ // Mock the API 404 response
276+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
277+ . matchHeader ( 'Content-Type' , 'application/json' )
278+ . reply ( 404 , {
279+ error : 'Function not found' ,
280+ code : 'FUNCTION_NOT_FOUND'
281+ } ) ;
282+
283+ // Call the function and expect it to throw
284+ await expect ( base44 . functions [ functionName ] ( functionData ) )
285+ . rejects
286+ . toThrow ( ) ;
287+
288+ // Verify all mocks were called
289+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
290+ } ) ;
291+
292+ test ( 'should handle null and undefined values in data' , async ( ) => {
293+ const functionName = 'handleNullValues' ;
294+ const functionData = {
295+ stringValue : 'test' ,
296+ nullValue : null ,
297+ undefinedValue : undefined ,
298+ emptyString : ''
299+ } ;
300+
301+ // Mock the API response
302+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
303+ . matchHeader ( 'Content-Type' , 'application/json' )
304+ . reply ( 200 , {
305+ received : true ,
306+ values : functionData
307+ } ) ;
308+
309+ // Call the function
310+ const result = await base44 . functions [ functionName ] ( functionData ) ;
311+
312+ // Verify the response
313+ expect ( result . data . received ) . toBe ( true ) ;
314+
315+ // Verify all mocks were called
316+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
317+ } ) ;
318+
319+ test ( 'should handle array values in data' , async ( ) => {
320+ const functionName = 'processArray' ;
321+ const functionData = {
322+ numbers : [ 1 , 2 , 3 , 4 , 5 ] ,
323+ strings : [ 'a' , 'b' , 'c' ] ,
324+ mixed : [ 1 , 'two' , { three : 3 } ]
325+ } ;
326+
327+ // Mock the API response
328+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` , functionData )
329+ . matchHeader ( 'Content-Type' , 'application/json' )
330+ . reply ( 200 , {
331+ processed : true ,
332+ count : 3
333+ } ) ;
334+
335+ // Call the function
336+ const result = await base44 . functions [ functionName ] ( functionData ) ;
337+
338+ // Verify the response
339+ expect ( result . data . processed ) . toBe ( true ) ;
340+ expect ( result . data . count ) . toBe ( 3 ) ;
341+
342+ // Verify all mocks were called
343+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
344+ } ) ;
345+
346+ test ( 'should create FormData correctly when files are present' , async ( ) => {
347+ const functionName = 'uploadFile' ;
348+ const file = new File ( [ 'test content' ] , 'test.txt' , { type : 'text/plain' } ) ;
349+ const functionData = {
350+ file : file ,
351+ description : 'Test file upload' ,
352+ category : 'documents'
353+ } ;
354+
355+ // Mock the API response
356+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` )
357+ . matchHeader ( 'Content-Type' , / ^ m u l t i p a r t \/ f o r m - d a t a / )
358+ . reply ( 200 , { success : true } ) ;
359+
360+ // Call the function
361+ const result = await base44 . functions [ functionName ] ( functionData ) ;
362+
363+ // Verify the response
364+ expect ( result . data . success ) . toBe ( true ) ;
365+
366+ // Verify all mocks were called
367+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
368+ } ) ;
369+
370+ test ( 'should create FormData correctly when FormData is passed directly' , async ( ) => {
371+ const functionName = 'submitForm' ;
372+ const formData = new FormData ( ) ;
373+ formData . append ( 'name' , 'John Doe' ) ;
374+ formData . append ( 'email' , 'john@example.com' ) ;
375+
376+ // Mock the API response
377+ scope . post ( `/api/apps/${ appId } /functions/${ functionName } ` )
378+ . matchHeader ( 'Content-Type' , / ^ m u l t i p a r t \/ f o r m - d a t a / )
379+ . reply ( 200 , { success : true } ) ;
380+
381+ // Call the function
382+ const result = await base44 . functions [ functionName ] ( formData ) ;
383+
384+ // Verify the response
385+ expect ( result . data . success ) . toBe ( true ) ;
386+
387+ // Verify all mocks were called
388+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
389+ } ) ;
390+ } ) ;
0 commit comments