Skip to content

Commit cce29ab

Browse files
committed
unit functions
1 parent 4361c4c commit cce29ab

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed

tests/unit/functions.test.js

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
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', /^multipart\/form-data/)
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', /^multipart\/form-data/)
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', /^multipart\/form-data/)
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', /^multipart\/form-data/)
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', /^multipart\/form-data/)
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

Comments
 (0)