OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / serve-device-blob.test.js

76 lines · javascript ref:
// get-blob responder (#16): blobs could be UPLOADED but never read back — the get-blob RPC responder was
// never implemented, so peer-front.getBlob got null and the bridge 404'd every blob (BlobNotFound), regardless
// of who held the bytes. This proves the round-trip now works over the swarm control surface: upload-blob then
// get-blob returns the exact bytes; a public (non-writer) client can read; an unknown CID returns null (→404).
import '../lib/bare-globals.js'
import test from 'brittle'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import c from 'compact-encoding'
import Hyperswarm from 'hyperswarm'
import ProtomuxRPC from 'protomux-rpc'
import createTestnet from '@hyperswarm/testnet'
import { SwarmDevice } from '../lib/swarm-device.js'
import { serveDevice, DEVICE_PROTOCOL } from '../lib/serve-device.js'
import { BlobStore } from 'atproto-store/lib/blob-store.js'

const DID = 'did:plc:blob22222222222222222222'

function memKV () {
  const m = new Map()
  return {
    async set (k, v) { m.set(k, v) },
    async del (k) { m.delete(k) },
    async get (k) { return m.has(k) ? m.get(k) : null },
    async list (prefix = '') { const o = {}; for (const [k, v] of m) if (k.startsWith(prefix)) o[k] = v; return o }
  }
}
function clientRpc (swarm, serverKeyHex) {
  return new Promise((resolve) => {
    swarm.on('connection', (conn) => resolve(new ProtomuxRPC(conn, { protocol: DEVICE_PROTOCOL, valueEncoding: c.json })))
    swarm.join(b4a.from(serverKeyHex, 'hex'), { client: true, server: false })
  })
}

test('get-blob: peer serves stored blobs (the previously-missing read path)', async (t) => {
  const testnet = await createTestnet(3)
  const device = new SwarmDevice(memKV(), { did: DID, privateKey: crypto.randomBytes(32), self: 'deviceA' })
  const blobs = await new BlobStore('data/blob-test-' + Date.now(), { name: 'test-blobs:' + DID }).ready()

  const serverSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap })
  const authedClient = new Hyperswarm({ bootstrap: testnet.bootstrap })
  const strangerClient = new Hyperswarm({ bootstrap: testnet.bootstrap })
  const authedPub = b4a.toString(authedClient.keyPair.publicKey, 'hex')

  const serving = serveDevice(device, serverSwarm, { canWrite: (hex) => hex === authedPub, blobs })
  await serving.ready()
  t.teardown(async () => {
    serving.close()
    await serverSwarm.destroy(); await authedClient.destroy(); await strangerClient.destroy()
    try { await blobs.close() } catch {}
    await testnet.destroy()
  })

  const rpc = await clientRpc(authedClient, serving.serverKey)

  // upload (writer-gated) then read back (public) — the exact round-trip that used to 404.
  const bytes = b4a.from('the actual image bytes for the avatar and post')
  const up = await rpc.request('upload-blob', { bytes: b4a.toString(bytes, 'hex'), mimeType: 'image/png' })
  t.ok(up.cid, 'upload-blob returned a content-addressed CID')

  const got = await rpc.request('get-blob', { cid: up.cid })
  t.ok(got, 'get-blob returned a blob (was null before the responder existed → BlobNotFound)')
  t.is(got.mimeType, 'image/png', 'mime type preserved')
  t.is(got.bytes, b4a.toString(bytes, 'hex'), 'bytes round-trip exactly (hex)')
  t.alike(b4a.from(got.bytes, 'hex'), bytes, 'decoded bytes equal the original')

  // get-blob is PUBLIC: a non-allowlisted client (cannot upload) can still read.
  const rpc2 = await clientRpc(strangerClient, serving.serverKey)
  const pub = await rpc2.request('get-blob', { cid: up.cid })
  t.is(pub && pub.bytes, b4a.toString(bytes, 'hex'), 'a public (non-writer) client can read a blob')

  // a miss is null (server-bare maps null → 404 BlobNotFound), not an error.
  const miss = await rpc.request('get-blob', { cid: 'bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' })
  t.is(miss, null, 'unknown CID returns null (→ 404)')
})