agent-ecosystem/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts
2026-04-16 22:52:56 +03:00

155 lines
4.7 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
const execCliMock = vi.fn();
const buildProviderAwareCliEnvMock = vi.fn();
vi.mock('@main/utils/childProcess', () => ({
execCli: (...args: Parameters<typeof execCliMock>) => execCliMock(...args),
}));
vi.mock('@main/services/runtime/providerAwareCliEnv', () => ({
buildProviderAwareCliEnv: (...args: Parameters<typeof buildProviderAwareCliEnvMock>) =>
buildProviderAwareCliEnvMock(...args),
}));
import {
CliProviderModelAvailabilityService,
type ProviderModelAvailabilityContext,
} from '@main/services/runtime/CliProviderModelAvailabilityService';
function createContext(models: string[]): ProviderModelAvailabilityContext {
return {
binaryPath: '/usr/local/bin/claude',
installedVersion: '2.3.4',
provider: {
providerId: 'codex',
models,
supported: true,
authenticated: true,
authMethod: 'oauth_token',
selectedBackendId: 'chatgpt',
resolvedBackendId: 'chatgpt',
capabilities: {
teamLaunch: true,
oneShot: true,
},
backend: {
kind: 'openai',
label: 'OpenAI',
endpointLabel: 'chatgpt.com/backend-api/codex/responses',
},
},
};
}
describe('CliProviderModelAvailabilityService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('reuses probe cache for the same provider signature', async () => {
buildProviderAwareCliEnvMock.mockResolvedValue({
env: { HOME: '/Users/tester' },
connectionIssues: {},
});
execCliMock.mockResolvedValue({ stdout: 'PONG', stderr: '' });
const service = new CliProviderModelAvailabilityService();
const context = createContext(['gpt-5.4', 'gpt-5.3-codex']);
expect(service.getSnapshot(context).modelVerificationState).toBe('verifying');
expect(service.getSnapshot(context).modelVerificationState).toBe('verifying');
await vi.waitFor(() => {
expect(execCliMock).toHaveBeenCalledTimes(2);
});
expect(service.getSnapshot(context).modelAvailability).toEqual([
expect.objectContaining({ modelId: 'gpt-5.4', status: 'available' }),
expect.objectContaining({ modelId: 'gpt-5.3-codex', status: 'available' }),
]);
expect(execCliMock).toHaveBeenCalledTimes(2);
});
it('marks visible unsupported models as unavailable with the runtime reason', async () => {
buildProviderAwareCliEnvMock.mockResolvedValue({
env: { HOME: '/Users/tester' },
connectionIssues: {},
});
execCliMock.mockRejectedValue(
new Error(
"The 'gpt-5.1-codex-max' model is not supported when using Codex with a ChatGPT account."
)
);
const onUpdate = vi.fn();
const service = new CliProviderModelAvailabilityService(onUpdate);
service.getSnapshot(createContext(['gpt-5.1-codex-max']));
await vi.waitFor(() => {
expect(onUpdate).toHaveBeenCalledWith(
'codex',
expect.any(String),
expect.objectContaining({
modelAvailability: [
expect.objectContaining({
modelId: 'gpt-5.1-codex-max',
status: 'unavailable',
reason: 'Not available with Codex ChatGPT subscription',
}),
],
})
);
});
});
it('marks timeout-like probe failures as unknown instead of unavailable', async () => {
buildProviderAwareCliEnvMock.mockResolvedValue({
env: { HOME: '/Users/tester' },
connectionIssues: {},
});
execCliMock.mockRejectedValue(new Error('Command timed out after 45000ms'));
const onUpdate = vi.fn();
const service = new CliProviderModelAvailabilityService(onUpdate);
service.getSnapshot(createContext(['gpt-5.4']));
await vi.waitFor(() => {
expect(onUpdate).toHaveBeenCalledWith(
'codex',
expect.any(String),
expect.objectContaining({
modelAvailability: [
expect.objectContaining({
modelId: 'gpt-5.4',
status: 'unknown',
reason: 'Model verification timed out',
}),
],
})
);
});
});
it('invalidates the cache when the provider signature changes', async () => {
buildProviderAwareCliEnvMock.mockResolvedValue({
env: { HOME: '/Users/tester' },
connectionIssues: {},
});
execCliMock.mockResolvedValue({ stdout: 'PONG', stderr: '' });
const service = new CliProviderModelAvailabilityService();
service.getSnapshot(createContext(['gpt-5.4']));
await vi.waitFor(() => {
expect(execCliMock).toHaveBeenCalledTimes(1);
});
service.getSnapshot(createContext(['gpt-5.4', 'gpt-5.2']));
await vi.waitFor(() => {
expect(execCliMock).toHaveBeenCalledTimes(3);
});
});
});