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("falls back to the configured database when JDBC metadata returns no databases", () => { expect( databaseOptionsForConnection([], { db_type: "jdbc", database: "gbase_demo", }), ).toEqual(["gbase_demo"]); expect( databaseOptionsForConnection([], { db_type: "jdbc", database_info: { currentDatabase: "gbase_from_metadata" }, }), ).toEqual(["gbase_from_metadata"]); }); 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("expands Dameng schemas via listSchemas", async () => { mocks.getConfig.mockReturnValue({ db_type: "dameng" }); mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING", "SYS"]); const { databaseOptions, loadDatabaseOptions } = useDatabaseOptions(); await loadDatabaseOptions("connection-1"); // 达梦现在通过 listSchemas 获取 schema 列表,SYS 作为系统 schema 被过滤掉 expect(databaseOptions.value["connection-1"]).toEqual(["APP_USER", "REPORTING"]); expect(mocks.listSchemas).toHaveBeenCalledWith("connection-1", "", true); expect(mocks.listDatabases).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(); }); });