Files
AgentHub/server/__tests__/websocket-mine.test.js
T
nntrivi2001 c229b4b79b fix(pet): each user's pet tells only of their own sessions
An admin/analyst's pet counted every user's sessions (live, waiting, errors)
because their WebSocket carries everyone's. Server mode now marks their
session/agent/event frames `mine: true|false` (scope.broadcastAudience owners,
worked out once per broadcast), and GET /api/sessions?mine=1 lists only the
caller's own sessions (ownSessionWhere). The pet seeds from mine=1 and skips
frames marked mine: false. Developers and local mode are unchanged.
2026-11-03 09:01:57 +07:00

103 lines
3.5 KiB
JavaScript

/**
* @file websocket-mine.test.js
* @description Server mode broadcasts (server/websocket.js `broadcast`): an
* admin/analyst's session, agent and event frames say whether the session is
* their OWN (`mine`), a developer's and local mode's never carry it, and
* `GET /api/sessions?mine=1` (server/routes/sessions.js) lists only the
* caller's own sessions whatever their role — the pet counts only those.
* @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("node:path");
const fs = require("node:fs");
const os = require("node:os");
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "ws-mine-test-"));
process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db");
const { db } = require("../db");
const ws = require("../websocket");
const prevMode = process.env.AGENTHUB_MODE;
before(() => {
const lane = db.prepare("INSERT INTO lanes (cwd, tool, owner_id) VALUES (?, 'claude', ?)");
lane.run("/w/boss", 99); // id 1, the admin's
lane.run("/w/dev", 2); // id 2, a developer's
const s = db.prepare(
"INSERT INTO sessions (id, name, status, cwd, lane_id) VALUES (?, ?, 'active', '/w', ?)"
);
s.run("s-boss", "boss", 1);
s.run("s-dev", "dev", 2);
});
after(() => {
ws._setClientsForTest(null);
if (prevMode === undefined) delete process.env.AGENTHUB_MODE;
else process.env.AGENTHUB_MODE = prevMode;
fs.rmSync(TMP, { recursive: true, force: true });
});
function client(user) {
const got = [];
return { readyState: 1, user, got, send: (m) => got.push(JSON.parse(m)) };
}
describe("broadcast `mine`", () => {
it("marks an admin's frames by whose session it is; a developer gets only theirs, unmarked", () => {
process.env.AGENTHUB_MODE = "server";
const boss = client({ id: 99, username: "boss", role: "admin" });
const dev = client({ id: 2, username: "dev", role: "developer" });
ws._setClientsForTest(new Set([boss, dev]));
ws.broadcast("session_updated", { id: "s-boss", status: "active" });
ws.broadcast("new_event", { session_id: "s-dev", event_type: "PostToolUse" });
ws.broadcast("lane_update", { lane: { id: 2 } });
assert.deepEqual(
boss.got.map((m) => [m.type, m.mine]),
[
["session_updated", true],
["new_event", false],
["lane_update", undefined],
]
);
assert.deepEqual(
dev.got.map((m) => [m.type, m.mine]),
[
["new_event", undefined],
["lane_update", undefined],
]
);
});
it("adds nothing in local mode", () => {
delete process.env.AGENTHUB_MODE;
const me = client(null);
ws._setClientsForTest(new Set([me]));
ws.broadcast("session_updated", { id: "s-dev" });
assert.equal(me.got[0].mine, undefined);
});
});
describe("GET /api/sessions?mine=1", () => {
it("lists only the caller's own sessions, an admin's too", async () => {
const express = require("express");
const app = express();
app.use((req, _res, next) => {
req.user = { id: 99, username: "boss", role: "admin" };
next();
});
app.use("/api/sessions", require("../routes/sessions"));
const srv = app.listen(0);
try {
const base = `http://127.0.0.1:${srv.address().port}/api/sessions`;
const ids = async (qs) =>
(await (await fetch(base + qs)).json()).sessions.map((s) => s.id).sort();
assert.deepEqual(await ids("?mine=1"), ["s-boss"]);
assert.deepEqual(await ids(""), ["s-boss", "s-dev"]);
} finally {
srv.close();
}
});
});