153 lines
5.0 KiB
TypeScript
153 lines
5.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import {
|
|
executeQuery,
|
|
inferMongoColumns,
|
|
mongoAggregateWriteStage,
|
|
mongoDocumentsToQueryResult,
|
|
parseMongoAggregateCommand,
|
|
parseMongoCountDocumentsCommand,
|
|
parseMongoFindCommand,
|
|
parseMongoWriteCommand,
|
|
} from "../src/database.js";
|
|
|
|
test("parseMongoFindCommand accepts shell-style find commands", () => {
|
|
assert.deepEqual(parseMongoFindCommand('db.getCollection("operation_logs").find({"level":"info"}).sort({"ts":-1}).skip(5).limit(10)'), {
|
|
collection: "operation_logs",
|
|
filter: '{"level":"info"}',
|
|
skip: 5,
|
|
limit: 10,
|
|
sort: '{"ts":-1}',
|
|
});
|
|
});
|
|
|
|
test("parseMongoCountDocumentsCommand accepts shell-style count commands", () => {
|
|
assert.deepEqual(parseMongoCountDocumentsCommand('db.projects.countDocuments({"active":true})'), {
|
|
collection: "projects",
|
|
filter: '{"active":true}',
|
|
});
|
|
});
|
|
|
|
test("parseMongoAggregateCommand accepts aggregate pipelines", () => {
|
|
assert.deepEqual(parseMongoAggregateCommand('db.projects.aggregate([{"$match":{"active":true}},{"$group":{"_id":"$owner","total":{"$sum":1}}}])'), {
|
|
collection: "projects",
|
|
pipeline: '[{"$match":{"active":true}},{"$group":{"_id":"$owner","total":{"$sum":1}}}]',
|
|
});
|
|
});
|
|
|
|
test("mongoAggregateWriteStage detects write stages", () => {
|
|
assert.equal(mongoAggregateWriteStage('[{"$match":{"active":true}}]'), null);
|
|
assert.equal(mongoAggregateWriteStage('[{"$match":{}},{"$out":"projects_dump"}]'), "$out");
|
|
assert.equal(mongoAggregateWriteStage('[{"$merge":{"into":"projects_dump"}}]'), "$merge");
|
|
});
|
|
|
|
test("mongodb executeQuery blocks aggregate write stages without both env flags", async () => {
|
|
const oldAllowWrites = process.env.DBX_MCP_ALLOW_WRITES;
|
|
const oldAllowDangerous = process.env.DBX_MCP_ALLOW_DANGEROUS_SQL;
|
|
delete process.env.DBX_MCP_ALLOW_WRITES;
|
|
delete process.env.DBX_MCP_ALLOW_DANGEROUS_SQL;
|
|
const config = {
|
|
id: "mongo",
|
|
name: "mongo",
|
|
db_type: "mongodb",
|
|
host: "127.0.0.1",
|
|
port: 27017,
|
|
username: "",
|
|
password: "",
|
|
database: "app",
|
|
ssh_enabled: false,
|
|
ssl: false,
|
|
} as const;
|
|
|
|
await assert.rejects(executeQuery(config, 'db.projects.aggregate([{"$out":"projects_dump"}])'), /DBX_MCP_ALLOW_WRITES=1/);
|
|
|
|
process.env.DBX_MCP_ALLOW_WRITES = "1";
|
|
await assert.rejects(
|
|
executeQuery(config, 'db.projects.aggregate([{"$merge":{"into":"projects_dump"}}])'),
|
|
/DBX_MCP_ALLOW_DANGEROUS_SQL=1/,
|
|
);
|
|
|
|
if (oldAllowWrites === undefined) delete process.env.DBX_MCP_ALLOW_WRITES;
|
|
else process.env.DBX_MCP_ALLOW_WRITES = oldAllowWrites;
|
|
if (oldAllowDangerous === undefined) delete process.env.DBX_MCP_ALLOW_DANGEROUS_SQL;
|
|
else process.env.DBX_MCP_ALLOW_DANGEROUS_SQL = oldAllowDangerous;
|
|
});
|
|
|
|
test("parseMongoWriteCommand accepts supported write commands", () => {
|
|
assert.deepEqual(parseMongoWriteCommand('db.projects.insertOne({"name":"demo"})'), {
|
|
kind: "insert",
|
|
collection: "projects",
|
|
docsJson: '{"name":"demo"}',
|
|
});
|
|
assert.deepEqual(parseMongoWriteCommand('db.projects.updateOne({"_id":"1"},{"$set":{"name":"next"}})'), {
|
|
kind: "update",
|
|
collection: "projects",
|
|
filter: '{"_id":"1"}',
|
|
update: '{"$set":{"name":"next"}}',
|
|
many: false,
|
|
});
|
|
assert.deepEqual(parseMongoWriteCommand('db.projects.deleteMany({"stale":true})'), {
|
|
kind: "delete",
|
|
collection: "projects",
|
|
filter: '{"stale":true}',
|
|
many: true,
|
|
});
|
|
});
|
|
|
|
test("mongodb executeQuery blocks writes without the write env flag", async () => {
|
|
const oldAllowWrites = process.env.DBX_MCP_ALLOW_WRITES;
|
|
delete process.env.DBX_MCP_ALLOW_WRITES;
|
|
await assert.rejects(
|
|
executeQuery(
|
|
{
|
|
id: "mongo",
|
|
name: "mongo",
|
|
db_type: "mongodb",
|
|
host: "127.0.0.1",
|
|
port: 27017,
|
|
username: "",
|
|
password: "",
|
|
database: "app",
|
|
ssh_enabled: false,
|
|
ssl: false,
|
|
},
|
|
'db.projects.insertOne({"name":"demo"})',
|
|
),
|
|
/DBX_MCP_ALLOW_WRITES=1/,
|
|
);
|
|
if (oldAllowWrites === undefined) delete process.env.DBX_MCP_ALLOW_WRITES;
|
|
else process.env.DBX_MCP_ALLOW_WRITES = oldAllowWrites;
|
|
});
|
|
|
|
test("mongoDocumentsToQueryResult turns documents into rows", () => {
|
|
assert.deepEqual(mongoDocumentsToQueryResult([{ _id: "1", nested: { ok: true } }, { _id: "2", name: "demo" }], 2), {
|
|
columns: ["_id", "nested", "name"],
|
|
rows: [
|
|
{ _id: "1", nested: '{"ok":true}', name: undefined },
|
|
{ _id: "2", nested: undefined, name: "demo" },
|
|
],
|
|
row_count: 2,
|
|
});
|
|
});
|
|
|
|
test("inferMongoColumns marks _id as primary and reports observed types", () => {
|
|
assert.deepEqual(inferMongoColumns([{ _id: "1", active: true }, { _id: "2", active: null }]), [
|
|
{
|
|
name: "_id",
|
|
data_type: "string",
|
|
is_nullable: false,
|
|
column_default: null,
|
|
is_primary_key: true,
|
|
comment: null,
|
|
},
|
|
{
|
|
name: "active",
|
|
data_type: "boolean | null",
|
|
is_nullable: true,
|
|
column_default: null,
|
|
is_primary_key: false,
|
|
comment: null,
|
|
},
|
|
]);
|
|
});
|