/** * @file Two dashboards started at the same instant on a FRESH database must * both come up: server/db.js runs its probe-then-ALTER migrations under one * cross-process lock, so the second waits and then finds every column already * there instead of dying on "duplicate column name". * @author Nguyễn Ngọc Trí Vĩ */ const { it } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { spawn } = require("node:child_process"); const DB_MODULE = path.join(__dirname, "..", "db.js"); function openDb(dbPath, dataDir) { return new Promise((resolve) => { const child = spawn(process.execPath, ["-e", `require(${JSON.stringify(DB_MODULE)})`], { env: { ...process.env, DASHBOARD_DB_PATH: dbPath, DASHBOARD_DATA_DIR: dataDir }, stdio: ["ignore", "ignore", "pipe"], }); let err = ""; child.stderr.on("data", (d) => (err += d)); child.on("exit", (code) => resolve({ code, err })); }); } it("lets several processes open one fresh database at once", async () => { for (let round = 0; round < 3; round++) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "db-concurrent-")); try { const results = await Promise.all( Array.from({ length: 4 }, () => openDb(path.join(dir, "dashboard.db"), dir)) ); for (const r of results) assert.equal(r.code, 0, r.err.split("\n").slice(0, 6).join("\n")); } finally { fs.rmSync(dir, { recursive: true, force: true }); } } });