/** * @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ĩ */ 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(); } }); });