import { describe, it, expect, beforeEach, vi } from 'vitest'; const getFederationRegistry = vi.fn(); const putFederationRegistry = vi.fn(async () => ({ ok: true, updatedAt: 1 })); const ensurePeered = vi.fn(async () => ({ peeringStatus: 'active' })); vi.mock('../api/client', () => ({ api: { users: { getFederationRegistry: () => getFederationRegistry(), putFederationRegistry: (data: unknown) => putFederationRegistry(data), me: vi.fn(), }, federation: { ensurePeered: (data: unknown) => ensurePeered(data), }, }, createApiClient: () => ({}), })); vi.mock('../hooks/useWebSocket', () => ({ connectInstance: vi.fn(), disconnectInstance: vi.fn(), disconnectAllRemote: vi.fn(), })); vi.mock('../utils/dmOriginFailover', () => ({ failoverDmOriginsFromDisconnected: vi.fn(), })); vi.mock('../utils/federationOps', () => ({ clearPasswordSyncTimers: vi.fn() })); vi.mock('../audio/AudioManager', () => ({ AudioManager: { getInstance: vi.fn().mockReturnValue({ setOutputDevice: vi.fn(), setVolume: vi.fn() }) }, })); const mockUser = { id: 'user-1', username: 'youruser', homeInstance: null, homeUserId: 'user-1', replicatedInstances: [ { origin: 'https://orbit.example', username: 'youruser@nova.example' }, ], }; vi.mock('./authStore', () => ({ useAuthStore: Object.assign( (selector: (s: unknown) => unknown) => selector({ user: mockUser, token: 'tok' }), { getState: () => ({ user: mockUser, token: 'tok' }), setState: vi.fn(), subscribe: vi.fn() } ), })); import { useInstanceStore } from './instanceStore'; beforeEach(() => { getFederationRegistry.mockReset(); putFederationRegistry.mockClear(); ensurePeered.mockClear(); localStorage.clear(); useInstanceStore.setState({ instances: [], registry: new Map(), registryUpdatedAt: 0, _autoConnectDone: false, _registrySyncReady: false, pendingSyncOrigins: [], }); }); describe('instanceStore registry sync gating', () => { it('does NOT PUT registry when initial GET fails (prevents empty-clobber)', async () => { getFederationRegistry.mockRejectedValueOnce(new Error('network')); await useInstanceStore.getState().autoConnectAll(); expect(putFederationRegistry).not.toHaveBeenCalled(); expect(useInstanceStore.getState()._registrySyncReady).toBe(false); }); it('synthesizes replicatedInstances entries when GET fails so UI is not empty', async () => { getFederationRegistry.mockRejectedValueOnce(new Error('network')); await useInstanceStore.getState().autoConnectAll(); const reg = useInstanceStore.getState().registry; expect(reg.has('https://orbit.example')).toBe(true); expect(reg.get('https://orbit.example')?.status).toBe('auth_expired'); }); it('PUTs registry after a successful initial GET and flips _registrySyncReady', async () => { getFederationRegistry.mockResolvedValueOnce({ registry: [{ origin: 'https://orbit.example', label: 'Orbit', username: 'youruser@nova.example', remoteUserId: 'remote-1', status: 'auth_expired', addedAt: 1, lastConnectedAt: 1, disconnectedAt: null, errorMessage: null, }], updatedAt: 100, }); await useInstanceStore.getState().autoConnectAll(); expect(useInstanceStore.getState()._registrySyncReady).toBe(true); expect(putFederationRegistry).toHaveBeenCalledTimes(1); const payload = putFederationRegistry.mock.calls[0]![0] as { registry: unknown[] }; expect(payload.registry).toHaveLength(1); }); it('syncRegistry no-ops while _registrySyncReady is false (post-failure mutation)', async () => { getFederationRegistry.mockRejectedValueOnce(new Error('network')); await useInstanceStore.getState().autoConnectAll(); putFederationRegistry.mockClear(); useInstanceStore.setState({ registry: new Map([['https://other.example', { origin: 'https://other.example', label: 'Other', username: 'u', remoteUserId: '', status: 'connected' as const, addedAt: 1, lastConnectedAt: 1, disconnectedAt: null, errorMessage: null, }]]), registryUpdatedAt: Date.now(), }); await useInstanceStore.getState().syncRegistry(); expect(putFederationRegistry).not.toHaveBeenCalled(); }); it('reset clears _registrySyncReady', async () => { getFederationRegistry.mockResolvedValueOnce({ registry: [], updatedAt: 1 }); await useInstanceStore.getState().autoConnectAll(); expect(useInstanceStore.getState()._registrySyncReady).toBe(true); useInstanceStore.getState().reset(); expect(useInstanceStore.getState()._registrySyncReady).toBe(false); }); });