// RED/GREEN check for the SQLite-backed users/sessions store (dev-plan-3.md // T18). Run: node --test db.test.js const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { openDb, createUsersStore, createSessionsStore } = require("./db.js"); function tmpDbPath() { return path.join(fs.mkdtempSync(path.join(os.tmpdir(), "dressup-db-test-")), "data.sqlite"); } test("users: create/get/findByEmail round-trip, defaults filled in", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); const user = users.create({ email: "a@example.com", passwordHash: "hash1" }); assert.ok(user.id); assert.equal(user.email, "a@example.com"); assert.equal(user.emailVerified, false); assert.equal(user.role, "viewer"); assert.deepEqual(user.oauthProviders, []); assert.equal(user.creditBalance, 0); assert.deepEqual(users.get(user.id), user); assert.deepEqual(users.findByEmail("a@example.com"), user); assert.equal(users.findByEmail("nope@example.com"), null); assert.equal(users.get("nonexistent-id"), null); }); test("users: duplicate email is rejected as a conflict, not a second row", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); users.create({ email: "dup@example.com", passwordHash: "hash1" }); assert.throws(() => users.create({ email: "dup@example.com", passwordHash: "hash2" }), (err) => err.code === "EMAIL_TAKEN"); }); test("users: update() patches only whitelisted fields, ignores the rest", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); const user = users.create({ email: "b@example.com", passwordHash: "hash1" }); const updated = users.update(user.id, { emailVerified: true, nickname: "小美", id: "should-be-ignored", email: "should-be-ignored@example.com" }); assert.equal(updated.emailVerified, true); assert.equal(updated.nickname, "小美"); assert.equal(updated.id, user.id); assert.equal(updated.email, "b@example.com"); // unchanged original email, patch ignored }); test("users: legacyUid/ageConfirmed carry over from an existing anonymous cookie (T21's use case)", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); const ts = Date.now(); const user = users.create({ email: "c@example.com", passwordHash: "hash1", nickname: "老观众", legacyUid: "viewer-uid-123", ageConfirmed: true, ageConfirmedAt: ts, }); assert.equal(user.legacyUid, "viewer-uid-123"); assert.equal(user.ageConfirmed, true); assert.equal(user.ageConfirmedAt, ts); }); test("data survives reopening the same file path (simulated process restart)", () => { const dbPath = tmpDbPath(); const db1 = openDb(dbPath); const users1 = createUsersStore(db1); const created = users1.create({ email: "restart@example.com", passwordHash: "hash1" }); db1.close(); const db2 = openDb(dbPath); const users2 = createUsersStore(db2); assert.deepEqual(users2.get(created.id), created); }); test("sessions: create/findByToken round-trip", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); const sessions = createSessionsStore(db); const user = users.create({ email: "d@example.com", passwordHash: "hash1" }); const session = sessions.create({ userId: user.id, kind: "session", expiresAt: Date.now() + 100000 }); assert.ok(session.token); assert.equal(sessions.findByToken(session.token).id, session.id); assert.equal(sessions.findByToken("nonexistent-token"), null); }); test("sessions: an expired token is treated as absent and reaped", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); const sessions = createSessionsStore(db); const user = users.create({ email: "e@example.com", passwordHash: "hash1" }); const session = sessions.create({ userId: user.id, kind: "email_verify", expiresAt: Date.now() - 1000 }); assert.equal(sessions.findByToken(session.token), null); // reaped, not just filtered — a second lookup finds nothing to delete either assert.equal(sessions.findByToken(session.token), null); }); test("sessions: revoke() logs a session out immediately", () => { const db = openDb(tmpDbPath()); const users = createUsersStore(db); const sessions = createSessionsStore(db); const user = users.create({ email: "f@example.com", passwordHash: "hash1" }); const session = sessions.create({ userId: user.id, kind: "session", expiresAt: Date.now() + 100000 }); assert.ok(sessions.findByToken(session.token)); sessions.revoke(session.token); assert.equal(sessions.findByToken(session.token), null); });