Files
2026-07-13 13:09:14 +08:00

296 lines
7.6 KiB
TypeScript
Vendored

import assert from "node:assert/strict";
import { afterEach, test, vi } from "vitest";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ConnectionConfig } from "../src/connections.js";
const fakeMysqlQuery = vi.fn();
const fakeMysqlEnd = vi.fn().mockResolvedValue(undefined);
const fakePgQuery = vi.fn();
const fakePgEnd = vi.fn().mockResolvedValue(undefined);
const fakePgOn = vi.fn();
vi.mock("mysql2/promise", () => ({
default: {
createPool: vi.fn(() => ({
query: fakeMysqlQuery,
end: fakeMysqlEnd,
})),
},
}));
vi.mock("pg", () => ({
default: {
Pool: vi.fn(function MockPool() {
return {
query: fakePgQuery,
end: fakePgEnd,
on: fakePgOn,
};
}),
},
}));
import { closeDatabaseResources, describeTable } from "../src/database.js";
function mysqlConfig(): ConnectionConfig {
return {
id: "mysql-direct-test",
name: "mysql-direct",
db_type: "mysql",
host: "127.0.0.1",
port: 3306,
username: "root",
password: "secret",
database: "app",
ssl: false,
};
}
function postgresConfig(): ConnectionConfig {
return {
id: "postgres-direct-test",
name: "postgres-direct",
db_type: "postgres",
host: "127.0.0.1",
port: 5432,
username: "postgres",
password: "postgres",
database: "app",
ssl: false,
};
}
const bridgeConfig: ConnectionConfig = {
id: "pg-bridge",
name: "bridge-postgres",
db_type: "postgres",
host: "127.0.0.1",
port: 5432,
username: "postgres",
password: "postgres",
database: "postgres",
ssl: false,
transport_layers: [
{
type: "ssh",
id: "jump",
enabled: true,
host: "bastion.internal",
port: 22,
user: "dbx",
},
],
};
afterEach(async () => {
await closeDatabaseResources();
fakeMysqlQuery.mockReset();
fakeMysqlEnd.mockClear();
fakePgQuery.mockReset();
fakePgEnd.mockClear();
fakePgOn.mockClear();
});
test("describeTable maps mysql enum_values from metadata", async () => {
fakeMysqlQuery.mockResolvedValue([
[
{
name: "state",
data_type: "enum",
column_type: "enum('pending','active','archived')",
is_nullable: 0,
column_default: "pending",
is_primary_key: 0,
comment: "workflow state",
},
],
[{ name: "name" }],
]);
const columns = await describeTable(mysqlConfig(), "orders");
assert.match(String(fakeMysqlQuery.mock.calls[0]?.[0] ?? ""), /COLUMN_TYPE AS column_type/);
assert.deepEqual(fakeMysqlQuery.mock.calls[0]?.[1], ["orders"]);
assert.deepEqual(columns, [
{
name: "state",
data_type: "enum",
is_nullable: false,
column_default: "pending",
is_primary_key: false,
comment: "workflow state",
enum_values: ["pending", "active", "archived"],
},
]);
});
test("describeTable parses mysql enum literal edge cases", async () => {
fakeMysqlQuery.mockResolvedValue([
[
{
name: "empty_state",
data_type: "enum",
column_type: "enum('','a')",
is_nullable: 1,
column_default: null,
is_primary_key: 0,
comment: null,
},
{
name: "quoted_state",
data_type: "enum",
column_type: "enum('x'',''y','z')",
is_nullable: 1,
column_default: null,
is_primary_key: 0,
comment: null,
},
{
name: "escaped_state",
data_type: "enum",
column_type: String.raw`enum('it''s','quote\"d','back\\slash')`,
is_nullable: 1,
column_default: null,
is_primary_key: 0,
comment: null,
},
],
[{ name: "name" }],
]);
const columns = await describeTable(mysqlConfig(), "orders");
assert.deepEqual(
columns.map((column) => column.enum_values),
[
["", "a"],
["x','y", "z"],
["it's", 'quote"d', "back\\slash"],
],
);
});
test("describeTable preserves enum values from bridge metadata", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "dbx-node-core-"));
const previousDataDir = process.env.DBX_DATA_DIR;
const server = createServer((req, res) => {
assert.equal(req.url, "/data/describe-table");
res.writeHead(200, { "content-type": "application/json" });
res.end(
JSON.stringify([
{
name: "state",
data_type: "status",
is_nullable: false,
column_default: null,
is_primary_key: false,
comment: "workflow state",
enum_values: ["pending", "active", "archived"],
},
]),
);
});
try {
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
const address = server.address();
if (!address || typeof address === "string") throw new Error("expected TCP bridge address");
process.env.DBX_DATA_DIR = tempDir;
await writeFile(join(tempDir, "mcp-bridge-port"), String(address.port));
const columns = await describeTable(bridgeConfig, "orders", "public");
assert.deepEqual(columns, [
{
name: "state",
data_type: "status",
is_nullable: false,
column_default: null,
is_primary_key: false,
comment: "workflow state",
enum_values: ["pending", "active", "archived"],
},
]);
} finally {
server.close();
if (previousDataDir === undefined) {
delete process.env.DBX_DATA_DIR;
} else {
process.env.DBX_DATA_DIR = previousDataDir;
}
await rm(tempDir, { recursive: true, force: true });
}
});
test("describeTable reads postgres enum_values from the primary metadata query", async () => {
fakePgQuery.mockResolvedValueOnce({
rows: [
{
name: "state",
data_type: "status",
is_nullable: false,
column_default: null,
is_primary_key: false,
comment: "workflow state",
enum_values: ["pending", "active", "archived"],
},
],
fields: [{ name: "name" }],
});
const columns = await describeTable(postgresConfig(), "orders", "public");
assert.match(String(fakePgQuery.mock.calls[0]?.[0] ?? ""), /FROM pg_enum e WHERE e\.enumtypid = enum_t\.oid/);
assert.equal(fakePgQuery.mock.calls.length, 1);
assert.deepEqual(columns, [
{
name: "state",
data_type: "status",
is_nullable: false,
column_default: null,
is_primary_key: false,
comment: "workflow state",
enum_values: ["pending", "active", "archived"],
},
]);
});
test("describeTable falls back to compat postgres metadata query when enum joins fail", async () => {
fakePgQuery.mockRejectedValueOnce(new Error("pg_enum catalog unavailable")).mockResolvedValueOnce({
rows: [
{
name: "state",
data_type: "status",
is_nullable: false,
column_default: null,
is_primary_key: false,
comment: "workflow state",
enum_values: null,
},
],
fields: [{ name: "name" }],
});
const columns = await describeTable(postgresConfig(), "orders", "public");
assert.match(String(fakePgQuery.mock.calls[0]?.[0] ?? ""), /FROM pg_enum e WHERE e\.enumtypid = enum_t\.oid/);
assert.match(String(fakePgQuery.mock.calls[1]?.[0] ?? ""), /NULL AS enum_values/);
assert.doesNotMatch(String(fakePgQuery.mock.calls[1]?.[0] ?? ""), /pg_enum/);
assert.deepEqual(columns, [
{
name: "state",
data_type: "status",
is_nullable: false,
column_default: null,
is_primary_key: false,
comment: "workflow state",
enum_values: null,
},
]);
});