516 lines
17 KiB
JavaScript
516 lines
17 KiB
JavaScript
// Mock out postgres for this
|
|
jest.mock("pg")
|
|
jest.mock("node-fetch")
|
|
|
|
// Mock isProdAppID to we can later mock the implementation and pretend we are
|
|
// using prod app IDs
|
|
const authDb = require("@budibase/backend-core/db")
|
|
const { isProdAppID } = authDb
|
|
const mockIsProdAppID = jest.fn(isProdAppID)
|
|
authDb.isProdAppID = mockIsProdAppID
|
|
|
|
const setup = require("./utilities")
|
|
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
|
const { checkCacheForDynamicVariable } = require("../../../threads/utils")
|
|
const { basicQuery, basicDatasource } = setup.structures
|
|
const { events } = require("@budibase/backend-core")
|
|
|
|
describe("/queries", () => {
|
|
let request = setup.getRequest()
|
|
let config = setup.getConfig()
|
|
let datasource, query
|
|
|
|
afterAll(setup.afterAll)
|
|
|
|
beforeEach(async () => {
|
|
await config.init()
|
|
datasource = await config.createDatasource()
|
|
query = await config.createQuery()
|
|
})
|
|
|
|
async function createInvalidIntegration() {
|
|
const datasource = await config.createDatasource({
|
|
datasource: {
|
|
...basicDatasource().datasource,
|
|
source: "INVALID_INTEGRATION",
|
|
},
|
|
})
|
|
const query = await config.createQuery()
|
|
return { datasource, query }
|
|
}
|
|
|
|
const createQuery = async (query) => {
|
|
return request
|
|
.post(`/api/queries`)
|
|
.send(query)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
}
|
|
|
|
describe("create", () => {
|
|
it("should create a new query", async () => {
|
|
const { _id } = await config.createDatasource()
|
|
const query = basicQuery(_id)
|
|
jest.clearAllMocks()
|
|
const res = await createQuery(query)
|
|
|
|
expect(res.res.statusMessage).toEqual(
|
|
`Query ${query.name} saved successfully.`
|
|
)
|
|
expect(res.body).toEqual({
|
|
_rev: res.body._rev,
|
|
_id: res.body._id,
|
|
...query,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
})
|
|
expect(events.query.created).toBeCalledTimes(1)
|
|
expect(events.query.updated).not.toBeCalled()
|
|
})
|
|
})
|
|
|
|
describe("update", () => {
|
|
it("should update query", async () => {
|
|
const { _id } = await config.createDatasource()
|
|
const query = basicQuery(_id)
|
|
const res = await createQuery(query)
|
|
jest.clearAllMocks()
|
|
query._id = res.body._id
|
|
query._rev = res.body._rev
|
|
await createQuery(query)
|
|
|
|
expect(res.res.statusMessage).toEqual(
|
|
`Query ${query.name} saved successfully.`
|
|
)
|
|
expect(res.body).toEqual({
|
|
_rev: res.body._rev,
|
|
_id: res.body._id,
|
|
...query,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
})
|
|
expect(events.query.created).not.toBeCalled()
|
|
expect(events.query.updated).toBeCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe("fetch", () => {
|
|
it("returns all the queries from the server", async () => {
|
|
const res = await request
|
|
.get(`/api/queries`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
|
|
const queries = res.body
|
|
expect(queries).toEqual([
|
|
{
|
|
_rev: query._rev,
|
|
_id: query._id,
|
|
createdAt: new Date().toISOString(),
|
|
...basicQuery(datasource._id),
|
|
updatedAt: new Date().toISOString(),
|
|
readable: true,
|
|
},
|
|
])
|
|
})
|
|
|
|
it("should apply authorization to endpoint", async () => {
|
|
await checkBuilderEndpoint({
|
|
config,
|
|
method: "GET",
|
|
url: `/api/datasources`,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("find", () => {
|
|
it("should find a query in builder", async () => {
|
|
const query = await config.createQuery()
|
|
const res = await request
|
|
.get(`/api/queries/${query._id}`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body._id).toEqual(query._id)
|
|
})
|
|
|
|
it("should find a query in cloud", async () => {
|
|
await setup.switchToSelfHosted(async () => {
|
|
const query = await config.createQuery()
|
|
const res = await request
|
|
.get(`/api/queries/${query._id}`)
|
|
.set(await config.defaultHeaders())
|
|
.expect(200)
|
|
.expect("Content-Type", /json/)
|
|
expect(res.body.fields).toBeDefined()
|
|
expect(res.body.parameters).toBeDefined()
|
|
expect(res.body.schema).toBeDefined()
|
|
})
|
|
})
|
|
|
|
it("should remove sensitive info for prod apps", async () => {
|
|
// Mock isProdAppID to pretend we are using a prod app
|
|
mockIsProdAppID.mockClear()
|
|
mockIsProdAppID.mockImplementation(() => true)
|
|
|
|
const query = await config.createQuery()
|
|
const res = await request
|
|
.get(`/api/queries/${query._id}`)
|
|
.set(await config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body._id).toEqual(query._id)
|
|
expect(res.body.fields).toBeUndefined()
|
|
expect(res.body.parameters).toBeUndefined()
|
|
expect(res.body.schema).toBeDefined()
|
|
|
|
// Reset isProdAppID mock
|
|
expect(mockIsProdAppID).toHaveBeenCalledTimes(1)
|
|
mockIsProdAppID.mockImplementation(isProdAppID)
|
|
})
|
|
})
|
|
|
|
describe("destroy", () => {
|
|
it("deletes a query and returns a success message", async () => {
|
|
await request
|
|
.delete(`/api/queries/${query._id}/${query._rev}`)
|
|
.set(config.defaultHeaders())
|
|
.expect(200)
|
|
|
|
const res = await request
|
|
.get(`/api/queries`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
|
|
expect(res.body).toEqual([])
|
|
expect(events.query.deleted).toBeCalledTimes(1)
|
|
expect(events.query.deleted).toBeCalledWith(datasource, query)
|
|
})
|
|
|
|
it("should apply authorization to endpoint", async () => {
|
|
await checkBuilderEndpoint({
|
|
config,
|
|
method: "DELETE",
|
|
url: `/api/queries/${config._id}/${config._rev}`,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("preview", () => {
|
|
it("should be able to preview the query", async () => {
|
|
const query = {
|
|
datasourceId: datasource._id,
|
|
parameters: {},
|
|
fields: {},
|
|
queryVerb: "read",
|
|
name: datasource.name,
|
|
}
|
|
const res = await request
|
|
.post(`/api/queries/preview`)
|
|
.send(query)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
// these responses come from the mock
|
|
expect(res.body.schemaFields).toEqual(["a", "b"])
|
|
expect(res.body.rows.length).toEqual(1)
|
|
expect(events.query.previewed).toBeCalledTimes(1)
|
|
datasource.config = { schema: "public" }
|
|
expect(events.query.previewed).toBeCalledWith(datasource, query)
|
|
})
|
|
|
|
it("should apply authorization to endpoint", async () => {
|
|
await checkBuilderEndpoint({
|
|
config,
|
|
method: "POST",
|
|
url: `/api/queries/preview`,
|
|
})
|
|
})
|
|
|
|
it("should fail with invalid integration type", async () => {
|
|
const { datasource } = await createInvalidIntegration()
|
|
await request
|
|
.post(`/api/queries/preview`)
|
|
.send({
|
|
datasourceId: datasource._id,
|
|
parameters: {},
|
|
fields: {},
|
|
queryVerb: "read",
|
|
})
|
|
.set(config.defaultHeaders())
|
|
.expect(400)
|
|
})
|
|
})
|
|
|
|
describe("execute", () => {
|
|
it("should be able to execute the query", async () => {
|
|
const res = await request
|
|
.post(`/api/queries/${query._id}`)
|
|
.send({
|
|
parameters: {},
|
|
})
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body.length).toEqual(1)
|
|
})
|
|
|
|
it("should fail with invalid integration type", async () => {
|
|
const { query, datasource } = await createInvalidIntegration()
|
|
await request
|
|
.post(`/api/queries/${query._id}`)
|
|
.send({
|
|
datasourceId: datasource._id,
|
|
parameters: {},
|
|
fields: {},
|
|
queryVerb: "read",
|
|
})
|
|
.set(config.defaultHeaders())
|
|
.expect(400)
|
|
})
|
|
})
|
|
|
|
describe("variables", () => {
|
|
async function preview(datasource, fields) {
|
|
return config.previewQuery(request, config, datasource, fields)
|
|
}
|
|
|
|
it("should work with static variables", async () => {
|
|
const datasource = await config.restDatasource({
|
|
staticVariables: {
|
|
variable: "google",
|
|
variable2: "1",
|
|
},
|
|
})
|
|
const res = await preview(datasource, {
|
|
path: "www.{{ variable }}.com",
|
|
queryString: "test={{ variable2 }}",
|
|
})
|
|
// these responses come from the mock
|
|
expect(res.body.schemaFields).toEqual(["url", "opts", "value"])
|
|
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
|
})
|
|
|
|
it("should work with dynamic variables", async () => {
|
|
const { datasource } = await config.dynamicVariableDatasource()
|
|
const res = await preview(datasource, {
|
|
path: "www.google.com",
|
|
queryString: "test={{ variable3 }}",
|
|
})
|
|
expect(res.body.schemaFields).toEqual(["url", "opts", "value"])
|
|
expect(res.body.rows[0].url).toContain("doctype html")
|
|
})
|
|
|
|
it("check that it automatically retries on fail with cached dynamics", async () => {
|
|
const { datasource, query: base } =
|
|
await config.dynamicVariableDatasource()
|
|
// preview once to cache
|
|
await preview(datasource, {
|
|
path: "www.google.com",
|
|
queryString: "test={{ variable3 }}",
|
|
})
|
|
// check its in cache
|
|
const contents = await checkCacheForDynamicVariable(base._id, "variable3")
|
|
expect(contents.rows.length).toEqual(1)
|
|
const res = await preview(datasource, {
|
|
path: "www.failonce.com",
|
|
queryString: "test={{ variable3 }}",
|
|
})
|
|
expect(res.body.schemaFields).toEqual(["fails", "url", "opts"])
|
|
expect(res.body.rows[0].fails).toEqual(1)
|
|
})
|
|
|
|
it("deletes variables when linked query is deleted", async () => {
|
|
const { datasource, query: base } =
|
|
await config.dynamicVariableDatasource()
|
|
// preview once to cache
|
|
await preview(datasource, {
|
|
path: "www.google.com",
|
|
queryString: "test={{ variable3 }}",
|
|
})
|
|
// check its in cache
|
|
let contents = await checkCacheForDynamicVariable(base._id, "variable3")
|
|
expect(contents.rows.length).toEqual(1)
|
|
|
|
// delete the query
|
|
await request
|
|
.delete(`/api/queries/${base._id}/${base._rev}`)
|
|
.set(config.defaultHeaders())
|
|
.expect(200)
|
|
|
|
// check variables no longer in cache
|
|
contents = await checkCacheForDynamicVariable(base._id, "variable3")
|
|
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: "<note> <email>{{[user].[email]}}</email> <code>{{testParam}}</code> " +
|
|
"<ref>{{userId}}</ref> <somestring>testing</somestring> </note>",
|
|
bodyType: "xml"
|
|
}, {
|
|
"testParam" : "1234",
|
|
"userId" : "{{[user].[firstName]}}"
|
|
})
|
|
|
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
|
const test = `<note> <email>${userDetails.email}</email> <code>1234</code> <ref>${userDetails.firstName}</ref> <somestring>testing</somestring> </note>`
|
|
|
|
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)
|
|
})
|
|
|
|
});
|
|
})
|