OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / deleted-releases.test.js

73 lines · javascript ref:
/**
 * DeletedReleases — the fold that stops a puller chasing retired versions.
 * See lib/services/deleted-releases.js for the 2026-08-29 incident this
 * exists to prevent.
 */
import test from 'brittle'
import {
  DeletedReleases, foldDeleted,
  OP_PUBLISHED, OP_PROMOTED, OP_DELETED
} from '../lib/services/deleted-releases.js'

test('a deleted version is tombstoned; an untouched one is not', (t) => {
  const s = new DeletedReleases()
  s.applyEntry({ op: OP_PUBLISHED, repo: 'r', version: 'v1' }, 0)
  s.applyEntry({ op: OP_PUBLISHED, repo: 'r', version: 'v2' }, 1)
  s.applyEntry({ op: OP_DELETED, repo: 'r', version: 'v1' }, 2)

  t.ok(s.has('r', 'v1'), 'deleted version is tombstoned')
  t.absent(s.has('r', 'v2'), 'live version is not')
  t.is(s.size, 1)
  t.alike(s.list(), [{ repo: 'r', version: 'v1' }])
})

test('versions are scoped per repo — same version string, different repo', (t) => {
  const s = new DeletedReleases()
  s.applyEntry({ op: OP_DELETED, repo: 'a', version: 'v1' }, 0)
  t.ok(s.has('a', 'v1'))
  t.absent(s.has('b', 'v1'), 'a delete in repo a says nothing about repo b')
})

test('later seq wins, and an out-of-order replay cannot clobber newer state', (t) => {
  const s = new DeletedReleases()
  s.applyEntry({ op: OP_DELETED, repo: 'r', version: 'v1' }, 5)
  t.ok(s.has('r', 'v1'))

  // An older entry arriving late must not un-delete it.
  s.applyEntry({ op: OP_PUBLISHED, repo: 'r', version: 'v1' }, 2)
  t.ok(s.has('r', 'v1'), 'stale replay ignored')

  // A genuine republish at a NEWER seq revives it — mistakes are reversible.
  s.applyEntry({ op: OP_PUBLISHED, repo: 'r', version: 'v1' }, 9)
  t.absent(s.has('r', 'v1'), 'republish revives the version')
})

test('channel-only ops carry no version and are ignored', (t) => {
  const s = new DeletedReleases()
  s.applyEntry({ op: OP_DELETED, repo: 'r', version: 'v1' }, 0)
  s.applyEntry({ op: OP_PROMOTED, repo: 'r', version: '', channel: 'stable' }, 1)
  t.ok(s.has('r', 'v1'), 'unaffected by a channel op')
  t.is(s.size, 1)
})

test('foldDeleted replays a whole log; isDeleted is a bare predicate', async (t) => {
  const log = {
    async * entries () {
      yield { op: OP_PUBLISHED, repo: 'r', version: 'v1' }
      yield { op: OP_PUBLISHED, repo: 'r', version: 'v2' }
      yield { op: OP_DELETED, repo: 'r', version: 'v1' }
    }
  }
  const s = await foldDeleted(log)
  t.is(s.size, 1)

  const pred = s.isDeleted
  t.ok(pred('r', 'v1'), 'predicate works detached from the instance')
  t.absent(pred('r', 'v2'))
})

test('foldDeleted tolerates a missing or malformed log', async (t) => {
  t.is((await foldDeleted(null)).size, 0)
  t.is((await foldDeleted({})).size, 0, 'no entries() — returns an empty set, does not throw')
})