OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / vdrive-locations-travel.test.js

132 lines · javascript ref:
/**
 * locations.json must travel with the release it describes (org #146).
 *
 * A vdrive stores `index -> contentHash`. Serving needs the other half,
 * `contentHash -> (file, offset, length)`, and rebuilding that by re-chunking
 * every published file is O(all bytes) on every boot. So it is written once at
 * publish time as `locations.json`.
 *
 * It lived only at `<releaseDir>/<repo>/.vdrive/locations.json` — OUTSIDE the
 * version dir, so it is not part of what replicates with a release. A mirror
 * therefore holds the artifacts and the key (which rides in manifest.json) but
 * cannot resolve a single block, and has to pay for a second full copy of every
 * byte to seed a drive it could otherwise serve for ~281KB.
 *
 * The index is CUMULATIVE: publishTree loads the prior index, adds the new
 * version's chunks, and saves the union. So the copy written into a version dir
 * is a snapshot of the whole drive at that point, not a per-version delta —
 * which is what makes the NEWEST version's copy sufficient on its own.
 */
import test from 'brittle'
import fsp from '#node:fs/promises'
import path from '#node:path'
import os from '#node:os'

import { publishVersionToDrive, vdriveStoreDir } from '../lib/vdrive-publish.js'

function tmpdir (name) {
  return fsp.mkdtemp(path.join(os.tmpdir(), 'pgv3-vdloc-' + name + '-'))
}

async function publish (releaseDir, repo, version, files) {
  const versionDir = path.join(releaseDir, repo, 'versions', version)
  await fsp.mkdir(versionDir, { recursive: true })
  for (const [name, data] of Object.entries(files)) {
    await fsp.writeFile(path.join(versionDir, name), data)
  }
  const { chunk } = await import('hyper-overlay/chunker')
  const res = await publishVersionToDrive({
    releaseDir,
    repo,
    version,
    channel: 'production',
    vdrive: { enabled: true, channels: null },
    chunk
  })
  return { res, versionDir }
}

test('locations.json is written into the version dir so it replicates', async (t) => {
  const releaseDir = await tmpdir('travel')
  const repo = 'demo'

  const { res, versionDir } = await publish(releaseDir, repo, '1.0.0', {
    'app.bin': Buffer.alloc(512 * 1024, 3)
  })
  t.is(res.ok, true, 'published')

  const inVersion = path.join(versionDir, 'locations.json')
  const inStore = path.join(vdriveStoreDir(releaseDir, repo), 'locations.json')

  t.ok(await exists(inStore), 'the drive keeps its working copy')
  t.ok(await exists(inVersion), 'and a copy travels with the version')

  const a = JSON.parse(await fsp.readFile(inStore, 'utf8'))
  const b = JSON.parse(await fsp.readFile(inVersion, 'utf8'))
  t.alike(b, a, 'the travelling copy is the same index')
  t.ok(b.entries.length > 0, 'and it is not empty')

  await fsp.rm(releaseDir, { recursive: true, force: true })
})

test('every recorded path is relative and inside the root', async (t) => {
  // An absolute path would not resolve on another machine and would bake this
  // machine's layout into an artifact peers can read.
  const releaseDir = await tmpdir('rel')
  const repo = 'demo'
  const { versionDir } = await publish(releaseDir, repo, '1.0.0', {
    'app.bin': Buffer.alloc(256 * 1024, 9),
    'notes.txt': 'hello'
  })

  const idx = JSON.parse(await fsp.readFile(path.join(versionDir, 'locations.json'), 'utf8'))
  for (const [, rel] of idx.entries) {
    t.absent(path.isAbsolute(rel), 'relative: ' + rel)
    t.absent(rel.startsWith('..'), 'inside the root: ' + rel)
  }

  await fsp.rm(releaseDir, { recursive: true, force: true })
})

test("the newest version's copy covers earlier versions too", async (t) => {
  // The index accumulates, so a mirror needs only the latest version's copy to
  // serve the whole drive. If this ever became a per-version delta, a mirror
  // would silently fail to resolve blocks belonging to older releases.
  const releaseDir = await tmpdir('accum')
  const repo = 'demo'

  const first = await publish(releaseDir, repo, '1.0.0', {
    'one.bin': Buffer.alloc(256 * 1024, 1)
  })
  t.is(first.res.ok, true, 'first published')

  const second = await publish(releaseDir, repo, '2.0.0', {
    'two.bin': Buffer.alloc(256 * 1024, 2)
  })
  t.is(second.res.ok, true, 'second published')

  const older = JSON.parse(
    await fsp.readFile(path.join(first.versionDir, 'locations.json'), 'utf8')
  )
  const newest = JSON.parse(
    await fsp.readFile(path.join(second.versionDir, 'locations.json'), 'utf8')
  )

  const newestHashes = new Set(newest.entries.map((e) => e[0]))
  for (const [hash] of older.entries) {
    t.ok(newestHashes.has(hash), 'newest snapshot still carries ' + hash.slice(0, 12))
  }
  t.ok(newest.entries.length >= older.entries.length, 'and it only grew')

  await fsp.rm(releaseDir, { recursive: true, force: true })
})

async function exists (p) {
  try {
    await fsp.stat(p)
    return true
  } catch {
    return false
  }
}