Files
AgentHub/server/__tests__/alert-rules-migration.test.js
T
nntrivi2001 4aae61297f feat: analytics parity — tool cost, date ranges, budgets, query explorer, replay, snapshots (v2.4.0)
Analytics parity phases 2–7 (docs/plans/analytics-parity/), built on
138e706 and ported onto v2.3.1:
- Cost by AI tool (?tool= on cost/analytics/stats/workflows, by_tool,
  Codex fast/priority and flex tiers, agenthub_cost_usd_total{tool,model})
- Date ranges (from/to) on cost/analytics/workflows; Analytics range select
  with Today / This week (Monday) / This month (1st) / 7d / 30d / 90d / All /
  Custom; workflow filters carried into workflow-stats.js (read worker)
- Budgets and cost alerts as alert rule types (alert_rules rebuilt without
  its rule_type CHECK), GET /api/budgets/status, rule preview, spend pace,
  status-bar budget chip; rules stay editable in place
- Query Explorer (/query, allowlisted sort, CSV/JSON export) and scheduled
  reports (report_definitions/report_runs, Settings → Reports)
- Session Replay tab and immutable snapshots (session_snapshots, /snapshot/:token)
- Remote Codex sync, push ingest with a hashed token, privacy redaction and
  ignored folders (Settings → Data), Cursor sessions (no cost)
- Read-only MCP server, statusline, Grafana/Prometheus files

Also: resizable sidebar, pet run/wave animations, the workspace home keeps a
half-typed prompt, and the pre-commit hook runs only the related tests for a
small commit.
2026-10-06 17:12:28 +07:00

70 lines
2.9 KiB
JavaScript

/**
* @file Regression test for the alert_rules rebuild that drops its 4-type
* `rule_type` CHECK: an existing DB keeps every rule and every fired alert
* (alert_events cascades on rule delete, so the rebuild must run with foreign
* keys off), and the new types can be stored afterwards.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
"use strict";
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 ROOT = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "alert-mig-")));
const DB_PATH = path.join(ROOT, "t.db");
process.env.DASHBOARD_DB_PATH = DB_PATH;
process.env.CLAUDE_HOME = path.join(ROOT, "claude");
process.env.DASHBOARD_DATA_DIR = path.join(ROOT, "data");
// The schema an older build created.
{
const Database = require("better-sqlite3");
const old = new Database(DB_PATH);
old.pragma("foreign_keys = ON");
old.exec(`
CREATE TABLE alert_rules (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
rule_type TEXT NOT NULL CHECK(rule_type IN ('event_pattern','inactivity','status_duration','token_threshold')),
config TEXT NOT NULL DEFAULT '{}',
enabled INTEGER NOT NULL DEFAULT 1,
cooldown_seconds INTEGER NOT NULL DEFAULT 300,
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'))
);
CREATE TABLE alert_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rule_id TEXT NOT NULL,
rule_name TEXT NOT NULL,
rule_type TEXT NOT NULL,
session_id TEXT,
agent_id TEXT,
message TEXT NOT NULL,
details TEXT,
triggered_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
acknowledged_at TEXT,
FOREIGN KEY (rule_id) REFERENCES alert_rules(id) ON DELETE CASCADE
);
INSERT INTO alert_rules (id, name, rule_type, config) VALUES ('r1', 'Old', 'inactivity', '{"minutes":5}');
INSERT INTO alert_events (rule_id, rule_name, rule_type, message) VALUES ('r1', 'Old', 'inactivity', 'fired');
`);
old.close();
}
const { db, stmts } = require("../db");
it("keeps rules and fired alerts, and accepts the new types", () => {
assert.equal(db.prepare("SELECT name FROM alert_rules WHERE id = 'r1'").get().name, "Old");
assert.equal(db.prepare("SELECT COUNT(*) c FROM alert_events WHERE rule_id = 'r1'").get().c, 1);
stmts.insertAlertRule.run("r2", "Budget", "budget", '{"period":"day","limit_usd":1}', 1, 0);
assert.equal(stmts.getAlertRule.get("r2").rule_type, "budget");
// The cascade still works after the rename.
stmts.deleteAlertRule.run("r1");
assert.equal(db.prepare("SELECT COUNT(*) c FROM alert_events").get().c, 0);
assert.equal(db.pragma("foreign_keys", { simple: true }), 1);
fs.rmSync(ROOT, { recursive: true, force: true });
});