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

384 lines
14 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import {
RUNTIME_PROVIDER_MANAGEMENT_CONNECT,
RUNTIME_PROVIDER_MANAGEMENT_CONNECT_API_KEY,
RUNTIME_PROVIDER_MANAGEMENT_DIRECTORY,
RUNTIME_PROVIDER_MANAGEMENT_MODELS,
RUNTIME_PROVIDER_MANAGEMENT_SETUP_FORM,
RUNTIME_PROVIDER_MANAGEMENT_VIEW,
} from '../../../../src/features/runtime-provider-management/contracts';
import { registerRuntimeProviderManagementIpc } from '../../../../src/features/runtime-provider-management/main';
import type {
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementModelsResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetupFormResponse,
RuntimeProviderManagementViewResponse,
} from '../../../../src/features/runtime-provider-management/contracts';
import type { RuntimeProviderManagementFeatureFacade } from '../../../../src/features/runtime-provider-management/main';
import type { IpcMain } from 'electron';
describe('registerRuntimeProviderManagementIpc', () => {
it('passes API keys through input only and returns provider DTOs without the raw secret', async () => {
const handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();
const ipcMain = {
handle: vi.fn((channel: string, handler: (...args: unknown[]) => Promise<unknown>) => {
handlers.set(channel, handler);
}),
removeHandler: vi.fn(),
} as unknown as IpcMain;
const viewResponse: RuntimeProviderManagementViewResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
view: {
runtimeId: 'opencode',
title: 'OpenCode',
runtime: {
state: 'ready',
cliPath: null,
version: null,
managedProfile: 'active',
localAuth: 'synced',
},
providers: [],
defaultModel: null,
fallbackModel: null,
diagnostics: [],
},
};
const connectedResponse: RuntimeProviderManagementProviderResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
provider: {
providerId: 'openrouter',
displayName: 'OpenRouter',
state: 'connected',
ownership: ['managed'],
recommended: true,
modelCount: 4,
defaultModelId: null,
authMethods: ['api'],
actions: [],
detail: null,
},
};
const directoryResponse: RuntimeProviderManagementDirectoryResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 0,
returnedCount: 0,
query: null,
filter: 'all',
limit: 100,
cursor: null,
nextCursor: null,
entries: [],
diagnostics: [],
fetchedAt: '2026-04-25T00:00:00.000Z',
},
};
const forgottenResponse: RuntimeProviderManagementProviderResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
provider: {
providerId: 'openrouter',
displayName: 'OpenRouter',
state: 'available',
ownership: [],
recommended: true,
modelCount: 4,
defaultModelId: null,
authMethods: ['api'],
actions: [],
detail: null,
},
};
const modelsResponse: RuntimeProviderManagementModelsResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
models: {
runtimeId: 'opencode',
providerId: 'openrouter',
models: [],
defaultModelId: null,
diagnostics: [],
},
};
const testResponse: RuntimeProviderManagementModelTestResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'openrouter',
modelId: 'openrouter/openai/gpt-oss-20b:free',
ok: true,
availability: 'available',
message: 'Model probe passed',
diagnostics: [],
},
};
const setupFormResponse: RuntimeProviderManagementSetupFormResponse = {
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openrouter',
displayName: 'OpenRouter',
method: 'api',
supported: true,
title: 'Connect OpenRouter',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
};
const feature: RuntimeProviderManagementFeatureFacade = {
loadView: vi.fn(() => Promise.resolve(viewResponse)),
loadProviderDirectory: vi.fn(() => Promise.resolve(directoryResponse)),
loadSetupForm: vi.fn(() => Promise.resolve(setupFormResponse)),
connectProvider: vi.fn(() => Promise.resolve(connectedResponse)),
connectWithApiKey: vi.fn(() => Promise.resolve(connectedResponse)),
forgetCredential: vi.fn(() => Promise.resolve(forgottenResponse)),
loadModels: vi.fn(() => Promise.resolve(modelsResponse)),
testModel: vi.fn(() => Promise.resolve(testResponse)),
setDefaultModel: vi.fn(() => Promise.resolve(viewResponse)),
};
registerRuntimeProviderManagementIpc(ipcMain, feature);
await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_VIEW)?.({}, { runtimeId: 'opencode' });
await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_DIRECTORY)?.(
{},
{
runtimeId: 'opencode',
query: 'deep',
filter: 'connectable',
limit: 10,
}
);
expect(feature.loadProviderDirectory).toHaveBeenCalledWith({
runtimeId: 'opencode',
query: 'deep',
filter: 'connectable',
limit: 10,
});
await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_SETUP_FORM)?.(
{},
{
runtimeId: 'opencode',
providerId: 'openrouter',
}
);
expect(feature.loadSetupForm).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openrouter',
});
const genericConnectResponse = await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_CONNECT)?.(
{},
{
runtimeId: 'opencode',
providerId: 'openrouter',
method: 'api',
apiKey: 'sk-secret-value',
metadata: {},
}
);
expect(feature.connectProvider).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openrouter',
method: 'api',
apiKey: 'sk-secret-value',
metadata: {},
});
expect(JSON.stringify(genericConnectResponse)).not.toContain('sk-secret-value');
const response = await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_CONNECT_API_KEY)?.(
{},
{
runtimeId: 'opencode',
providerId: 'openrouter',
apiKey: 'sk-secret-value',
}
);
expect(feature.connectWithApiKey).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openrouter',
apiKey: 'sk-secret-value',
});
expect(JSON.stringify(response)).not.toContain('sk-secret-value');
await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_MODELS)?.(
{},
{ runtimeId: 'opencode', providerId: 'openrouter', query: 'free', limit: 10 }
);
expect(feature.loadModels).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openrouter',
query: 'free',
limit: 10,
});
});
it('sanitizes unexpected IPC error messages before returning them to the renderer', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();
const ipcMain = {
handle: vi.fn((channel: string, handler: (...args: unknown[]) => Promise<unknown>) => {
handlers.set(channel, handler);
}),
removeHandler: vi.fn(),
} as unknown as IpcMain;
const feature: RuntimeProviderManagementFeatureFacade = {
loadView: vi.fn(() =>
Promise.reject(
new Error(
'\u001B]8;;https://logs.example/secret\u0007\u001B[31mProvider failed with api_key: sk-secret-value-123456 and Authorization: Bearer live-token-123456789 and key=AIzaSyD-test-secret-value-123456789 and OPENAI_API_KEY=plain_provider_secret_123456 and PROVIDER_TOKEN=provider_token_value_123456\u001B[0m\u001B]8;;\u0007'
)
)
),
loadProviderDirectory: vi.fn(),
loadSetupForm: vi.fn(),
connectProvider: vi.fn(),
connectWithApiKey: vi.fn(),
forgetCredential: vi.fn(),
loadModels: vi.fn(),
testModel: vi.fn(),
setDefaultModel: vi.fn(),
};
registerRuntimeProviderManagementIpc(ipcMain, feature);
const response = (await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_VIEW)?.(
{},
{ runtimeId: 'opencode' }
)) as RuntimeProviderManagementViewResponse;
expect(response.error?.message).toContain('api_key: ...redacted');
expect(response.error?.message).toContain('Authorization: Bearer ...redacted');
expect(response.error?.message).toContain('key=...redacted');
expect(response.error?.message).toContain('OPENAI_API_KEY=...redacted');
expect(response.error?.message).toContain('PROVIDER_TOKEN=...redacted');
expect(response.error?.message).not.toContain('sk-secret-value-123456');
expect(response.error?.message).not.toContain('live-token-123456789');
expect(response.error?.message).not.toContain('AIzaSyD-test-secret-value-123456789');
expect(response.error?.message).not.toContain('plain_provider_secret_123456');
expect(response.error?.message).not.toContain('provider_token_value_123456');
expect(response.error?.message).not.toContain('logs.example/secret');
expect(response.error?.message).not.toContain('[31m');
expect(response.error?.message).not.toContain(']8;;');
expect(response.error?.diagnostics?.summary).toContain('api_key: ...redacted');
expect(response.error?.diagnostics?.errorCode).toBe('runtime-unhealthy');
expect(response.error?.diagnostics?.stderrPreview).toContain(
'Authorization: Bearer ...redacted'
);
expect(JSON.stringify(response.error?.diagnostics)).not.toContain('sk-secret-value-123456');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).toContain('api_key: ...redacted');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).toContain('key=...redacted');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).not.toContain('sk-secret-value-123456');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).not.toContain('live-token-123456789');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).not.toContain(
'AIzaSyD-test-secret-value-123456789'
);
consoleErrorSpy.mockRestore();
});
it('bounds unexpected IPC diagnostics before returning them to the renderer', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();
const ipcMain = {
handle: vi.fn((channel: string, handler: (...args: unknown[]) => Promise<unknown>) => {
handlers.set(channel, handler);
}),
removeHandler: vi.fn(),
} as unknown as IpcMain;
const feature: RuntimeProviderManagementFeatureFacade = {
loadView: vi.fn(() => Promise.reject(new Error(`x${'y'.repeat(3_000)}`))),
loadProviderDirectory: vi.fn(),
loadSetupForm: vi.fn(),
connectProvider: vi.fn(),
connectWithApiKey: vi.fn(),
forgetCredential: vi.fn(),
loadModels: vi.fn(),
testModel: vi.fn(),
setDefaultModel: vi.fn(),
};
registerRuntimeProviderManagementIpc(ipcMain, feature);
const response = (await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_VIEW)?.(
{},
{ runtimeId: 'opencode' }
)) as RuntimeProviderManagementViewResponse;
expect(response.error?.message.endsWith('...')).toBe(true);
expect(response.error?.message.length).toBeLessThanOrEqual(1_603);
expect(response.error?.diagnostics?.stderrPreview).toBe(response.error?.message);
consoleErrorSpy.mockRestore();
});
it('does not log raw secrets when connect handlers throw non-Error values', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();
const ipcMain = {
handle: vi.fn((channel: string, handler: (...args: unknown[]) => Promise<unknown>) => {
handlers.set(channel, handler);
}),
removeHandler: vi.fn(),
} as unknown as IpcMain;
const feature: RuntimeProviderManagementFeatureFacade = {
loadView: vi.fn(),
loadProviderDirectory: vi.fn(),
loadSetupForm: vi.fn(),
connectProvider: vi.fn(() =>
Promise.reject(
'Provider failed with api_key: sk-secret-value-123456 and token=provider-token-123456789'
)
),
connectWithApiKey: vi.fn(),
forgetCredential: vi.fn(),
loadModels: vi.fn(),
testModel: vi.fn(),
setDefaultModel: vi.fn(),
};
registerRuntimeProviderManagementIpc(ipcMain, feature);
const response = (await handlers.get(RUNTIME_PROVIDER_MANAGEMENT_CONNECT)?.(
{},
{
runtimeId: 'opencode',
providerId: 'openrouter',
method: 'api',
apiKey: 'sk-input-secret-value',
metadata: {},
}
)) as RuntimeProviderManagementProviderResponse;
expect(response.error?.message).toContain('api_key: ...redacted');
expect(response.error?.message).toContain('token=...redacted');
expect(response.error?.diagnostics?.errorCode).toBe('auth-failed');
expect(response.error?.diagnostics?.stderrPreview).toContain('token=...redacted');
expect(JSON.stringify(response)).not.toContain('sk-input-secret-value');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).toContain('api_key: ...redacted');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).toContain('token=...redacted');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).not.toContain('sk-secret-value-123456');
expect(JSON.stringify(consoleErrorSpy.mock.calls)).not.toContain('provider-token-123456789');
consoleErrorSpy.mockRestore();
});
});