diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte index ab01d4224e..f236eaaf33 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte @@ -96,10 +96,13 @@ } if (cloneQuery?.fields?.requestBody) { - cloneQuery.fields.requestBody = runtimeToReadableBinding( - restBindings, - cloneQuery.fields.requestBody - ) + cloneQuery.fields.requestBody = + typeof cloneQuery.fields.requestBody === "object" + ? runtimeToReadableMap(restBindings, cloneQuery.fields.requestBody) + : runtimeToReadableBinding( + restBindings, + cloneQuery.fields.requestBody + ) } if (cloneQuery?.parameters) { @@ -141,10 +144,11 @@ restBindings, newQuery.fields.headers ) - newQuery.fields.requestBody = readableToRuntimeBinding( - restBindings, - newQuery.fields.requestBody - ) + newQuery.fields.requestBody = + typeof newQuery.fields.requestBody === "object" + ? readableToRuntimeMap(restBindings, newQuery.fields.requestBody) + : readableToRuntimeBinding(restBindings, newQuery.fields.requestBody) + newQuery.fields.path = url.split("?")[0] newQuery.fields.queryString = queryString newQuery.fields.authConfigId = authConfigId diff --git a/packages/server/__mocks__/node-fetch.ts b/packages/server/__mocks__/node-fetch.ts index 350680691b..1a7015fa52 100644 --- a/packages/server/__mocks__/node-fetch.ts +++ b/packages/server/__mocks__/node-fetch.ts @@ -15,6 +15,15 @@ module FetchMock { }, }, json: async () => { + //x-www-form-encoded body is a URLSearchParams + //The call to stringify it leaves it blank + if (body?.opts?.body instanceof URLSearchParams) { + const paramArray = Array.from(body.opts.body.entries()) + body.opts.body = paramArray.reduce((acc: any, pair: any) => { + acc[pair[0]] = pair[1] + return acc + }, {}) + } return body }, } diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index 5dacda3505..273bdb9993 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -346,4 +346,170 @@ describe("/queries", () => { expect(contents).toBe(null) }) }) + + describe("Current User Request Mapping", () => { + + async function previewGet(datasource, fields, params) { + return config.previewQuery(request, config, datasource, fields, params) + } + + async function previewPost(datasource, fields, params) { + return config.previewQuery(request, config, datasource, fields, params, "create") + } + + it("should parse global and query level header mappings", async () => { + const userDetails = config.getUserDetails() + + const datasource = await config.restDatasource({ + defaultHeaders: { + "test": "headerVal", + "emailHdr": "{{[user].[email]}}" + } + }) + const res = await previewGet(datasource, { + path: "www.google.com", + queryString: "email={{[user].[email]}}", + headers: { + queryHdr : "{{[user].[firstName]}}", + secondHdr : "1234" + } + }) + + const parsedRequest = JSON.parse(res.body.extra.raw) + expect(parsedRequest.opts.headers).toEqual({ + "test": "headerVal", + "emailHdr": userDetails.email, + "queryHdr": userDetails.firstName, + "secondHdr" : "1234" + }) + expect(res.body.rows[0].url).toEqual("http://www.google.com?email=" + userDetails.email) + }) + + it("should bind the current user to query parameters", async () => { + const userDetails = config.getUserDetails() + + const datasource = await config.restDatasource() + + const res = await previewGet(datasource, { + path: "www.google.com", + queryString: "test={{myEmail}}&testName={{myName}}&testParam={{testParam}}", + }, { + "myEmail" : "{{[user].[email]}}", + "myName" : "{{[user].[firstName]}}", + "testParam" : "1234" + }) + + expect(res.body.rows[0].url).toEqual("http://www.google.com?test=" + userDetails.email + + "&testName=" + userDetails.firstName + "&testParam=1234") + }) + + it("should bind the current user the request body - plain text", async () => { + const userDetails = config.getUserDetails() + const datasource = await config.restDatasource() + + const res = await previewPost(datasource, { + path: "www.google.com", + queryString: "testParam={{testParam}}", + requestBody: "This is plain text and this is my email: {{[user].[email]}}. This is a test param: {{testParam}}", + bodyType: "text" + }, { + "testParam" : "1234" + }) + + const parsedRequest = JSON.parse(res.body.extra.raw) + expect(parsedRequest.opts.body).toEqual(`This is plain text and this is my email: ${userDetails.email}. This is a test param: 1234`) + expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234") + }) + + it("should bind the current user the request body - json", async () => { + const userDetails = config.getUserDetails() + const datasource = await config.restDatasource() + + const res = await previewPost(datasource, { + path: "www.google.com", + queryString: "testParam={{testParam}}", + requestBody: "{\"email\":\"{{[user].[email]}}\",\"queryCode\":{{testParam}},\"userRef\":\"{{userRef}}\"}", + bodyType: "json" + }, { + "testParam" : "1234", + "userRef" : "{{[user].[firstName]}}" + }) + + const parsedRequest = JSON.parse(res.body.extra.raw) + const test = `{"email":"${userDetails.email}","queryCode":1234,"userRef":"${userDetails.firstName}"}` + expect(parsedRequest.opts.body).toEqual(test) + expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234") + }) + + it("should bind the current user the request body - xml", async () => { + const userDetails = config.getUserDetails() + const datasource = await config.restDatasource() + + const res = await previewPost(datasource, { + path: "www.google.com", + queryString: "testParam={{testParam}}", + requestBody: " {{[user].[email]}} {{testParam}} " + + "{{userId}} testing ", + bodyType: "xml" + }, { + "testParam" : "1234", + "userId" : "{{[user].[firstName]}}" + }) + + const parsedRequest = JSON.parse(res.body.extra.raw) + const test = ` ${userDetails.email} 1234 ${userDetails.firstName} testing ` + + expect(parsedRequest.opts.body).toEqual(test) + expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234") + }) + + it("should bind the current user the request body - form-data", async () => { + const userDetails = config.getUserDetails() + const datasource = await config.restDatasource() + + const res = await previewPost(datasource, { + path: "www.google.com", + queryString: "testParam={{testParam}}", + requestBody: "{\"email\":\"{{[user].[email]}}\",\"queryCode\":{{testParam}},\"userRef\":\"{{userRef}}\"}", + bodyType: "form" + }, { + "testParam" : "1234", + "userRef" : "{{[user].[firstName]}}" + }) + + const parsedRequest = JSON.parse(res.body.extra.raw) + + const emailData = parsedRequest.opts.body._streams[1] + expect(emailData).toEqual(userDetails.email) + + const queryCodeData = parsedRequest.opts.body._streams[4] + expect(queryCodeData).toEqual("1234") + + const userRef = parsedRequest.opts.body._streams[7] + expect(userRef).toEqual(userDetails.firstName) + + expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234") + }) + + it("should bind the current user the request body - encoded", async () => { + const userDetails = config.getUserDetails() + const datasource = await config.restDatasource() + + const res = await previewPost(datasource, { + path: "www.google.com", + queryString: "testParam={{testParam}}", + requestBody: "{\"email\":\"{{[user].[email]}}\",\"queryCode\":{{testParam}},\"userRef\":\"{{userRef}}\"}", + bodyType: "encoded" + }, { + "testParam" : "1234", + "userRef" : "{{[user].[firstName]}}" + }) + const parsedRequest = JSON.parse(res.body.extra.raw) + + expect(parsedRequest.opts.body.email).toEqual(userDetails.email) + expect(parsedRequest.opts.body.queryCode).toEqual("1234") + expect(parsedRequest.opts.body.userRef).toEqual(userDetails.firstName) + }) + + }); }) diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts index 6174613fc8..e9c5f59fad 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -286,7 +286,7 @@ module RestModule { input.body = form break case BodyTypes.XML: - if (object != null) { + if (object != null && Object.keys(object).length) { string = new XmlBuilder().buildObject(object) } input.body = string diff --git a/packages/server/src/integrations/tests/rest.spec.js b/packages/server/src/integrations/tests/rest.spec.js index 8f3c7f7f58..0bb1e3a75d 100644 --- a/packages/server/src/integrations/tests/rest.spec.js +++ b/packages/server/src/integrations/tests/rest.spec.js @@ -155,12 +155,27 @@ describe("REST Integration", () => { expect(output.headers["Content-Type"]).toEqual("application/json") }) - it("should allow XML", () => { + it("should allow raw XML", () => { + const output = config.integration.addBody("xml", "12", {}) + expect(output.body.includes("1")).toEqual(true) + expect(output.body.includes("2")).toEqual(true) + expect(output.headers["Content-Type"]).toEqual("application/xml") + }) + + it("should allow a valid js object and parse the contents to xml", () => { const output = config.integration.addBody("xml", input, {}) expect(output.body.includes("1")).toEqual(true) expect(output.body.includes("2")).toEqual(true) expect(output.headers["Content-Type"]).toEqual("application/xml") }) + + it("should allow a valid json string and parse the contents to xml", () => { + const output = config.integration.addBody("xml", JSON.stringify(input), {}) + expect(output.body.includes("1")).toEqual(true) + expect(output.body.includes("2")).toEqual(true) + expect(output.headers["Content-Type"]).toEqual("application/xml") + }) + }) describe("response", () => { diff --git a/packages/server/src/migrations/tests/index.spec.ts b/packages/server/src/migrations/tests/index.spec.ts index ca30fbca06..84400f3df3 100644 --- a/packages/server/src/migrations/tests/index.spec.ts +++ b/packages/server/src/migrations/tests/index.spec.ts @@ -91,8 +91,24 @@ describe("migrations", () => { await clearMigrations() const appId = config.prodAppId const roles = { [appId]: "role_12345" } - await config.createUser(undefined, undefined, false, true, roles) // admin only - await config.createUser(undefined, undefined, false, false, roles) // non admin non builder + await config.createUser( + undefined, + undefined, + undefined, + undefined, + false, + true, + roles + ) // admin only + await config.createUser( + undefined, + undefined, + undefined, + undefined, + false, + false, + roles + ) // non admin non builder await config.createTable() await config.createRow() await config.createRow() diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index fc4d302c63..baa4ec13b8 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -28,6 +28,8 @@ const { encrypt } = require("@budibase/backend-core/encryption") const GLOBAL_USER_ID = "us_uuid1" const EMAIL = "babs@babs.com" +const FIRSTNAME = "Barbara" +const LASTNAME = "Barbington" const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306" class TestConfiguration { @@ -59,6 +61,15 @@ class TestConfiguration { return this.prodAppId } + getUserDetails() { + return { + globalId: GLOBAL_USER_ID, + email: EMAIL, + firstName: FIRSTNAME, + lastName: LASTNAME, + } + } + async doInContext(appId, task) { if (!appId) { appId = this.appId @@ -118,6 +129,8 @@ class TestConfiguration { // USER / AUTH async globalUser({ id = GLOBAL_USER_ID, + firstName = FIRSTNAME, + lastName = LASTNAME, builder = true, admin = false, email = EMAIL, @@ -135,6 +148,8 @@ class TestConfiguration { ...existing, roles: roles || {}, tenantId: TENANT_ID, + firstName, + lastName, } await createASession(id, { sessionId: "sessionid", @@ -161,6 +176,8 @@ class TestConfiguration { async createUser( id = null, + firstName = FIRSTNAME, + lastName = LASTNAME, email = EMAIL, builder = true, admin = false, @@ -169,6 +186,8 @@ class TestConfiguration { const globalId = !id ? `us_${Math.random()}` : `us_${id}` const resp = await this.globalUser({ id: globalId, + firstName, + lastName, email, builder, admin, @@ -520,14 +539,14 @@ class TestConfiguration { // QUERY - async previewQuery(request, config, datasource, fields) { + async previewQuery(request, config, datasource, fields, params, verb) { return request .post(`/api/queries/preview`) .send({ datasourceId: datasource._id, - parameters: {}, + parameters: params || {}, fields, - queryVerb: "read", + queryVerb: verb || "read", name: datasource.name, }) .set(config.defaultHeaders())