agent-ecosystem/test/main/features/runtime-provider-management/AgentTeamsRuntimeProviderManagementCliClient.test.ts

212 lines
6.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const buildProviderAwareCliEnvMock = vi.fn();
const resolveBinaryMock = vi.fn();
const execCliMock = vi.fn();
const spawnCliMock = vi.fn();
const resolveInteractiveShellEnvMock = vi.fn();
vi.mock('@main/services/runtime/providerAwareCliEnv', () => ({
buildProviderAwareCliEnv: (...args: unknown[]) => buildProviderAwareCliEnvMock(...args),
}));
vi.mock('@main/services/team/ClaudeBinaryResolver', () => ({
ClaudeBinaryResolver: {
resolve: () => resolveBinaryMock(),
},
}));
vi.mock('@main/utils/childProcess', () => ({
execCli: (...args: unknown[]) => execCliMock(...args),
spawnCli: (...args: unknown[]) => spawnCliMock(...args),
killProcessTree: vi.fn(),
}));
vi.mock('@main/utils/shellEnv', () => ({
resolveInteractiveShellEnv: () => resolveInteractiveShellEnvMock(),
}));
import { AgentTeamsRuntimeProviderManagementCliClient } from '../../../../src/features/runtime-provider-management/main/infrastructure/AgentTeamsRuntimeProviderManagementCliClient';
describe('AgentTeamsRuntimeProviderManagementCliClient', () => {
beforeEach(() => {
vi.clearAllMocks();
resolveBinaryMock.mockResolvedValue('/repo/cli-dev');
resolveInteractiveShellEnvMock.mockResolvedValue({ PATH: '/Users/test/.bun/bin:/usr/bin' });
buildProviderAwareCliEnvMock.mockResolvedValue({
env: { PATH: '/Users/test/.bun/bin:/usr/bin' },
connectionIssues: {},
providerArgs: [],
});
});
it('returns stderr details for failed model tests instead of hiding them behind the command', async () => {
const error = new Error('Command failed: /repo/cli-dev runtime providers test-model');
Object.assign(error, {
stderr: './cli-dev: line 47: exec: bun: not found\n',
stdout: '',
});
execCliMock.mockRejectedValue(error);
const client = new AgentTeamsRuntimeProviderManagementCliClient();
const response = await client.testModel({
runtimeId: 'opencode',
providerId: 'opencode',
modelId: 'opencode/nemotron-3-super-free',
});
expect(response.error?.message).toBe('./cli-dev: line 47: exec: bun: not found');
expect(response.error?.message).not.toContain('runtime providers test-model');
});
it('parses JSON error responses from stdout when the CLI exits non-zero', async () => {
const error = new Error('Command failed: /repo/cli-dev runtime providers test-model');
Object.assign(error, {
stdout: JSON.stringify({
schemaVersion: 1,
runtimeId: 'opencode',
error: {
code: 'auth-required',
message: 'Provider opencode must be connected before testing a model',
recoverable: true,
},
}),
stderr: '',
});
execCliMock.mockRejectedValue(error);
const client = new AgentTeamsRuntimeProviderManagementCliClient();
const response = await client.testModel({
runtimeId: 'opencode',
providerId: 'opencode',
modelId: 'opencode/nemotron-3-super-free',
});
expect(response.error?.code).toBe('auth-required');
expect(response.error?.message).toBe(
'Provider opencode must be connected before testing a model'
);
});
it('parses JSON error responses from failed forget commands', async () => {
const error = new Error('Command failed: /repo/cli-dev runtime providers forget');
Object.assign(error, {
stdout: JSON.stringify({
schemaVersion: 1,
runtimeId: 'opencode',
error: {
code: 'unsupported-action',
message: 'This OpenCode runtime does not advertise credential removal through /doc',
recoverable: true,
},
}),
stderr: '',
});
execCliMock.mockRejectedValue(error);
const client = new AgentTeamsRuntimeProviderManagementCliClient();
const response = await client.forgetCredential({
runtimeId: 'opencode',
providerId: 'openrouter',
});
expect(response.error?.code).toBe('unsupported-action');
expect(response.error?.message).toBe(
'This OpenCode runtime does not advertise credential removal through /doc'
);
});
it('passes project path as cwd and CLI flag for project-aware provider management', async () => {
execCliMock.mockResolvedValue({
stdout: JSON.stringify({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
runtimeId: 'opencode',
title: 'OpenCode',
runtime: {
state: 'ready',
cliPath: '/opt/homebrew/bin/opencode',
version: '1.0.0',
managedProfile: 'active',
localAuth: 'synced',
},
providers: [],
defaultModel: null,
fallbackModel: null,
diagnostics: [],
},
}),
stderr: '',
});
const client = new AgentTeamsRuntimeProviderManagementCliClient();
await client.loadView({
runtimeId: 'opencode',
projectPath: '/Users/test/project',
});
expect(execCliMock).toHaveBeenCalledWith(
'/repo/cli-dev',
expect.arrayContaining(['--project-path', '/Users/test/project']),
expect.objectContaining({ cwd: '/Users/test/project' })
);
});
it('loads provider directory with optional args and omits absent values', async () => {
execCliMock.mockResolvedValue({
stdout: JSON.stringify({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: 'deep',
filter: 'connectable',
limit: 10,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [],
diagnostics: [],
},
}),
stderr: '',
});
const client = new AgentTeamsRuntimeProviderManagementCliClient();
const response = await client.loadProviderDirectory({
runtimeId: 'opencode',
projectPath: '/Users/test/project',
query: 'deep',
filter: 'connectable',
limit: 10,
refresh: true,
});
expect(response.directory?.query).toBe('deep');
expect(execCliMock).toHaveBeenCalledWith(
'/repo/cli-dev',
[
'runtime',
'providers',
'directory',
'--runtime',
'opencode',
'--json',
'--project-path',
'/Users/test/project',
'--query',
'deep',
'--filter',
'connectable',
'--limit',
'10',
'--refresh',
],
expect.objectContaining({ cwd: '/Users/test/project' })
);
expect(JSON.stringify(execCliMock.mock.calls[0])).not.toContain('undefined');
});
});