/**
* Default NameRegistry subscription — a fresh machine must resolve bare
* app names without a manual `pgh names subscribe`.
*
* Bug this pins: name resolution is subscription-based (`_resolveNameAcross`
* = own registry, then each subscribed replica). Nothing seeded either source
* on a fresh machine — not `pgh init`, not install.sh, not any package.json
* default — so a new peer booted its OWN empty NameRegistry, had zero
* replicas, and every documented `pgh app install <name>` failed with
* "name not found in pgh-sidecar's NameRegistry". Only a pgh:// URL worked.
*
* Contract: configuration WINS OUTRIGHT over the default (no merging), so a
* peer subscribed to its own namespace registries never has ours forced on it.
*
* Constructor-only — no boot, no swarm. The subscription decision happens in
* the Sidecar constructor; opening the replicas is gated behind enableSeed.
*/
import test from 'brittle'
import fs from '#node:fs'
import path from '#node:path'
import os from '#node:os'
import process from '#node:process'
import { DEFAULT_NAME_REGISTRIES } from 'pgh/bootstrap'
import { Sidecar } from '../lib/index.js'
const OTHER_KEY = 'a'.repeat(64)
const THIRD_KEY = 'b'.repeat(64)
// Point subscriptions.json at a scratch file so the test never reads (or
// writes) the real ~/.config/pgh/subscriptions.json.
function withSubscriptions (contents, fn) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgh-subs-'))
const file = path.join(dir, 'subscriptions.json')
if (contents !== null) fs.writeFileSync(file, JSON.stringify(contents, null, 2))
// NB: under Bare `process.env` is a Proxy whose deleteProperty trap returns
// false — `delete process.env.X` THROWS. Both readers treat '' as unset
// (plain falsy checks), so clear by assignment instead.
const prevPath = process.env.PGH_SUBSCRIPTIONS_PATH || ''
const prevEnv = process.env.PGH_NAME_REGISTRIES || ''
process.env.PGH_SUBSCRIPTIONS_PATH = file
process.env.PGH_NAME_REGISTRIES = ''
try {
return fn()
} finally {
process.env.PGH_SUBSCRIPTIONS_PATH = prevPath
process.env.PGH_NAME_REGISTRIES = prevEnv
fs.rmSync(dir, { recursive: true, force: true })
}
}
// Constructing a Sidecar touches no disk and starts nothing — `_open` does.
function subscriptionsFor (opts = {}) {
return new Sidecar({ enableSeed: false, ...opts }).nameRegistrySubscriptions
}
test('default is non-empty and well-formed (64-hex keys)', (t) => {
t.ok(DEFAULT_NAME_REGISTRIES.length > 0, 'ships at least one default registry')
for (const k of DEFAULT_NAME_REGISTRIES) {
t.ok(/^[0-9a-f]{64}$/.test(k), k.slice(0, 12) + '… is a 64-char hex key')
}
})
test('fresh machine (no subscriptions.json) gets the default', (t) => {
withSubscriptions(null, () => {
const subs = subscriptionsFor()
for (const k of DEFAULT_NAME_REGISTRIES) {
t.ok(subs.includes(k), 'subscribed to default ' + k.slice(0, 12) + '…')
}
})
})
test('subscriptions.json without a nameRegistries key gets the default', (t) => {
// The real-world fresh shape: `pgh app install pgh://…` writes `registries`
// (release) and never touches `nameRegistries`.
withSubscriptions({ registries: [OTHER_KEY] }, () => {
const subs = subscriptionsFor()
t.alike(subs, [...DEFAULT_NAME_REGISTRIES], 'release subs do not count as name subs')
})
})
test('configured nameRegistries win outright — default is NOT merged in', (t) => {
withSubscriptions({ nameRegistries: [OTHER_KEY] }, () => {
const subs = subscriptionsFor()
t.alike(subs, [OTHER_KEY], 'exactly what the user configured')
for (const k of DEFAULT_NAME_REGISTRIES) {
t.absent(subs.includes(k), 'default not forced on a configured peer')
}
})
})
test('PGH_NAME_REGISTRIES wins outright over the default', (t) => {
withSubscriptions(null, () => {
process.env.PGH_NAME_REGISTRIES = THIRD_KEY
const subs = subscriptionsFor()
t.alike(subs, [THIRD_KEY], 'env-configured set replaces the default')
})
})
test('explicit opts win over everything', (t) => {
withSubscriptions(null, () => {
t.alike(subscriptionsFor({ nameRegistrySubscriptions: [OTHER_KEY] }), [OTHER_KEY])
// An explicit empty list is a real choice (subscribe to nothing), not
// "unset" — it must not fall through to the default.
t.alike(subscriptionsFor({ nameRegistrySubscriptions: [] }), [], 'explicit [] opts out')
})
})