feat(mcp): centralize connection access policies

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-07-20 22:47:40 +08:00
1 parent 29f493f7da
commit d642d01eac
56 files changed
+6393 -617

No files matched your search

@@ -0,0 +1,89 @@
// @vitest-environment happy-dom
import { describe, expect, it, vi } from "vitest";
import { dispatch, findAll, findOne, mountComponent } from "@/components/grid/__tests__/vueHostHarness";
import type { ConnectionConfig } from "@/types/database";
vi.mock("vue-i18n", () => ({
useI18n: () => ({
t: (key: string, values?: Record<string, unknown>) => `${key}${values ? ` ${JSON.stringify(values)}` : ""}`,
}),
}));
vi.mock("@lucide/vue", async () => {
const { createPassthroughStub } = await import("@/components/grid/__tests__/vueHostHarness");
const icon = createPassthroughStub("Icon", "i");
return { AlertTriangle: icon, Minus: icon, Plus: icon, Search: icon };
});
vi.mock("@/components/ui/badge", async () => ({ Badge: (await import("@/components/grid/__tests__/vueHostHarness")).createPassthroughStub("Badge", "span") }));
vi.mock("@/components/ui/button", async () => ({ Button: (await import("@/components/grid/__tests__/vueHostHarness")).createPassthroughStub("Button", "button") }));
vi.mock("@/components/ui/input", async () => ({ Input: (await import("@/components/grid/__tests__/vueHostHarness")).createPassthroughStub("Input", "input") }));
vi.mock("@/components/ui/TruncatedTextTooltip.vue", async () => ({ default: (await import("@/components/grid/__tests__/vueHostHarness")).createPassthroughStub("TruncatedTextTooltip", "span") }));
import McpConnectionScopePicker from "@/components/settings/McpConnectionScopePicker.vue";
function connection(id: string): ConnectionConfig {
return {
id,
name: `${id}-name`,
db_type: "mysql",
host: "127.0.0.1",
port: 3306,
username: "test",
password: "",
};
}
describe("McpConnectionScopePicker", () => {
it("separates allowed and available connections and emits direct moves", () => {
const update = vi.fn();
const mounted = mountComponent(McpConnectionScopePicker, {
connections: [connection("one"), connection("two")],
allowedConnectionIds: ["one"],
"onUpdate:allowedConnectionIds": update,
});
const allowedPane = findOne(mounted.root, (node) => node.props["data-scope-pane"] === "allowed");
const availablePane = findOne(mounted.root, (node) => node.props["data-scope-pane"] === "available");
expect(findAll(allowedPane, (node) => node.props["data-stub"] === "TruncatedTextTooltip").map((node) => node.props.text)).toContain("one-name");
expect(findAll(allowedPane, (node) => node.props["data-stub"] === "TruncatedTextTooltip").map((node) => node.props.text)).not.toContain("two-name");
expect(findAll(availablePane, (node) => node.props["data-stub"] === "TruncatedTextTooltip").map((node) => node.props.text)).toContain("two-name");
const add = findOne(availablePane, (node) => node.props["data-connection-id"] === "two");
dispatch(add, "click");
expect(update).toHaveBeenCalledWith(["one", "two"]);
const remove = findOne(allowedPane, (node) => node.props["data-connection-id"] === "one");
dispatch(remove, "click");
expect(update).toHaveBeenCalledWith([]);
});
it("keeps dynamic allow-all distinct from an explicit current-connection list", () => {
const update = vi.fn();
const mounted = mountComponent(McpConnectionScopePicker, {
connections: [connection("one"), connection("two")],
allowedConnectionIds: null,
"onUpdate:allowedConnectionIds": update,
});
const modeInputs = findAll(mounted.root, (node) => node.type === "input" && node.props.name === "mcp-scope-mode");
expect(modeInputs.find((input) => input.props.value === "all")?.props.checked).toBe(true);
dispatch(
findOne(mounted.root, (node) => node.type === "input" && node.props.value === "selected"),
"change",
);
expect(update).toHaveBeenCalledWith(["one", "two"]);
});
it("shows unavailable allowlist entries only in the allowed pane", () => {
const mounted = mountComponent(McpConnectionScopePicker, {
connections: [connection("one")],
allowedConnectionIds: ["one", "missing-id"],
});
const allowedPane = findOne(mounted.root, (node) => node.props["data-scope-pane"] === "allowed");
const availablePane = findOne(mounted.root, (node) => node.props["data-scope-pane"] === "available");
expect(findAll(allowedPane, (node) => node.props["data-stub"] === "TruncatedTextTooltip").map((node) => node.props.text)).toContain("missing-id");
expect(findAll(availablePane, (node) => node.props["data-stub"] === "TruncatedTextTooltip").map((node) => node.props.text)).not.toContain("missing-id");
});
});