OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / ref-reconciler-manager.test.js

89 lines · javascript ref:
// gip-overlay P3e — RefReconcilerManager.
//
// The consumer-side lifecycle: ensureFor(repo) opens the per-repo tracker,
// inits the bare repo if missing, wires the overlay-backed ensureObjects, and
// starts a RefReconciler so the bare repo follows the autobase. Deterministic,
// in-process (real git + real tracker + real overlay, no swarm); the over-
// swarm version is the two-node e2e.

import test from 'brittle'
import Corestore from 'corestore'
import { mkdtempSync, writeFileSync, existsSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { HyperIssues } from 'hyper-issues'
// hyper-overlay exports the class as `HyperOverlay`; alias it the same way
// lib/pear-git.js does so the V2-era call sites below keep reading naturally.
import { HyperOverlay as HyperOverlayV2 } from 'hyper-overlay'

import { execFileSync } from '../lib/_compat-exec.js'
import { publishRefUpdate } from '../lib/gip-publish.js'
import { RefReconcilerManager } from '../lib/services/ref-reconciler-manager.js'

function git (args, opts = {}) {
  return execFileSync('git', args, { encoding: 'utf8', ...opts }).toString().trim()
}

test('ensureFor inits a missing bare repo and materializes it from the autobase', async (t) => {
  const dir = mkdtempSync(join(tmpdir(), 'pgv3-recmgr-'))

  // origin bare repo with one commit
  const origin = join(dir, 'origin.git')
  const work = join(dir, 'work')
  git(['init', '--bare', '-q', origin])
  git(['init', '-q', work])
  git(['-C', work, 'config', 'user.email', 't@t'])
  git(['-C', work, 'config', 'user.name', 'T'])
  git(['-C', work, 'config', 'commit.gpgsign', 'false'])
  writeFileSync(join(work, 'a.txt'), 'hello\n')
  git(['-C', work, 'add', '.'])
  git(['-C', work, 'commit', '-q', '-m', 'c1'])
  const oid = git(['-C', work, 'rev-parse', 'HEAD'])
  git(['-C', work, 'push', '-q', origin, 'HEAD:refs/heads/main'])

  // real overlay + tracker; publish the commit
  const store = new Corestore(join(dir, 'store'))
  const overlay = new HyperOverlayV2(store, { namespace: 'overlay-v2', destDir: join(dir, 'fetch') })
  await overlay.ready()
  const tracker = new HyperIssues(store, { namespace: 'issues:repo:gip-test' })
  await tracker.ready()
  t.teardown(async () => { try { await tracker.close() } catch {}; try { await overlay.close() } catch {}; try { await store.close() } catch {} })

  await publishRefUpdate({ repoPath: origin, repoName: 'gip-test', ref: 'refs/heads/main', oldOid: null, newOid: oid, tracker, overlay, spoolDir: join(dir, 'gip-bundles'), userId: 'u' })

  // consumer node: a separate reposDir, no bare repo yet
  const reposDir = join(dir, 'consumer-repos')
  const mgr = new RefReconcilerManager({
    issueRegistry: { async getForRepo () { return tracker } }, // same tracker stands in for the replicated one
    overlay,
    reposDir
  })
  t.teardown(async () => { try { await mgr.closeAll() } catch {} })

  const target = join(reposDir, 'gip-test.git')
  t.absent(existsSync(target), 'no bare repo before ensureFor')

  await mgr.ensureFor('gip-test')

  t.ok(existsSync(target), 'bare repo initialized by ensureFor')
  t.is(git(['--git-dir', target, 'rev-parse', 'refs/heads/main']), oid, 'ref materialized from the autobase')
  t.is(git(['--git-dir', target, 'cat-file', '-p', oid + ':a.txt']), 'hello', 'blob byte-identical')
})

test('ensureFor is idempotent (one reconciler per repo)', async (t) => {
  const dir = mkdtempSync(join(tmpdir(), 'pgv3-recmgr2-'))
  const store = new Corestore(join(dir, 'store'))
  const overlay = new HyperOverlayV2(store, { namespace: 'overlay-v2', destDir: join(dir, 'fetch') })
  await overlay.ready()
  const tracker = new HyperIssues(store, { namespace: 'issues:repo:r' })
  await tracker.ready()
  t.teardown(async () => { try { await tracker.close() } catch {}; try { await overlay.close() } catch {}; try { await store.close() } catch {} })

  const mgr = new RefReconcilerManager({ issueRegistry: { async getForRepo () { return tracker } }, overlay, reposDir: join(dir, 'repos') })
  t.teardown(async () => { try { await mgr.closeAll() } catch {} })

  const a = await mgr.ensureFor('r')
  const b = await mgr.ensureFor('r')
  t.ok(a === b, 'same reconciler instance returned')
})