Files
registry/test/duckdbMigration.test.js
jif2.zhang 6bbd4bc471
build-and-deploy / build-and-deploy (push) Successful in 37s
feat: 持久化从 JSON 迁移到 DuckDB,启动自动迁移存量数据并全面异步化
- 新增 src/persistence/duckdbAdapter:与 jsonAdapter 同接口的异步适配器,
  数据存于 data/registry.duckdb,首次启动自动从 JSON 迁移并备份 json-backup-<ts>
- 全部服务层(project/config/user/token)与路由改为 async/await
- MCP 工具与鉴权同步异步化,系统 mode 读写迁移至 DuckDB
- 服务启动流程:init DB -> 自动迁移 -> 确保默认管理员 -> listen
- 新增 DuckDB 往返/迁移测试,测试目录隔离 REGISTRY_DATA_DIR
2026-09-14 10:35:38 +08:00

76 lines
3.2 KiB
JavaScript

/**
* JSON -> DuckDB 自动迁移测试
* 独立进程运行:模块在 require 时读取 REGISTRY_DATA_DIR,需在 require 前注入种子数据目录
* 运行:npm test
*/
const test = require("node:test");
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
// 构造一个含 JSON 存量数据的种子目录,并设置为数据目录
const seedDir = path.join(os.tmpdir(), "registry-seed-" + Date.now());
fs.mkdirSync(seedDir, { recursive: true });
fs.writeFileSync(path.join(seedDir, "projects.json"), JSON.stringify([
{ id: "proj-mig-1", name: "迁移项目A", enableIdc: false },
]));
fs.writeFileSync(path.join(seedDir, "users.json"), JSON.stringify([
{ id: "u-mig", username: "migrator", displayName: "迁移用户", role: "user", enabled: true },
]));
fs.writeFileSync(path.join(seedDir, "api-tokens.json"), JSON.stringify([
{ id: "t-mig", name: "迁移Token", value: "mig-secret", enabled: true },
]));
fs.writeFileSync(path.join(seedDir, "system.json"), JSON.stringify({ mode: "plan" }));
fs.writeFileSync(path.join(seedDir, "mcp.json"), JSON.stringify({ enabled: true, token: "mcp-token" }));
const configsDir = path.join(seedDir, "configs");
fs.mkdirSync(configsDir, { recursive: true });
fs.writeFileSync(path.join(configsDir, "proj-mig-1.json"), JSON.stringify([
{ id: "cfg-mig", key: "db.host", value: "10.0.0.1", type: "string" },
]));
process.env.REGISTRY_DATA_DIR = seedDir;
const duckdbAdapter = require("../src/persistence/duckdbAdapter");
test("自动迁移全部 JSON 存量到 DuckDB,数据无损且备份 JSON", async () => {
await duckdbAdapter.init();
const projects = await duckdbAdapter.loadProjects();
assert.strictEqual(projects.length, 1);
assert.strictEqual(projects[0].name, "迁移项目A");
assert.strictEqual(projects[0].id, "proj-mig-1");
const users = await duckdbAdapter.loadUsers();
assert.strictEqual(users[0].username, "migrator");
const tokens = await duckdbAdapter.loadApiTokens();
assert.strictEqual(tokens[0].value, "mig-secret");
const system = await duckdbAdapter.loadSystem();
assert.strictEqual(system.mode, "plan");
const mcp = await duckdbAdapter.loadMcpConfig();
assert.strictEqual(mcp.token, "mcp-token");
const configs = await duckdbAdapter.loadConfigs("proj-mig-1");
assert.strictEqual(configs.length, 1);
assert.strictEqual(configs[0].value, "10.0.0.1");
// projectId 不应被注入进记录本体
assert.strictEqual("projectId" in configs[0], false);
// 数据迁移后应生成 DB 文件
assert.ok(fs.existsSync(path.join(seedDir, "registry.duckdb")), "应生成 registry.duckdb");
// JSON 源文件应被备份移除(防止二次导入)
assert.ok(!fs.existsSync(path.join(seedDir, "projects.json")), "迁移后 projects.json 应已移走");
const backupDirs = fs.readdirSync(seedDir).filter((n) => n.startsWith("json-backup-"));
assert.strictEqual(backupDirs.length, 1, "应生成 json-backup-<ts> 目录");
});
test("再次初始化(DB 已存在)不重复迁移、不报错", async () => {
const before = await duckdbAdapter.loadProjects();
await duckdbAdapter.init();
const after = await duckdbAdapter.loadProjects();
assert.deepStrictEqual(after, before);
});