OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / identity-federation.test.js

128 lines · javascript ref:
/**
 * Federation end-to-end tests for Autobase-backed IdentityRegistry
 *
 * Verifies that two registries can sync users via Autobase replication:
 * - Server A creates a registry, Server B joins with bootstrap key
 * - Users created on A are visible on B after sync (and vice versa)
 * - Identity lookups (by public key, by identity) work across peers
 */

import test from 'brittle'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import Corestore from 'corestore'
import { IdentityRegistry } from '../../lib/auth/identity-registry.js'

const tmpDir = path.join(os.tmpdir(), 'pear-git-test-federation-' + Date.now())
let storeIdx = 0

function nextStorePath () {
  return path.join(tmpDir, 'store-' + (storeIdx++))
}

test('setup: create temp dir', async (t) => {
  await fs.promises.mkdir(tmpDir, { recursive: true })
  t.pass('temp dir created')
})

test('two registries sync users via Autobase', async (t) => {
  // Server A creates registry (no bootstrapKey = new Autobase)
  const storeA = new Corestore(nextStorePath())
  const registryA = new IdentityRegistry({ corestore: storeA })
  await registryA.ready()

  // Server B joins with A's bootstrap key
  const storeB = new Corestore(nextStorePath())
  const registryB = new IdentityRegistry({
    corestore: storeB,
    bootstrapKey: registryA.bootstrapKey
  })
  await registryB.ready()

  // Replicate corestores (simulates driveSwarm connection)
  const s1 = storeA.replicate(true)
  const s2 = storeB.replicate(false)
  s1.pipe(s2).pipe(s1)

  // Server A adds Server B as a writer (federation handshake)
  await registryA.addWriter(registryB.localKey)

  // Server A creates a user
  const userA = await registryA.createUser({ name: 'Alice' })
  t.ok(userA.userId, 'Server A created user')

  // Server B should see it after sync
  await registryB.sync({ timeout: 3000 })
  const foundOnB = await registryB.getUser(userA.userId)
  t.ok(foundOnB, 'Server B sees user created on Server A')
  t.is(foundOnB.name, 'Alice')

  // Server B creates a user (now writable after addWriter)
  const userB = await registryB.createUser({ name: 'Bob' })
  t.ok(userB.userId, 'Server B created user')

  // Server A should see it after sync
  await registryA.sync({ timeout: 3000 })
  const foundOnA = await registryA.getUser(userB.userId)
  t.ok(foundOnA, 'Server A sees user created on Server B')
  t.is(foundOnA.name, 'Bob')

  // Both see all users
  const usersA = await registryA.listAllUsers()
  const usersB = await registryB.listAllUsers()
  t.is(usersA.length, 2, 'Server A has 2 users')
  t.is(usersB.length, 2, 'Server B has 2 users')

  // Cleanup
  s1.destroy()
  s2.destroy()
  await registryA.close()
  await registryB.close()
})

test('identity created on A is resolvable on B', async (t) => {
  const storeA = new Corestore(nextStorePath())
  const registryA = new IdentityRegistry({ corestore: storeA })
  await registryA.ready()

  const storeB = new Corestore(nextStorePath())
  const registryB = new IdentityRegistry({
    corestore: storeB,
    bootstrapKey: registryA.bootstrapKey
  })
  await registryB.ready()

  const s1 = storeA.replicate(true)
  const s2 = storeB.replicate(false)
  s1.pipe(s2).pipe(s1)

  // A creates user + web identity
  const { user, identity } = await registryA.autoRegister({
    identityId: 'web-pubkey-alice-123',
    method: 'web-identity',
    name: 'Alice',
    credentials: { publicKey: 'web-pubkey-alice-123' }
  })

  // B should resolve identity by public key
  await registryB.sync({ timeout: 3000 })
  const found = await registryB.getIdentityByPublicKey('web-pubkey-alice-123')
  t.ok(found, 'Server B resolves identity by public key')
  t.is(found.identityId, 'web-pubkey-alice-123')

  const foundUser = await registryB.getUserByIdentity('web-pubkey-alice-123')
  t.ok(foundUser, 'Server B resolves user by identity')
  t.is(foundUser.name, 'Alice')

  s1.destroy()
  s2.destroy()
  await registryA.close()
  await registryB.close()
})

test('cleanup: remove temp dir', async (t) => {
  await fs.promises.rm(tmpDir, { recursive: true, force: true })
  t.pass('temp dir removed')
})