// gip-overlay P3d — the producer is wired into ref-update-observed.
//
// Boot a real daemon, create a bare repo with a commit, fire the
// ref-update-observed RPC (as the post-receive hook does), and assert the
// gip producer projected it: the per-repo autobase has the ref + bundle-chain
// entry, and a bundle file was spooled. Proves the live hot-path wiring, not
// just the unit (covered by gip-publish-roundtrip).
import test from 'brittle'
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, readdirSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { Client } from 'hyper-svc'
import { startPearGit } from '../index.js'
import { execFileSync } from '../lib/_compat-exec.js'
const ZERO = '0000000000000000000000000000000000000000'
function git (args, opts = {}) {
return execFileSync('git', args, { encoding: 'utf8', ...opts }).toString().trim()
}
function tmpStorage () { return mkdtempSync(join(tmpdir(), 'pgv3-gipw-')) }
function tmpSock () { return join(mkdtempSync(join(tmpdir(), 'pgv3-gipw-sock-')), 'svc.sock') }
async function until (fn, ms = 5000) {
for (let waited = 0; waited < ms; waited += 100) {
const v = await fn()
if (v) return v
await new Promise(r => setTimeout(r, 100))
}
return null
}
test('ref-update-observed publishes the ref + bundle into the per-repo autobase', async (t) => {
const storage = tmpStorage()
const node = await startPearGit({ storage, uds: tmpSock(), http: { port: 0 }, swarm: false, autoImportSSH: false })
t.teardown(async () => { try { await node.close() } catch {} })
// create a bare repo in the daemon's reposDir + a commit on main
const reposDir = node.svc._reposDir
mkdirSync(reposDir, { recursive: true })
const bare = join(reposDir, 'gip-test.git')
git(['init', '--bare', '-q', bare])
const work = join(storage, 'work')
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'), 'one\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', bare, 'HEAD:refs/heads/main'])
// fire the RPC the post-receive hook posts
const client = await Client.discover({ uds: node.svc.uds.path })
const res = await client.call('ref-update-observed', { name: 'gip-test', ref: 'refs/heads/main', oldSha: ZERO, newSha: oid })
t.ok(res && res.ok, 'ref-update-observed accepted')
// producer is fire-and-forget — wait for the autobase to reflect it
const tracker = await node.svc.issueRegistry.getForRepo('gip-test')
const ref = await until(async () => {
const r = await tracker.getRef('refs/heads/main')
return r && r.hash === oid ? r : null
})
t.ok(ref, 'ref published into the per-repo autobase at the pushed oid')
const bundle = await tracker.getBundle(oid)
t.ok(bundle && bundle.contentHash, 'bundle-chain entry recorded with a content hash')
t.is(bundle.baseOid, ZERO, 'creation bundle baseOid is ZERO')
const spool = join(storage, 'gip-bundles', 'gip-test')
t.ok(existsSync(spool) && readdirSync(spool).some(f => f.endsWith('.bundle')), 'a bundle file was spooled')
try { await client.close() } catch {}
})