query and datasource unit tests
This commit is contained in:
parent
83910f0aab
commit
882823671a
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { backendUiStore } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import { Input, TextArea, Spacer } from "@budibase/bbui"
|
||||
import ICONS from "../icons"
|
||||
|
||||
|
|
|
@ -35,8 +35,6 @@
|
|||
notifier.success(`Datasource ${name} created successfully.`)
|
||||
analytics.captureEvent("Datasource Created", { name })
|
||||
|
||||
console.log(response)
|
||||
|
||||
// Navigate to new datasource
|
||||
$goto(`./datasource/${response._id}`)
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
{#each $backendUiStore.tables as table, idx}
|
||||
<NavItem
|
||||
border={idx > 0}
|
||||
icon={table.integration?.type ? 'ri-database-2-line' : `ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
|
||||
icon={`ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
|
||||
text={table.name}
|
||||
selected={selectedView === `all_${table._id}`}
|
||||
on:click={() => selectTable(table)}>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { Input, Label, ModalContent, Button, Spacer } from "@budibase/bbui"
|
||||
import { Input, Label, ModalContent, Button, Spacer, Toggle } from "@budibase/bbui"
|
||||
import TableDataImport from "../TableDataImport.svelte"
|
||||
import analytics from "analytics"
|
||||
import screenTemplates from "builderStore/store/screenTemplates"
|
||||
|
@ -19,7 +19,6 @@
|
|||
let modal
|
||||
let name
|
||||
let dataImport
|
||||
let integration
|
||||
let error = ""
|
||||
let createAutoscreens = true
|
||||
|
||||
|
|
|
@ -1,59 +1,162 @@
|
|||
<script>
|
||||
import cm from "./codemirror"
|
||||
import CodeMirror from "./codemirror"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { themeStore } from "builderStore"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value
|
||||
export let mode = "sql"
|
||||
const THEMES = {
|
||||
DARK: "tomorrow-night-eighties",
|
||||
LIGHT: "default",
|
||||
}
|
||||
|
||||
let editor
|
||||
let codemirror
|
||||
export let value = ""
|
||||
export let readOnly = false
|
||||
export let lineNumbers = true
|
||||
export let tab = true
|
||||
export let mode
|
||||
|
||||
$: {
|
||||
if (codemirror) {
|
||||
const { left, top } = codemirror.getScrollInfo()
|
||||
codemirror.setValue(value)
|
||||
codemirror.scrollTo(left, top)
|
||||
let width
|
||||
let height
|
||||
|
||||
// We have to expose set and update methods, rather
|
||||
// than making this state-driven through props,
|
||||
// because it's difficult to update an editor
|
||||
// without resetting scroll otherwise
|
||||
export async function set(new_value, new_mode) {
|
||||
if (new_mode !== mode) {
|
||||
await createEditor((mode = new_mode))
|
||||
}
|
||||
|
||||
value = new_value
|
||||
updating_externally = true
|
||||
if (editor) editor.setValue(value)
|
||||
updating_externally = false
|
||||
}
|
||||
|
||||
export function update(new_value) {
|
||||
value = new_value
|
||||
|
||||
if (editor) {
|
||||
const { left, top } = editor.getScrollInfo()
|
||||
editor.setValue((value = new_value))
|
||||
editor.scrollTo(left, top)
|
||||
}
|
||||
}
|
||||
|
||||
export function resize() {
|
||||
editor.refresh()
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
editor.focus()
|
||||
}
|
||||
|
||||
const modes = {
|
||||
js: {
|
||||
name: "javascript",
|
||||
json: false,
|
||||
},
|
||||
json: {
|
||||
name: "javascript",
|
||||
json: true,
|
||||
},
|
||||
sql: {
|
||||
name: "sql",
|
||||
},
|
||||
svelte: {
|
||||
name: "handlebars",
|
||||
base: "text/html",
|
||||
},
|
||||
}
|
||||
|
||||
const refs = {}
|
||||
let editor
|
||||
let updating_externally = false
|
||||
let marker
|
||||
let error_line
|
||||
let destroyed = false
|
||||
|
||||
$: if (editor && width && height) {
|
||||
editor.refresh()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (codemirror) codemirror.toTextArea()
|
||||
|
||||
codemirror = cm.fromTextArea(editor, {
|
||||
lineNumbers: true,
|
||||
mode,
|
||||
lineWrapping: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
autoCloseBrackets: true,
|
||||
autoCloseTags: true,
|
||||
extraKeys: {
|
||||
"Ctrl-/": "toggleComment",
|
||||
},
|
||||
createEditor(mode).then(() => {
|
||||
if (editor) editor.setValue(value || "")
|
||||
})
|
||||
codemirror.on("change", instance => {
|
||||
const code = instance.getValue()
|
||||
dispatch("change", code)
|
||||
})
|
||||
|
||||
codemirror.setValue(value || "")
|
||||
|
||||
return () => {
|
||||
if (codemirror) codemirror.toTextArea()
|
||||
destroyed = true
|
||||
if (editor) editor.toTextArea()
|
||||
}
|
||||
})
|
||||
|
||||
let first = true
|
||||
|
||||
async function createEditor(mode) {
|
||||
if (destroyed || !CodeMirror) return
|
||||
|
||||
if (editor) editor.toTextArea()
|
||||
|
||||
const opts = {
|
||||
lineNumbers,
|
||||
lineWrapping: true,
|
||||
indentWithTabs: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
value: "",
|
||||
mode: modes[mode] || {
|
||||
name: mode,
|
||||
},
|
||||
readOnly,
|
||||
autoCloseBrackets: true,
|
||||
autoCloseTags: true,
|
||||
theme: $themeStore.darkMode ? THEMES.DARK : THEMES.LIGHT,
|
||||
}
|
||||
|
||||
if (!tab)
|
||||
opts.extraKeys = {
|
||||
Tab: tab,
|
||||
"Shift-Tab": tab,
|
||||
}
|
||||
|
||||
// Creating a text editor is a lot of work, so we yield
|
||||
// the main thread for a moment. This helps reduce jank
|
||||
if (first) await sleep(50)
|
||||
|
||||
if (destroyed) return
|
||||
|
||||
editor = CodeMirror.fromTextArea(refs.editor, opts)
|
||||
|
||||
editor.on("change", instance => {
|
||||
if (!updating_externally) {
|
||||
const value = instance.getValue()
|
||||
dispatch("change", { value })
|
||||
}
|
||||
})
|
||||
|
||||
if (first) await sleep(50)
|
||||
editor.refresh()
|
||||
|
||||
first = false
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(fulfil => setTimeout(fulfil, ms))
|
||||
}
|
||||
</script>
|
||||
|
||||
<textarea bind:value bind:this={editor} />
|
||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
background: var(--background);
|
||||
border-radius: var(--border-radius-m);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:global(.CodeMirror) {
|
||||
height: auto !important;
|
||||
border-radius: var(--border-radius-m);
|
||||
font-family: var(--font-sans) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
Label,
|
||||
Input,
|
||||
Heading,
|
||||
Spacer,
|
||||
Select
|
||||
} from "@budibase/bbui"
|
||||
|
||||
|
@ -13,53 +12,62 @@
|
|||
export let schema
|
||||
export let editable
|
||||
|
||||
let customSchema = {}
|
||||
let draftField = {}
|
||||
|
||||
$: fieldKeys = Object.keys(fields)
|
||||
$: schemaKeys = Object.keys(schema.fields)
|
||||
|
||||
$: console.log({ fields, schema })
|
||||
|
||||
function addField() {
|
||||
// Add the new field to custom fields for the query
|
||||
customSchema[draftField.name] = {
|
||||
schema.fields[draftField.name] = {
|
||||
type: draftField.type
|
||||
}
|
||||
// reset the draft field
|
||||
draftField = {}
|
||||
}
|
||||
|
||||
function removeField(field) {
|
||||
delete fields[field]
|
||||
fields = fields
|
||||
|
||||
delete schema.fields[field]
|
||||
schema = schema
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault>
|
||||
{#each Object.keys(schema.fields) as field}
|
||||
{#each schemaKeys as field}
|
||||
<Label extraSmall grey>{field}</Label>
|
||||
<Input
|
||||
disabled={!editable}
|
||||
type={schema.fields[field]?.type}
|
||||
required={schema.fields[field]?.required}
|
||||
bind:value={fields[field]} />
|
||||
<Spacer medium />
|
||||
<div class="field">
|
||||
<Input
|
||||
disabled={!editable}
|
||||
type={schema.fields[field]?.type}
|
||||
required={schema.fields[field]?.required}
|
||||
bind:value={fields[field]} />
|
||||
{#if !schema.fields[field]?.required}
|
||||
<i class="ri-close-circle-line" on:click={() => removeField(field)} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if schema.customisable && editable}
|
||||
<Spacer large />
|
||||
<Label>Add Custom Field</Label>
|
||||
{#each Object.keys(customSchema) as field}
|
||||
<Label extraSmall grey>{field}</Label>
|
||||
<Input
|
||||
thin
|
||||
type={customSchema[field]?.type}
|
||||
bind:value={fields[field]}
|
||||
/>
|
||||
<Spacer medium />
|
||||
{/each}
|
||||
<div class="new-field">
|
||||
<Label extraSmall grey>Name</Label>
|
||||
<Label extraSmall grey>Type</Label>
|
||||
<Input thin bind:value={draftField.name} />
|
||||
<Select thin secondary bind:value={draftField.name}>
|
||||
<option value={"text"}>String</option>
|
||||
<option value={"number"}>Number</option>
|
||||
</Select>
|
||||
</div>
|
||||
<Button small thin secondary on:click={addField}>Add Field</Button>
|
||||
{/if}
|
||||
</form>
|
||||
{#if schema.customisable && editable}
|
||||
<div>
|
||||
<Label>Add Custom Field</Label>
|
||||
<div class="new-field">
|
||||
<Label extraSmall grey>Name</Label>
|
||||
<Label extraSmall grey>Type</Label>
|
||||
<Input thin bind:value={draftField.name} />
|
||||
<Select thin secondary bind:value={draftField.type}>
|
||||
<option value={"text"}>String</option>
|
||||
<option value={"number"}>Number</option>
|
||||
</Select>
|
||||
</div>
|
||||
<Button small thin secondary on:click={addField}>Add Field</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<style>
|
||||
.new-field {
|
||||
|
@ -69,4 +77,23 @@
|
|||
margin-top: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2%;
|
||||
grid-gap: var(--spacing-m);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
i {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
transform: scale(1.1);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,162 +0,0 @@
|
|||
<script>
|
||||
import CodeMirror from "./codemirror"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { themeStore } from "builderStore"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const THEMES = {
|
||||
DARK: "tomorrow-night-eighties",
|
||||
LIGHT: "default",
|
||||
}
|
||||
|
||||
export let value = ""
|
||||
export let readOnly = false
|
||||
export let lineNumbers = true
|
||||
export let tab = true
|
||||
export let mode
|
||||
|
||||
let width
|
||||
let height
|
||||
|
||||
// We have to expose set and update methods, rather
|
||||
// than making this state-driven through props,
|
||||
// because it's difficult to update an editor
|
||||
// without resetting scroll otherwise
|
||||
export async function set(new_value, new_mode) {
|
||||
if (new_mode !== mode) {
|
||||
await createEditor((mode = new_mode))
|
||||
}
|
||||
|
||||
value = new_value
|
||||
updating_externally = true
|
||||
if (editor) editor.setValue(value)
|
||||
updating_externally = false
|
||||
}
|
||||
|
||||
export function update(new_value) {
|
||||
value = new_value
|
||||
|
||||
if (editor) {
|
||||
const { left, top } = editor.getScrollInfo()
|
||||
editor.setValue((value = new_value))
|
||||
editor.scrollTo(left, top)
|
||||
}
|
||||
}
|
||||
|
||||
export function resize() {
|
||||
editor.refresh()
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
editor.focus()
|
||||
}
|
||||
|
||||
const modes = {
|
||||
js: {
|
||||
name: "javascript",
|
||||
json: false,
|
||||
},
|
||||
json: {
|
||||
name: "javascript",
|
||||
json: true,
|
||||
},
|
||||
sql: {
|
||||
name: "sql",
|
||||
},
|
||||
svelte: {
|
||||
name: "handlebars",
|
||||
base: "text/html",
|
||||
},
|
||||
}
|
||||
|
||||
const refs = {}
|
||||
let editor
|
||||
let updating_externally = false
|
||||
let marker
|
||||
let error_line
|
||||
let destroyed = false
|
||||
|
||||
$: if (editor && width && height) {
|
||||
editor.refresh()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
createEditor(mode).then(() => {
|
||||
if (editor) editor.setValue(value || "")
|
||||
})
|
||||
|
||||
return () => {
|
||||
destroyed = true
|
||||
if (editor) editor.toTextArea()
|
||||
}
|
||||
})
|
||||
|
||||
let first = true
|
||||
|
||||
async function createEditor(mode) {
|
||||
if (destroyed || !CodeMirror) return
|
||||
|
||||
if (editor) editor.toTextArea()
|
||||
|
||||
const opts = {
|
||||
lineNumbers,
|
||||
lineWrapping: true,
|
||||
indentWithTabs: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
value: "",
|
||||
mode: modes[mode] || {
|
||||
name: mode,
|
||||
},
|
||||
readOnly,
|
||||
autoCloseBrackets: true,
|
||||
autoCloseTags: true,
|
||||
theme: $themeStore.darkMode ? THEMES.DARK : THEMES.LIGHT,
|
||||
}
|
||||
|
||||
if (!tab)
|
||||
opts.extraKeys = {
|
||||
Tab: tab,
|
||||
"Shift-Tab": tab,
|
||||
}
|
||||
|
||||
// Creating a text editor is a lot of work, so we yield
|
||||
// the main thread for a moment. This helps reduce jank
|
||||
if (first) await sleep(50)
|
||||
|
||||
if (destroyed) return
|
||||
|
||||
editor = CodeMirror.fromTextArea(refs.editor, opts)
|
||||
|
||||
editor.on("change", instance => {
|
||||
if (!updating_externally) {
|
||||
const value = instance.getValue()
|
||||
dispatch("change", { value })
|
||||
}
|
||||
})
|
||||
|
||||
if (first) await sleep(50)
|
||||
editor.refresh()
|
||||
|
||||
first = false
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(fulfil => setTimeout(fulfil, ms))
|
||||
}
|
||||
</script>
|
||||
|
||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:global(.CodeMirror) {
|
||||
height: auto !important;
|
||||
border-radius: var(--border-radius-m);
|
||||
font-family: var(--font-sans) !important;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { TextArea, Label, Input, Heading, Spacer } from "@budibase/bbui"
|
||||
import Editor from "./SvelteEditor.svelte"
|
||||
import Editor from "./QueryEditor.svelte"
|
||||
import ParameterBuilder from "./QueryParameterBuilder.svelte"
|
||||
import FieldsBuilder from "./QueryFieldsBuilder.svelte"
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
|||
SQL: "sql",
|
||||
JSON: "json",
|
||||
FIELDS: "fields",
|
||||
LIST: "list",
|
||||
}
|
||||
|
||||
export let query
|
||||
|
@ -26,7 +27,7 @@
|
|||
{/if}
|
||||
|
||||
<Heading extraSmall black>Query</Heading>
|
||||
<Spacer large />
|
||||
<Spacer medium />
|
||||
|
||||
{#if schema}
|
||||
{#if schema.type === QueryTypes.SQL}
|
||||
|
@ -46,5 +47,5 @@
|
|||
value={query.fields.json} />
|
||||
{:else if schema.type === QueryTypes.FIELDS}
|
||||
<FieldsBuilder bind:fields={query.fields} {schema} {editable} />
|
||||
{/if}
|
||||
{:else if schema.type === QueryTypes.LIST}LIST STUFF{/if}
|
||||
{/if}
|
||||
|
|
|
@ -25,20 +25,6 @@
|
|||
parameters: [],
|
||||
fields: {},
|
||||
}
|
||||
|
||||
// $: {
|
||||
// if ($params.query !== "new") {
|
||||
// query = $backendUiStore.queries.find(query => query._id === $params.query)
|
||||
// } else {
|
||||
// // New query
|
||||
// query = {
|
||||
// datasourceId: $params.selectedDatasource,
|
||||
// name: "New Query",
|
||||
// parameters: [],
|
||||
// fields: {},
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<section>
|
||||
|
|
|
@ -32,7 +32,7 @@ exports.save = async function(ctx) {
|
|||
datasource._rev = response.rev
|
||||
|
||||
ctx.status = 200
|
||||
ctx.message = "Datasource created successfully."
|
||||
ctx.message = "Datasource saved successfully."
|
||||
ctx.body = datasource
|
||||
} catch (err) {
|
||||
ctx.throw(err.status, err)
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
const {
|
||||
supertest,
|
||||
createApplication,
|
||||
defaultHeaders,
|
||||
builderEndpointShouldBlockNormalUsers,
|
||||
getDocument,
|
||||
insertDocument
|
||||
} = require("./couchTestUtils")
|
||||
let { generateDatasourceID, generateQueryID } = require("../../../db/utils")
|
||||
|
||||
const DATASOURCE_ID = generateDatasourceID()
|
||||
const TEST_DATASOURCE = {
|
||||
_id: DATASOURCE_ID,
|
||||
type: "datasource",
|
||||
name: "Test",
|
||||
source: "POSTGRES",
|
||||
config: {},
|
||||
type: "datasource",
|
||||
}
|
||||
|
||||
const TEST_QUERY = {
|
||||
_id: generateQueryID(DATASOURCE_ID),
|
||||
datasourceId: DATASOURCE_ID,
|
||||
name:"New Query",
|
||||
parameters:[],
|
||||
fields:{},
|
||||
schema:{},
|
||||
queryVerb:"read",
|
||||
queryType:"Table",
|
||||
}
|
||||
|
||||
describe("/datasources", () => {
|
||||
let request
|
||||
let server
|
||||
let app
|
||||
let appId
|
||||
let datasource
|
||||
|
||||
beforeAll(async () => {
|
||||
({ request, server } = await supertest())
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createApplication(request)
|
||||
appId = app.instance._id
|
||||
});
|
||||
|
||||
async function createDatasource() {
|
||||
return await insertDocument(appId, TEST_DATASOURCE)
|
||||
}
|
||||
|
||||
async function createQuery() {
|
||||
return await insertDocument(appId, TEST_QUERY)
|
||||
}
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a new datasource", async () => {
|
||||
const res = await request
|
||||
.post(`/api/datasources`)
|
||||
.send(TEST_DATASOURCE)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.res.statusMessage).toEqual("Datasource saved successfully.");
|
||||
expect(res.body.name).toEqual("Test");
|
||||
})
|
||||
});
|
||||
|
||||
describe("fetch", () => {
|
||||
let datasource
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await createDatasource()
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
});
|
||||
|
||||
it("returns all the datasources from the server", async () => {
|
||||
const res = await request
|
||||
.get(`/api/datasources`)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
const datasources = res.body;
|
||||
expect(datasources).toEqual([
|
||||
{
|
||||
"_rev": datasources[0]._rev,
|
||||
...TEST_DATASOURCE
|
||||
}
|
||||
]);
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await builderEndpointShouldBlockNormalUsers({
|
||||
request,
|
||||
method: "GET",
|
||||
url: `/api/datasources`,
|
||||
appId: appId,
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
describe("destroy", () => {
|
||||
let datasource;
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await createDatasource()
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
});
|
||||
|
||||
it("deletes queries for the datasource after deletion and returns a success message", async () => {
|
||||
await createQuery(datasource.id)
|
||||
|
||||
await request
|
||||
.delete(`/api/datasources/${datasource.id}/${datasource.rev}`)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect(200)
|
||||
|
||||
const res = await request
|
||||
.get(`/api/datasources`)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toEqual([])
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await builderEndpointShouldBlockNormalUsers({
|
||||
request,
|
||||
method: "DELETE",
|
||||
url: `/api/datasources/${datasource._id}/${datasource._rev}`,
|
||||
appId: appId,
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
});
|
|
@ -0,0 +1,153 @@
|
|||
const {
|
||||
supertest,
|
||||
createApplication,
|
||||
defaultHeaders,
|
||||
builderEndpointShouldBlockNormalUsers,
|
||||
getDocument,
|
||||
insertDocument
|
||||
} = require("./couchTestUtils")
|
||||
let { generateDatasourceID, generateQueryID } = require("../../../db/utils")
|
||||
|
||||
const DATASOURCE_ID = generateDatasourceID()
|
||||
const TEST_DATASOURCE = {
|
||||
_id: DATASOURCE_ID,
|
||||
type: "datasource",
|
||||
name: "Test",
|
||||
source: "POSTGRES",
|
||||
config: {},
|
||||
type: "datasource",
|
||||
}
|
||||
|
||||
const TEST_QUERY = {
|
||||
_id: generateQueryID(DATASOURCE_ID),
|
||||
datasourceId: DATASOURCE_ID,
|
||||
name:"New Query",
|
||||
parameters:[],
|
||||
fields:{},
|
||||
schema:{},
|
||||
queryVerb:"read",
|
||||
queryType:"Table",
|
||||
}
|
||||
|
||||
describe("/queries", () => {
|
||||
let request
|
||||
let server
|
||||
let app
|
||||
let appId
|
||||
let datasource
|
||||
let query
|
||||
|
||||
beforeAll(async () => {
|
||||
({ request, server } = await supertest())
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createApplication(request)
|
||||
appId = app.instance._id
|
||||
});
|
||||
|
||||
async function createDatasource() {
|
||||
return await insertDocument(appId, TEST_DATASOURCE)
|
||||
}
|
||||
|
||||
async function createQuery() {
|
||||
return await insertDocument(appId, TEST_QUERY)
|
||||
}
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a new query", async () => {
|
||||
const res = await request
|
||||
.post(`/api/queries`)
|
||||
.send(TEST_QUERY)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.res.statusMessage).toEqual(`Query ${TEST_QUERY.name} saved successfully.`);
|
||||
expect(res.body).toEqual({
|
||||
_rev: res.body._rev,
|
||||
...TEST_QUERY,
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe("fetch", () => {
|
||||
let datasource
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await createDatasource()
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
});
|
||||
|
||||
it("returns all the queries from the server", async () => {
|
||||
const query = await createQuery()
|
||||
const res = await request
|
||||
.get(`/api/queries`)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
const queries = res.body;
|
||||
expect(queries).toEqual([
|
||||
{
|
||||
"_rev": query.rev,
|
||||
...TEST_QUERY
|
||||
}
|
||||
]);
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await builderEndpointShouldBlockNormalUsers({
|
||||
request,
|
||||
method: "GET",
|
||||
url: `/api/datasources`,
|
||||
appId: appId,
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
describe("destroy", () => {
|
||||
let datasource;
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await createDatasource()
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
});
|
||||
|
||||
it("deletes a query and returns a success message", async () => {
|
||||
const query = await createQuery()
|
||||
|
||||
await request
|
||||
.delete(`/api/queries/${query.id}/${query.rev}`)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect(200)
|
||||
|
||||
const res = await request
|
||||
.get(`/api/queries`)
|
||||
.set(defaultHeaders(appId))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toEqual([])
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await builderEndpointShouldBlockNormalUsers({
|
||||
request,
|
||||
method: "DELETE",
|
||||
url: `/api/datasources/${datasource._id}/${datasource._rev}`,
|
||||
appId: appId,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue