fix(xugu): require database before connecting
This commit is contained in:
1 parent
95c17abfd1
commit
4a5dca63fb
16 files changed
+206
-3
No files matched your search
+20
@@ -0,0 +1,20 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("ConnectionDialog Xugu database requirement", () => {
|
||||
it("shows the existing required-database hint only for Xugu", () => {
|
||||
const placeholder = dialogSource.slice(dialogSource.indexOf("const databasePlaceholder = computed"), dialogSource.indexOf("const transportLayers"));
|
||||
expect(placeholder).toContain('if (form.value.db_type === "xugu") return t("connection.databasePlaceholderRequired")');
|
||||
expect(placeholder).toContain('if (form.value.db_type === "kingbase") return t("connection.databasePlaceholderRequired")');
|
||||
});
|
||||
|
||||
it("validates Xugu's effective database before connection testing or saving", () => {
|
||||
const submit = dialogSource.slice(dialogSource.indexOf("function connectionConfigForSubmit"), dialogSource.indexOf("function ", dialogSource.indexOf("function connectionConfigForSubmit") + 10));
|
||||
expect(submit).toContain('if (config.db_type === "xugu")');
|
||||
expect(submit).toContain("hasXuguConnectionDatabase(config.database, config.connection_string)");
|
||||
expect(submit).toContain('throw new Error(t("connection.xuguDatabaseRequired"))');
|
||||
expect(submit).toContain('if (config.db_type === "kingbase")');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { hasXuguConnectionDatabase, xuguDatabaseFromConnectionString } from "@/lib/connection/xuguDatabase";
|
||||
|
||||
describe("Xugu connection database validation", () => {
|
||||
it("accepts a database selected in the structured field", () => {
|
||||
expect(hasXuguConnectionDatabase(" XUGU_TEST_DB ", undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a missing or whitespace-only database", () => {
|
||||
expect(hasXuguConnectionDatabase(undefined, undefined)).toBe(false);
|
||||
expect(hasXuguConnectionDatabase(" \t ", undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it.each(["xugu://user:password@host:5138/XUGU_TEST_DB", "jdbc:xugu://host:5138/XUGU_TEST_DB"])("accepts a database encoded in a Xugu URL: %s", (url) => {
|
||||
expect(xuguDatabaseFromConnectionString(url)).toBe("XUGU_TEST_DB");
|
||||
expect(hasXuguConnectionDatabase(undefined, url)).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts a database in a native Xugu DSN, including quoted separators", () => {
|
||||
expect(xuguDatabaseFromConnectionString("IP=db.example;DB='shop;east';User=app")).toBe("shop;east");
|
||||
expect(xuguDatabaseFromConnectionString("IP=db.example;DB='sales''east';User=app")).toBe("sales'east");
|
||||
});
|
||||
|
||||
it("rejects a missing or empty database in a native Xugu DSN", () => {
|
||||
expect(xuguDatabaseFromConnectionString("IP=db.example;User=app")).toBeUndefined();
|
||||
expect(xuguDatabaseFromConnectionString("IP=db.example;DB=;User=app")).toBeUndefined();
|
||||
expect(xuguDatabaseFromConnectionString("IP=db.example;DB=' ';User=app")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not treat another database's URL as a Xugu database", () => {
|
||||
expect(xuguDatabaseFromConnectionString("postgres://user:password@host:5432/app")).toBeUndefined();
|
||||
expect(hasXuguConnectionDatabase(undefined, "postgres://user:password@host:5432/app")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { parseConnectionUrl } from "@/lib/connection/connectionUrl";
|
||||
|
||||
function splitXuguDsnParts(value: string): string[] {
|
||||
const parts: string[] = [];
|
||||
let current = "";
|
||||
let inQuotes = false;
|
||||
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
const character = value[index];
|
||||
if (character === "'") {
|
||||
current += character;
|
||||
if (inQuotes && value[index + 1] === "'") {
|
||||
current += value[index + 1];
|
||||
index += 1;
|
||||
} else {
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
} else if (character === ";" && !inQuotes) {
|
||||
parts.push(current);
|
||||
current = "";
|
||||
} else {
|
||||
current += character;
|
||||
}
|
||||
}
|
||||
|
||||
parts.push(current);
|
||||
return parts;
|
||||
}
|
||||
|
||||
function xuguDatabaseFromDsn(value: string): string | undefined {
|
||||
let database: string | undefined;
|
||||
for (const part of splitXuguDsnParts(value)) {
|
||||
const separator = part.indexOf("=");
|
||||
if (separator < 0 || part.slice(0, separator).trim().toLowerCase() !== "db") continue;
|
||||
|
||||
let candidate = part.slice(separator + 1).trim();
|
||||
if (candidate.startsWith("'") && candidate.endsWith("'")) {
|
||||
candidate = candidate.slice(1, -1).replaceAll("''", "'");
|
||||
}
|
||||
database = candidate.trim() || undefined;
|
||||
}
|
||||
return database;
|
||||
}
|
||||
|
||||
export function xuguDatabaseFromConnectionString(connectionString: string | undefined): string | undefined {
|
||||
const value = connectionString?.trim();
|
||||
if (!value) return undefined;
|
||||
|
||||
if (/^(?:ip|ips)\s*=/i.test(value)) {
|
||||
return xuguDatabaseFromDsn(value);
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parseConnectionUrl(value, "xugu");
|
||||
return parsed.dbType === "xugu" ? parsed.database?.trim() || undefined : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasXuguConnectionDatabase(database: string | undefined, connectionString: string | undefined): boolean {
|
||||
return !!database?.trim() || !!xuguDatabaseFromConnectionString(connectionString);
|
||||
}
|
||||
Reference in new issue
Block a user