OverviewFilesCommitsIssuesDocsReleasesPeers

favela-web / test / blobs.test.mjs

103 lines · javascript ref:
// favela-web blob path — avatar + post images. Pure: stubs the hpp `_call`.
//
// The load-bearing assertion is the blob REF SHAPE. It is not a guess: it was read out of the fleet's
// live records by decoding wj62's app.bsky.actor.profile/self and its working image post. Both store
//
//     { $type:'blob', ref:{ $link:'<cid string>' }, mimeType, size }
//
// with `ref` a PLAIN object — NOT a multiformats CID instance, and NOT a CBOR tag-42 link (verified
// from the raw bytes: no d82a, a literal "$link" text key). That is off-spec for DAG-CBOR, where a blob
// ref should be a CID link — but it is what the whole repo already contains and what bsky demonstrably
// hydrates into cdn.bsky.app thumb/fullsize URLs. Emitting canonical refs for NEW records only would
// make the repo internally inconsistent, and fixing it properly is a content migration of every
// existing record. So new writes MATCH the existing shape, deliberately, and this test pins it so the
// decision is explicit rather than accidental.
//
// Run: node test/blobs.test.mjs
import { Writer } from '../lib/writer.js'

const assert = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } console.log('  ok -', m) }

const mk = () => {
  const w = new Writer({})
  const calls = []
  w._call = async (method, params) => {
    calls.push({ method, params })
    if (method === 'upload-blob') return { cid: 'bafkreitestcid000000000000000000000000000000000000000', mimeType: params.mimeType, size: 1234 }
    return { ok: true }
  }
  return { w, calls }
}

// ── blobRef shape (the byte-level contract with the existing repo) ────────────────────────────────
{
  const ref = Writer.blobRef({ cid: 'bafkreiabc', mimeType: 'image/jpeg', size: 42 })
  assert(ref.$type === 'blob', 'blobRef is $type blob')
  assert(ref.mimeType === 'image/jpeg' && ref.size === 42, 'carries mimeType + size')
  assert(typeof ref.ref === 'object' && ref.ref !== null, 'ref is an object')
  assert(ref.ref.$link === 'bafkreiabc', 'ref.$link is the cid STRING — matches every existing record on disk')
  assert(ref.ref.constructor === Object, 'ref is a PLAIN object, not a CID instance (same as the live records)')
  assert(JSON.stringify(Object.keys(ref).sort()) === JSON.stringify(['$type', 'mimeType', 'ref', 'size']), 'exactly the four blob-ref fields, no extras')
}

// ── uploadBlob relays bytes as hex over the UDS (matching serve-device-uds) ───────────────────────
{
  const { w, calls } = mk()
  const out = await w.uploadBlob(Buffer.from([0xff, 0xd8, 0xff, 0xe0]), 'image/jpeg')
  assert(calls[0].method === 'upload-blob', 'calls upload-blob on the hpp')
  assert(calls[0].params.bytes === 'ffd8ffe0', 'bytes travel as HEX (the c.json RPC value cannot carry raw bytes)')
  assert(calls[0].params.mimeType === 'image/jpeg', 'mime is passed through')
  assert(out.cid && out.size === 1234, 'returns cid + size for the ref')
}

// ── post() with images builds an app.bsky.embed.images embed ──────────────────────────────────────
{
  const { w, calls } = mk()
  const blob = Writer.blobRef({ cid: 'bafkreiimg', mimeType: 'image/png', size: 99 })
  await w.post('look', { images: [{ blob, alt: 'a description' }] })
  const rec = calls[0].params.record
  assert(rec.embed && rec.embed.$type === 'app.bsky.embed.images', 'embed is app.bsky.embed.images')
  assert(rec.embed.images.length === 1, 'one image')
  assert(rec.embed.images[0].image.ref.$link === 'bafkreiimg', 'the blob ref is embedded verbatim')
  assert(rec.embed.images[0].alt === 'a description', 'alt text carried')
  assert(rec.text === 'look' && Array.isArray(rec.langs), 'text + langs still correct alongside the embed')
}

// bsky REJECTS more than 4 images outright — a record the relay refuses federates as nothing while
// looking fine locally, so the cap is enforced at write time, not left to the UI.
{
  const { w, calls } = mk()
  const blob = Writer.blobRef({ cid: 'bafkreix', mimeType: 'image/png', size: 1 })
  await w.post('many', { images: [1, 2, 3, 4, 5, 6].map(() => ({ blob, alt: '' })) })
  assert(calls[0].params.record.embed.images.length === 4, 'capped at 4 (app.bsky.embed.images maxLength)')
}

// no images -> NO embed key at all (an empty embed would be an invalid record)
{
  const { w, calls } = mk()
  await w.post('plain')
  assert(!('embed' in calls[0].params.record), 'a text-only post carries no embed key')
  const { w: w2, calls: c2 } = mk()
  await w2.post('plain', { images: [] })
  assert(!('embed' in c2[0].params.record), 'an EMPTY images array also yields no embed')
}

// ── alt text defaults to '' rather than undefined (undefined would drop the required field) ───────
{
  const { w, calls } = mk()
  const blob = Writer.blobRef({ cid: 'bafkreiy', mimeType: 'image/png', size: 1 })
  await w.post('noalt', { images: [{ blob }] })
  assert(calls[0].params.record.embed.images[0].alt === '', 'alt defaults to empty string, never undefined')
}

// ── putRecord relays an update through the hpp (the avatar path) ──────────────────────────────────
{
  const { w, calls } = mk()
  await w.putRecord('app.bsky.actor.profile', 'self', { $type: 'app.bsky.actor.profile', displayName: 'x' }, { action: 'update' })
  assert(calls[0].method === 'apply-write', 'putRecord goes through apply-write')
  assert(calls[0].params.action === 'update' && calls[0].params.rkey === 'self', 'action + rkey correct')
  assert(calls[0].params.collection === 'app.bsky.actor.profile', 'collection correct')
}

console.log('\nBLOBS OK: blob-ref shape matches the live records; embeds capped at 4; avatar writes via apply-write')