OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-identity / test / contract.test.js

74 lines · javascript ref:
// hyper-identity (autobase) must satisfy the shared contract, assertion for assertion.
//
// The contract lives in hyper-identity-core and is imported, never restated. hyper-identity2 runs
// the SAME function against autobee — that is what makes "both engines agree" a fact rather than
// two suites agreeing with themselves.
//
// This arm is also the CONTROL: it exercises the shipping path, so a divergence localises to the
// new binding rather than to the contract.

import test from 'brittle'
import Corestore from 'corestore'
import Hyperbee from 'hyperbee'
import path from '#node:path'
import os from '#node:os'
import crypto from 'hypercore-crypto'
import { contract } from 'hyper-identity-core/test/contract.js'
import { IdentityRegistry } from '../lib/auth/identity-registry.js'
import { adaptHost } from '../lib/auth/adapt-host.js'

async function open () {
  const dir = path.join(os.tmpdir(), 'hi-contract-' + crypto.randomBytes(8).toString('hex'))
  const store = new Corestore(dir)
  await store.ready()
  const db = new Hyperbee(store.get({ name: 'legacy-db' }), { keyEncoding: 'utf-8', valueEncoding: 'json' })
  await db.ready()
  const registry = new IdentityRegistry(store, { db })
  await registry.ready()
  return { registry, cleanup: async () => { await registry.close(); await db.close(); await store.close() } }
}

test('[autobase] the IdentityRegistry contract', async (t) => {
  const { registry, cleanup } = await open()
  t.teardown(cleanup)
  // ⛔ The package IS the engine. This cannot be an option that lies about what was constructed —
  // an earlier version of this suite had exactly that bug and compared autobase to autobase.
  t.is(registry.engine, 'autobase', 'the package reports what it is')
  await contract(t, registry)
})

// ⛔ The API break that fails SILENTLY, so it gets its own test.
//
// The core speaks `{ isIndexer }`. autobase spells it `{ indexer }`; autobee spells it
// `{ isIndexer }` and DEFAULTS IT TO TRUE. So an option lost in translation makes a plain writer
// here and an INDEXER there — the thing identity-registry warns deadlocks a manifest migration.
//
// An earlier version of this test asked the ENGINE whether a writer had become an indexer. Its own
// positive control disproved it: autobase parks a granted indexer in `pendingIndexers` until its
// core exists and acks, so a fresh key is in neither list and the accessor said "no" to everything,
// including the case it existed to catch. The defect is a translation; test the translation.
test('⛔ [autobase] adaptHost translates the core vocabulary, and pins the default', (t) => {
  const seen = []
  const host = adaptHost({
    addWriter: (key, opts) => { seen.push(['add', key, opts]) },
    removeWriter: (key) => { seen.push(['remove', key]) }
  })

  host.addWriter('k1', { isIndexer: true })
  host.addWriter('k2', { isIndexer: false })
  host.addWriter('k3', {})
  host.removeWriter('k1')

  t.alike(seen[0], ['add', 'k1', { indexer: true }], 'isIndexer:true becomes autobase indexer:true')
  t.alike(seen[1], ['add', 'k2', { indexer: false }], 'and false stays false')
  t.alike(seen[2], ['add', 'k3', { indexer: false }], 'an ABSENT flag is false, never an engine default')
  t.alike(seen[3], ['remove', 'k1'], 'removeWriter passes through')
  t.absent('isIndexer' in seen[0][2], 'the core spelling never reaches autobase')
})

test('adaptHost leaves a host with no writer controls alone', (t) => {
  const bare = { somethingElse: 1 }
  t.is(adaptHost(bare), bare, 'nothing to translate, nothing wrapped')
  t.is(adaptHost(null), null)
})