/** * @file Scheduled reports: definition validation and CRUD behind the * same-origin guard, `lastSlot`/`reportWindow` in local time, a run writing * escaped HTML and JSON under /reports//, the download route * reading only what a run recorded, and the scheduler claiming each slot once. * @author Nguyễn Ngọc Trí Vĩ */ "use strict"; const { describe, it, before, after } = 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 http = require("node:http"); const ROOT = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "reports-"))); process.env.DASHBOARD_DB_PATH = path.join(ROOT, "t.db"); process.env.CLAUDE_HOME = path.join(ROOT, "claude"); process.env.DASHBOARD_DATA_DIR = path.join(ROOT, "data"); process.env.DASHBOARD_REMOTE_SYNC_MS = "0"; process.env.DASHBOARD_LIVENESS_PROBE = "0"; const { createApp } = require("../index"); const { db, stmts } = require("../db"); const reports = require("../lib/reports"); let server; let base; async function call(method, url, body, headers = {}) { const res = await fetch(`${base}${url}`, { method, headers: { "content-type": "application/json", ...headers }, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text(); let parsed = text; try { parsed = JSON.parse(text); } catch { /* a file */ } return { status: res.status, headers: res.headers, body: parsed }; } const DEF = { name: "Weekly &", window_days: 7, frequency: "weekly", hour: 8, weekday: 1, tz_offset: -420, formats: ["html", "json"], }; before(async () => { server = http.createServer(createApp()); await new Promise((r) => server.listen(0, "127.0.0.1", r)); base = `http://127.0.0.1:${server.address().port}`; db.prepare( "INSERT OR REPLACE INTO model_pricing (model_pattern, display_name, input_per_mtok, output_per_mtok, cache_read_per_mtok, cache_write_per_mtok) VALUES ('rep-model%', 'R', 1, 0, 0, 0)" ).run(); const day = new Date(Date.now() - 2 * 86_400_000).toISOString(); db.prepare( "INSERT INTO sessions (id, name, status, started_at, updated_at, tool) VALUES ('rs', 'rs', 'completed', ?, ?, 'claude')" ).run(day, day); stmts.setTokenUsage.run( "rs", "rep-model", "standard", "global", "standard", 2e6, 0, 0, 0, 0, 0, 0, 0 ); db.prepare( "INSERT INTO events (session_id, event_type, tool_name, created_at) VALUES ('rs', 'PreToolUse', '