Files
dbx/apps/desktop/src/composables/__tests__/useDatabaseOptions.spec.ts
T

222 lines
8.0 KiB
TypeScript
Vendored

import { beforeEach, describe, expect, it, vi } from "vitest";
import {
databaseAfterCatalogChange,
databaseOptionsForConnection,
fetchCatalogNamespaceOptions,
fetchNamespaceOptionsForConnection,
fetchSqlFileTargetOptions,
namespaceOptionsAreSchemas,
normalizedQueryTabCatalog,
queryCatalogSelectorVisible,
selectedQueryCatalogName,
useDatabaseOptions,
} from "@/composables/useDatabaseOptions";
const mocks = vi.hoisted(() => ({
ensureConnected: vi.fn(),
getConfig: vi.fn(),
listDatabases: vi.fn(),
listSchemas: vi.fn(),
redisListDatabases: vi.fn(),
mongoListDatabases: vi.fn(),
listDorisCatalogs: vi.fn(),
listDorisCatalogDatabases: vi.fn(),
}));
vi.mock("@/lib/backend/api", () => ({
listDatabases: mocks.listDatabases,
listSchemas: mocks.listSchemas,
redisListDatabases: mocks.redisListDatabases,
mongoListDatabases: mocks.mongoListDatabases,
listDorisCatalogs: mocks.listDorisCatalogs,
listDorisCatalogDatabases: mocks.listDorisCatalogDatabases,
}));
vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: () => ({
ensureConnected: mocks.ensureConnected,
getConfig: mocks.getConfig,
}),
}));
const internalCatalog = { name: "default_catalog", catalog_type: "internal", is_current: true };
const paimonCatalog = { name: "paimon_catalog", catalog_type: "paimon", is_current: false };
describe("query catalog options", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("shows the selector only when an external catalog exists", () => {
expect(queryCatalogSelectorVisible([internalCatalog])).toBe(false);
expect(queryCatalogSelectorVisible([internalCatalog, paimonCatalog])).toBe(true);
});
it("maps the internal catalog to an undefined tab catalog", () => {
const catalogs = [internalCatalog, paimonCatalog];
expect(selectedQueryCatalogName(catalogs)).toBe("default_catalog");
expect(normalizedQueryTabCatalog(catalogs, "default_catalog")).toBeUndefined();
expect(normalizedQueryTabCatalog(catalogs, "paimon_catalog")).toBe("paimon_catalog");
});
it("keeps a database only when it exists in the selected catalog", () => {
expect(databaseAfterCatalogChange("bi", ["bi", "default"])).toBe("bi");
expect(databaseAfterCatalogChange("bi", ["analytics"])).toBe("");
});
it("loads and caches catalog-scoped databases", async () => {
mocks.getConfig.mockReturnValue({ db_type: "starrocks" });
mocks.listDorisCatalogs.mockResolvedValue([internalCatalog, paimonCatalog]);
mocks.listDorisCatalogDatabases.mockResolvedValue([{ name: "bi" }, { name: "analytics" }]);
const options = useDatabaseOptions();
await expect(options.loadCatalogOptions("connection-1")).resolves.toEqual([internalCatalog, paimonCatalog]);
await expect(options.loadCatalogDatabaseOptions("connection-1", "paimon_catalog")).resolves.toEqual(["bi", "analytics"]);
await options.loadCatalogDatabaseOptions("connection-1", "paimon_catalog");
expect(options.catalogDatabaseOptions.value["connection-1:paimon_catalog"]).toEqual(["bi", "analytics"]);
expect(mocks.listDorisCatalogDatabases).toHaveBeenCalledTimes(1);
});
});
describe("namespace options", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("uses Dameng schemas so independent schemas remain selectable", async () => {
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING", "SYS"]);
const options = await fetchNamespaceOptionsForConnection("connection-1", {
db_type: "dameng",
database: "APP_USER",
visible_databases: ["APP_USER", "REPORTING"],
});
expect(options).toEqual(["APP_USER", "REPORTING"]);
expect(mocks.listSchemas).toHaveBeenCalledWith("connection-1", "APP_USER", true);
expect(mocks.listDatabases).not.toHaveBeenCalled();
});
it("honors the configured Dameng schema filter before the legacy database filter", async () => {
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING", "ARCHIVE"]);
const options = await fetchNamespaceOptionsForConnection("connection-1", {
db_type: "dameng",
database: "APP_USER",
visible_databases: ["APP_USER", "REPORTING"],
visible_schemas: { APP_USER: ["ARCHIVE"] },
});
expect(options).toEqual(["ARCHIVE"]);
});
it("preserves listDatabases and visible database filtering for other databases", async () => {
mocks.listDatabases.mockResolvedValue([{ name: "app" }, { name: "analytics" }, { name: "postgres" }]);
const options = await fetchNamespaceOptionsForConnection("connection-2", {
db_type: "postgres",
database: "app",
visible_databases: ["analytics"],
});
expect(options).toEqual(["analytics"]);
expect(mocks.listDatabases).toHaveBeenCalledWith("connection-2");
expect(mocks.listSchemas).not.toHaveBeenCalled();
});
it("preserves visible database filtering for MongoDB transfer options", () => {
expect(
databaseOptionsForConnection(["app", "analytics", "admin"], {
db_type: "mongodb",
visible_databases: ["analytics"],
}),
).toEqual(["analytics"]);
});
it("preserves visible database filtering for catalog-scoped transfer options", async () => {
mocks.listDorisCatalogDatabases.mockResolvedValue([{ name: "app" }, { name: "analytics" }]);
await expect(
fetchCatalogNamespaceOptions("connection-1", "hive", {
db_type: "starrocks",
visible_databases: ["analytics"],
}),
).resolves.toEqual(["analytics"]);
expect(mocks.listDorisCatalogDatabases).toHaveBeenCalledWith("connection-1", "hive");
});
it("propagates metadata loading errors", async () => {
const error = new Error("schema metadata failed");
mocks.listSchemas.mockRejectedValue(error);
await expect(
fetchNamespaceOptionsForConnection("connection-1", {
db_type: "dameng",
database: "APP_USER",
}),
).rejects.toBe(error);
});
it("keeps the SQL file target on the shared namespace loader", async () => {
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING"]);
await expect(
fetchSqlFileTargetOptions("connection-1", {
db_type: "dameng",
database: "APP_USER",
}),
).resolves.toEqual(["APP_USER", "REPORTING"]);
});
it("identifies only Dameng top-level options as schemas", () => {
expect(namespaceOptionsAreSchemas({ db_type: "dameng" })).toBe(true);
expect(namespaceOptionsAreSchemas({ db_type: "oracle" })).toBe(false);
expect(namespaceOptionsAreSchemas({ db_type: "postgres" })).toBe(false);
});
it("does not expand the global database options composable to Dameng schemas", async () => {
mocks.getConfig.mockReturnValue({ db_type: "dameng" });
mocks.listDatabases.mockResolvedValue([]);
const { databaseOptions, loadDatabaseOptions } = useDatabaseOptions();
await loadDatabaseOptions("connection-1");
expect(databaseOptions.value["connection-1"]).toEqual([]);
expect(mocks.listDatabases).toHaveBeenCalledWith("connection-1");
expect(mocks.listSchemas).not.toHaveBeenCalled();
});
});
describe("database options loader", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("uses the Redis database API", async () => {
mocks.getConfig.mockReturnValue({ db_type: "redis" });
mocks.redisListDatabases.mockResolvedValue([{ db: 0 }, { db: 1 }]);
const options = useDatabaseOptions();
await options.loadDatabaseOptions("connection-1");
expect(options.databaseOptions.value["connection-1"]).toEqual(["0", "1"]);
expect(mocks.redisListDatabases).toHaveBeenCalledWith("connection-1");
expect(mocks.listDatabases).not.toHaveBeenCalled();
});
it("uses the MongoDB database API", async () => {
mocks.getConfig.mockReturnValue({ db_type: "mongodb" });
mocks.mongoListDatabases.mockResolvedValue(["app", "analytics"]);
const options = useDatabaseOptions();
await options.loadDatabaseOptions("connection-1");
expect(options.databaseOptions.value["connection-1"]).toEqual(["app", "analytics"]);
expect(mocks.mongoListDatabases).toHaveBeenCalledWith("connection-1");
expect(mocks.listDatabases).not.toHaveBeenCalled();
});
});