mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
feat(db): seed reserved guest principal idempotently
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c9a0719128
commit
271504eec1
4 files changed
+46
-2
No files matched your search
@@ -1,5 +1,9 @@
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { createDatabase, type BotDatabase, type BotInstance, type PlayHistoryEntry } from "./database.js";
|
||||
import { createUserStore, GUEST_USER_ID } from "./users.js";
|
||||
|
||||
describe("database", () => {
|
||||
let botDb: BotDatabase;
|
||||
@@ -148,3 +152,28 @@ describe("database", () => {
|
||||
expect(botDb.getCustomAvatarPath("bot-1")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("guest principal migration", () => {
|
||||
it("creates exactly one reserved guest row, idempotently", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "tsmb-db-"));
|
||||
const p = join(dir, "t.db");
|
||||
const a = createDatabase(p); a.db.close();
|
||||
const b = createDatabase(p); // run again — must not duplicate
|
||||
const row = b.db.prepare("SELECT id, role FROM users WHERE id = ?").get(GUEST_USER_ID) as { id: string; role: string } | undefined;
|
||||
expect(row?.role).toBe("guest");
|
||||
const n = (b.db.prepare("SELECT COUNT(*) AS n FROM users WHERE role='guest'").get() as { n: number }).n;
|
||||
expect(n).toBe(1);
|
||||
b.db.close();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("guest row does not break first-run detection (countUsers excludes it)", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "tsmb-db2-"));
|
||||
const p = join(dir, "t.db");
|
||||
const d = createDatabase(p);
|
||||
const users = createUserStore(d.db);
|
||||
expect(users.countUsers()).toBe(0); // guest excluded → still needs setup
|
||||
d.db.close();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { CAPABILITIES, BOTS_ALL } from "./permissions.js";
|
||||
import { GUEST_USER_ID, GUEST_USERNAME } from "./users.js";
|
||||
|
||||
export interface PlayHistoryEntry {
|
||||
botId: string;
|
||||
@@ -241,6 +242,19 @@ export function backfillMemberPermissions(db: Database.Database): void {
|
||||
tx();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the reserved guest principal exists. Idempotent via the PK on
|
||||
* `users.id`. This row only backs login-less guest sessions; it is excluded
|
||||
* from countUsers()/listUsers() so it never interferes with first-run setup
|
||||
* or the user-management UI, and holds an unusable password hash.
|
||||
*/
|
||||
export function ensureGuestUser(db: Database.Database): void {
|
||||
const now = Date.now();
|
||||
db.prepare(
|
||||
"INSERT OR IGNORE INTO users (id, username, passwordHash, createdAt, updatedAt, role) VALUES (?, ?, '!', ?, ?, 'guest')"
|
||||
).run(GUEST_USER_ID, GUEST_USERNAME, now, now);
|
||||
}
|
||||
|
||||
export function createDatabase(dbPath: string): BotDatabase {
|
||||
const db = new Database(dbPath);
|
||||
db.pragma("journal_mode = WAL");
|
||||
@@ -248,6 +262,7 @@ export function createDatabase(dbPath: string): BotDatabase {
|
||||
initTables(db);
|
||||
migrateSchema(db);
|
||||
backfillMemberPermissions(db);
|
||||
ensureGuestUser(db);
|
||||
|
||||
const insertHistory = db.prepare(`
|
||||
INSERT INTO play_history (botId, songId, songName, artist, album, platform, coverUrl)
|
||||
|
||||
@@ -136,7 +136,7 @@ describe("guest sessions", () => {
|
||||
sessions = createSessionStore(botDb.db);
|
||||
// Create the synthetic guest user row to satisfy the sessions FK.
|
||||
botDb.db
|
||||
.prepare("INSERT INTO users (id, username, passwordHash, createdAt, updatedAt, role) VALUES ('__guest__','游客','!',?,?, 'guest')")
|
||||
.prepare("INSERT OR IGNORE INTO users (id, username, passwordHash, createdAt, updatedAt, role) VALUES ('__guest__','游客','!',?,?, 'guest')")
|
||||
.run(Date.now(), Date.now());
|
||||
});
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ describe("guest row exclusion", () => {
|
||||
await users.createUser("alice", "password123", "member");
|
||||
// Insert the reserved guest row directly (mirrors the migration).
|
||||
botDb.db.prepare(
|
||||
"INSERT INTO users (id, username, passwordHash, createdAt, updatedAt, role) VALUES (?, ?, ?, ?, ?, 'guest')"
|
||||
"INSERT OR IGNORE INTO users (id, username, passwordHash, createdAt, updatedAt, role) VALUES (?, ?, ?, ?, ?, 'guest')"
|
||||
).run(GUEST_USER_ID, GUEST_USERNAME, "!", Date.now(), Date.now());
|
||||
|
||||
expect(users.countUsers()).toBe(1); // alice only
|
||||
|
||||
Reference in new issue
Block a user