wip: mysql driver

add tests for pg driver and mysql driver (in progress)
This commit is contained in:
2024-07-11 11:20:36 +02:00
parent 711c426898
commit 024eb61a41
12 changed files with 1027 additions and 38 deletions

Binary file not shown.

32
api/docker-compose.yml Normal file
View File

@@ -0,0 +1,32 @@
version: '3.3'
services:
adminer:
image: adminer
restart: always
ports:
- "8080:8080"
postgres:
container_name: db-postgres-test
image: postgres:latest
environment:
POSTGRES_PASSWORD: mysecretpassword
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
db:
container_name: db-mysql-test
image: mysql:latest
environment:
- MYSQL_PASSWORD=mysecretpassword
- MYSQL_ROOT_PASSWORD=mysecretpassword
ports:
- "3306:3306"
volumes:
- mysqldata:/var/lib/mysql
volumes:
pgdata:
mysqldata:

View File

@@ -7,9 +7,15 @@
},
"dependencies": {
"@elysiajs/cors": "^1.0.2",
"@elysiajs/eden": "^1.0.14",
"@elysiajs/jwt": "^1.0.2",
"@it-incubator/prettier-config": "^0.1.2",
"@types/cookie": "^0.6.0",
"@types/jsonwebtoken": "^9.0.6",
"cookie": "^0.6.0",
"elysia": "latest",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.10.2",
"postgres": "^3.4.4"
},
"devDependencies": {

View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Start Docker containers
docker-compose up -d
# Wait for PostgreSQL to be ready
until docker exec db-postgres-test pg_isready -U postgres; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done
# Wait for MySQL to be ready
until docker exec db-mysql-test mysqladmin ping -h "localhost" --silent; do
>&2 echo "MySQL is unavailable - sleeping"
sleep 1
done
# Set up PostgreSQL test data
docker exec -i db-postgres-test psql -U postgres <<EOF
CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(50));
INSERT INTO test_table (name) VALUES ('John Doe');
EOF
# Set up MySQL test data
docker exec -i db-mysql-test mysql -uroot -pmysecretpassword<<EOF
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE IF NOT EXISTS test_table (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
INSERT INTO test_table (name) VALUES ('Jane Doe');
EOF
echo "Test databases are ready"

View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Clean up PostgreSQL test data
docker exec -i db-postgres-test psql -U postgres <<EOF
DROP TABLE IF EXISTS test_table;
EOF
# Clean up MySQL test data
docker exec -i db-mysql-test mysql -uroot -pmysecretpassword --database=test_db<<EOF
DROP TABLE IF EXISTS test_table;
EOF
# Stop and remove Docker containers and volumes
docker-compose down -v
echo "Test databases are cleaned up"

376
api/src/drivers/mysql.ts Normal file
View File

@@ -0,0 +1,376 @@
import mysql, { type ResultSetHeader } from "mysql2/promise";
import type {
Credentials,
Driver,
WithSort,
WithSortPagination,
} from "./driver.interface";
const isResultSetHeader = (data: unknown): data is ResultSetHeader => {
if (!data || typeof data !== "object") return false;
const keys = [
"fieldCount",
"affectedRows",
"insertId",
"info",
"serverStatus",
"warningStatus",
"changedRows",
];
return keys.every((key) => key in data);
};
export class MySQLDriver implements Driver {
parseCredentials({
username,
password,
host,
type,
port,
database,
}: {
username: string;
password: string;
host: string;
type: string;
port: string;
database: string;
ssl: string;
}) {
return {
user: username,
password,
host,
type,
port: Number.parseInt(port, 10),
database,
ssl: {
rejectUnauthorized: false,
},
};
}
private async queryRunner(credentials: Credentials) {
let connection: mysql.Connection;
try {
if ("connectionString" in credentials) {
connection = await mysql.createConnection(credentials.connectionString);
} else {
connection = await mysql.createConnection(
this.parseCredentials(credentials),
);
}
} catch (error) {
console.error(error);
throw new Error(`Invalid connection string, ${JSON.stringify(error)}`);
}
return connection;
}
private getActions(matchString: string) {
const onActions = "RESTRICT|NO ACTION|CASCADE|SET NULL|SET DEFAULT";
const onDeleteRegex = new RegExp(`ON DELETE (${onActions})`);
const onUpdateRegex = new RegExp(`ON UPDATE (${onActions})`);
const onDeleteMatch = matchString.match(onDeleteRegex);
const onUpdateMatch = matchString.match(onUpdateRegex);
const onDeleteAction = onDeleteMatch ? onDeleteMatch[1] : "NO ACTION";
const onUpdateAction = onUpdateMatch ? onUpdateMatch[1] : "NO ACTION";
return {
onDelete: onDeleteAction,
onUpdate: onUpdateAction,
};
}
async getAllDatabases(credentials: Credentials) {
console.log("Get all databases");
const connection = await this.queryRunner(credentials);
const databases: Array<string> = [];
const [databases_raw] = await connection.query("SHOW DATABASES;"); // Get all databases
for (const db of Array.from(databases_raw)) {
databases.push(db.Database);
}
connection.destroy();
return databases;
}
async getAllTables(
credentials: Credentials,
{ sortDesc, sortField, dbName }: WithSort<{ dbName: string }>,
) {
const connection = await this.queryRunner(credentials);
const tablesQuery = `
SELECT
TABLE_NAME as table_name,
TABLE_SCHEMA as schema_name,
TABLE_ROWS as row_count,
(DATA_LENGTH + INDEX_LENGTH) as total_size,
DATA_LENGTH as table_size,
INDEX_LENGTH as index_size,
TABLE_COMMENT as comments
FROM
information_schema.tables
WHERE
table_schema = ?;
`;
const [tables] = await connection.execute(tablesQuery, [dbName]);
const primaryKeysQuery = `
SELECT
TABLE_NAME,
COLUMN_NAME
FROM
information_schema.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = ? AND
CONSTRAINT_NAME = 'PRIMARY';
`;
const [primaryKeys] = await connection.execute(primaryKeysQuery, [dbName]);
const indexesQuery = `
SELECT
TABLE_NAME
FROM
information_schema.STATISTICS
WHERE
TABLE_SCHEMA = ?;
`;
const [indexes] = await connection.execute(indexesQuery, [dbName]);
const formattedTables = tables.map((table) => {
const primaryKey = primaryKeys
.filter((pk) => pk.TABLE_NAME === table.table_name)
.map((pk) => pk.COLUMN_NAME)
.join(", ");
const tableIndexes = indexes
.filter((idx) => idx.TABLE_NAME === table.table_name)
.map((idx) => idx.TABLE_NAME)
.join(", ");
return {
comments: table.comments,
index_size: table.index_size,
indexes: tableIndexes,
owner: null, // No information on table owner in `information_schema`
primary_key: primaryKey,
row_count: table.row_count,
table_name: table.table_name,
table_size: table.table_size,
total_size: table.total_size,
schema_name: table.schema_name,
};
});
connection.destroy();
return formattedTables;
}
async getTableData(
credentials: Credentials,
{
tableName,
dbName,
perPage,
page,
sortDesc,
sortField,
}: WithSortPagination<{ tableName: string; dbName: string }>,
) {
const connection = await this.queryRunner(credentials);
const offset = perPage * page;
// Get the count of rows
const [rows] = await connection.execute(
`SELECT COUNT(*) as count FROM ${dbName}.${tableName}`,
);
if ("fieldCount" in rows) {
return;
}
const count = rows[0].count;
// Construct the query for table data with optional sorting
let query = `SELECT * FROM ${dbName}.${tableName}`;
const params = [];
if (sortField) {
query += ` ORDER BY ${sortField} ${sortDesc ? "DESC" : "ASC"}`;
}
console.log(perPage, offset, page);
query += ` LIMIT ${perPage} OFFSET ${offset}`;
params.push(perPage.toString());
params.push(offset.toString());
// Execute the query with parameters
const [data] = await connection.execute(query);
await connection.end();
return {
count: count,
data,
};
}
async getTableColumns(
credentials: Credentials,
{ tableName, dbName }: { dbName: string; tableName: string },
) {
const connection = await this.queryRunner(credentials);
const [rows] = await connection.execute(
`
SELECT
COLUMN_NAME as column_name,
DATA_TYPE as data_type,
COLUMN_TYPE as udt_name,
COLUMN_COMMENT as column_comment
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = ?
AND TABLE_NAME = ?
`,
[dbName, tableName],
);
await connection.end();
return (rows as any[]).map((row) => ({
column_name: row.column_name,
data_type: row.data_type,
udt_name: row.udt_name,
column_comment: row.column_comment,
}));
}
async getTableIndexes(
credentials: Credentials,
{ dbName, tableName }: { dbName: string; tableName: string },
) {
const connection = await this.queryRunner(credentials);
try {
const [rows] = await connection.execute(
`
SELECT
index_name AS relname,
index_name AS \`key\`,
CASE
WHEN non_unique = 0 AND index_name = 'PRIMARY' THEN 'PRIMARY'
WHEN non_unique = 0 THEN 'UNIQUE'
ELSE 'INDEX'
END AS type,
GROUP_CONCAT(column_name ORDER BY seq_in_index) AS columns
FROM
information_schema.statistics
WHERE
table_schema = ?
AND table_name = ?
GROUP BY
index_name, non_unique
`,
[dbName, tableName],
);
await connection.end();
return (rows as any[]).map((row) => ({
relname: row.relname,
key: row.key,
type: row.type,
columns: row.columns.split(","),
}));
} catch (error) {
console.error("Error fetching indexes:", error);
await connection.end();
throw error;
}
}
async getTableForeignKeys(
credentials: Credentials,
{ dbName, tableName }: { dbName: string; tableName: string },
) {
const sql = await this.queryRunner(credentials);
const result = await sql`
SELECT
conname,
condeferrable::int AS deferrable,
pg_get_constraintdef(oid) AS definition
FROM
pg_constraint
WHERE
conrelid = (
SELECT pc.oid
FROM pg_class AS pc
INNER JOIN pg_namespace AS pn ON (pn.oid = pc.relnamespace)
WHERE pc.relname = ${tableName}
AND pn.nspname = ${dbName}
)
AND contype = 'f'::char
ORDER BY conkey, conname
`;
void sql.end();
return result.map((row) => {
const match = row.definition.match(
/FOREIGN KEY\s*\((.+)\)\s*REFERENCES (.+)\((.+)\)(.*)$/iy,
);
if (match) {
const sourceColumns = match[1]
.split(",")
.map((col) => col.replaceAll('"', "").trim());
const targetTableMatch = match[2].match(
/^(("([^"]|"")+"|[^"]+)\.)?"?("([^"]|"")+"|[^"]+)$/,
);
const targetTable = targetTableMatch
? targetTableMatch[0].trim()
: null;
const targetColumns = match[3]
.split(",")
.map((col) => col.replaceAll('"', "").trim());
const { onDelete, onUpdate } = this.getActions(match[4]);
return {
conname: row.conname,
deferrable: Boolean(row.deferrable),
definition: row.definition,
source: sourceColumns,
ns: targetTableMatch
? targetTableMatch[0].replaceAll('"', "").trim()
: null,
table: targetTable.replaceAll('"', ""),
target: targetColumns,
on_delete: onDelete ?? "NO ACTION",
on_update: onUpdate ?? "NO ACTION",
};
}
});
}
async executeQuery(credentials: Credentials, query: string) {
const sql = await this.queryRunner(credentials);
const result = await sql.unsafe(query);
void sql.end();
return {
count: result.length,
data: result,
};
}
}
export const mySQLDriver = new MySQLDriver();

View File

@@ -180,7 +180,7 @@ export class PostgresDriver implements Driver {
void sql.end();
return {
count: count.count,
count: Number.parseInt(count.count, 10),
data,
};
}

View File

@@ -1,6 +1,8 @@
import cors from "@elysiajs/cors";
import { jwt } from "@elysiajs/jwt";
import { Elysia, t } from "elysia";
import type { Driver } from "./drivers/driver.interface";
import { mySQLDriver } from "./drivers/mysql";
import { postgresDriver } from "./drivers/postgres";
const credentialsSchema = t.Union([
@@ -15,8 +17,21 @@ const credentialsSchema = t.Union([
}),
t.Object({
connectionString: t.String(),
type: t.String(),
}),
]);
const getDriver = (type: string): Driver => {
switch (type) {
case "mysql":
return mySQLDriver;
case "postgres":
return postgresDriver;
default:
throw new Error("Invalid type");
}
};
const app = new Elysia({ prefix: "/api" })
.use(
jwt({
@@ -29,7 +44,9 @@ const app = new Elysia({ prefix: "/api" })
.post(
"/auth/login",
async ({ body, jwt, cookie: { auth } }) => {
const databases = await postgresDriver.getAllDatabases(body);
const driver = getDriver(body.type);
const databases = await driver.getAllDatabases(body);
auth.set({
value: await jwt.sign(body),
@@ -42,13 +59,15 @@ const app = new Elysia({ prefix: "/api" })
)
.get("/databases", async ({ jwt, set, cookie: { auth } }) => {
const credentials = await jwt.verify(auth.value);
console.log(auth.value);
if (!credentials) {
set.status = 401;
return "Unauthorized";
}
const driver = getDriver(credentials.type);
const databases = await driver.getAllDatabases(credentials);
const databases = await postgresDriver.getAllDatabases(credentials);
return new Response(JSON.stringify(databases, null, 2)).json();
})
.get(
@@ -62,8 +81,8 @@ const app = new Elysia({ prefix: "/api" })
set.status = 401;
return "Unauthorized";
}
const tables = await postgresDriver.getAllTables(credentials, {
const driver = getDriver(credentials.type);
const tables = await driver.getAllTables(credentials, {
dbName,
sortField,
sortDesc: sortDesc === "true",
@@ -73,7 +92,7 @@ const app = new Elysia({ prefix: "/api" })
},
)
.get(
"databases/:dbName/tables/:tableName/data",
"/databases/:dbName/tables/:tableName/data",
async ({ query, params, jwt, set, cookie: { auth } }) => {
const { tableName, dbName } = params;
const { perPage = "50", page = "0", sortField, sortDesc } = query;
@@ -83,8 +102,9 @@ const app = new Elysia({ prefix: "/api" })
set.status = 401;
return "Unauthorized";
}
const driver = getDriver(credentials.type);
return postgresDriver.getTableData(credentials, {
return driver.getTableData(credentials, {
tableName,
dbName,
perPage: Number.parseInt(perPage, 10),
@@ -95,7 +115,7 @@ const app = new Elysia({ prefix: "/api" })
},
)
.get(
"databases/:dbName/tables/:tableName/columns",
"/databases/:dbName/tables/:tableName/columns",
async ({ params, jwt, set, cookie: { auth } }) => {
const { tableName, dbName } = params;
const credentials = await jwt.verify(auth.value);
@@ -105,7 +125,9 @@ const app = new Elysia({ prefix: "/api" })
return "Unauthorized";
}
const columns = await postgresDriver.getTableColumns(credentials, {
const driver = getDriver(credentials.type);
const columns = await driver.getTableColumns(credentials, {
dbName,
tableName,
});
@@ -113,7 +135,7 @@ const app = new Elysia({ prefix: "/api" })
},
)
.get(
"databases/:dbName/tables/:tableName/indexes",
"/databases/:dbName/tables/:tableName/indexes",
async ({ params, jwt, set, cookie: { auth } }) => {
const { tableName, dbName } = params;
const credentials = await jwt.verify(auth.value);
@@ -123,7 +145,9 @@ const app = new Elysia({ prefix: "/api" })
return "Unauthorized";
}
const indexes = await postgresDriver.getTableIndexes(credentials, {
const driver = getDriver(credentials.type);
const indexes = await driver.getTableIndexes(credentials, {
dbName,
tableName,
});
@@ -131,7 +155,7 @@ const app = new Elysia({ prefix: "/api" })
},
)
.get(
"databases/:dbName/tables/:tableName/foreign-keys",
"/databases/:dbName/tables/:tableName/foreign-keys",
async ({ params, jwt, set, cookie: { auth } }) => {
const { tableName, dbName } = params;
const credentials = await jwt.verify(auth.value);
@@ -141,19 +165,18 @@ const app = new Elysia({ prefix: "/api" })
return "Unauthorized";
}
const foreignKeys = await postgresDriver.getTableForeignKeys(
credentials,
{
dbName,
tableName,
},
);
const driver = getDriver(credentials.type);
const foreignKeys = await driver.getTableForeignKeys(credentials, {
dbName,
tableName,
});
return new Response(JSON.stringify(foreignKeys, null, 2)).json();
},
)
.post(
"raw",
"/raw",
async ({ body, jwt, set, cookie: { auth } }) => {
const credentials = await jwt.verify(auth.value);
@@ -161,9 +184,10 @@ const app = new Elysia({ prefix: "/api" })
set.status = 401;
return "Unauthorized";
}
const driver = getDriver(credentials.type);
const { query } = body;
return await postgresDriver.executeQuery(credentials, query);
return await driver.executeQuery(credentials, query);
},
{
body: t.Object({
@@ -182,3 +206,5 @@ const app = new Elysia({ prefix: "/api" })
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);
export type AppType = typeof app;

487
api/src/test/index.test.ts Normal file
View File

@@ -0,0 +1,487 @@
import { describe, expect, it } from "bun:test";
import { edenFetch } from "@elysiajs/eden";
import cookie from "cookie";
import jwt from "jsonwebtoken";
import type { AppType } from "../index";
const fetch = edenFetch<AppType>("http://localhost:3000");
const pgCookie = cookie.serialize(
"auth",
jwt.sign(
// {
// type: "postgres",
// username: "postgres",
// password: "mysecretpassword",
// host: "localhost",
// port: "5432",
// database: "postgres",
// ssl: "prefer",
// },
{
type: "postgres",
connectionString:
"postgresql://flashcards_owner:pBYW18waUHtV@ep-gentle-heart-a225yqws.eu-central-1.aws.neon.tech/flashcards?sslmode=require&schema=flashcards",
},
"Fischl von Luftschloss Narfidort",
{ noTimestamp: true },
),
);
const mysqlCookie = cookie.serialize(
"auth",
jwt.sign(
{
type: "mysql",
username: "root",
password: "mysecretpassword",
host: "localhost",
port: "3306",
database: "mysql",
ssl: "prefer",
},
"Fischl von Luftschloss Narfidort",
{ noTimestamp: true },
),
);
describe("/auth/login", () => {
it("should log in correctly with PostgreSQL", async () => {
const res = await fetch("/api/auth/login", {
method: "POST",
body: {
type: "postgres",
username: "postgres",
password: "mysecretpassword",
host: "localhost",
port: "5432",
database: "postgres",
ssl: "prefer",
},
});
expect(res.status).toEqual(200);
expect(res.data?.databases).toEqual([
"pg_toast",
"pg_catalog",
"public",
"information_schema",
]);
});
it("should log in correctly with MySQL", async () => {
const res = await fetch("/api/auth/login", {
method: "POST",
body: {
type: "mysql",
username: "root",
password: "mysecretpassword",
host: "localhost",
port: "3306",
database: "mysql",
ssl: "prefer",
},
});
expect(res.status).toEqual(200);
expect(res.data?.databases).toEqual([
"information_schema",
"mysql",
"performance_schema",
"sys",
"test_db",
]);
});
});
describe("/databases", () => {
it("should return correct data from PostgreSQL", async () => {
const res = await fetch("/api/databases", {
method: "GET",
headers: {
cookie: pgCookie,
},
});
expect(res.status).toEqual(200);
expect(res.data).toEqual([
"pg_toast",
"pg_catalog",
"public",
"information_schema",
]);
});
it("should return correct data from MySQL", async () => {
const res = await fetch("/api/databases", {
method: "GET",
headers: {
cookie: mysqlCookie,
},
});
expect(res.status).toEqual(200);
expect(res.data).toEqual([
"information_schema",
"mysql",
"performance_schema",
"sys",
"test_db",
]);
});
});
describe("/databases/:dbName/tables", () => {
it("should return correct data from PostgreSQL", async () => {
const res = await fetch("/api/databases/:dbName/tables", {
params: {
dbName: "public",
},
method: "GET",
headers: {
cookie: pgCookie,
},
});
expect(res.status).toEqual(200);
for (const table of res.data) {
expect(table).toContainAllKeys([
"comments",
"index_size",
"indexes",
"owner",
"primary_key",
"row_count",
"schema_name",
"table_name",
"table_size",
"total_size",
]);
expect(table.comments).toBeString();
expect(table.index_size).toBeNumber();
expect(table.indexes).toBeString();
expect(
typeof table.owner === "string" || table.owner === null,
).toBeTrue();
expect(table.primary_key).toBeString();
expect(table.row_count).toBeNumber();
expect(table.schema_name).toBeString();
expect(table.table_name).toBeString();
expect(table.table_size).toBeNumber();
expect(table.total_size).toBeNumber();
}
});
it("should return correct data from MySQL", async () => {
const res = await fetch("/api/databases/:dbName/tables", {
params: {
dbName: "test_db",
},
method: "GET",
headers: {
cookie: mysqlCookie,
},
});
expect(res.status).toEqual(200);
for (const table of res.data) {
expect(table).toContainAllKeys([
"comments",
"index_size",
"indexes",
"owner",
"primary_key",
"row_count",
"schema_name",
"table_name",
"table_size",
"total_size",
]);
expect(table.comments).toBeString();
expect(table.index_size).toBeNumber();
expect(table.indexes).toBeString();
expect(
typeof table.owner === "string" || table.owner === null,
).toBeTrue();
expect(table.primary_key).toBeString();
expect(table.row_count).toBeNumber();
expect(table.schema_name).toBeString();
expect(table.table_name).toBeString();
expect(table.table_size).toBeNumber();
expect(table.total_size).toBeNumber();
}
});
});
describe("databases/:dbName/tables/:tableName/data", () => {
it("should return correct data from PostgreSQL", async () => {
const res = await fetch("/api/databases/:dbName/tables/:tableName/data", {
params: {
dbName: "public",
tableName: "test_table",
},
method: "GET",
headers: {
cookie: pgCookie,
},
});
expect(res.status).toEqual(200);
expect(res.data).not.toEqual("Unauthorized");
if (res.data === "Unauthorized") return;
expect(res.data).toBeDefined();
if (!res.data) return;
expect(res.data?.data.length).toBeGreaterThan(0);
expect(res.data?.count).toEqual(res.data?.data.length);
for (const row of res.data.data) {
expect(row).toBeObject();
}
});
it("should return correct data from MySQL", async () => {
const res = await fetch("/api/databases/:dbName/tables/:tableName/data", {
params: {
dbName: "test_db",
tableName: "test_table",
},
method: "GET",
headers: {
cookie: mysqlCookie,
},
});
expect(res.status).toEqual(200);
expect(res.data).not.toEqual("Unauthorized");
if (res.data === "Unauthorized") return;
expect(res.data).toBeDefined();
if (!res.data) return;
expect(res.data?.data.length).toBeGreaterThan(0);
expect(res.data?.count).toEqual(res.data?.data.length);
for (const row of res.data.data) {
expect(row).toBeObject();
}
});
});
describe("databases/:dbName/tables/:tableName/indexes", () => {
it("should return correct data from PostgreSQL", async () => {
const res = await fetch(
"/api/databases/:dbName/tables/:tableName/indexes",
{
params: {
dbName: "public",
tableName: "test_table",
},
method: "GET",
headers: {
cookie: pgCookie,
},
},
);
expect(res.status).toEqual(200);
expect(res.data).not.toEqual("Unauthorized");
if (res.data === "Unauthorized") return;
expect(res.data).toBeDefined();
if (!res.data) return;
expect(res.data).toBeArray();
expect(res.data[0].relname).toBeString();
expect(res.data[0].key).toBeString();
expect(res.data[0].type).toBeString();
expect(res.data[0].columns).toBeArray();
expect(res.data[0].columns[0]).toBeString();
});
it("should return correct data from MySQL", async () => {
const res = await fetch(
"/api/databases/:dbName/tables/:tableName/indexes",
{
params: {
dbName: "test_db",
tableName: "test_table",
},
method: "GET",
headers: {
cookie: mysqlCookie,
},
},
);
expect(res.status).toEqual(200);
expect(res.data).not.toEqual("Unauthorized");
if (res.data === "Unauthorized") return;
expect(res.data).toBeDefined();
if (!res.data) return;
expect(res.data).toBeArray();
expect(res.data[0].relname).toBeString();
expect(res.data[0].key).toBeString();
expect(res.data[0].type).toBeString();
expect(res.data[0].columns).toBeArray();
expect(res.data[0].columns[0]).toBeString();
});
});
describe("databases/:dbName/tables/:tableName/columns", () => {
it("should return correct data from PostgreSQL", async () => {
const res = await fetch(
"/api/databases/:dbName/tables/:tableName/columns",
{
params: {
dbName: "public",
tableName: "test_table",
},
method: "GET",
headers: {
cookie: pgCookie,
},
},
);
expect(res.status).toEqual(200);
expect(res.data).not.toEqual("Unauthorized");
if (res.data === "Unauthorized") return;
expect(res.data).toBeDefined();
if (!res.data) return;
expect(res.data).toBeArray();
for (const row of res.data) {
expect(row).toContainAllKeys([
"column_name",
"data_type",
"udt_name",
"column_comment",
]);
expect(row.column_name).toBeString();
expect(row.data_type).toBeString();
expect(row.udt_name).toBeString();
expect(
row.column_comment === null || typeof row.column_comment === "string",
).toBeTrue();
}
});
it("should return correct data from MySQL", async () => {
const res = await fetch(
"/api/databases/:dbName/tables/:tableName/columns",
{
params: {
dbName: "test_db",
tableName: "test_table",
},
method: "GET",
headers: {
cookie: mysqlCookie,
},
},
);
expect(res.data).toBeDefined();
if (!res.data) return;
expect(res.data).toBeArray();
for (const row of res.data) {
expect(row).toContainAllKeys([
"column_name",
"data_type",
"udt_name",
"column_comment",
]);
expect(row.column_name).toBeString();
expect(row.data_type).toBeString();
expect(row.udt_name).toBeString();
expect(
row.column_comment === null || typeof row.column_comment === "string",
).toBeTrue();
}
});
});
describe("databases/:dbName/tables/:tableName/foreign-keys", () => {
// it("should return correct data from PostgreSQL", async () => {
// const res = await fetch(
// "/api/databases/:dbName/tables/:tableName/foreign-keys",
// {
// params: {
// dbName: "public",
// tableName: "test_table",
// },
// method: "GET",
// headers: {
// cookie: pgCookie,
// },
// },
// );
// expect(res.status).toEqual(200);
//
// expect(res.data).not.toEqual("Unauthorized");
// if (res.data === "Unauthorized") return;
//
// console.log(res.data);
//
// expect(res.data).toBeDefined();
// if (!res.data) return;
//
// expect(res.data).toBeArray();
//
// for (const row of res.data) {
// expect(row).toContainAllKeys([
// "column_name",
// "data_type",
// "udt_name",
// "column_comment",
// ]);
// expect(row.column_name).toBeString();
// expect(row.data_type).toBeString();
// expect(row.udt_name).toBeString();
// expect(
// row.column_comment === null || typeof row.column_comment === "string",
// ).toBeTrue();
// }
// });
// it("should return correct data from MySQL", async () => {
// const res = await fetch(
// "/api/databases/:dbName/tables/:tableName/columns",
// {
// params: {
// dbName: "test_db",
// tableName: "test_table",
// },
// method: "GET",
// headers: {
// cookie: mysqlCookie,
// },
// },
// );
// console.log(res.data);
//
// expect(res.data).toBeDefined();
// if (!res.data) return;
//
// expect(res.data).toBeArray();
//
// for (const row of res.data) {
// expect(row).toContainAllKeys([
// "column_name",
// "data_type",
// "udt_name",
// "column_comment",
// ]);
// expect(row.column_name).toBeString();
// expect(row.data_type).toBeString();
// expect(row.udt_name).toBeString();
// expect(
// row.column_comment === null || typeof row.column_comment === "string",
// ).toBeTrue();
// }
// });
});