Files
backspace/packages/server/test/helpers/seedPeer.ts
T
Jannis Braun c5e3d36689 test(federation-identity): #3 soft / #5 full — DB cascade verification + setup fixture
Adds setupFullDeletionFixture (federated user joins remote space, authors 2
messages with reactions, opens 1-on-1 DM with a live observer). Tests #3 (soft
mode: tombstone + dm_members cleared, messages/reactions retained) and #5 (full
mode: tombstone + messages/reactions purged, surviving 1-on-1 DM channel).

Also fixes seedPeer to install both the URL form (outbound lookup on sender)
and the DOMAIN-claim form (inbound auth on receiver) — required because the
test harness's ephemeral http://127.0.0.1 origin and DOMAIN-derived
getOurOrigin() return different strings, while production has them coincide.
This was latent: test #1 (leave mode) skips S2S, so #3 was the first test to
actually exercise the S2S delete path and surfaced the dual-origin gap.
2026-05-03 23:46:20 +02:00

81 lines
3.2 KiB
TypeScript

import crypto from 'node:crypto';
import type { SpawnedInstance } from './twoInstanceHarness.js';
/**
* Install matching federation_peers rows on both instances pointing at each other,
* with a single shared HMAC secret. Returns the secret for tests that need to sign
* raw S2S requests directly.
*
* IMPORTANT — dual-origin reality in tests:
* Production code stores `peer.origin` as a single string and uses it for BOTH
* (a) outbound URL: `fetch(${peer.origin}/api/...)`, AND
* (b) inbound auth: `WHERE origin = X-Federation-Origin` claim from inbound headers.
* In production with `DOMAIN=example.com`, both reduce to `https://example.com`.
*
* In our test harness, the URL is `http://127.0.0.1:<ephemeral>` but `getOurOrigin()`
* returns `https://${DOMAIN}` (= `https://home.test.local`). These two values are
* DIFFERENT, so we cannot satisfy both with one `peer.origin` row.
*
* Workaround: insert TWO rows per direction — one with the URL form (for outbound
* lookup on the sender) and one with the DOMAIN-claim form (for inbound auth on
* the receiver). The schema has a UNIQUE constraint on `origin`, but the two
* rows have distinct origins so there is no conflict.
*/
export async function peerInstances(
a: SpawnedInstance,
b: SpawnedInstance,
): Promise<string> {
const sharedSecret = crypto.randomBytes(32).toString('hex');
const seedOn = async (target: SpawnedInstance, peerOrigin: string, peerInstanceName: string) => {
const res = await fetch(`${target.origin}/api/admin/test/seed-peer`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
origin: peerOrigin,
hmacSecret: sharedSecret,
status: 'active',
instanceName: peerInstanceName,
}),
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`seed-peer failed on ${target.origin}: ${res.status} ${txt}`);
}
};
// Outbound lookup form (URL) — what users.ts / federation.ts use to find the
// hmacSecret given a body-supplied or DB-stored origin URL.
await seedOn(a, b.origin, b.domain);
await seedOn(b, a.origin, a.domain);
// Inbound auth form (DOMAIN claim) — what the receiver uses to look up the
// peer when validating the X-Federation-Origin header from a sender whose
// `getOurOrigin()` returns `https://${DOMAIN}`.
await seedOn(a, `https://${b.domain}`, b.domain);
await seedOn(b, `https://${a.domain}`, a.domain);
return sharedSecret;
}
/**
* Seed an unreachable peer pointing at a guaranteed-unbound port. Returns the origin string.
* Used by tests #9 and #17 to provoke `error: 'unreachable'` without spawning a real instance.
*/
export async function seedUnreachablePeer(target: SpawnedInstance): Promise<string> {
const fakeOrigin = 'http://127.0.0.1:1';
const sharedSecret = crypto.randomBytes(32).toString('hex');
const res = await fetch(`${target.origin}/api/admin/test/seed-peer`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
origin: fakeOrigin,
hmacSecret: sharedSecret,
status: 'active',
instanceName: 'unreachable.test',
}),
});
if (!res.ok) throw new Error(`seedUnreachablePeer failed: ${res.status}`);
return fakeOrigin;
}