OverviewFilesCommitsIssuesDocsReleasesPeers

pgh / test / release-registry-replicate.test.js

163 lines · javascript ref:
/**
 * ReleaseRegistry replication tests
 *
 * Proves two things:
 *   1. A reader on a separate corestore can bootstrap by the writer's
 *      autobase key and see entries that were already written
 *   2. After the writer appends a new entry and both sides exchange
 *      hypercore blocks, the reader's view picks it up
 */

import test from 'brittle'
import fs from '#node:fs'
import path from '#node:path'
import os from '#node:os'
import Corestore from 'corestore'
import { ReleaseRegistry } from '../lib/release-registry.js'

function tmpDir (label) {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'rr-' + label + '-'))
}

/**
 * Hook the two corestores together via their stream so hypercore
 * blocks flow in both directions. No Hyperswarm needed.
 */
function replicateStores (a, b) {
  const sA = a.replicate(true)
  const sB = b.replicate(false)
  sA.pipe(sB).pipe(sA)
  return { sA, sB }
}

function fakeEntry (overrides = {}) {
  return {
    name: 'test-app',
    version: '1.0.0',
    treeHash: 'a'.repeat(64),
    upgrade: 'pgh://fleeky/test-app',
    publishedAt: new Date().toISOString(),
    ...overrides
  }
}

test('bootstrap key — reader replicates writer\'s registry and reads', async (t) => {
  // Writer
  const writerStore = new Corestore(tmpDir('w'))
  const writer = new ReleaseRegistry(writerStore)
  await writer.ready()
  await writer.add(fakeEntry({ version: '1.0.0', treeHash: '1'.repeat(64) }))
  await writer.add(fakeEntry({ version: '2.0.0', treeHash: '2'.repeat(64) }))
  const bootstrap = writer.key

  // Reader on a brand-new corestore, bootstrapping on the writer's key
  const readerStore = new Corestore(tmpDir('r'))
  const reader = new ReleaseRegistry(readerStore, { bootstrap })
  await reader.ready()

  // Connect stores so blocks replicate
  replicateStores(writerStore, readerStore)

  // Give the replicator time to catch up
  await new Promise(r => setTimeout(r, 200))
  await reader.update()

  const got = await reader.get('test-app', '2.0.0')
  t.ok(got, 'reader sees 2.0.0 entry')
  t.is(got.treeHash, '2'.repeat(64))

  const latest = await reader.latest('test-app')
  t.ok(latest, 'reader sees latest pointer')
  t.is(latest.version, '2.0.0')

  // Reader is NOT a writer
  t.absent(reader.isWriter)

  await writer.close()
  await reader.close()
})

test('live propagation — writer appends after reader connected', async (t) => {
  const writerStore = new Corestore(tmpDir('w'))
  const writer = new ReleaseRegistry(writerStore)
  await writer.ready()
  await writer.add(fakeEntry({ version: '1.0.0', treeHash: 'a'.repeat(64) }))
  const bootstrap = writer.key

  const readerStore = new Corestore(tmpDir('r'))
  const reader = new ReleaseRegistry(readerStore, { bootstrap })
  await reader.ready()
  replicateStores(writerStore, readerStore)

  await new Promise(r => setTimeout(r, 150))
  await reader.update()

  t.is((await reader.latest('test-app')).version, '1.0.0', 'initial entry visible')

  // Writer appends a new release while reader is connected
  await writer.add(fakeEntry({ version: '1.1.0', treeHash: 'b'.repeat(64) }))

  await new Promise(r => setTimeout(r, 200))
  await reader.update()

  const latest = await reader.latest('test-app')
  t.is(latest.version, '1.1.0', 'new release propagates to reader')

  await writer.close()
  await reader.close()
})

test('reader cannot add — attempt throws', async (t) => {
  const writerStore = new Corestore(tmpDir('w'))
  const writer = new ReleaseRegistry(writerStore)
  await writer.ready()

  const readerStore = new Corestore(tmpDir('r'))
  const reader = new ReleaseRegistry(readerStore, { bootstrap: writer.key })
  await reader.ready()

  t.absent(reader.isWriter)
  await t.exception(reader.add(fakeEntry()), /not a writer/)

  await writer.close()
  await reader.close()
})

test('addWriter — second device can be added + can write', async (t) => {
  // Primary writer
  const s1 = new Corestore(tmpDir('w1'))
  const w1 = new ReleaseRegistry(s1)
  await w1.ready()
  const bootstrap = w1.key

  // Secondary — starts as reader
  const s2 = new Corestore(tmpDir('w2'))
  const w2 = new ReleaseRegistry(s2, { bootstrap })
  await w2.ready()
  replicateStores(s1, s2)

  t.absent(w2.isWriter, 'w2 starts as reader')

  // Primary writer grants w2 write access
  const w2LocalKey = w2.localWriterKey
  t.ok(w2LocalKey, 'w2 has a local writer key')
  await w1.addWriter(w2LocalKey)

  await new Promise(r => setTimeout(r, 300))
  await w2.update()

  t.ok(w2.isWriter, 'w2 is now a writer after addWriter op propagated')

  // w2 appends a release
  await w2.add(fakeEntry({ name: 'from-w2', treeHash: 'c'.repeat(64) }))

  await new Promise(r => setTimeout(r, 300))
  await w1.update()

  const got = await w1.get('from-w2', '1.0.0')
  t.ok(got, 'primary sees entry appended by w2')

  await w1.close()
  await w2.close()
})