132 lines
4.3 KiB
TypeScript
Vendored
132 lines
4.3 KiB
TypeScript
Vendored
import assert from "node:assert/strict";
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { test } from "vitest";
|
|
import Database from "better-sqlite3";
|
|
import type { ConnectionConfig } from "../src/connections.js";
|
|
import { describeTable, executeQuery, listTables } from "../src/database.js";
|
|
|
|
function sqliteConfig(path: string): ConnectionConfig {
|
|
return {
|
|
id: "sqlite-test",
|
|
name: "local-sqlite",
|
|
db_type: "sqlite",
|
|
host: path,
|
|
port: 0,
|
|
username: "",
|
|
password: "",
|
|
ssh_enabled: false,
|
|
ssl: false,
|
|
};
|
|
}
|
|
|
|
function mysqlSshConfig(): ConnectionConfig {
|
|
return {
|
|
id: "mysql-ssh-test",
|
|
name: "mysql-over-ssh",
|
|
db_type: "mysql",
|
|
host: "10.0.0.10",
|
|
port: 3306,
|
|
username: "root",
|
|
password: "secret",
|
|
ssl: false,
|
|
transport_layers: [
|
|
{
|
|
type: "ssh",
|
|
id: "bastion",
|
|
enabled: true,
|
|
host: "bastion.example.com",
|
|
port: 22,
|
|
user: "root",
|
|
password: "ssh-secret",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
test("queries SQLite connections without the DBX bridge", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "dbx-mcp-sqlite-"));
|
|
const path = join(dir, "app.db");
|
|
const db = new Database(path);
|
|
db.exec("create table users (id integer primary key, name text not null); insert into users (name) values ('Ada');");
|
|
db.close();
|
|
|
|
try {
|
|
const result = await executeQuery(sqliteConfig(path), "select id, name from users");
|
|
|
|
assert.deepEqual(result.columns, ["id", "name"]);
|
|
assert.deepEqual(result.rows, [{ id: 1, name: "Ada" }]);
|
|
assert.equal(result.row_count, 1);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("applies query row limits to SQLite connections", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "dbx-mcp-sqlite-"));
|
|
const path = join(dir, "app.db");
|
|
const db = new Database(path);
|
|
db.exec("create table users (id integer primary key, name text not null); insert into users (name) values ('Ada'), ('Grace');");
|
|
db.close();
|
|
|
|
try {
|
|
const result = await executeQuery(sqliteConfig(path), "select id, name from users order by id", { maxRows: 1 });
|
|
|
|
assert.deepEqual(result.columns, ["id", "name"]);
|
|
assert.deepEqual(result.rows, [{ id: 1, name: "Ada" }]);
|
|
assert.equal(result.row_count, 1);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("lists and describes SQLite tables without the DBX bridge", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "dbx-mcp-sqlite-"));
|
|
const path = join(dir, "app.db");
|
|
const db = new Database(path);
|
|
db.exec("create table users (id integer primary key, name text not null);");
|
|
db.close();
|
|
|
|
try {
|
|
const tables = await listTables(sqliteConfig(path));
|
|
const columns = await describeTable(sqliteConfig(path), "users");
|
|
|
|
assert.deepEqual(tables, [{ name: "users", type: "table" }]);
|
|
assert.deepEqual(
|
|
columns.map((column) => ({
|
|
name: column.name,
|
|
data_type: column.data_type,
|
|
is_nullable: column.is_nullable,
|
|
is_primary_key: column.is_primary_key,
|
|
})),
|
|
[
|
|
{ name: "id", data_type: "INTEGER", is_nullable: true, is_primary_key: true },
|
|
{ name: "name", data_type: "TEXT", is_nullable: false, is_primary_key: false },
|
|
],
|
|
);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("routes SSH transport layer connections through the DBX bridge", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "dbx-mcp-bridge-home-"));
|
|
const originalHome = process.env.HOME;
|
|
const originalDbxDataDir = process.env.DBX_DATA_DIR;
|
|
process.env.HOME = dir;
|
|
process.env.DBX_DATA_DIR = dir;
|
|
|
|
try {
|
|
await assert.rejects(() => executeQuery(mysqlSshConfig(), "select 1"), /DBX desktop app is not running/);
|
|
await assert.rejects(() => listTables(mysqlSshConfig()), /DBX desktop app is not running/);
|
|
await assert.rejects(() => describeTable(mysqlSshConfig(), "users"), /DBX desktop app is not running/);
|
|
} finally {
|
|
if (originalHome === undefined) delete process.env.HOME;
|
|
else process.env.HOME = originalHome;
|
|
if (originalDbxDataDir === undefined) delete process.env.DBX_DATA_DIR;
|
|
else process.env.DBX_DATA_DIR = originalDbxDataDir;
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|