Flesh out Postgres tests, add MySQL tests.

This commit is contained in:
Sam Rose 2024-02-08 15:32:38 +00:00
parent bf9fbf8849
commit 75c2392b60
No known key found for this signature in database
5 changed files with 367 additions and 4 deletions

View File

@ -0,0 +1,239 @@
import { Datasource, Query } from "@budibase/types"
import * as setup from "../utilities"
import { databaseTestProviders } from "../../../../integrations/tests/utils"
import mysql from "mysql2/promise"
jest.unmock("mysql2")
jest.unmock("mysql2/promise")
const createTableSQL = `
CREATE TABLE test_table (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL
)
`
const insertSQL = `
INSERT INTO test_table (name) VALUES ('one'), ('two'), ('three'), ('four'), ('five')
`
const dropTableSQL = `
DROP TABLE test_table
`
describe("/queries", () => {
let config = setup.getConfig()
let datasource: Datasource
async function createQuery(query: Partial<Query>): Promise<Query> {
const defaultQuery: Query = {
datasourceId: datasource._id!,
name: "New Query",
parameters: [],
fields: {},
schema: {},
queryVerb: "read",
transformer: "return data",
readable: true,
}
return await config.api.query.create({ ...defaultQuery, ...query })
}
async function withConnection(
callback: (client: mysql.Connection) => Promise<void>
): Promise<void> {
const ds = await databaseTestProviders.mysql.datasource()
const con = await mysql.createConnection(ds.config!)
try {
await callback(con)
} finally {
con.end()
}
}
afterAll(async () => {
await databaseTestProviders.mysql.stop()
setup.afterAll()
})
beforeAll(async () => {
await config.init()
datasource = await config.api.datasource.create(
await databaseTestProviders.mysql.datasource()
)
})
beforeEach(async () => {
await withConnection(async connection => {
const resp = await connection.query(createTableSQL)
await connection.query(insertSQL)
})
})
afterEach(async () => {
await withConnection(async connection => {
await connection.query(dropTableSQL)
})
})
it("should execute a query", async () => {
const query = await createQuery({
fields: {
sql: "SELECT * FROM test_table ORDER BY id",
},
})
const result = await config.api.query.execute(query._id!)
expect(result.data).toEqual([
{
id: 1,
name: "one",
},
{
id: 2,
name: "two",
},
{
id: 3,
name: "three",
},
{
id: 4,
name: "four",
},
{
id: 5,
name: "five",
},
])
})
it("should be able to transform a query", async () => {
const query = await createQuery({
fields: {
sql: "SELECT * FROM test_table WHERE id = 1",
},
transformer: `
data[0].id = data[0].id + 1;
return data;
`,
})
const result = await config.api.query.execute(query._id!)
expect(result.data).toEqual([
{
id: 2,
name: "one",
},
])
})
it("should be able to insert with bindings", async () => {
const query = await createQuery({
fields: {
sql: "INSERT INTO test_table (name) VALUES ({{ foo }})",
},
parameters: [
{
name: "foo",
default: "bar",
},
],
queryVerb: "create",
})
const result = await config.api.query.execute(query._id!, {
parameters: {
foo: "baz",
},
})
expect(result.data).toEqual([
{
created: true,
},
])
await withConnection(async connection => {
const [rows] = await connection.query(
"SELECT * FROM test_table WHERE name = 'baz'"
)
expect(rows).toHaveLength(1)
})
})
it("should be able to update rows", async () => {
const query = await createQuery({
fields: {
sql: "UPDATE test_table SET name = {{ name }} WHERE id = {{ id }}",
},
parameters: [
{
name: "id",
default: "",
},
{
name: "name",
default: "updated",
},
],
queryVerb: "update",
})
const result = await config.api.query.execute(query._id!, {
parameters: {
id: "1",
name: "foo",
},
})
expect(result.data).toEqual([
{
updated: true,
},
])
await withConnection(async connection => {
const [rows] = await connection.query(
"SELECT * FROM test_table WHERE id = 1"
)
expect(rows).toEqual([{ id: 1, name: "foo" }])
})
})
it("should be able to delete rows", async () => {
const query = await createQuery({
fields: {
sql: "DELETE FROM test_table WHERE id = {{ id }}",
},
parameters: [
{
name: "id",
default: "",
},
],
queryVerb: "delete",
})
const result = await config.api.query.execute(query._id!, {
parameters: {
id: "1",
},
})
expect(result.data).toEqual([
{
deleted: true,
},
])
await withConnection(async connection => {
const [rows] = await connection.query(
"SELECT * FROM test_table WHERE id = 1"
)
expect(rows).toHaveLength(0)
})
})
})

View File

@ -167,4 +167,77 @@ describe("/queries", () => {
expect(rows).toHaveLength(1)
})
})
it("should be able to update rows", async () => {
const query = await createQuery({
fields: {
sql: "UPDATE test_table SET name = {{ name }} WHERE id = {{ id }}",
},
parameters: [
{
name: "id",
default: "",
},
{
name: "name",
default: "updated",
},
],
queryVerb: "update",
})
const result = await config.api.query.execute(query._id!, {
parameters: {
id: "1",
name: "foo",
},
})
expect(result.data).toEqual([
{
updated: true,
},
])
await withClient(async client => {
const { rows } = await client.query(
"SELECT * FROM test_table WHERE id = 1"
)
expect(rows).toEqual([{ id: 1, name: "foo" }])
})
})
it("should be able to delete rows", async () => {
const query = await createQuery({
fields: {
sql: "DELETE FROM test_table WHERE id = {{ id }}",
},
parameters: [
{
name: "id",
default: "",
},
],
queryVerb: "delete",
})
const result = await config.api.query.execute(query._id!, {
parameters: {
id: "1",
},
})
expect(result.data).toEqual([
{
deleted: true,
},
])
await withClient(async client => {
const { rows } = await client.query(
"SELECT * FROM test_table WHERE id = 1"
)
expect(rows).toHaveLength(0)
})
})
})

View File

@ -3,6 +3,7 @@ jest.unmock("pg")
import { Datasource } from "@budibase/types"
import * as postgres from "./postgres"
import * as mongodb from "./mongodb"
import * as mysql from "./mysql"
import { StartedTestContainer } from "testcontainers"
jest.setTimeout(30000)
@ -13,4 +14,4 @@ export interface DatabaseProvider {
datasource(): Promise<Datasource>
}
export const databaseTestProviders = { postgres, mongodb }
export const databaseTestProviders = { postgres, mongodb, mysql }

View File

@ -0,0 +1,52 @@
import { Datasource, SourceName } from "@budibase/types"
import { GenericContainer, Wait, StartedTestContainer } from "testcontainers"
let container: StartedTestContainer | undefined
export async function start(): Promise<StartedTestContainer> {
return await new GenericContainer("mysql:8.3")
.withExposedPorts(3306)
.withEnvironment({ MYSQL_ROOT_PASSWORD: "password" })
.withWaitStrategy(
Wait.forSuccessfulCommand(
// Because MySQL first starts itself up, runs an init script, then restarts,
// it's possible for the mysqladmin ping to succeed early and then tests to
// run against a MySQL that's mid-restart and fail. To avoid this, we run
// the ping command three times with a small delay between each.
`
mysqladmin ping -h localhost -P 3306 -u root -ppassword && sleep 0.5 &&
mysqladmin ping -h localhost -P 3306 -u root -ppassword && sleep 0.5 &&
mysqladmin ping -h localhost -P 3306 -u root -ppassword
`
)
)
.start()
}
export async function datasource(): Promise<Datasource> {
if (!container) {
container = await start()
}
const host = container.getHost()
const port = container.getMappedPort(3306)
return {
type: "datasource_plus",
source: SourceName.MYSQL,
plus: true,
config: {
host,
port,
user: "root",
password: "password",
database: "mysql",
},
}
}
export async function stop() {
if (container) {
await container.stop()
container = undefined
}
}

View File

@ -8,9 +8,7 @@ export async function start(): Promise<StartedTestContainer> {
.withExposedPorts(5432)
.withEnvironment({ POSTGRES_PASSWORD: "password" })
.withWaitStrategy(
Wait.forSuccessfulCommand(
"pg_isready -h localhost -p 5432"
).withStartupTimeout(10000)
Wait.forSuccessfulCommand("pg_isready -h localhost -p 5432")
)
.start()
}