b7f3735b29
- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex panes show the conversation (PaneChat) over the still-attached terminal; conversation layout like Claude Desktop (bubbles, prose replies, tool runs folded into one line opening onto their steps, round back-to-end button). - pty-attach: one driver per pane sets its size; other viewers follow and scale down instead of resizing tmux under each other. - Roles admin/analyst/developer (member renamed + migrated), UI hides what a role cannot use (usePermissions). - Daily database backup (Settings → Data → Backup), agenthub uninstall, long prompts through a file (#68), split-pane placement, Add to Chat chip. Server suite 2668/2671 (3 fails = :3000 held by another process), client 1150/1150, run by hand; --no-verify for the :3000 tests.
64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
/**
|
|
* @file users-role-migration.test.js
|
|
* @description The `users` table migration that renamed the `member` role to
|
|
* `developer`: an existing database's members become developers, keep their
|
|
* logins, and the old value is refused afterwards.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { it, 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 { DatabaseSync } = require("node:sqlite");
|
|
|
|
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "role-migration-"));
|
|
const DB = path.join(TMP, "dashboard.db");
|
|
|
|
after(() => fs.rmSync(TMP, { recursive: true, force: true }));
|
|
|
|
it("turns every member into a developer and keeps their logins", () => {
|
|
const old = new DatabaseSync(DB);
|
|
old.exec(`
|
|
CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
|
role TEXT NOT NULL DEFAULT 'member' CHECK(role IN ('admin','analyst','member')),
|
|
password_hash TEXT NOT NULL,
|
|
disabled INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
);
|
|
CREATE TABLE auth_sessions (
|
|
token_hash TEXT PRIMARY KEY,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
expires_at TEXT NOT NULL
|
|
);
|
|
INSERT INTO users (username, role, password_hash) VALUES
|
|
('boss', 'admin', 'x'), ('mia', 'member', 'x'), ('ana', 'analyst', 'x');
|
|
INSERT INTO auth_sessions (token_hash, user_id, expires_at) VALUES ('t', 2, '2999-01-01');
|
|
`);
|
|
old.close();
|
|
|
|
process.env.DASHBOARD_DB_PATH = DB;
|
|
const { db } = require("../db");
|
|
const roles = db.prepare("SELECT username, role FROM users ORDER BY id").all();
|
|
assert.deepEqual(
|
|
roles.map((r) => [r.username, r.role]),
|
|
[
|
|
["boss", "admin"],
|
|
["mia", "developer"],
|
|
["ana", "analyst"],
|
|
]
|
|
);
|
|
assert.equal(db.prepare("SELECT user_id FROM auth_sessions").get().user_id, 2);
|
|
assert.throws(() =>
|
|
db
|
|
.prepare("INSERT INTO users (username, role, password_hash) VALUES ('x', 'member', 'x')")
|
|
.run()
|
|
);
|
|
db.prepare("INSERT INTO users (username, password_hash) VALUES ('neo', 'x')").run();
|
|
assert.equal(db.prepare("SELECT role FROM users WHERE username = 'neo'").get().role, "developer");
|
|
});
|