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.
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @file Tests for editing time-limited introductory pricing via PUT /api/pricing.
|
||||
*
|
||||
* The Settings page lets users edit a model's introductory (promo) rates, not
|
||||
* just its standard rates. This suite verifies the route contract that backs
|
||||
* that UI, and that the mechanism is generic (works for any model pattern, not
|
||||
* just the seeded Sonnet 5 promo):
|
||||
*
|
||||
* 1. PUT with intro fields + a valid intro_until persists the intro rates.
|
||||
* 2. PUT that omits every intro field preserves an existing promo (backward
|
||||
* compatible with older clients that only send standard rates).
|
||||
* 3. PUT with an empty intro_until clears the promo AND zeroes the intro
|
||||
* rates so a stale value can't resurface later.
|
||||
* 4. A malformed intro_until is rejected with 400 and writes nothing.
|
||||
* 5. Editing standard rates never disturbs the intro block.
|
||||
*
|
||||
* It also covers the route's numeric-rate validation: any present rate field
|
||||
* must be a non-negative finite number, else the PUT is rejected with 400 and
|
||||
* nothing is written (NaN/negative rates would corrupt all cost math).
|
||||
*
|
||||
* @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");
|
||||
const http = require("http");
|
||||
|
||||
const TEST_DB = path.join(os.tmpdir(), `dashboard-intro-edit-${Date.now()}-${process.pid}.db`);
|
||||
process.env.DASHBOARD_DB_PATH = TEST_DB;
|
||||
|
||||
const { createApp, startServer } = require("../index");
|
||||
const { db, stmts } = require("../db");
|
||||
|
||||
let server;
|
||||
let BASE;
|
||||
|
||||
function fetch(urlPath, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(urlPath, BASE);
|
||||
const opts = {
|
||||
hostname: url.hostname,
|
||||
port: url.port,
|
||||
path: url.pathname + url.search,
|
||||
method: options.method || "GET",
|
||||
headers: { "Content-Type": "application/json", ...options.headers },
|
||||
};
|
||||
const req = http.request(opts, (res) => {
|
||||
let body = "";
|
||||
res.on("data", (c) => (body += c));
|
||||
res.on("end", () => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(body);
|
||||
} catch {
|
||||
parsed = body;
|
||||
}
|
||||
resolve({ status: res.statusCode, body: parsed });
|
||||
});
|
||||
});
|
||||
req.on("error", reject);
|
||||
if (options.body) req.write(JSON.stringify(options.body));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
const PATTERN = "test-promo-model%";
|
||||
|
||||
before(async () => {
|
||||
const app = createApp();
|
||||
server = await startServer(app, 0);
|
||||
BASE = `http://127.0.0.1:${server.address().port}`;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (server) server.close();
|
||||
if (db) db.close();
|
||||
for (const suffix of ["", "-wal", "-shm"]) {
|
||||
try {
|
||||
fs.unlinkSync(TEST_DB + suffix);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe("PUT /api/pricing — introductory rate editing", () => {
|
||||
it("persists intro rates when a valid intro_until is supplied", async () => {
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
input_per_mtok: 3,
|
||||
output_per_mtok: 15,
|
||||
intro_until: "2026-08-31",
|
||||
intro_input_per_mtok: 2,
|
||||
intro_output_per_mtok: 10,
|
||||
intro_cache_read_per_mtok: 0.2,
|
||||
intro_cache_write_per_mtok: 2.5,
|
||||
intro_cache_write_1h_per_mtok: 4,
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 200);
|
||||
const row = stmts.getPricing.get(PATTERN);
|
||||
assert.equal(row.intro_until, "2026-08-31");
|
||||
assert.equal(row.intro_input_per_mtok, 2);
|
||||
assert.equal(row.intro_output_per_mtok, 10);
|
||||
assert.equal(row.intro_cache_read_per_mtok, 0.2);
|
||||
assert.equal(row.intro_cache_write_per_mtok, 2.5);
|
||||
assert.equal(row.intro_cache_write_1h_per_mtok, 4);
|
||||
// Standard rates persisted too.
|
||||
assert.equal(row.input_per_mtok, 3);
|
||||
assert.equal(row.output_per_mtok, 15);
|
||||
});
|
||||
|
||||
it("preserves an existing promo when the PUT omits all intro fields", async () => {
|
||||
// Older client shape: standard rates only, no intro_* keys at all.
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model (renamed)",
|
||||
input_per_mtok: 4,
|
||||
output_per_mtok: 16,
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 200);
|
||||
const row = stmts.getPricing.get(PATTERN);
|
||||
// Standard rates updated…
|
||||
assert.equal(row.input_per_mtok, 4);
|
||||
assert.equal(row.display_name, "Test Promo Model (renamed)");
|
||||
// …but the promo is untouched.
|
||||
assert.equal(row.intro_until, "2026-08-31");
|
||||
assert.equal(row.intro_input_per_mtok, 2);
|
||||
assert.equal(row.intro_output_per_mtok, 10);
|
||||
});
|
||||
|
||||
it("rejects a malformed intro_until without mutating the row", async () => {
|
||||
const before = stmts.getPricing.get(PATTERN);
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
input_per_mtok: 4,
|
||||
output_per_mtok: 16,
|
||||
intro_until: "August 31",
|
||||
intro_input_per_mtok: 1,
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 400);
|
||||
const after = stmts.getPricing.get(PATTERN);
|
||||
assert.deepEqual(after, before);
|
||||
});
|
||||
|
||||
it("clears the promo and zeroes intro rates when intro_until is emptied", async () => {
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
input_per_mtok: 4,
|
||||
output_per_mtok: 16,
|
||||
intro_until: "",
|
||||
// Even though rates are still sent, an empty date clears them so a
|
||||
// re-added date later can't silently resurrect stale values.
|
||||
intro_input_per_mtok: 2,
|
||||
intro_output_per_mtok: 10,
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 200);
|
||||
const row = stmts.getPricing.get(PATTERN);
|
||||
assert.equal(row.intro_until, null);
|
||||
assert.equal(row.intro_input_per_mtok, 0);
|
||||
assert.equal(row.intro_output_per_mtok, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PUT /api/pricing — numeric rate validation", () => {
|
||||
it("rejects a non-numeric rate without mutating the row", async () => {
|
||||
const before = stmts.getPricing.get(PATTERN);
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
input_per_mtok: "abc",
|
||||
output_per_mtok: 16,
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 400);
|
||||
assert.equal(res.body.error.code, "INVALID_INPUT");
|
||||
assert.match(res.body.error.message, /input_per_mtok/);
|
||||
assert.deepEqual(stmts.getPricing.get(PATTERN), before);
|
||||
});
|
||||
|
||||
it("rejects a negative rate without mutating the row", async () => {
|
||||
const before = stmts.getPricing.get(PATTERN);
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
input_per_mtok: 4,
|
||||
output_per_mtok: -1,
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 400);
|
||||
assert.match(res.body.error.message, /output_per_mtok/);
|
||||
assert.deepEqual(stmts.getPricing.get(PATTERN), before);
|
||||
});
|
||||
|
||||
it("rejects NaN in intro and fast rate fields too", async () => {
|
||||
for (const field of ["fast_input_per_mtok", "intro_output_per_mtok"]) {
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
intro_until: "2026-12-31",
|
||||
[field]: "not-a-number",
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 400, `${field} should be rejected`);
|
||||
assert.match(res.body.error.message, new RegExp(field));
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts numeric strings by coercing them to numbers", async () => {
|
||||
const res = await fetch("/api/pricing", {
|
||||
method: "PUT",
|
||||
body: {
|
||||
model_pattern: PATTERN,
|
||||
display_name: "Test Promo Model",
|
||||
input_per_mtok: "4.5",
|
||||
output_per_mtok: "18",
|
||||
},
|
||||
});
|
||||
assert.equal(res.status, 200);
|
||||
const row = stmts.getPricing.get(PATTERN);
|
||||
assert.equal(row.input_per_mtok, 4.5);
|
||||
assert.equal(row.output_per_mtok, 18);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user