import { test } from 'node:test'; import assert from 'node:assert/strict'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { rm, readFile } from 'node:fs/promises'; // Isolated users file + a known secret BEFORE importing the module (it reads env // at import time). No Supabase needed — this is the DB-less path. const USERS = join(tmpdir(), `cms-users-${process.pid}.json`); process.env.USERS_FILE = USERS; process.env.JWT_SECRET ||= 'test-secret-xyz'; const local = await import('../src/auth-local.js'); const { verify } = await import('hono/jwt'); test.after(async () => { await rm(USERS, { force: true }); }); test('create → list → login mints a GoTrue-shaped, verifiable token', async () => { const id = await local.createUser({ email: 'Karim@Example.com', password: 'hunter2', role: 'admin' }); assert.ok(id); const list = await local.listUsers(['someone@else.com']); assert.equal(list.length, 1); assert.equal(list[0].email, 'Karim@Example.com'); assert.equal(list[0].role, 'admin'); // wrong password rejected, right password accepted (case-insensitive email) assert.equal(await local.login('karim@example.com', 'nope'), null); const res = await local.login('karim@example.com', 'hunter2'); assert.ok(res?.access_token); assert.equal(res.user.email, 'Karim@Example.com'); const claims = await verify(res.access_token, process.env.JWT_SECRET, 'HS256'); assert.equal(claims.sub, id); assert.equal(claims.email, 'Karim@Example.com'); assert.equal(claims.app_metadata.role, 'admin'); // same shape auth.js/roleOf read assert.ok(claims.exp > Math.floor(Date.now() / 1000)); }); test('passwords are bcrypt-hashed at rest (never plaintext)', async () => { const raw = JSON.parse(await readFile(USERS, 'utf8')); assert.match(raw[0].password, /^\$2[aby]\$/); // bcrypt hash prefix assert.notEqual(raw[0].password, 'hunter2'); }); test('countByRole reflects roles; fixed-admin email forced to admin', async () => { await local.createUser({ email: 'red@x.com', password: 'pw', role: 'editor' }); const counts = await local.countByRole([]); assert.equal(counts.total, 2); assert.equal(counts.admin, 1); assert.equal(counts.editor, 1); // an env-admin email overrides the stored role in the listing const forced = await local.listUsers(['red@x.com']); assert.equal(forced.find((u) => u.email === 'red@x.com').role, 'admin'); }); test('update + delete', async () => { const id = await local.createUser({ email: 'tmp@x.com', password: 'pw' }); await local.updateUser(id, { role: 'editor' }); assert.equal((await local.listUsers([])).find((u) => u.id === id).role, 'editor'); await local.deleteUser(id); assert.equal((await local.listUsers([])).some((u) => u.id === id), false); await assert.rejects(() => local.deleteUser('nope'), /nicht gefunden/); }); test('duplicate email rejected', async () => { await assert.rejects(() => local.createUser({ email: 'red@x.com', password: 'pw' }), /existiert bereits/); });