OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / release-index-after-vdrive.test.js

97 lines · javascript ref:
/**
 * The release index must describe the version directory as it ACTUALLY IS
 * after the vdrive step, not as it was before.
 *
 * ci-orchestrator step 7 publishes via ReleaseManager, which writes _index.json
 * and hashes manifest.json. Step 8 then publishes the vdrive, and
 * vdrive-publish.js:annotateManifest() appends the `vdrive` block to that same
 * manifest while copyLocations() drops locations.json beside it. Nothing
 * re-indexed, so the index advertised a content hash for bytes that no longer
 * existed.
 *
 * Measured in prod 2026-09-05 (ifsrenderer-linux linux-port-52962c7):
 *
 *   14:50:30.106  _index.json written      -> manifest.json @ 665 bytes
 *   14:50:39.166  manifest.json rewritten  -> 880 bytes
 *
 * The mirror fetched 5 of 6 files, failed on the manifest, and without it the
 * version reads `incomplete` on the far side: rendered "— no files", never
 * re-served, channel pin stranded on the previous build. See pear-git-v3 #18.
 */
import test from 'brittle'
import { mkdtemp, readFile, writeFile, stat } from '#node:fs/promises'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { hashChunk } from 'hyper-overlay/chunker'

import { ReleaseManager, VDRIVE_LOCATIONS_FILE } from '../lib/release-manager.js'

const REPO = 'demo-repo'
const VERSION = 'linux-port-deadbee'

async function published () {
  const dir = await mkdtemp(join(tmpdir(), 'pgv3-relidx-'))
  const rm = new ReleaseManager({ releaseDir: dir })
  await rm.publish({
    repo: REPO,
    version: VERSION,
    channel: 'experimental',
    artifacts: [{ path: 'dist/app.AppImage', data: Buffer.from('an artifact') }]
  })
  return { dir, rm }
}

/** What the vdrive step does to a version directory AFTER it was published. */
async function runVdriveStep (dir) {
  const vdir = join(dir, REPO, 'versions', VERSION)
  const manifestPath = join(vdir, 'manifest.json')
  const raw = JSON.parse(await readFile(manifestPath, 'utf8'))
  raw.vdrive = { link: 'pear://xyzzy', key: 'ab'.repeat(32), blocks: 1839, locations: 1217 }
  await writeFile(manifestPath, JSON.stringify(raw, null, 2))
  await writeFile(join(vdir, VDRIVE_LOCATIONS_FILE), JSON.stringify({ 0: 'x' }))
}

const indexed = async (dir) =>
  JSON.parse(await readFile(join(dir, '_index.json'), 'utf8')).files || []

test('the index describes the manifest as it is on disk after the vdrive step', async (t) => {
  const { dir, rm } = await published()
  await runVdriveStep(dir)

  await rm.reannounce(REPO, VERSION, 'experimental')

  const rel = REPO + '/versions/' + VERSION + '/manifest.json'
  const entry = (await indexed(dir)).find(f => f.path === rel)
  t.ok(entry, 'manifest.json is indexed')

  const abs = join(dir, rel)
  t.is(entry.size, (await stat(abs)).size,
    'indexed size matches disk — a stale size means a mirror fetches a hash nothing holds')
  t.is(entry.hash, hashChunk(await readFile(abs)), 'indexed hash matches disk')
})

test('locations.json is indexed, so the vdrive index can actually replicate', async (t) => {
  const { dir, rm } = await published()
  await runVdriveStep(dir)

  await rm.reannounce(REPO, VERSION, 'experimental')

  const rel = REPO + '/versions/' + VERSION + '/' + VDRIVE_LOCATIONS_FILE
  const entry = (await indexed(dir)).find(f => f.path === rel)

  // Copied into the version dir explicitly "so it replicates". Without an index
  // entry a mirror holds every byte and the vdrive key yet cannot resolve a
  // single block — the exact failure that copy exists to prevent.
  t.ok(entry, 'locations.json is in the index')
  if (entry) t.is(entry.hash, hashChunk(await readFile(join(dir, rel))), 'and its hash matches')
})

test('a version with no vdrive is unaffected — the sidecar is simply absent', async (t) => {
  const { dir, rm } = await published()

  const files = await indexed(dir)
  const rel = REPO + '/versions/' + VERSION + '/' + VDRIVE_LOCATIONS_FILE
  t.absent(files.find(f => f.path === rel), 'no phantom entry for a file that does not exist')
  t.ok(files.find(f => f.path.endsWith('/manifest.json')), 'manifest still indexed')
})