import test from 'brittle'
import { createRepoAccess, ensureExportMarker, PUBLIC_MARKER, DENY_RETIRED, DENY_UNMARKED, DENY_BAD_NAME, ALLOW_PUBLIC, ALLOW_LOCAL, ALLOW_READER } from '../lib/repo-access.js'
// Fake fs: a set of paths that "exist". Keeps these tests pure — no tmpdirs,
// no cleanup, and the marker path is asserted exactly.
function fakeFs (existing = []) {
const set = new Set(existing)
return { existsSync: (p) => set.has(p) }
}
const REPOS = '/repos'
const marker = (name) => REPOS + '/' + name + '.git/' + PUBLIC_MARKER
test('unmarked repo is denied when enforcing', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: true })
const r = access('secret')
t.is(r.read, false, 'read denied')
t.is(r.reason, DENY_UNMARKED)
t.is(r.wouldDeny, true)
})
test('marked repo is allowed', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([marker('public-one')]), enforce: true })
const r = access('public-one')
t.is(r.read, true)
t.is(r.reason, ALLOW_PUBLIC)
t.is(r.wouldDeny, false)
})
test('ANONYMOUS clone of a PUBLIC repo is allowed — the point of a forge', (t) => {
// Load-bearing, and easy to break by "tightening" the gate: a marked repo
// must be readable with NO identity, NO session, over a remote transport.
// Public read/clone stays open to the world; only WRITE is gated. If this
// ever fails, the forge has stopped being a forge.
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([marker('open-source')]), enforce: true })
const anonHttp = access('open-source', { transport: 'http' })
t.is(anonHttp.read, true, 'anonymous HTTP clone allowed')
t.is(anonHttp.reason, ALLOW_PUBLIC, 'allowed by the marker, not by identity')
t.is(access('open-source', { transport: 'ssh' }).read, true, 'anonymous SSH read allowed')
t.is(access('open-source', { transport: 'swarm' }).read, true, 'anonymous P2P read allowed')
t.is(access('open-source', {}).read, true, 'no transport, no identity — still allowed')
// ...and it is still only READ. Public never implies push.
t.is(anonHttp.write, false, 'public does not grant write')
})
test('the marker path is exactly <reposDir>/<name>.git/pear-git-export-ok', (t) => {
// Guards against a refactor that changes the layout out from under the
// backfill script — those two MUST agree or the migration silently no-ops.
const seen = []
const access = createRepoAccess({
reposDir: REPOS,
fs: { existsSync: (p) => { seen.push(p); return false } },
enforce: true
})
access('repo-x')
t.is(seen.length, 1)
t.is(seen[0], '/repos/repo-x.git/pear-git-export-ok')
})
test('NOT enforcing: decision is computed but not acted on', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: false })
const r = access('secret')
t.is(r.read, true, 'still readable — rollout must not break the fleet')
t.is(r.wouldDeny, true, 'but the deny is reported so it can be logged')
t.is(r.enforced, false)
t.is(r.reason, DENY_UNMARKED)
})
test('enforce defaults to false', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]) })
t.is(access('anything').enforced, false, 'safe by default — opt in to enforcement')
})
test('retirement outranks a public marker', (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([marker('gone')]),
isRetired: (n) => n === 'gone',
enforce: true
})
const r = access('gone')
t.is(r.read, false, 'retired ∧ public => denied')
t.is(r.reason, DENY_RETIRED)
})
test('local transports read unmarked repos (host-it-privately case)', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: true })
t.is(access('secret', { transport: 'uds' }).read, true)
t.is(access('secret', { transport: 'uds' }).reason, ALLOW_LOCAL)
t.is(access('secret', { transport: 'local' }).read, true)
})
test('http and swarm are NOT local', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: true })
t.is(access('secret', { transport: 'http' }).read, false, 'http must not be treated as local')
t.is(access('secret', { transport: 'swarm' }).read, false)
t.is(access('secret', {}).read, false, 'absent transport is not local')
})
test('invalid / empty names are denied, never thrown on', (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([]),
isValidName: (n) => /^[a-z0-9-]+$/.test(n),
enforce: true
})
t.is(access('').reason, DENY_BAD_NAME)
t.is(access(null).reason, DENY_BAD_NAME)
t.is(access('../../etc/passwd').reason, DENY_BAD_NAME)
t.is(access('../../etc/passwd').read, false)
})
test('an fs that throws is treated as not-public, not as a crash', (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: { existsSync: () => { throw new Error('EIO') } },
enforce: true
})
const r = access('repo')
t.is(r.read, false, 'fail closed')
t.is(r.reason, DENY_UNMARKED)
})
test('write is never granted here — push gates keep that authority', (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([marker('pub')]), enforce: true })
t.is(access('pub').write, false, 'this module governs READ only')
t.is(access('pub', { transport: 'uds' }).write, false)
})
test('reposDir is required', (t) => {
t.exception(() => createRepoAccess({}), /requires \{ reposDir \}/)
})
// ── ensureExportMarker ─────────────────────────────────────────
function writableFs (existing = []) {
const set = new Set(existing)
return {
existsSync: (p) => set.has(p),
writeFileSync: (p) => set.add(p),
_has: (p) => set.has(p),
_size: () => set.size
}
}
test('ensureExportMarker writes to the SAME path the gate reads', (t) => {
// The whole rollout hinges on these agreeing. If they diverged, creation
// would mark repos with a file the gate ignores and every new repo would be
// invisible the moment enforcement turned on.
const f = writableFs()
t.is(ensureExportMarker(REPOS, 'newrepo', f), true, 'wrote')
t.is(f._has(marker('newrepo')), true, 'exactly the path the gate checks')
const access = createRepoAccess({ reposDir: REPOS, fs: f, enforce: true })
t.is(access('newrepo').read, true, 'and the gate now allows it')
})
test('ensureExportMarker is idempotent', (t) => {
const f = writableFs([marker('r')])
t.is(ensureExportMarker(REPOS, 'r', f), false, 'already marked — no write')
t.is(f._size(), 1, 'nothing added')
})
test('ensureExportMarker never throws on a failing fs', (t) => {
// A marker is not worth failing a clone or a pull over.
const f = {
existsSync: () => false,
writeFileSync: () => { throw new Error('EROFS') }
}
t.is(ensureExportMarker(REPOS, 'r', f), false, 'reports failure, does not throw')
})
test('ensureExportMarker ignores missing arguments', (t) => {
const f = writableFs()
t.is(ensureExportMarker(null, 'r', f), false)
t.is(ensureExportMarker(REPOS, null, f), false)
t.is(f._size(), 0, 'wrote nothing')
})
// ── .async — identity-aware variant ────────────────────────────
test('async: an authorized reader may read an unmarked repo', async (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([]),
enforce: true,
isReader: async () => true
})
t.is(access('priv').read, false, 'sync form still denies')
const r = await access.async('priv', { identity: { publicKey: 'pk' } })
t.is(r.read, true)
t.is(r.reason, ALLOW_READER)
t.is(r.wouldDeny, false)
})
test('async: a non-reader is still denied', async (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: true, isReader: async () => false })
const r = await access.async('priv', {})
t.is(r.read, false)
t.is(r.reason, DENY_UNMARKED)
})
test('async: isReader receives the repo name and ctx', async (t) => {
const seen = []
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([]),
enforce: true,
isReader: async (name, ctx) => { seen.push({ name, ctx }); return false }
})
await access.async('repo-y', { identity: { publicKey: 'abc' }, transport: 'http' })
t.is(seen.length, 1)
t.is(seen[0].name, 'repo-y')
t.is(seen[0].ctx.identity.publicKey, 'abc')
})
test('async: retirement is NOT rescuable by identity', async (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([]),
isRetired: () => true,
enforce: true,
isReader: async () => true
})
const r = await access.async('gone', {})
t.is(r.read, false, 'no identity resurrects a tombstoned repo')
t.is(r.reason, DENY_RETIRED)
})
test('async: an invalid name is NOT rescuable by identity', async (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([]),
isValidName: (n) => /^[a-z-]+$/.test(n),
enforce: true,
isReader: async () => true
})
const r = await access.async('../../etc/passwd', {})
t.is(r.read, false)
t.is(r.reason, DENY_BAD_NAME)
})
test('async: isReader THROWING fails closed', async (t) => {
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([]),
enforce: true,
isReader: async () => { throw new Error('tracker unreachable') }
})
const r = await access.async('priv', {})
t.is(r.read, false, 'an unreachable tracker must not open a private repo')
t.is(r.reason, DENY_UNMARKED)
})
test('async: a non-boolean truthy from isReader does not grant', async (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: true, isReader: async () => 'yes' })
t.is((await access.async('priv', {})).read, false, 'strict true required')
})
test('async: no isReader configured behaves exactly like the sync form', async (t) => {
const access = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: true })
const r = await access.async('priv', {})
t.is(r.read, false)
t.is(r.reason, DENY_UNMARKED)
})
test('async: a public repo short-circuits without consulting isReader', async (t) => {
let called = 0
const access = createRepoAccess({
reposDir: REPOS,
fs: fakeFs([marker('pub')]),
enforce: true,
isReader: async () => { called++; return true }
})
const r = await access.async('pub', {})
t.is(r.reason, ALLOW_PUBLIC)
t.is(called, 0, 'no tracker lookup for a public repo')
})
test('async: observe mode still allows, but reports the identity outcome', async (t) => {
const denied = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: false, isReader: async () => false })
const r1 = await denied.async('priv', {})
t.is(r1.read, true, 'observe never breaks the request')
t.is(r1.wouldDeny, true, 'but reports it would have denied')
const allowed = createRepoAccess({ reposDir: REPOS, fs: fakeFs([]), enforce: false, isReader: async () => true })
const r2 = await allowed.async('priv', {})
t.is(r2.wouldDeny, false, 'an authorized reader would not have been denied')
})