Files
Claude-Code-Monitor/server/__tests__/sessions-transcript-path-migration.test.js
nntrivi2001 57dc91585d feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 14:39:03 +07:00

115 lines
3.6 KiB
JavaScript

/**
* @file Verifies the idempotent ALTER TABLE migration adds a transcript_path
* column to sessions and that a fresh db.js load on an existing DB does not
* throw or duplicate the column.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
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");
let TEST_DB;
before(() => {
TEST_DB = path.join(os.tmpdir(), `dashboard-tp-migration-${Date.now()}-${process.pid}.db`);
process.env.DASHBOARD_DB_PATH = TEST_DB;
});
after(() => {
try {
fs.unlinkSync(TEST_DB);
} catch {}
try {
fs.unlinkSync(TEST_DB + "-wal");
} catch {}
try {
fs.unlinkSync(TEST_DB + "-shm");
} catch {}
});
describe("sessions.transcript_path migration", () => {
it("adds transcript_path column on first load", () => {
delete require.cache[require.resolve("../db")];
const { db } = require("../db");
const cols = db.prepare("PRAGMA table_info(sessions)").all();
const names = cols.map((c) => c.name);
assert.ok(
names.includes("transcript_path"),
`expected transcript_path; got: ${names.join(",")}`
);
});
it("is idempotent — loading db.js a second time does not throw", () => {
delete require.cache[require.resolve("../db")];
assert.doesNotThrow(() => require("../db"));
});
it("transcript_path is nullable and accepts an UPDATE", () => {
const { db, stmts } = require("../db");
stmts.insertSession.run("s-tp-1", "name", "active", "/tmp/proj", "claude", null);
db.prepare("UPDATE sessions SET transcript_path = ? WHERE id = ?").run(
"/tmp/foo.jsonl",
"s-tp-1"
);
const row = db.prepare("SELECT transcript_path FROM sessions WHERE id = ?").get("s-tp-1");
assert.equal(row.transcript_path, "/tmp/foo.jsonl");
});
});
describe("hooks ingestion populates sessions.transcript_path", () => {
it("sets transcript_path on first event that carries it", async () => {
delete require.cache[require.resolve("../db")];
const { db, stmts } = require("../db");
// Pre-create a session without transcript_path (simulate legacy state)
stmts.insertSession.run("s-hook-1", "n", "active", "/tmp/proj", "claude", null);
let row = db.prepare("SELECT transcript_path FROM sessions WHERE id = ?").get("s-hook-1");
assert.equal(row.transcript_path, null);
// Spin up app and POST a hook event with transcript_path
delete require.cache[require.resolve("../index")];
const { createApp, startServer } = require("../index");
const app = createApp();
const server = await startServer(app, 0);
const port = server.address().port;
const payload = JSON.stringify({
hook_type: "PostToolUse",
data: {
session_id: "s-hook-1",
transcript_path: "/tmp/somewhere/session.jsonl",
cwd: "/tmp/proj",
},
});
await new Promise((resolve, reject) => {
const http = require("http");
const req = http.request(
{
hostname: "127.0.0.1",
port,
path: "/api/hooks/event",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload),
},
},
(res) => {
res.resume();
res.once("end", resolve);
}
);
req.on("error", reject);
req.write(payload);
req.end();
});
server.close();
row = db.prepare("SELECT transcript_path FROM sessions WHERE id = ?").get("s-hook-1");
assert.equal(row.transcript_path, "/tmp/somewhere/session.jsonl");
});
});