From bee13817f06995cf690ee4e4aafed956be78ab69 Mon Sep 17 00:00:00 2001 From: xxxigm Date: Mon, 15 Jun 2026 20:31:42 +0700 Subject: [PATCH] test(desktop): cover $connection resync on profile switch Asserts ensureGatewayProfile keeps $connection in lockstep with the active profile's backend: activating a remote pool profile flips mode to remote, returning to default resyncs to local, a failed descriptor fetch leaves the prior connection intact, and a same-profile activation doesn't churn it. Regression coverage for #46651. --- apps/desktop/src/store/profile.test.ts | 89 ++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 apps/desktop/src/store/profile.test.ts diff --git a/apps/desktop/src/store/profile.test.ts b/apps/desktop/src/store/profile.test.ts new file mode 100644 index 00000000000..d98ee703085 --- /dev/null +++ b/apps/desktop/src/store/profile.test.ts @@ -0,0 +1,89 @@ +import { atom } from 'nanostores' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import type { HermesConnection } from '@/global' + +// Keep profile.ts's side-effecting imports inert: the gateway socket layer and +// the REST query client must not run for real in a unit test. +const ensureGatewayForProfile = vi.fn(async () => undefined) +const $gateway = atom({ id: 'live-socket' }) + +vi.mock('@/store/gateway', () => ({ $gateway, ensureGatewayForProfile })) +vi.mock('@/hermes', () => ({ + getProfiles: vi.fn(async () => ({ profiles: [] })), + setApiRequestProfile: vi.fn() +})) +vi.mock('@/lib/query-client', () => ({ queryClient: { invalidateQueries: vi.fn() } })) + +const { $activeGatewayProfile, ensureGatewayProfile } = await import('./profile') +const { $connection } = await import('./session') + +const remoteConn = (over: Partial = {}): HermesConnection => + ({ baseUrl: 'https://hermes-roy.tail.ts.net', mode: 'remote', profile: 'vps-remote', ...over }) as HermesConnection + +const localConn = (over: Partial = {}): HermesConnection => + ({ baseUrl: '', mode: 'local', profile: 'default', ...over }) as HermesConnection + +const getConnection = vi.fn<(profile?: string | null) => Promise>() + +beforeEach(() => { + getConnection.mockReset() + ensureGatewayForProfile.mockClear() + $gateway.set({ id: 'live-socket' }) + $activeGatewayProfile.set('default') + $connection.set(localConn()) + vi.stubGlobal('window', { hermesDesktop: { getConnection } }) +}) + +afterEach(() => { + vi.unstubAllGlobals() + $connection.set(null) +}) + +describe('ensureGatewayProfile → $connection sync (#46651)', () => { + it('refreshes $connection to the remote descriptor when activating a remote pool profile', async () => { + // Regression: the primary window backend is local, so $connection.mode is + // "local". Activating the remote profile must flip it to "remote" — without + // this, image attach uses path-based image.attach against the remote + // gateway ("image not found: C:\\…") instead of image.attach_bytes. + getConnection.mockResolvedValue(remoteConn()) + + await ensureGatewayProfile('vps-remote') + + expect(ensureGatewayForProfile).toHaveBeenCalledWith('vps-remote') + expect(getConnection).toHaveBeenCalledWith('vps-remote') + expect($connection.get()?.mode).toBe('remote') + expect($connection.get()?.profile).toBe('vps-remote') + }) + + it('resyncs $connection back to local when returning to the default profile', async () => { + $activeGatewayProfile.set('vps-remote') + $connection.set(remoteConn()) + getConnection.mockResolvedValue(localConn()) + + await ensureGatewayProfile('default') + + expect(getConnection).toHaveBeenCalledWith('default') + expect($connection.get()?.mode).toBe('local') + }) + + it('leaves the prior connection intact when the descriptor fetch fails', async () => { + getConnection.mockRejectedValue(new Error('backend unreachable')) + + await ensureGatewayProfile('vps-remote') + + // Best-effort: boot/reconnect resyncs later; we must not null it out here. + expect($connection.get()?.mode).toBe('local') + }) + + it('does not churn $connection when the target is already the active profile', async () => { + $activeGatewayProfile.set('vps-remote') + $connection.set(remoteConn()) + + await ensureGatewayProfile('vps-remote') + + expect(getConnection).not.toHaveBeenCalled() + expect(ensureGatewayForProfile).not.toHaveBeenCalled() + expect($connection.get()?.mode).toBe('remote') + }) +})