Skip to content

Commit 5abbf77

Browse files
committed
SDK - camel api, internally switch to snake for python
1 parent 7b5104c commit 5abbf77

File tree

3 files changed

+69
-23
lines changed

3 files changed

+69
-23
lines changed

src/modules/custom-integrations.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,18 @@ export function createCustomIntegrationsModule(
3131
throw new Error("Operation ID is required and cannot be empty");
3232
}
3333

34+
// Convert camelCase to snake_case for Python backend
35+
const { pathParams, queryParams, ...rest } = params ?? {};
36+
const body = {
37+
...rest,
38+
...(pathParams && { path_params: pathParams }),
39+
...(queryParams && { query_params: queryParams }),
40+
};
41+
3442
// Make the API call
35-
// Note: axios interceptor extracts response.data, so we get the payload directly
3643
const response = await axios.post(
3744
`/apps/${appId}/integrations/custom/${slug}/${operationId}`,
38-
params ?? {}
45+
body
3946
);
4047

4148
// The axios interceptor extracts response.data, so we get the payload directly

src/modules/custom-integrations.types.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ export interface CustomIntegrationCallParams {
1010
/**
1111
* Path parameters to substitute in the URL (e.g., `{ owner: "user", repo: "repo" }`).
1212
*/
13-
path_params?: Record<string, string>;
13+
pathParams?: Record<string, string>;
1414

1515
/**
1616
* Query string parameters to append to the URL.
1717
*/
18-
query_params?: Record<string, any>;
18+
queryParams?: Record<string, any>;
1919

2020
/**
2121
* Additional headers to send with this specific request.
@@ -64,8 +64,8 @@ export interface CustomIntegrationCallResponse {
6464
* "github", // integration slug (defined by workspace admin)
6565
* "listIssues", // operation ID from the OpenAPI spec
6666
* {
67-
* path_params: { owner: "myorg", repo: "myrepo" },
68-
* query_params: { state: "open", per_page: 100 }
67+
* pathParams: { owner: "myorg", repo: "myrepo" },
68+
* queryParams: { state: "open", per_page: 100 }
6969
* }
7070
* );
7171
*
@@ -83,7 +83,7 @@ export interface CustomIntegrationCallResponse {
8383
* "github",
8484
* "createIssue",
8585
* {
86-
* path_params: { owner: "myorg", repo: "myrepo" },
86+
* pathParams: { owner: "myorg", repo: "myrepo" },
8787
* payload: {
8888
* title: "Bug report",
8989
* body: "Something is broken",
@@ -99,7 +99,7 @@ export interface CustomIntegrationsModule {
9999
*
100100
* @param slug - The integration's unique identifier (slug), as defined by the workspace admin.
101101
* @param operationId - The operation ID from the OpenAPI spec (e.g., "listIssues", "getUser").
102-
* @param params - Optional parameters including payload, path params, query params, and headers.
102+
* @param params - Optional parameters including payload, pathParams, queryParams, and headers.
103103
* @returns Promise resolving to the integration call response.
104104
*
105105
* @throws {Error} If slug is not provided.

tests/unit/custom-integrations.test.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ describe('Custom Integrations Module', () => {
2424
nock.cleanAll();
2525
});
2626

27-
test('custom.call() should send POST request to correct endpoint', async () => {
27+
test('custom.call() should convert camelCase params to snake_case for backend', async () => {
2828
const slug = 'github';
2929
const operationId = 'listIssues';
30-
const params = {
30+
31+
// SDK call uses camelCase (JS convention)
32+
const sdkParams = {
33+
payload: { title: 'Test Issue' },
34+
pathParams: { owner: 'testuser', repo: 'testrepo' },
35+
queryParams: { state: 'open' },
36+
};
37+
38+
// Backend expects snake_case (Python convention)
39+
const expectedBody = {
3140
payload: { title: 'Test Issue' },
3241
path_params: { owner: 'testuser', repo: 'testrepo' },
3342
query_params: { state: 'open' },
@@ -39,13 +48,13 @@ describe('Custom Integrations Module', () => {
3948
data: { issues: [{ id: 1, title: 'Test Issue' }] },
4049
};
4150

42-
// Mock the API response
51+
// Mock expects snake_case body
4352
scope
44-
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, params)
53+
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, expectedBody)
4554
.reply(200, mockResponse);
4655

47-
// Call the API
48-
const result = await base44.integrations.custom.call(slug, operationId, params);
56+
// SDK call uses camelCase
57+
const result = await base44.integrations.custom.call(slug, operationId, sdkParams);
4958

5059
// Verify the response
5160
expect(result.success).toBe(true);
@@ -195,7 +204,7 @@ describe('Custom Integrations Module', () => {
195204
metadata: { key: `value_${i}` },
196205
}));
197206

198-
const params = {
207+
const sdkParams = {
199208
payload: { items: largeArray },
200209
};
201210

@@ -207,11 +216,11 @@ describe('Custom Integrations Module', () => {
207216

208217
// Mock the API response
209218
scope
210-
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, params)
219+
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, sdkParams)
211220
.reply(200, mockResponse);
212221

213222
// Call the API with large payload
214-
const result = await base44.integrations.custom.call(slug, operationId, params);
223+
const result = await base44.integrations.custom.call(slug, operationId, sdkParams);
215224

216225
// Verify the response
217226
expect(result.success).toBe(true);
@@ -224,7 +233,7 @@ describe('Custom Integrations Module', () => {
224233
test('custom.call() should include custom headers in request', async () => {
225234
const slug = 'myapi';
226235
const operationId = 'getData';
227-
const params = {
236+
const sdkParams = {
228237
headers: { 'X-Custom-Header': 'custom-value' },
229238
};
230239

@@ -236,11 +245,11 @@ describe('Custom Integrations Module', () => {
236245

237246
// Mock the API response
238247
scope
239-
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, params)
248+
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, sdkParams)
240249
.reply(200, mockResponse);
241250

242251
// Call the API
243-
const result = await base44.integrations.custom.call(slug, operationId, params);
252+
const result = await base44.integrations.custom.call(slug, operationId, sdkParams);
244253

245254
// Verify the response
246255
expect(result.success).toBe(true);
@@ -252,7 +261,7 @@ describe('Custom Integrations Module', () => {
252261
test('custom.call() should pass through multiple headers', async () => {
253262
const slug = 'myapi';
254263
const operationId = 'secureEndpoint';
255-
const params = {
264+
const sdkParams = {
256265
headers: {
257266
'X-API-Key': 'secret-key-123',
258267
'X-Request-ID': 'req-456',
@@ -269,11 +278,11 @@ describe('Custom Integrations Module', () => {
269278

270279
// Mock the API response - verify all headers are passed in the body
271280
scope
272-
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, params)
281+
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, sdkParams)
273282
.reply(200, mockResponse);
274283

275284
// Call the API
276-
const result = await base44.integrations.custom.call(slug, operationId, params);
285+
const result = await base44.integrations.custom.call(slug, operationId, sdkParams);
277286

278287
// Verify the response
279288
expect(result.success).toBe(true);
@@ -283,6 +292,36 @@ describe('Custom Integrations Module', () => {
283292
expect(scope.isDone()).toBe(true);
284293
});
285294

295+
test('custom.call() should only include defined params in body', async () => {
296+
const slug = 'github';
297+
const operationId = 'getUser';
298+
299+
// SDK call with only pathParams
300+
const sdkParams = {
301+
pathParams: { username: 'octocat' },
302+
};
303+
304+
// Expected body should only have path_params, not empty payload/query_params/headers
305+
const expectedBody = {
306+
path_params: { username: 'octocat' },
307+
};
308+
309+
const mockResponse = {
310+
success: true,
311+
status_code: 200,
312+
data: { login: 'octocat' },
313+
};
314+
315+
scope
316+
.post(`/api/apps/${appId}/integrations/custom/${slug}/${operationId}`, expectedBody)
317+
.reply(200, mockResponse);
318+
319+
const result = await base44.integrations.custom.call(slug, operationId, sdkParams);
320+
321+
expect(result.success).toBe(true);
322+
expect(scope.isDone()).toBe(true);
323+
});
324+
286325
test('custom property should not interfere with other integration packages', async () => {
287326
// Test that Core still works
288327
const coreParams = {

0 commit comments

Comments
 (0)