/**
* 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()
})