112 lines
4.6 KiB
TypeScript
112 lines
4.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const appSource = readFileSync(new URL("../src/App.tsx", import.meta.url), "utf8");
|
|
const panelSource = readFileSync(
|
|
new URL("../src/panels/system/InterfaceCenterPanel.tsx", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const apiSource = readFileSync(new URL("../src/api.ts", import.meta.url), "utf8");
|
|
const composeSource = readFileSync(new URL("../../docker-compose.yml", import.meta.url), "utf8");
|
|
const serverComposeSource = readFileSync(
|
|
new URL("../../docker-compose.server.yml", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const dockerfileSource = readFileSync(new URL("../../Dockerfile", import.meta.url), "utf8");
|
|
const backupScriptSource = readFileSync(
|
|
new URL("../../scripts/backup_mysql_encrypted.sh", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const storageGuardSource = readFileSync(
|
|
new URL("../../docker/mysql-storage-guard.sh", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const dataCenterSource = readFileSync(
|
|
new URL("../src/panels/data-platform/DataCenterPanel.tsx", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const interfaceServiceSource = readFileSync(
|
|
new URL("../../app/data_platform/interface_service.py", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
test("Interface Center is available under System", () => {
|
|
assert.match(appSource, /key: "\/system\/interfaces"[\s\S]*label: "接口中心"/);
|
|
assert.match(appSource, /path="\/admin\/system\/interfaces"/);
|
|
assert.match(appSource, /<InterfaceCenterPanel \/>/);
|
|
});
|
|
|
|
test("Interface Center is designed for inbound server access", () => {
|
|
for (const label of ["DBeaver 管理接入", "选择服务器数据库", "连接参数", "生成密钥与数据库账号"]) {
|
|
assert.match(panelSource, new RegExp(label));
|
|
}
|
|
assert.match(panelSource, /SSH 加密隧道/);
|
|
assert.match(panelSource, /Main 页不要填写服务器公网 IP/);
|
|
assert.match(panelSource, /Show all databases/);
|
|
assert.doesNotMatch(panelSource, /JDBC URL/);
|
|
assert.doesNotMatch(panelSource, /测试连接/);
|
|
});
|
|
|
|
test("DBeaver access defaults to a loopback-only MySQL endpoint", () => {
|
|
assert.match(composeSource, /MYSQL_HOST_BIND:-127\.0\.0\.1/);
|
|
assert.match(composeSource, /--local-infile=OFF/);
|
|
assert.match(composeSource, /DATA_MYSQL_SSH_TUNNEL_REQUIRED/);
|
|
assert.match(interfaceServiceSource, /"mysql_publicly_bound": publicly_bound/);
|
|
assert.match(interfaceServiceSource, /"database_host"/);
|
|
assert.match(interfaceServiceSource, /"ssh_host"/);
|
|
});
|
|
|
|
test("production deployment fails closed and preserves recoverability", () => {
|
|
for (const setting of [
|
|
"--secure-file-priv=NULL",
|
|
"--log-bin=mysql-bin",
|
|
"--binlog-format=ROW",
|
|
"--sync-binlog=1",
|
|
"--innodb-flush-log-at-trx-commit=1",
|
|
]) {
|
|
assert.match(composeSource, new RegExp(setting.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
}
|
|
assert.match(serverComposeSource, /SECURITY_STRICT_MODE: "true"/);
|
|
assert.match(serverComposeSource, /API_BIND_HOST:-127\.0\.0\.1/);
|
|
assert.match(serverComposeSource, /必须设置至少 32 位 AUTH_SECRET/);
|
|
assert.match(serverComposeSource, /mysql-storage-guard/);
|
|
assert.match(serverComposeSource, /MYSQL_STORAGE_MOUNT:\?必须设置 MYSQL_STORAGE_MOUNT/);
|
|
assert.match(serverComposeSource, /MYSQL_DATA_DIR:\?必须设置 MYSQL_DATA_DIR/);
|
|
assert.match(serverComposeSource, /create_host_path: false/);
|
|
assert.match(serverComposeSource, /127\.0\.0\.1:\$\{MYSQL_PORT:-3307\}:3306/);
|
|
assert.match(storageGuardSource, /storage identity mismatch/);
|
|
assert.match(backupScriptSource, /拒绝将生产备份写入代码仓库/);
|
|
assert.match(backupScriptSource, /verify_mysql_backup\.sh/);
|
|
assert.match(backupScriptSource, /DATA_BACKUP_RETENTION_DAYS/);
|
|
assert.match(dockerfileSource, /USER appuser/);
|
|
assert.match(dataCenterSource, /安全只读模式/);
|
|
});
|
|
|
|
test("Interface Center keeps the DBeaver flow focused", () => {
|
|
for (const label of ["私钥不会上传", "一人一号、单库授权", "一键撤销", "复制全部连接参数"]) {
|
|
assert.match(panelSource, new RegExp(label));
|
|
}
|
|
for (const removedLabel of ["HTTPS API", "客户端与密钥", "权限策略", "调用日志"]) {
|
|
assert.doesNotMatch(panelSource, new RegExp(removedLabel));
|
|
}
|
|
});
|
|
|
|
test("frontend uses only the managed DBeaver endpoints on this page", () => {
|
|
for (const path of [
|
|
"/interface-center/summary",
|
|
"/interface-center/catalog",
|
|
"/interface-center/dbeaver-access",
|
|
]) {
|
|
assert.match(apiSource, new RegExp(path));
|
|
}
|
|
for (const unusedCall of [
|
|
"listInterfaceClients",
|
|
"listInterfaceCredentials",
|
|
"listInterfacePolicies",
|
|
"listInterfaceCallLogs",
|
|
]) {
|
|
assert.doesNotMatch(panelSource, new RegExp(unusedCall));
|
|
}
|
|
});
|