// Regression guard for the firewalled↔firewalled holepunch fix (2026-07-06).
//
// Both the self-custody peer AND the bridge fronting it are typically NAT'd. Hyperswarm/hyperdht
// only completes a holepunch between two firewalled members if BOTH join client:true — a
// client:false member never sends outbound punch packets, so its NAT mapping never opens and the
// other side's packets are dropped. Proven empirically on two firewalled boxes: symmetric
// {server,client} connects in ~2s; asymmetric (peer server-only / bridge client-only) never opens
// (bridge logs cAtt>0 but cOpen:0). A real holepunch can't be reproduced in-process (testnet peers
// are on localhost, never firewalled — the membrane-serve e2e passes with EITHER config), so we
// assert the load-bearing join OPTIONS directly via a spy swarm.
import '../lib/bare-globals.js'
import test from 'brittle'
import b4a from 'b4a'
import { Peer } from '../lib/peer.js'
test('peer.serve joins its rendezvous topics symmetrically (client:true is load-bearing for NAT holepunch)', (t) => {
const joins = []
const fakeSwarm = {
keyPair: { publicKey: b4a.alloc(32, 7) },
on () {}, off () {},
join (topic, opts) { joins.push({ topic, opts }); return { flushed: async () => {} } }
}
// serve() only touches this.did synchronously; corestore/_attachRpc are used inside the (unfired)
// connection handler, so a minimal `this` is enough to exercise the join wiring.
const fakePeer = { did: 'did:plc:holepunchsymmetry0000', corestore: {}, _attachRpc () {} }
Peer.prototype.serve.call(fakePeer, fakeSwarm)
t.is(joins.length, 2, 'serve joins BOTH the serverKey topic and the DID discovery topic')
for (const j of joins) {
t.is(j.opts.server, true, 'joins as server (announce)')
t.is(j.opts.client, true, 'joins as client — REQUIRED so a firewalled peer punches outbound')
}
})