OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / registry-held-locally.test.js

151 lines · javascript ref:
// The "Published to pgh" list must not advertise installs that cannot work.
//
// The registry and the tree store have OPPOSITE lifecycle rules:
//
//   - the tree store is content-addressed and garbage-collected; a tree
//     survives only while something references it, and PublishedIndex keeps
//     maxVersions = 1, so old versions are swept by `pgh store gc`.
//   - the ReleaseRegistry is an append-only signed autobase with NO retention.
//
// They therefore diverge by construction. Measured on tabertron 2026-08-29 for
// hyper-tps: the registry advertised 382 distinct tree hashes, of which 10 were
// present in the local store and 372 were not. Every one of those 372 rows
// printed a confident `pgh app install pgh://<key>/hyper-tps@<version>` that
// resolves to nothing here.
//
// Marking is deliberately chosen over pruning: the registry is signed and
// replicated, so removing entries is a far heavier action, and "not held HERE"
// is not the same as "gone" -- another peer may still hold the tree. The label
// has to say the former, never the latter.

import test from 'brittle'

import { markHeldLocally } from '../lib/handlers/pgh-tree.js'

test('entries are marked by whether their tree is held locally', (t) => {
  const entries = [
    { name: 'app', version: '1.0.0', treeHash: 'aa'.repeat(32) },
    { name: 'app', version: '0.9.0', treeHash: 'bb'.repeat(32) },
    { name: 'app', version: '0.8.0', treeHash: 'cc'.repeat(32) }
  ]
  const held = new Set([ 'aa'.repeat(32) ])

  const out = markHeldLocally(entries, (h) => held.has(h))

  t.is(out.length, 3, 'nothing is dropped -- the registry is reported as it is')
  t.ok(out[0].heldLocally, 'present tree marked held')
  t.absent(out[1].heldLocally, 'absent tree marked NOT held')
  t.absent(out[2].heldLocally)
})

test('an entry with no tree hash is not claimed to be held', (t) => {
  const out = markHeldLocally([{ name: 'app', version: '1.0.0' }], () => true)
  t.absent(out[0].heldLocally, 'no hash means we cannot assert it is resolvable')
})

test('a throwing probe degrades to "unknown", never to a false claim', (t) => {
  // A stat can fail for reasons that say nothing about the tree (permissions,
  // a store that has not been created yet). Reporting "held" there would put
  // an install command on screen that does not work.
  const out = markHeldLocally(
    [{ name: 'app', version: '1.0.0', treeHash: 'dd'.repeat(32) }],
    () => { throw new Error('EACCES') }
  )
  t.absent(out[0].heldLocally, 'probe failure is not treated as held')
})

test('marking is pure -- the input entries are not mutated', (t) => {
  const entries = [{ name: 'app', version: '1.0.0', treeHash: 'ee'.repeat(32) }]
  markHeldLocally(entries, () => true)
  t.absent('heldLocally' in entries[0], 'caller keeps its own objects intact')
})

test('an empty or missing list is handled', (t) => {
  t.alike(markHeldLocally([], () => true), [])
  t.alike(markHeldLocally(null, () => true), [])
  t.alike(markHeldLocally(undefined, () => true), [])
})

// ── Page rendering ───────────────────────────────────────────

import { renderRepoReleases } from '../lib/web/pages/repo-releases.js'

function svcWith (entries) {
  return {
    _releases: { async list () { return { versions: [], channels: {} } } },
    async dispatch (name) {
      if (name === 'tree-registry-list') {
        return { registryKey: 'k'.repeat(64), count: entries.length, entries }
      }
      return {}
    }
  }
}

test('the page marks absent trees and refuses to print an install for them', async (t) => {
  // (svc, ctx, repoName, opts) -- the order the router actually uses.
  const html = await renderRepoReleases(svcWith([
    { name: 'demo', version: '2.0.0', treeHash: 'aa'.repeat(32), publishedAt: 2, heldLocally: true },
    { name: 'demo', version: '1.0.0', treeHash: 'bb'.repeat(32), publishedAt: 1, heldLocally: false }
  ]), {}, 'demo', { showAll: true })

  t.ok(html.includes('not stored here'), 'the absent row is labelled')
  t.ok(html.includes('tree-absent'), 'and dimmed via its own class')
  t.ok(html.includes('<strong>1</strong> of 2'), 'summary counts it (copy is not the contract)')

  // The held version keeps a usable command; the absent one must not have one.
  t.ok(html.includes('pgh app install pgh://' + 'k'.repeat(64) + '/demo@2.0.0'),
    'held version still offers an install')
  t.absent(html.includes('/demo@1.0.0'),
    'absent version does NOT advertise an install that cannot resolve')
  t.ok(html.includes('unavailable from this forge'), 'it says so instead')
})

test('a fully-held list shows no summary and no markers', async (t) => {
  const html = await renderRepoReleases(svcWith([
    { name: 'demo', version: '2.0.0', treeHash: 'aa'.repeat(32), publishedAt: 2, heldLocally: true }
  ]), {}, 'demo', {})

  t.absent(html.includes('not stored here'), 'no marker')
  t.absent(html.includes('not stored on this forge'), 'no summary noise when everything resolves')
  t.ok(html.includes('pgh app install'), 'install still offered')
})

// ── Hidden by default ────────────────────────────────────────
//
// 377 of 384 hyper-tps rows were entries this forge cannot serve. Rendering
// them by default buried the 7 usable ones and shipped ~280KB of markup to say
// so. They are OMITTED now, not merely dimmed, with a link to see them.

test('not-stored entries are omitted by default', async (t) => {
  const html = await renderRepoReleases(svcWith([
    { name: 'demo', version: '2.0.0', treeHash: 'aa'.repeat(32), publishedAt: 2, heldLocally: true },
    { name: 'demo', version: '1.0.0', treeHash: 'bb'.repeat(32), publishedAt: 1, heldLocally: false },
    { name: 'demo', version: '0.9.0', treeHash: 'cc'.repeat(32), publishedAt: 0, heldLocally: false }
  ]), {}, 'demo', {})

  t.ok(html.includes('demo@2.0.0'), 'the servable version is shown')
  t.absent(html.includes('0.9.0'), 'a not-stored version is not rendered at all')
  t.absent(html.includes('1.0.0'), 'nor the other one')
  t.ok(html.includes('?all=1'), 'but there is a way to see them')
  t.ok(/2 (older )?entr/i.test(html) || html.includes('>2<'), 'and the count is stated')
})

test('showAll renders them, marked', async (t) => {
  const html = await renderRepoReleases(svcWith([
    { name: 'demo', version: '2.0.0', treeHash: 'aa'.repeat(32), publishedAt: 2, heldLocally: true },
    { name: 'demo', version: '1.0.0', treeHash: 'bb'.repeat(32), publishedAt: 1, heldLocally: false }
  ]), {}, 'demo', { showAll: true })

  t.ok(html.includes('1.0.0'), 'the not-stored version appears')
  t.ok(html.includes('not stored here'), 'still marked')
  t.absent(html.includes('/demo@1.0.0'), 'and still offers no install')
})

test('when everything is servable there is no hidden-count line', async (t) => {
  const html = await renderRepoReleases(svcWith([
    { name: 'demo', version: '2.0.0', treeHash: 'aa'.repeat(32), publishedAt: 2, heldLocally: true }
  ]), {}, 'demo', {})
  t.absent(html.includes('?all=1'), 'no toggle offered when nothing is hidden')
})