import { describe, expect, mock, test } from 'bun:test'; import type { SidecarCapabilities } from '@shared/contracts/sidecar'; mock.module('electron', () => { const electronMock = { app: { isPackaged: false, getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx', getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures', }, dialog: { showOpenDialog: async () => ({ canceled: true, filePaths: [] }), }, shell: { openPath: async () => '', }, }; return { ...electronMock, default: electronMock, }; }); mock.module('keytar', () => ({ default: { getPassword: async () => null, setPassword: async () => undefined, deletePassword: async () => false, }, })); const { AryxAppService } = await import('@main/AryxAppService'); const SIDECAR_STOPPED_BEFORE_COMPLETION_MESSAGE = 'The .NET sidecar was stopped before the command completed.'; const CAPABILITIES_FIXTURE: SidecarCapabilities = { runtime: 'dotnet-maf', modes: { single: { available: true }, sequential: { available: true }, concurrent: { available: true }, handoff: { available: true }, magentic: { available: false, reason: 'Not yet supported.' }, 'group-chat': { available: true }, }, models: [], runtimeTools: [], connection: { status: 'ready', summary: 'Connected', checkedAt: '2026-03-25T00:00:00.000Z', }, }; function setSidecar( service: InstanceType, describeCapabilities: () => Promise, ): void { ( service as unknown as { sidecar: { describeCapabilities: () => Promise; dispose: () => Promise; }; } ).sidecar = { describeCapabilities, dispose: async () => undefined, }; } function createDeferred() { let resolve!: (value: T) => void; const promise = new Promise((resolvePromise) => { resolve = resolvePromise; }); return { promise, resolve }; } describe('AryxAppService sidecar capabilities', () => { test('retries describe-capabilities after an intentional sidecar stop', async () => { const service = new AryxAppService(); let attempts = 0; setSidecar(service, async () => { attempts += 1; if (attempts === 1) { throw new Error(SIDECAR_STOPPED_BEFORE_COMPLETION_MESSAGE); } return CAPABILITIES_FIXTURE; }); await expect(service.describeSidecarCapabilities()).resolves.toEqual(CAPABILITIES_FIXTURE); expect(attempts).toBe(2); }); test('coalesces concurrent capability requests while the cache is empty', async () => { const service = new AryxAppService(); const deferred = createDeferred(); let calls = 0; setSidecar(service, async () => { calls += 1; return deferred.promise; }); const first = service.describeSidecarCapabilities(); const second = service.describeSidecarCapabilities(); expect(calls).toBe(1); deferred.resolve(CAPABILITIES_FIXTURE); await expect(first).resolves.toEqual(CAPABILITIES_FIXTURE); await expect(second).resolves.toEqual(CAPABILITIES_FIXTURE); await expect(service.describeSidecarCapabilities()).resolves.toEqual(CAPABILITIES_FIXTURE); expect(calls).toBe(1); }); });