/** * @file Verifies the legacy `lanes_new` rebuild in db.js (triggered when the * lanes table's stored SQL still contains the old single-column * `cwd TEXT NOT NULL UNIQUE`) drops that constraint in favor of the composite * `idx_lanes_cwd_tool` unique index, preserves existing rows, and recreates * idx_lanes_session / idx_lanes_slot. * * Regression coverage for the codex+claude coexistence feature: a cwd used to * back at most one lane; now one cwd can back a `claude` lane and a `codex` * lane at once, which requires dropping the old column-level UNIQUE. * * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, before, after } = require("node:test"); const assert = require("node:assert/strict"); const path = require("path"); const fs = require("fs"); const os = require("os"); const Database = require("better-sqlite3"); let TEST_DB; before(() => { TEST_DB = path.join(os.tmpdir(), `dashboard-lanes-legacy-${Date.now()}-${process.pid}.db`); process.env.DASHBOARD_DB_PATH = TEST_DB; // Hand-build a legacy lanes table using the original single-column UNIQUE // cwd, none of the columns added by later migrations (kind, source_repo, // base_branch, slug, tool, detected_*, slot, ports, active_feature_id). // This is the exact shape the "cwd TEXT NOT NULL UNIQUE" detector is // looking for. const raw = new Database(TEST_DB); raw.pragma("foreign_keys = OFF"); raw.exec(` CREATE TABLE lanes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL DEFAULT '', cwd TEXT NOT NULL UNIQUE, branch TEXT, pipeline TEXT NOT NULL DEFAULT 'default', session_id TEXT, run_id TEXT, stage TEXT NOT NULL DEFAULT 'idle', stage_since TEXT, status TEXT NOT NULL DEFAULT 'idle', gate_decision TEXT, ci_status TEXT, needs_action TEXT, links TEXT NOT NULL DEFAULT '{}', stages TEXT NOT NULL DEFAULT '{}', notes TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); `); raw .prepare("INSERT INTO lanes (title, cwd) VALUES (?, ?)") .run("legacy lane", "/tmp/legacy-lane-cwd"); raw.close(); }); after(() => { try { delete require.cache[require.resolve("../db")]; } catch {} for (const suffix of ["", "-wal", "-shm"]) { try { fs.unlinkSync(TEST_DB + suffix); } catch {} } }); describe("legacy lanes_new rebuild (pre-tool, single-column cwd UNIQUE DB)", () => { it("loads db.js against the legacy DB without throwing", () => { delete require.cache[require.resolve("../db")]; assert.doesNotThrow(() => require("../db")); }); it("preserves the pre-existing lane row and defaults tool to claude", () => { const { db } = require("../db"); const row = db.prepare("SELECT * FROM lanes WHERE cwd = ?").get("/tmp/legacy-lane-cwd"); assert.ok(row, "expected the legacy lane row to survive the rebuild"); assert.equal(row.title, "legacy lane"); assert.equal(row.tool, "claude"); }); it("keeps every column added before the rebuild, router_profile included", () => { const { db } = require("../db"); const cols = db .prepare("PRAGMA table_info(lanes)") .all() .map((c) => c.name); for (const name of ["router_profile", "needs_action_session"]) { assert.ok(cols.includes(name), `expected ${name} to survive the rebuild`); } }); it("recreates idx_lanes_session and idx_lanes_slot", () => { const { db } = require("../db"); for (const name of ["idx_lanes_session", "idx_lanes_slot"]) { const idx = db .prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?") .get(name); assert.ok(idx, `expected ${name} to exist after the rebuild`); } }); it("drops the single-column cwd UNIQUE, allowing a second lane at the same cwd on a different tool", () => { const { db } = require("../db"); const legacy = db.prepare("SELECT id FROM lanes WHERE cwd = ?").get("/tmp/legacy-lane-cwd"); assert.doesNotThrow(() => { db.prepare("INSERT INTO lanes (title, cwd, tool) VALUES (?, ?, ?)").run( "codex sibling", "/tmp/legacy-lane-cwd", "codex" ); }); const both = db .prepare("SELECT id, tool FROM lanes WHERE cwd = ? ORDER BY id") .all("/tmp/legacy-lane-cwd"); assert.equal(both.length, 2); assert.equal(both[0].id, legacy.id); assert.equal(both[0].tool, "claude"); assert.equal(both[1].tool, "codex"); }); it("still enforces uniqueness within the same (cwd, tool) pair", () => { const { db } = require("../db"); assert.throws(() => { db.prepare("INSERT INTO lanes (title, cwd, tool) VALUES (?, ?, ?)").run( "duplicate claude", "/tmp/legacy-lane-cwd", "claude" ); }, /UNIQUE/); }); });