fix(federation): heal path broadcasts user_updated so tombstoned-stub DMs update live (S4)

This commit is contained in:
Jannis Braun
2026-07-02 15:50:08 +02:00
parent 3ebdd048bd
commit d03f8e4f77
2 changed files with 41 additions and 1 deletions
@@ -279,6 +279,33 @@ describe('healResetIncarnation — heal after authenticated re-peer', () => {
expect(stub.isDeleted, `reason=${reason}`).toBe(0);
}
});
it('broadcasts user_updated to the survivor of a tombstoned stub 1-on-1 DM', async () => {
seedPeer();
seedJournal('E0'); // deadEpoch E0 (differs from the E1 we heal with)
seedUser('stub-1', { passwordHash: STUB }); // pure S2S stub on ORIGIN
flag('stub-1'); // federation_heal_pending = 1
// Survivor (local native user) + 1-on-1 DM with the flagged stub.
testDb.insert(schema.users).values({
id: 'survivor', username: 'survivor', passwordHash: '$2b$10$localhash',
homeInstance: null, homeUserId: null, isDeleted: 0, createdAt: Date.now(),
}).run();
testDb.insert(schema.dmChannels).values({ id: 'dm_heal', ownerId: null, createdAt: Date.now() }).run();
testDb.insert(schema.dmMembers).values({ dmChannelId: 'dm_heal', userId: 'stub-1', closed: 0 }).run();
testDb.insert(schema.dmMembers).values({ dmChannelId: 'dm_heal', userId: 'survivor', closed: 0 }).run();
const { connectionManager } = await import('../ws/handler.js');
const sendToUser = connectionManager.sendToUser as ReturnType<typeof vi.fn>;
sendToUser.mockClear();
const { healResetIncarnation } = await import('./federationReset.js');
healResetIncarnation(ORIGIN, 'E1', 'initiate_accepted'); // genuine reset (E0 != E1) → tombstones stub-1
const call = sendToUser.mock.calls.find(([uid, ev]) => uid === 'survivor' && ev?.type === 'user_updated');
expect(call).toBeTruthy();
expect(call![1].user).toMatchObject({ id: 'stub-1', isDeleted: true, username: 'Deleted User' });
});
});
describe('healResetIncarnation — real-account quarantine (Phase 2)', () => {