Files
dbx/apps/desktop/src/lib/__tests__/database/DatabaseUserAdmin.component.spec.ts
T

145 lines
5.2 KiB
TypeScript
Vendored

// @vitest-environment happy-dom
import { createApp, defineComponent, h, nextTick, type Component } from "vue";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ConnectionConfig } from "@/types/database";
const mocks = vi.hoisted(() => ({
ensureConnected: vi.fn(),
executeQuery: vi.fn(),
}));
function passthrough(tag: string): Component {
return defineComponent({
inheritAttrs: false,
setup(_, { attrs, slots }) {
return () => h(tag, attrs, slots.default?.());
},
});
}
vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key: string) => key }) }));
vi.mock("@lucide/vue", () => {
const Icon = passthrough("span");
return {
AlertTriangle: Icon,
Check: Icon,
KeyRound: Icon,
Lock: Icon,
Loader2: Icon,
Plus: Icon,
RefreshCcw: Icon,
Search: Icon,
ShieldCheck: Icon,
Trash2: Icon,
Unlock: Icon,
UserRound: Icon,
};
});
vi.mock("@/components/ui/button", () => ({ Button: passthrough("button") }));
vi.mock("@/components/ui/badge", () => ({ Badge: passthrough("span") }));
vi.mock("@/components/ui/dialog", () => ({
Dialog: passthrough("div"),
DialogContent: passthrough("div"),
DialogFooter: passthrough("div"),
DialogHeader: passthrough("div"),
DialogTitle: passthrough("div"),
}));
vi.mock("@/components/ui/input", () => ({ Input: passthrough("input") }));
vi.mock("@/components/ui/PasswordInput.vue", () => ({ default: passthrough("input") }));
vi.mock("@/components/ui/select", () => ({
Select: passthrough("div"),
SelectContent: passthrough("div"),
SelectItem: passthrough("div"),
SelectTrigger: passthrough("div"),
SelectValue: passthrough("span"),
}));
vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: () => ({ ensureConnected: mocks.ensureConnected }),
}));
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: vi.fn() }) }));
vi.mock("@/composables/useSqlHighlighter", () => ({ useSqlHighlighter: () => ({ highlight: (sql: string) => sql }) }));
vi.mock("@/lib/backend/api", () => ({
executeQuery: mocks.executeQuery,
executeMulti: vi.fn(),
listDatabases: vi.fn(),
listSchemas: vi.fn(),
}));
import DatabaseUserAdmin from "@/components/admin/DatabaseUserAdmin.vue";
const connection: ConnectionConfig = {
id: "oceanbase",
name: "OceanBase",
db_type: "jdbc",
driver_profile: "mysql",
host: "localhost",
port: 2881,
username: "root",
password: "",
};
let app: ReturnType<typeof createApp> | undefined;
let root: HTMLDivElement | undefined;
afterEach(() => {
app?.unmount();
root?.remove();
app = undefined;
root = undefined;
vi.clearAllMocks();
});
describe("DatabaseUserAdmin MySQL grant loading", () => {
it("syncs privilege buttons and grant option from loaded SHOW GRANTS rows", async () => {
mocks.ensureConnected.mockResolvedValue(undefined);
mocks.executeQuery.mockResolvedValueOnce({ columns: ["user", "host", "plugin"], rows: [["root", "%", "mysql_native_password"]] }).mockResolvedValueOnce({ columns: ["Grants for root@%"], rows: [["GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION"]] });
root = document.createElement("div");
document.body.append(root);
app = createApp(DatabaseUserAdmin, { connection });
app.mount(root);
await vi.waitFor(() => expect(mocks.executeQuery).toHaveBeenCalledTimes(2));
await nextTick();
const privilegeButton = Array.from(root.querySelectorAll("button")).find((button) => button.textContent?.trim() === "INSERT");
const grantOptionLabel = Array.from(root.querySelectorAll("label")).find((label) => label.textContent?.includes("userAdmin.grantOption"));
const grantOptionInput = grantOptionLabel?.querySelector<HTMLInputElement>('input[type="checkbox"]');
expect(privilegeButton?.className).toContain("border-primary");
expect(grantOptionInput?.checked).toBe(true);
});
it("falls back to the current Doris user when SHOW ALL GRANTS requires GRANT_PRIV", async () => {
const dorisConnection: ConnectionConfig = {
...connection,
id: "doris-limited",
name: "Doris limited",
db_type: "doris",
driver_profile: "doris",
port: 9030,
username: "dbx_limited",
};
const currentUserGrant = {
columns: ["UserIdentity", "Comment", "Password", "Roles", "GlobalPrivs", "DatabasePrivs", "TablePrivs"],
rows: [["'dbx_limited'@'%'", "", "Yes", null, null, "internal.analytics: Select_priv", null]],
};
mocks.ensureConnected.mockResolvedValue(undefined);
mocks.executeQuery.mockRejectedValueOnce(new Error("Access denied; you need the (GRANT) privilege"));
mocks.executeQuery.mockResolvedValueOnce(currentUserGrant).mockResolvedValueOnce(currentUserGrant);
root = document.createElement("div");
document.body.append(root);
app = createApp(DatabaseUserAdmin, { connection: dorisConnection });
app.mount(root);
await vi.waitFor(() => expect(mocks.executeQuery).toHaveBeenCalledTimes(3));
await nextTick();
expect(mocks.executeQuery.mock.calls.map((call) => call[2])).toEqual(["SHOW ALL GRANTS;", "SHOW GRANTS;", "SHOW GRANTS FOR 'dbx_limited'@'%';"]);
expect(root.textContent).toContain("dbx_limited@%");
expect(root.textContent).not.toContain("Access denied");
});
});