Files
AgentHub/server/__tests__/settings-import.test.js
T
nntrivi2001 198fbb7980 fix: event-loop freezes, plugin-install boot crash, tmux attach/scrollbar, Codex transcripts
- GET /api/workflows: one LEAD() window pass in a read worker (read-pool.js,
  workflow-stats.js); idx_events_session restored, idx_events_tool added
- compat-sqlite gains function/pluck/readonly so a plugin install without a
  better-sqlite3 build boots again; antigravity-usage uses db.js's Database
- install-hooks adds no checkout hooks while the agenthub plugin is enabled
  (events were doubled)
- terminal attach runs the live tmux server's own binary; the pane scrollbar
  gets a visible 2-column style, and claude panes start with
  CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 so tmux keeps history to scroll
- Codex sessions' conversation in GET /api/sessions/:id/transcript
- Codex/Antigravity sessions claimed by their workspace without hooks
- UI-audit follow-ups, docs and OpenAPI
2026-10-05 17:01:05 +07:00

124 lines
3.9 KiB
JavaScript

/**
* @file settings-import.test.js
* @description Restoring a backup: `GET /api/settings/export` writes every table
* the restore reads (with the format stamp), and `POST /api/settings/import`
* takes that file as a multipart upload, restores it without overwriting
* anything, refuses a file that is not an export without echoing its text, and
* refuses a cross-origin caller.
* @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 TEST_DB = path.join(os.tmpdir(), `settings-import-${Date.now()}-${process.pid}.db`);
process.env.DASHBOARD_DB_PATH = TEST_DB;
process.env.DASHBOARD_LIVENESS_PROBE = "0";
const { createApp, startServer } = require("../index");
const { db } = require("../db");
let server;
let BASE;
function upload(text, headers = {}) {
const form = new FormData();
form.append("file", new Blob([text], { type: "application/json" }), "backup.json");
return fetch(`${BASE}/api/settings/import`, { method: "POST", body: form, headers });
}
before(async () => {
server = await startServer(createApp(), 0);
BASE = `http://127.0.0.1:${server.address().port}`;
db.prepare("INSERT INTO sessions (id, name, status, started_at) VALUES (?, ?, ?, ?)").run(
"imp-1",
"Backed up",
"completed",
"2026-01-10T10:00:00.000Z"
);
db.prepare("INSERT INTO events (session_id, event_type, data) VALUES (?, ?, ?)").run(
"imp-1",
"Stop",
"{}"
);
});
after(() => {
if (server) server.close();
if (db) db.close();
for (const f of [TEST_DB, TEST_DB + "-wal", TEST_DB + "-shm"]) {
try {
fs.unlinkSync(f);
} catch {
// ignore
}
}
});
describe("backup and restore", () => {
let exported;
it("the export carries the format stamp and every table the restore reads", async () => {
const res = await fetch(`${BASE}/api/settings/export`);
assert.equal(res.status, 200);
exported = await res.text();
const bundle = JSON.parse(exported);
assert.equal(bundle.format, "agenthub-export");
assert.equal(bundle.version, 1);
for (const t of [
"sessions",
"agents",
"events",
"token_usage",
"workflows",
"dashboard_runs",
"alert_rules",
"model_pricing",
]) {
assert.ok(Array.isArray(bundle[t]), `missing ${t}`);
}
});
it("restores an uploaded export, skipping what is already there", async () => {
let res = await upload(exported);
let body = await res.json();
assert.equal(res.status, 200, JSON.stringify(body));
assert.equal(body.sessions_imported, 0);
assert.equal(body.sessions_skipped, 1);
db.prepare("DELETE FROM events WHERE session_id = 'imp-1'").run();
db.prepare("DELETE FROM sessions WHERE id = 'imp-1'").run();
res = await upload(exported);
body = await res.json();
assert.equal(res.status, 200, JSON.stringify(body));
assert.equal(body.sessions_imported, 1);
assert.equal(body.events, 1);
assert.ok(db.prepare("SELECT 1 FROM sessions WHERE id = 'imp-1'").get());
});
it("refuses a file that is not an export, without echoing its text", async () => {
const res = await upload("root:x:0:0:secret-line");
const body = await res.json();
assert.equal(res.status, 400);
assert.equal(body.error.code, "INVALID_FORMAT");
assert.doesNotMatch(JSON.stringify(body), /secret-line/);
const notBundle = await upload(JSON.stringify({ foo: 1 }));
assert.equal(notBundle.status, 400);
});
it("asks for a file", async () => {
const res = await fetch(`${BASE}/api/settings/import`, { method: "POST" });
assert.equal(res.status, 400);
assert.equal((await res.json()).error.code, "NO_FILE");
});
it("refuses a cross-origin caller", async () => {
const res = await upload(exported, { Origin: "https://evil.example" });
assert.equal(res.status, 403);
});
});