import test from 'brittle'
import { IdentityRegistry } from '../../lib/auth/identity-registry.js'
import { MockRegistry } from '../helpers/fixtures.js'
test('IdentityRegistry - create user with defaults', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const user = await reg.createUser()
t.ok(user.userId, 'has userId')
t.is(user.name, 'Anonymous', 'default name')
t.alike(user.globalPermissions, ['read'], 'default read perms')
t.is(user.notebooks.length, 1, 'has default notebook entry')
t.is(user.notebooks[0].name, '*', 'wildcard notebook')
t.alike(user.notebooks[0].permissions, ['read'], 'read perms on notebook')
t.alike(user.identities, [], 'no identities yet')
})
test('IdentityRegistry - create user with custom data', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const user = await reg.createUser({
name: 'Alice',
email: 'alice@example.com',
permissions: ['read', 'write', 'admin'],
notebooks: [{ name: 'private', permissions: ['read', 'write'] }]
})
t.is(user.name, 'Alice')
t.is(user.email, 'alice@example.com')
t.alike(user.globalPermissions, ['read', 'write', 'admin'])
t.is(user.notebooks[0].name, 'private')
})
test('IdentityRegistry - create and link identity', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const user = await reg.createUser({ name: 'Bob' })
const identity = await reg.createIdentity({
identityId: 'pk_abc123',
method: 'web-identity',
credentials: { publicKey: 'pk_abc123' }
})
t.ok(identity.identityId, 'identity created')
t.is(identity.userId, null, 'not linked yet')
const result = await reg.linkIdentity('pk_abc123', user.userId)
t.is(result.identity.userId, user.userId, 'identity linked to user')
t.ok(result.user.identities.includes('pk_abc123'), 'user has identity')
})
test('IdentityRegistry - autoRegister', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const { user, identity } = await reg.autoRegister({
identityId: 'pk_auto123',
method: 'web-identity',
credentials: { publicKey: 'pk_auto123' }
})
t.ok(user.userId, 'user created')
t.is(identity.userId, user.userId, 'identity linked')
t.ok(user.identities.includes('pk_auto123'), 'identity in user list')
})
test('IdentityRegistry - getUserByIdentity', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const { user } = await reg.autoRegister({
identityId: 'pk_lookup',
method: 'web-identity',
credentials: { publicKey: 'pk_lookup' }
})
const found = await reg.getUserByIdentity('pk_lookup')
t.ok(found, 'found user')
t.is(found.userId, user.userId, 'correct user')
})
test('IdentityRegistry - getIdentityByPublicKey', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
await reg.autoRegister({
identityId: 'pk_pubkey',
method: 'web-identity',
credentials: { publicKey: 'pk_pubkey' }
})
const identity = await reg.getIdentityByPublicKey('pk_pubkey')
t.ok(identity, 'found identity')
t.is(identity.identityId, 'pk_pubkey')
})
test('IdentityRegistry - upgrade notebook permissions', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const user = await reg.createUser({ name: 'Writer' })
const updated = await reg.upgradePermissions(user.userId, 'my-notes', ['write'])
const nb = updated.notebooks.find(r => r.name === 'my-notes')
t.ok(nb, 'notebook permission entry created')
t.ok(nb.permissions.includes('write'), 'write permission added')
})
test('IdentityRegistry - revoke and restore identity', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const { identity } = await reg.autoRegister({
identityId: 'pk_revoke',
method: 'web-identity',
credentials: { publicKey: 'pk_revoke' }
})
// Revoke
const revoked = await reg.revokeIdentity('pk_revoke', { reason: 'test' })
t.ok(revoked.revoked, 'identity marked revoked')
const isRevoked = await reg.isRevoked('pk_revoke')
t.ok(isRevoked, 'isRevoked returns true')
// Restore
const restored = await reg.restoreIdentity('pk_revoke')
t.absent(restored.revoked, 'revoked flag removed')
const isStillRevoked = await reg.isRevoked('pk_revoke')
t.absent(isStillRevoked, 'isRevoked returns false after restore')
})
test('IdentityRegistry - merge accounts transfers notebooks', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const primary = await reg.createUser({
name: 'Primary',
notebooks: [{ name: 'work', permissions: ['read', 'write'] }]
})
const secondary = await reg.createUser({
name: 'Secondary',
notebooks: [{ name: 'personal', permissions: ['read'] }]
})
await reg.createIdentity({
identityId: 'pk_secondary',
method: 'web-identity',
userId: secondary.userId,
credentials: { publicKey: 'pk_secondary' }
})
secondary.identities.push('pk_secondary')
await db.put(`user:${secondary.userId}`, secondary)
const result = await reg.mergeAccounts(primary.userId, secondary.userId)
t.is(result.stats.identitiesTransferred, 1, 'identity transferred')
t.ok(result.primary.notebooks.find(r => r.name === 'personal'), 'secondary notebook merged')
t.alike(result.merged.notebooks, [], 'secondary notebooks cleared')
})
test('IdentityRegistry - deactivateUser is a soft-delete (preserves records, blocks auth)', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const { user } = await reg.autoRegister({
identityId: 'pk_deact',
method: 'web-identity',
credentials: { publicKey: 'pk_deact' }
})
await reg.deactivateUser(user.userId, { by: 'admin-1', reason: 'spam' })
// User record is PRESERVED — never hard-deleted
const after = await reg.getUser(user.userId)
t.ok(after, 'user record still exists (not hard-deleted)')
t.is(after.deactivated, true, 'user flagged deactivated')
t.is(after.deactivatedBy, 'admin-1', 'records who deactivated it')
// Identity is revoked → providers will reject auth; identity row preserved
t.ok(await reg.isRevoked('pk_deact'), 'linked identity is revoked (auth blocked)')
t.ok(await reg.getIdentity('pk_deact'), 'identity record still exists')
})
test('IdentityRegistry - reactivateUser reverses a soft-delete', async (t) => {
const db = new MockRegistry()
const reg = new IdentityRegistry(db)
const { user } = await reg.autoRegister({
identityId: 'pk_react',
method: 'web-identity',
credentials: { publicKey: 'pk_react' }
})
await reg.deactivateUser(user.userId, { by: 'admin-1' })
await reg.reactivateUser(user.userId)
const after = await reg.getUser(user.userId)
t.absent(after.deactivated, 'deactivated flag cleared')
t.absent(await reg.isRevoked('pk_react'), 'identity restored (no longer revoked)')
})