OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / ref-diff.test.js

83 lines · javascript ref:
// ref-diff — the view-vs-disk comparison that makes ref replication observable.
//
// Grounded in a real 2026-08-08 failure: `refs/heads/release` was deleted on the
// origin for 5 repos and believed to have propagated fleet-wide. It propagated
// for 2. For the other 3 the deletion never entered the view (gipPublishRef
// returns silently when the tracker is not writable, or when the view never had
// the ref) while `ref-update-observed` still answered ok:true. Nothing could show
// the view's refs, so the no-op was indistinguishable from success.

import test from 'brittle'

import {
  classifyRefs, refDisagreements, IN_SYNC, DIVERGENT, VIEW_ONLY, DISK_ONLY
} from '../lib/services/ref-diff.js'

const A = 'a'.repeat(40)
const B = 'b'.repeat(40)

test('agreeing refs are in-sync', (t) => {
  const rows = classifyRefs(new Map([['refs/heads/main', A]]), new Map([['refs/heads/main', A]]))
  t.is(rows.length, 1)
  t.is(rows[0].state, IN_SYNC)
  t.alike(refDisagreements(rows), [], 'nothing for an operator to look at')
})

test('same ref at different oids is divergent', (t) => {
  // The view wins: RefReconciler rolls disk BACK to the view's oid. That is how a
  // push accepted locally but non-FF in the linearized order gets undone.
  const rows = classifyRefs(new Map([['refs/heads/main', A]]), new Map([['refs/heads/main', B]]))
  t.is(rows[0].state, DIVERGENT)
  t.is(rows[0].view, A)
  t.is(rows[0].disk, B)
})

test('viewOnly means deleting from disk alone will NOT stick', (t) => {
  // RefReconciler materializes view refs onto disk, so a local `update-ref -d`
  // gets undone on the next reconcile.
  const rows = classifyRefs(new Map([['refs/heads/release', A]]), new Map())
  t.is(rows[0].state, VIEW_ONLY)
  t.is(rows[0].disk, null)
  t.is(refDisagreements(rows).length, 1)
})

test('diskOnly is the resurrection hazard — the exact 2026-08-08 state', (t) => {
  // stortron after the origin-side delete silently failed to publish: disk still
  // has the ref, the view does not. ref-publish-reconciler reads that as "disk
  // ahead of view" and re-announces, undoing the deletion fleet-wide.
  const rows = classifyRefs(new Map(), new Map([['refs/heads/release', A]]))
  t.is(rows[0].state, DISK_ONLY)
  t.is(rows[0].view, null)
  t.is(rows[0].disk, A)
})

test('diskOnly cannot distinguish "never propagated" from "deleted elsewhere"', (t) => {
  // Both an out-of-band ref and a ref deleted on another node look identical from
  // here, because deletion removes the key rather than leaving a tombstone. This
  // pins the ambiguity as a known property, so a future tombstone can be judged
  // against it rather than added speculatively.
  const outOfBand = classifyRefs(new Map(), new Map([['refs/heads/internal', A]]))
  const deletedElsewhere = classifyRefs(new Map(), new Map([['refs/heads/release', A]]))
  t.is(outOfBand[0].state, deletedElsewhere[0].state,
    'indistinguishable by state alone — only intent separates them')
})

test('a full repo picture sorts and covers every combination', (t) => {
  const rows = classifyRefs(
    new Map([['refs/heads/main', A], ['refs/heads/dev', A], ['refs/heads/gone-locally', A]]),
    new Map([['refs/heads/main', A], ['refs/heads/dev', B], ['refs/heads/release', A]])
  )
  t.alike(rows.map(r => r.ref), [
    'refs/heads/dev', 'refs/heads/gone-locally', 'refs/heads/main', 'refs/heads/release'
  ], 'sorted by ref name')
  t.alike(rows.map(r => r.state), [DIVERGENT, VIEW_ONLY, IN_SYNC, DISK_ONLY])
  t.is(refDisagreements(rows).length, 3, 'only the in-sync one is filtered out')
})

test('empty and missing inputs are not errors', (t) => {
  t.alike(classifyRefs(new Map(), new Map()), [])
  t.alike(classifyRefs(null, null), [], 'null tolerated — a repo with no tracker yet')
  t.alike(classifyRefs([['refs/heads/main', A]], [['refs/heads/main', A]])[0].state, IN_SYNC,
    'accepts plain entry arrays as well as Maps')
})