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 { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
import { createDatabase, type BotDatabase, type BotInstance, type PlayHistoryEntry } from "./database.js";
|
import { createDatabase, type BotDatabase, type BotInstance, type PlayHistoryEntry } from "./database.js";
|
||||||
|
import { createUserStore, GUEST_USER_ID } from "./users.js";
|
||||||
|
|
||||||
describe("database", () => {
|
describe("database", () => {
|
||||||
let botDb: BotDatabase;
|
let botDb: BotDatabase;
|
||||||
@@ -148,3 +152,28 @@ describe("database", () => {
|
|||||||
expect(botDb.getCustomAvatarPath("bot-1")).toBeNull();
|
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 Database from "better-sqlite3";
|
||||||
import { CAPABILITIES, BOTS_ALL } from "./permissions.js";
|
import { CAPABILITIES, BOTS_ALL } from "./permissions.js";
|
||||||
|
import { GUEST_USER_ID, GUEST_USERNAME } from "./users.js";
|
||||||
|
|
||||||
export interface PlayHistoryEntry {
|
export interface PlayHistoryEntry {
|
||||||
botId: string;
|
botId: string;
|
||||||
@@ -241,6 +242,19 @@ export function backfillMemberPermissions(db: Database.Database): void {
|
|||||||
tx();
|
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 {
|
export function createDatabase(dbPath: string): BotDatabase {
|
||||||
const db = new Database(dbPath);
|
const db = new Database(dbPath);
|
||||||
db.pragma("journal_mode = WAL");
|
db.pragma("journal_mode = WAL");
|
||||||
@@ -248,6 +262,7 @@ export function createDatabase(dbPath: string): BotDatabase {
|
|||||||
initTables(db);
|
initTables(db);
|
||||||
migrateSchema(db);
|
migrateSchema(db);
|
||||||
backfillMemberPermissions(db);
|
backfillMemberPermissions(db);
|
||||||
|
ensureGuestUser(db);
|
||||||
|
|
||||||
const insertHistory = db.prepare(`
|
const insertHistory = db.prepare(`
|
||||||
INSERT INTO play_history (botId, songId, songName, artist, album, platform, coverUrl)
|
INSERT INTO play_history (botId, songId, songName, artist, album, platform, coverUrl)
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ describe("guest sessions", () => {
|
|||||||
sessions = createSessionStore(botDb.db);
|
sessions = createSessionStore(botDb.db);
|
||||||
// Create the synthetic guest user row to satisfy the sessions FK.
|
// Create the synthetic guest user row to satisfy the sessions FK.
|
||||||
botDb.db
|
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());
|
.run(Date.now(), Date.now());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ describe("guest row exclusion", () => {
|
|||||||
await users.createUser("alice", "password123", "member");
|
await users.createUser("alice", "password123", "member");
|
||||||
// Insert the reserved guest row directly (mirrors the migration).
|
// Insert the reserved guest row directly (mirrors the migration).
|
||||||
botDb.db.prepare(
|
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());
|
).run(GUEST_USER_ID, GUEST_USERNAME, "!", Date.now(), Date.now());
|
||||||
|
|
||||||
expect(users.countUsers()).toBe(1); // alice only
|
expect(users.countUsers()).toBe(1); // alice only
|
||||||
|
|||||||
Reference in new issue
Block a user