From b5df3d5075a99c79859bd80e97a6b40cc2fd0ca3 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 4 May 2026 00:13:14 +0200 Subject: [PATCH] =?UTF-8?q?test(federation-identity):=20#7=20#8=20?= =?UTF-8?q?=E2=80=94=20owned-spaces=20409=20+=20transfer-and-retry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fixes per-IP rate limit exhaustion in test environments: @fastify/rate-limit v9 has no skip(); use allowList(() => NODE_ENV==='test') which propagates to per-route overrides via mergeParams Object.assign merge. --- packages/server/src/index.ts | 4 ++ .../test/federation-identity-deletion.test.ts | 65 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index f04f9899..af84c459 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -83,6 +83,10 @@ async function main(): Promise { max: 200, timeWindow: '1 minute', keyGenerator: (request) => (request as any).userId || request.ip, + // In test environments every request originates from 127.0.0.1, so + // per-IP rate limits would exhaust across unrelated tests. Bypass all + // rate limiting (both global and per-route) when NODE_ENV=test. + allowList: () => process.env.NODE_ENV === 'test', errorResponseBuilder: (_request, context) => ({ statusCode: 429, error: 'Too Many Requests', diff --git a/packages/server/test/federation-identity-deletion.test.ts b/packages/server/test/federation-identity-deletion.test.ts index 153f35e7..44743f4f 100644 --- a/packages/server/test/federation-identity-deletion.test.ts +++ b/packages/server/test/federation-identity-deletion.test.ts @@ -399,6 +399,71 @@ describe('Federation identity deletion — server suite', () => { expect(remote.dmChannelExists(fx.dmChannelId)).toBe(true); remote.close(); }); + + it('#7 owned-spaces 409: ownership prevents deletion, registry preserved', async () => { + const { createFederatedUser } = await import('./helpers/testUsers.js'); + const { openInspector } = await import('./helpers/dbInspect.js'); + const { seedOwnedSpace } = await import('./helpers/seedSpaceWithStubOwner.js'); + const { homeUser, remoteUser } = await createFederatedUser(harness.home, harness.remote, 't7'); + + // Direct DB seed: the federated user owns a space on the remote + const { spaceId } = seedOwnedSpace(harness.remote, remoteUser.id, 't7-space'); + + const res = await fetch(`${harness.home.origin}/api/users/@me/federation-identity/delete`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${homeUser.token}` }, + body: JSON.stringify({ origins: [harness.remote.origin], mode: 'full' }), + }); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.results[harness.remote.origin].success).toBe(false); + expect(body.results[harness.remote.origin].error).toBe('owns_spaces'); + expect(body.results[harness.remote.origin].ownedSpaces).toEqual([ + expect.objectContaining({ id: spaceId, name: 't7-space' }), + ]); + + // Remote user UNCHANGED, home registry UNCHANGED + const remote = openInspector(harness.remote); + expect(remote.user(remoteUser.id)!.isDeleted).toBe(0); + remote.close(); + const home = openInspector(harness.home); + expect(home.registryRow(homeUser.id, harness.remote.origin)).toBeTruthy(); + home.close(); + }); + + it('#8 owned-spaces resolved by transferring ownership, retry succeeds', async () => { + const { createFederatedUser, registerLocal } = await import('./helpers/testUsers.js'); + const { openInspector } = await import('./helpers/dbInspect.js'); + const { seedOwnedSpace } = await import('./helpers/seedSpaceWithStubOwner.js'); + const { homeUser, remoteUser } = await createFederatedUser(harness.home, harness.remote, 't8'); + const newOwner = await registerLocal(harness.remote, 't8_newowner'); + const { spaceId } = seedOwnedSpace(harness.remote, remoteUser.id, 't8-space'); + + // Transfer ownership via direct DB write — there is no production endpoint + // for a federated stub to transfer ownership (federated stubs aren't supposed + // to BE owners in the first place; we only got here via test seeding). + const Database = (await import('better-sqlite3')).default; + const db = new Database(harness.remote.dbPath); + try { + db.prepare('UPDATE spaces SET owner_id = ? WHERE id = ?').run(newOwner.id, spaceId); + } finally { + db.close(); + } + + const res = await fetch(`${harness.home.origin}/api/users/@me/federation-identity/delete`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${homeUser.token}` }, + body: JSON.stringify({ origins: [harness.remote.origin], mode: 'full' }), + }); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.results[harness.remote.origin].success).toBe(true); + + const remote = openInspector(harness.remote); + expect(remote.user(remoteUser.id)!.isDeleted).toBe(1); + remote.close(); + }); + }); let multiHarness: MultiRemoteHarness;