dbx/packages/app-tests/mongoShellCommand.test.ts

184 lines
6.7 KiB
TypeScript

import { strict as assert } from "node:assert";
import { test } from "vitest";
import {
evaluateMongoAggregateSafety,
mongoAggregateWriteStage,
mongoCountToQueryResult,
mongoDocumentsToQueryResult,
mongoIndexesToQueryResult,
parseMongoAggregateCommand,
parseMongoCountDocumentsCommand,
parseMongoFindCommand,
parseMongoGetIndexesCommand,
parseMongoWriteCommand,
} from "../../apps/desktop/src/lib/mongoShellCommand.ts";
test("parseMongoFindCommand parses db collection find with an empty JSON filter", () => {
assert.deepEqual(parseMongoFindCommand("db.users.find({})"), {
collection: "users",
filter: "{}",
skip: 0,
limit: 100,
sort: undefined,
});
});
test("parseMongoFindCommand parses getCollection find with chained sort skip and limit", () => {
assert.deepEqual(parseMongoFindCommand('db.getCollection("audit.logs").find({"level":"warn"}).sort({"createdAt":-1}).skip(20).limit(10)'), {
collection: "audit.logs",
filter: '{"level":"warn"}',
skip: 20,
limit: 10,
sort: '{"createdAt":-1}',
});
});
test("parseMongoFindCommand accepts Compass-style unquoted keys and ObjectId", () => {
const command = parseMongoFindCommand("db.products.find({_id: ObjectId('6a045a92d2971e44243771a1')}).limit(1)");
assert.ok(command);
assert.equal(command.collection, "products");
assert.equal(command.limit, 1);
assert.deepEqual(JSON.parse(command.filter), { _id: { $oid: "6a045a92d2971e44243771a1" } });
});
test("parseMongoFindCommand accepts single-quoted string values and unquoted sort keys", () => {
const command = parseMongoFindCommand("db.products.find({category: 'Electronics'}).sort({price: -1}).limit(2)");
assert.ok(command);
assert.equal(command.collection, "products");
assert.equal(command.limit, 2);
assert.deepEqual(JSON.parse(command.filter), { category: "Electronics" });
assert.deepEqual(JSON.parse(command.sort || "{}"), { price: -1 });
});
test("parseMongoFindCommand rejects unsupported mongo shell commands", () => {
assert.equal(parseMongoFindCommand("db.users.drop()"), null);
});
test("parseMongoWriteCommand accepts unquoted insert and update commands", () => {
assert.deepEqual(parseMongoWriteCommand("db.products.insertOne({name: 'demo', price: 1})"), {
kind: "insert",
collection: "products",
docsJson: '{"name": "demo", "price": 1}',
});
assert.deepEqual(parseMongoWriteCommand("db.products.updateOne({_id: ObjectId('507f1f77bcf86cd799439011')}, {$set: {stock: 3}})"), {
kind: "update",
collection: "products",
filter: '{"_id": {"$oid":"507f1f77bcf86cd799439011"}}',
update: '{"$set": {"stock": 3}}',
many: false,
});
});
test("parseMongoCountDocumentsCommand parses db collection countDocuments", () => {
assert.deepEqual(parseMongoCountDocumentsCommand("db.products.countDocuments({})"), {
collection: "products",
filter: "{}",
});
});
test("parseMongoAggregateCommand parses db collection aggregate", () => {
assert.deepEqual(parseMongoAggregateCommand('db.products.aggregate([{"$match":{"active":true}},{"$count":"total"}])'), {
collection: "products",
pipeline: '[{"$match":{"active":true}},{"$count":"total"}]',
});
});
test("parseMongoAggregateCommand accepts an empty pipeline", () => {
assert.deepEqual(parseMongoAggregateCommand("db.products.aggregate([])"), {
collection: "products",
pipeline: "[]",
});
});
test("parseMongoAggregateCommand rejects non-array pipelines and extra arguments", () => {
assert.equal(parseMongoAggregateCommand('db.products.aggregate({"$match":{}})'), null);
assert.equal(parseMongoAggregateCommand("db.products.aggregate([], {})"), null);
assert.equal(parseMongoAggregateCommand("db.products.aggregate([]).limit(10)"), null);
});
test("parseMongoAggregateCommand normalises ObjectId arguments with either quote style", () => {
const oid = "507f1f77bcf86cd799439011";
for (const quote of ['"', "'"]) {
const command = parseMongoAggregateCommand(`db.orders.aggregate([{"$match":{"_id":ObjectId(${quote}${oid}${quote})}}])`);
assert.ok(command, `quote=${quote} should parse`);
assert.equal(command.collection, "orders");
assert.deepEqual(JSON.parse(command.pipeline), [{ $match: { _id: { $oid: oid } } }]);
}
});
test("parseMongoGetIndexesCommand parses collection index commands", () => {
assert.deepEqual(parseMongoGetIndexesCommand("db.web_log.getIndexes();"), {
collection: "web_log",
});
assert.deepEqual(parseMongoGetIndexesCommand('db.getCollection("audit.logs").getIndexes()'), {
collection: "audit.logs",
});
assert.equal(parseMongoGetIndexesCommand("db.web_log.getIndexes({})"), null);
});
test("evaluateMongoAggregateSafety blocks write stages unless MCP write flags allow them", () => {
const out = parseMongoAggregateCommand('db.products.aggregate([{"$out":"products_copy"}])');
assert.ok(out);
assert.equal(mongoAggregateWriteStage(out.pipeline), "$out");
assert.match(evaluateMongoAggregateSafety(out, {}).reason || "", /DBX_MCP_ALLOW_WRITES=1/);
const merge = parseMongoAggregateCommand('db.products.aggregate([{"$merge":{"into":"products_copy"}}])');
assert.ok(merge);
assert.equal(mongoAggregateWriteStage(merge.pipeline), "$merge");
assert.match(evaluateMongoAggregateSafety(merge, { allowWrites: true }).reason || "", /DBX_MCP_ALLOW_DANGEROUS_SQL=1/);
assert.equal(evaluateMongoAggregateSafety(merge, { allowWrites: true, allowDangerous: true }).allowed, true);
});
test("mongoIndexesToQueryResult formats index metadata", () => {
assert.deepEqual(
mongoIndexesToQueryResult(
[
{
name: "_id_",
columns: ["_id"],
is_unique: false,
is_primary: true,
index_type: "_id: 1",
filter: null,
},
],
7,
),
{
columns: ["name", "columns", "unique", "primary", "type", "filter"],
rows: [["_id_", "_id", false, true, "_id: 1", null]],
affected_rows: 1,
execution_time_ms: 7,
},
);
});
test("mongoCountToQueryResult returns a single count row", () => {
assert.deepEqual(mongoCountToQueryResult(42, 5), {
columns: ["count"],
rows: [[42]],
affected_rows: 42,
execution_time_ms: 5,
});
});
test("mongoDocumentsToQueryResult turns mongo documents into grid rows", () => {
const result = mongoDocumentsToQueryResult(
[
{ _id: "1", name: "Ada", profile: { role: "admin" } },
{ _id: "2", active: true, name: "Lin" },
],
5,
12,
);
assert.deepEqual(result.columns, ["_id", "name", "profile", "active"]);
assert.deepEqual(result.rows, [
["1", "Ada", '{"role":"admin"}', null],
["2", "Lin", null, true],
]);
assert.equal(result.affected_rows, 12);
assert.equal(result.execution_time_ms, 5);
assert.equal(result.truncated, true);
});