96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { PiManagedInputRevisionCoordinator } from '@electron/coding-runtime/pi/managed-input-revision';
|
|
import { PiProviderRefreshCoordinator } from '@electron/coding-runtime/pi/provider-refresh';
|
|
|
|
describe('Pi Provider refresh coordination', () => {
|
|
it('coalesces concurrent refreshes for one account but not different accounts', async () => {
|
|
const coordinator = new PiProviderRefreshCoordinator();
|
|
let releaseFirst!: () => void;
|
|
const firstWait = new Promise<void>((resolve) => { releaseFirst = resolve; });
|
|
const sameAccountRefresh = vi.fn(async () => { await firstWait; });
|
|
const otherAccountRefresh = vi.fn(async () => undefined);
|
|
|
|
const first = coordinator.refreshAccount('account-a', sameAccountRefresh);
|
|
const second = coordinator.refreshAccount('account-a', sameAccountRefresh);
|
|
const other = coordinator.refreshAccount('account-b', otherAccountRefresh);
|
|
await other;
|
|
expect(coordinator.pendingAccountCount).toBe(1);
|
|
releaseFirst();
|
|
await Promise.all([first, second]);
|
|
|
|
expect(sameAccountRefresh).toHaveBeenCalledTimes(1);
|
|
expect(otherAccountRefresh).toHaveBeenCalledTimes(1);
|
|
expect(coordinator.pendingAccountCount).toBe(0);
|
|
});
|
|
|
|
it('refreshes and reopens at most once for one failed operation', async () => {
|
|
const coordinator = new PiProviderRefreshCoordinator();
|
|
const authError = new Error('401');
|
|
const operation = vi.fn(async () => { throw authError; });
|
|
const refreshCredential = vi.fn(async () => undefined);
|
|
const reopenWorker = vi.fn(async () => undefined);
|
|
|
|
await expect(coordinator.withSingleAuthRecovery({
|
|
accountId: 'account-a',
|
|
operation,
|
|
isAuthenticationError: (error) => error === authError,
|
|
refreshCredential,
|
|
reopenWorker,
|
|
})).rejects.toBe(authError);
|
|
|
|
expect(operation).toHaveBeenCalledTimes(2);
|
|
expect(operation.mock.calls.map(([attempt]) => attempt)).toEqual([0, 1]);
|
|
expect(refreshCredential).toHaveBeenCalledTimes(1);
|
|
expect(reopenWorker).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not refresh for a non-authentication failure', async () => {
|
|
const coordinator = new PiProviderRefreshCoordinator();
|
|
const failure = new Error('timeout');
|
|
const refreshCredential = vi.fn(async () => undefined);
|
|
const reopenWorker = vi.fn(async () => undefined);
|
|
await expect(coordinator.withSingleAuthRecovery({
|
|
accountId: 'account-a',
|
|
operation: async () => { throw failure; },
|
|
isAuthenticationError: () => false,
|
|
refreshCredential,
|
|
reopenWorker,
|
|
})).rejects.toBe(failure);
|
|
expect(refreshCredential).not.toHaveBeenCalled();
|
|
expect(reopenWorker).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Pi managed input revision coordination', () => {
|
|
it('rebuilds an idle stale worker before its next prompt', () => {
|
|
const coordinator = new PiManagedInputRevisionCoordinator();
|
|
coordinator.registerWorker('worker');
|
|
coordinator.markProviderStale();
|
|
|
|
expect(coordinator.beforePrompt('worker')).toEqual({
|
|
action: 'rebuild-before-prompt',
|
|
revision: { provider: 2, resources: 1 },
|
|
});
|
|
coordinator.applyCurrentRevision('worker');
|
|
expect(coordinator.beforePrompt('worker').action).toBe('reuse');
|
|
});
|
|
|
|
it('keeps a running snapshot and schedules rebuild only after settlement', () => {
|
|
const coordinator = new PiManagedInputRevisionCoordinator();
|
|
coordinator.registerWorker('worker');
|
|
const runRevision = coordinator.beginRun('worker');
|
|
coordinator.markResourcesStale();
|
|
|
|
expect(runRevision).toEqual({ provider: 1, resources: 1 });
|
|
expect(coordinator.beforePrompt('worker')).toEqual({
|
|
action: 'defer-until-settled',
|
|
revision: { provider: 1, resources: 1 },
|
|
});
|
|
expect(() => coordinator.applyCurrentRevision('worker')).toThrow('run is active');
|
|
expect(coordinator.settleRun('worker')).toEqual({
|
|
action: 'rebuild-after-settled',
|
|
revision: { provider: 1, resources: 2 },
|
|
});
|
|
});
|
|
});
|