Files
makelore/tests/unit/provider-routes.test.ts

1055 lines
39 KiB
TypeScript

import { EventEmitter } from 'node:events';
import type { IncomingMessage, ServerResponse } from 'http';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { handleProviderRoutes } from '@electron/api/routes/providers';
import { handleOpencodeRoutes } from '@electron/api/routes/opencode';
import type { ProviderAccount } from '@electron/shared/providers/types';
import {
clearWorksSquareAIGatewayCredential,
getWorksSquareAIGatewaySnapshot,
} from '@electron/services/works-square-ai-gateway';
import {
isRuntimeConfigRefreshPending,
withRuntimeConfigCoordinator,
} from '@electron/opencode/runtime-config-readiness';
const providerServiceMock = vi.hoisted(() => ({
getAccount: vi.fn(),
getAccountApiKey: vi.fn(),
createAccount: vi.fn(),
updateAccount: vi.fn(),
setDefaultAccount: vi.fn(),
}));
const getHostApiTokenMock = vi.hoisted(() => vi.fn());
vi.mock('@electron/services/providers/provider-service', () => ({
getProviderService: () => providerServiceMock,
}));
vi.mock('@electron/api/server', () => ({
getHostApiToken: () => getHostApiTokenMock(),
}));
function createResponse() {
const chunks: string[] = [];
const res = {
statusCode: 0,
setHeader: vi.fn(),
end: vi.fn((chunk?: string) => {
if (chunk) chunks.push(chunk);
}),
} as unknown as ServerResponse;
return {
res,
get statusCode() {
return res.statusCode;
},
json: () => JSON.parse(chunks.join('')) as unknown,
};
}
function createRequest(method: string, body?: unknown): IncomingMessage {
const req = new EventEmitter();
Object.assign(req, {
method,
headers: body === undefined ? {} : { 'content-type': 'application/json' },
[Symbol.asyncIterator]: async function* () {
if (body !== undefined) {
yield Buffer.from(JSON.stringify(body));
}
},
});
return req as IncomingMessage;
}
function createProviderAccount(overrides: Partial<ProviderAccount> = {}): ProviderAccount {
const now = '2026-06-19T00:00:00.000Z';
return {
id: 'openai-account-1',
vendorId: 'openai',
label: 'OpenAI',
authMode: 'api_key',
enabled: true,
isDefault: true,
createdAt: now,
updatedAt: now,
...overrides,
};
}
describe('provider host api routes', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.restoreAllMocks();
providerServiceMock.getAccount.mockResolvedValue(createProviderAccount());
providerServiceMock.createAccount.mockImplementation(async (account: ProviderAccount) => (
createProviderAccount(account)
));
providerServiceMock.updateAccount.mockResolvedValue(createProviderAccount({
updatedAt: '2026-06-19T01:00:00.000Z',
}));
providerServiceMock.setDefaultAccount.mockResolvedValue(undefined);
providerServiceMock.getAccountApiKey.mockResolvedValue(null);
getHostApiTokenMock.mockReturnValue('host-api-token');
clearWorksSquareAIGatewayCredential();
});
it('restarts a running opencode runtime after updating an account api key', async () => {
const response = createResponse();
const restart = vi.fn(async () => ({ state: 'running', port: 4096, pid: 4242 }));
const handled = await handleProviderRoutes(
createRequest('PUT', { updates: {}, apiKey: 'sk-new' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/openai-account-1'),
{
opencodeManager: {
getStatus: () => ({ state: 'running', port: 4096 }),
restart,
},
} as never,
);
expect(handled).toBe(true);
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({
success: true,
account: createProviderAccount({
updatedAt: '2026-06-19T01:00:00.000Z',
}),
});
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
'openai-account-1',
{},
'sk-new',
);
expect(restart).toHaveBeenCalledOnce();
});
it('imports current user Works Square model config into the provider store without returning the api key', async () => {
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 900,
models: ['gpt-4.1-mini', 'gpt-4o-mini'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
providerServiceMock.getAccount.mockResolvedValueOnce(null);
const response = createResponse();
const restart = vi.fn(async () => ({ state: 'running', port: 4096, pid: 4242 }));
const handled = await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'running', port: 4096 }),
restart,
},
} as never,
);
expect(handled).toBe(true);
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({
success: true,
account: expect.objectContaining({
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
model: 'gpt-4.1-mini',
fallbackModels: ['gpt-4o-mini'],
}),
importedModels: ['gpt-4.1-mini', 'gpt-4o-mini'],
runtimeRefreshRequired: true,
});
expect(JSON.stringify(response.json())).not.toContain('ws-ai-token');
expect(fetchMock).toHaveBeenCalledWith(
'https://square.nianxx.cn/api/auth/me/model-config',
{
method: 'GET',
headers: {
Authorization: 'Bearer access-token',
},
signal: expect.any(AbortSignal),
},
);
expect(providerServiceMock.createAccount).toHaveBeenCalledWith(
expect.objectContaining({
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
authMode: 'api_key',
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
apiProtocol: 'openai-completions',
model: 'gpt-4.1-mini',
fallbackModels: ['gpt-4o-mini'],
headers: undefined,
metadata: expect.objectContaining({
customModels: ['gpt-4.1-mini', 'gpt-4o-mini'],
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
worksSquareOneApiBaseUrl: 'https://one-api.example.com/v1',
}),
enabled: true,
isDefault: true,
}),
'host-api-token',
);
expect(getWorksSquareAIGatewaySnapshot()).toMatchObject({
accessToken: 'ws-ai-token',
oneApiBaseUrl: 'https://one-api.example.com/v1',
});
expect(providerServiceMock.setDefaultAccount).toHaveBeenCalledWith('niancode-user-models');
expect(restart).toHaveBeenCalledOnce();
});
it('normalizes a root Works Square AI gateway URL to the One API v1 endpoint', async () => {
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://token.nianxx.cn/',
api_key: 'ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 900,
models: ['deepseek-chat'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
providerServiceMock.getAccount.mockResolvedValueOnce(null);
const response = createResponse();
const handled = await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'stopped', port: 4096 }),
restart: vi.fn(),
},
} as never,
);
expect(handled).toBe(true);
expect(response.statusCode).toBe(200);
expect(providerServiceMock.createAccount).toHaveBeenCalledWith(
expect.objectContaining({
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
headers: undefined,
metadata: expect.objectContaining({
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
worksSquareOneApiBaseUrl: 'https://token.nianxx.cn/v1',
}),
}),
'host-api-token',
);
});
it('strips DeepSeek routing prefixes when importing Works Square models', async () => {
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 900,
models: ['deepseek/deepseek-v4-pro', 'deepseek/deepseek-chat'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
providerServiceMock.getAccount.mockResolvedValueOnce(null);
const response = createResponse();
const handled = await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'stopped', port: 4096 }),
restart: vi.fn(),
},
} as never,
);
expect(handled).toBe(true);
expect(response.statusCode).toBe(200);
expect(response.json()).toMatchObject({
success: true,
importedModels: ['deepseek-v4-pro', 'deepseek-chat'],
runtimeRefreshRequired: false,
});
expect(providerServiceMock.createAccount).toHaveBeenCalledWith(
expect.objectContaining({
model: 'deepseek-v4-pro',
fallbackModels: ['deepseek-chat'],
metadata: expect.objectContaining({
customModels: ['deepseek-v4-pro', 'deepseek-chat'],
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
}),
}),
'host-api-token',
);
});
it('does not restart a running runtime when importing the same local proxy provider shape', async () => {
const existingAccount = createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
authMode: 'api_key',
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
apiProtocol: 'openai-completions',
model: 'gpt-4.1-mini',
fallbackModels: ['gpt-4o-mini'],
enabled: true,
isDefault: true,
metadata: {
customModels: ['gpt-4.1-mini', 'gpt-4o-mini'],
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
worksSquareOneApiBaseUrl: 'https://one-api.example.com/v1',
},
});
providerServiceMock.getAccount.mockResolvedValueOnce(existingAccount);
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('host-api-token');
providerServiceMock.updateAccount.mockResolvedValueOnce(existingAccount);
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'fresh-ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini', 'gpt-4o-mini'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
const restart = vi.fn(async () => ({ state: 'running', port: 4096, pid: 4242 }));
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'running', port: 4096 }),
restart,
},
} as never,
);
expect(response.statusCode).toBe(200);
expect(restart).not.toHaveBeenCalled();
});
it('keeps the previously selected imported model when refreshing the Works Square model config', async () => {
const existingAccount = createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
authMode: 'api_key',
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
apiProtocol: 'openai-completions',
model: 'qwen3-coder',
fallbackModels: ['deepseek-chat', 'gpt-4.1-mini'],
enabled: true,
isDefault: true,
metadata: {
customModels: ['deepseek-chat', 'gpt-4.1-mini', 'qwen3-coder'],
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
worksSquareOneApiBaseUrl: 'https://one-api.example.com/v1',
},
});
providerServiceMock.getAccount.mockResolvedValueOnce(existingAccount);
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('host-api-token');
providerServiceMock.updateAccount.mockResolvedValueOnce(existingAccount);
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'fresh-ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['deepseek-chat', 'gpt-4.1-mini', 'qwen3-coder'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'stopped', port: 4096 }),
restart: vi.fn(),
},
} as never,
);
expect(response.statusCode).toBe(200);
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
'niancode-user-models',
expect.objectContaining({
model: 'qwen3-coder',
fallbackModels: ['deepseek-chat', 'gpt-4.1-mini'],
}),
'host-api-token',
);
});
it('restarts a running runtime when the local proxy Host API token changed', async () => {
const existingAccount = createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
authMode: 'api_key',
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
apiProtocol: 'openai-completions',
model: 'gpt-4.1-mini',
fallbackModels: ['gpt-4o-mini'],
enabled: true,
isDefault: true,
metadata: {
customModels: ['gpt-4.1-mini', 'gpt-4o-mini'],
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
worksSquareOneApiBaseUrl: 'https://one-api.example.com/v1',
},
});
providerServiceMock.getAccount.mockResolvedValueOnce(existingAccount);
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('old-host-api-token');
getHostApiTokenMock.mockReturnValueOnce('new-host-api-token');
providerServiceMock.updateAccount.mockResolvedValueOnce(existingAccount);
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'fresh-ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini', 'gpt-4o-mini'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
const restart = vi.fn(async () => ({ state: 'running', port: 4096, pid: 4242 }));
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'running', port: 4096 }),
restart,
},
} as never,
);
expect(response.statusCode).toBe(200);
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
'niancode-user-models',
expect.objectContaining({
baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1',
metadata: expect.objectContaining({
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
}),
}),
'new-host-api-token',
);
expect(restart).toHaveBeenCalledOnce();
});
it('defers a direct API-key rotation without restarting and reports the runtime refresh requirement', async () => {
const existingAccount = createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
label: 'Makelore Models',
authMode: 'api_key',
baseUrl: 'https://token.nianxx.cn/v1',
apiProtocol: 'openai-completions',
headers: {
Authorization: 'Bearer {env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}',
'X-Works-Square-AI-Token': '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}',
},
model: 'qwen3.7-max',
fallbackModels: [],
enabled: true,
isDefault: true,
metadata: {
customModels: ['qwen3.7-max'],
worksSquareCredentialMode: 'api_key',
},
});
providerServiceMock.getAccount.mockResolvedValueOnce(existingAccount);
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('direct-key-k1');
providerServiceMock.updateAccount.mockResolvedValueOnce(existingAccount);
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://token.nianxx.cn/v1',
api_key: 'direct-key-k2',
credential_mode: 'api_key',
api_key_expires_in: 3600,
models: ['qwen3.7-max'],
}), { status: 200 })));
const response = createResponse();
const restart = vi.fn();
const manager = {
getStatus: () => ({ state: 'running' as const, port: 4096 }),
getRuntimeGeneration: () => 4,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart,
};
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'defer' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: manager,
} as never,
);
expect(response.statusCode).toBe(200);
expect(response.json()).toMatchObject({
success: true,
runtimeRefreshRequired: true,
});
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
'niancode-user-models',
expect.any(Object),
'direct-key-k2',
);
expect(restart).not.toHaveBeenCalled();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
});
it('returns a failure when the runtime restart fails after importing a new token', async () => {
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'fresh-ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 }),
);
vi.stubGlobal('fetch', fetchMock);
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('old-ws-ai-token');
const restart = vi.fn().mockRejectedValue(new Error('runtime restart failed'));
const response = createResponse();
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => 1,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart,
};
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
expect(response.statusCode).toBe(500);
expect(response.json()).toEqual({
success: false,
error: 'runtime restart failed',
});
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
'niancode-user-models',
expect.objectContaining({ model: 'gpt-4.1-mini' }),
'host-api-token',
);
expect(restart).toHaveBeenCalledOnce();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
});
it('retains a sticky latch when restart advances generation before rejecting', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'direct-key-k2',
credential_mode: 'api_key',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 })));
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
baseUrl: 'https://one-api.example.com/v1',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('direct-key-k1');
let generation = 1;
const restart = vi.fn(async () => {
generation = 2;
throw new Error('fresh restart rejected');
});
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => generation,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart,
};
const response = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'apply' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
expect(response.statusCode).toBe(500);
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
generation = 3;
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
});
it('does not seed the local gateway before provider readiness can be established', async () => {
vi.useFakeTimers();
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'gateway-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 })));
providerServiceMock.getAccount.mockImplementationOnce(async () => await new Promise(() => undefined));
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => 1,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart: vi.fn(),
};
const response = createResponse();
try {
const request = handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'defer' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
await vi.waitFor(() => expect(providerServiceMock.getAccount).toHaveBeenCalledOnce());
await vi.advanceTimersByTimeAsync(10_000);
await expect(request).resolves.toBe(true);
expect(response.statusCode).toBe(500);
expect(getWorksSquareAIGatewaySnapshot()).toBeNull();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(false);
} finally {
vi.useRealTimers();
}
});
it('keeps the active generation pending when selecting the imported default account fails', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'fresh-ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 })));
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('old-ws-ai-token');
providerServiceMock.setDefaultAccount.mockRejectedValueOnce(new Error('default selection failed'));
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => 1,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart: vi.fn(),
};
const response = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
expect(response.statusCode).toBe(500);
expect(manager.restart).not.toHaveBeenCalled();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
});
it('does not arm a stopped sticky latch when explicit apply persistence fails', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'direct-key-k2',
credential_mode: 'api_key',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 })));
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
baseUrl: 'https://one-api.example.com/v1',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('direct-key-k1');
providerServiceMock.setDefaultAccount.mockRejectedValueOnce(new Error('default persistence failed'));
let generation = 1;
const manager = {
getStatus: () => ({ state: 'stopped' }),
getRuntimeGeneration: () => generation,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart: vi.fn(),
};
await withRuntimeConfigCoordinator(manager, async (lease) => {
lease.markRefreshPending();
lease.retainRefreshPending();
});
const response = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'apply' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
expect(response.statusCode).toBe(500);
expect(manager.restart).not.toHaveBeenCalled();
generation = 2;
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
});
it('applies an existing deferred latch even when the persisted provider values are unchanged', async () => {
const payload = JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'direct-key-k2',
credential_mode: 'api_key',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
});
vi.stubGlobal('fetch', vi.fn()
.mockResolvedValueOnce(new Response(payload, { status: 200 }))
.mockResolvedValueOnce(new Response(payload, { status: 200 })));
let savedAccount = createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
baseUrl: 'https://one-api.example.com/v1',
model: 'old-model',
});
let savedApiKey = 'direct-key-k1';
providerServiceMock.getAccount.mockImplementation(async () => savedAccount);
providerServiceMock.getAccountApiKey.mockImplementation(async () => savedApiKey);
providerServiceMock.updateAccount.mockImplementation(async (
_id: string,
account: ProviderAccount,
apiKey: string,
) => {
savedAccount = account;
savedApiKey = apiKey;
return account;
});
let generation = 1;
const restart = vi.fn(async () => {
generation = 2;
return { state: 'running', port: 4097, pid: 4242 };
});
const manager = {
getStatus: () => ({ state: 'running', port: generation === 1 ? 4096 : 4097 }),
getRuntimeGeneration: () => generation,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart,
};
const deferred = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'defer' }),
deferred.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
expect(deferred.statusCode).toBe(200);
expect(restart).not.toHaveBeenCalled();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
const applied = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'apply' }),
applied.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
expect(applied.statusCode).toBe(200);
expect(restart).toHaveBeenCalledOnce();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(false);
});
it('serializes manual restart behind provider persistence', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'direct-key-k2',
credential_mode: 'api_key',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 })));
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
baseUrl: 'https://one-api.example.com/v1',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('direct-key-k1');
let releasePersistence!: () => void;
providerServiceMock.updateAccount.mockImplementationOnce(async () => await new Promise<ProviderAccount>((resolve) => {
releasePersistence = () => resolve(createProviderAccount({ id: 'niancode-user-models' }));
}));
let generation = 1;
const restart = vi.fn(async () => {
generation = 2;
return { state: 'running', port: 4097, pid: 4242 };
});
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => generation,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart,
};
const providerResponse = createResponse();
const persistence = handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'defer' }),
providerResponse.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
await vi.waitFor(() => expect(providerServiceMock.updateAccount).toHaveBeenCalledOnce());
const restartResponse = createResponse();
const lifecycle = handleOpencodeRoutes(
createRequest('POST'),
restartResponse.res,
new URL('http://127.0.0.1/api/opencode/restart'),
{ opencodeManager: manager } as never,
);
await Promise.resolve();
expect(restart).not.toHaveBeenCalled();
releasePersistence();
await expect(persistence).resolves.toBe(true);
await expect(lifecycle).resolves.toBe(true);
expect(providerResponse.statusCode).toBe(200);
expect(restartResponse.statusCode).toBe(200);
expect(restart).toHaveBeenCalledOnce();
});
it('revokes a timed-out provider persistence lease and suppresses its late continuation', async () => {
vi.useFakeTimers();
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'direct-key-k2',
credential_mode: 'api_key',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 })));
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
vendorId: 'custom',
baseUrl: 'https://one-api.example.com/v1',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('direct-key-k1');
let releasePersistence!: () => void;
providerServiceMock.updateAccount.mockImplementationOnce(async () => await new Promise<ProviderAccount>((resolve) => {
releasePersistence = () => resolve(createProviderAccount({ id: 'niancode-user-models' }));
}));
let timedOutGeneration = 1;
const restart = vi.fn(async () => {
timedOutGeneration = 2;
return { state: 'running', port: 4097, pid: 4242 };
});
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => timedOutGeneration,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart,
};
const response = createResponse();
try {
const request = handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'defer' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
await vi.waitFor(() => expect(providerServiceMock.updateAccount).toHaveBeenCalledOnce());
await vi.advanceTimersByTimeAsync(10_000);
await expect(request).resolves.toBe(true);
expect(response.statusCode).toBe(500);
expect(response.res.end).toHaveBeenCalledOnce();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
const restartResponse = createResponse();
await handleOpencodeRoutes(
createRequest('POST'),
restartResponse.res,
new URL('http://127.0.0.1/api/opencode/restart'),
{ opencodeManager: manager } as never,
);
expect(restartResponse.statusCode).toBe(200);
expect(restart).toHaveBeenCalledOnce();
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(true);
releasePersistence();
await Promise.resolve();
expect(providerServiceMock.setDefaultAccount).not.toHaveBeenCalled();
expect(response.res.end).toHaveBeenCalledOnce();
} finally {
releasePersistence?.();
vi.useRealTimers();
}
});
it('does not accept an attached server as the new provider runtime', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({
provider_type: 'openai-compatible',
label: 'Makelore Models',
base_url: 'https://one-api.example.com/v1',
api_key: 'fresh-ws-ai-token',
credential_mode: 'works_square_ai_gateway',
api_key_expires_in: 3600,
models: ['gpt-4.1-mini'],
}), { status: 200 }),
));
providerServiceMock.getAccount.mockResolvedValueOnce(createProviderAccount({
id: 'niancode-user-models',
model: 'old-model',
}));
providerServiceMock.getAccountApiKey.mockResolvedValueOnce('old-ws-ai-token');
const restart = vi.fn(async () => ({ state: 'running', port: 4096 }));
const response = createResponse();
await handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'running', port: 4096 }),
restart,
},
} as never,
);
expect(response.statusCode).toBe(500);
expect(response.json()).toEqual({
success: false,
error: 'opencode runtime did not restart with the new provider configuration',
});
expect(restart).toHaveBeenCalledOnce();
});
it('forwards Works Square auth errors when importing current user model config fails', async () => {
const fetchMock = vi.fn().mockResolvedValueOnce(
new Response(JSON.stringify({ detail: 'Invalid token' }), { status: 401 }),
);
vi.stubGlobal('fetch', fetchMock);
const response = createResponse();
const handled = await handleProviderRoutes(
createRequest('POST', { accessToken: 'bad-token' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{
opencodeManager: {
getStatus: () => ({ state: 'stopped' }),
restart: vi.fn(),
},
} as never,
);
expect(handled).toBe(true);
expect(response.statusCode).toBe(401);
expect(response.json()).toEqual({
success: false,
error: 'Invalid token',
});
expect(providerServiceMock.createAccount).not.toHaveBeenCalled();
expect(providerServiceMock.updateAccount).not.toHaveBeenCalled();
});
it('aborts a hung Works model-config fetch without acquiring the manager coordinator', async () => {
vi.useFakeTimers();
let fetchSignal: AbortSignal | undefined;
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
fetchSignal = init?.signal ?? undefined;
return new Response(new ReadableStream<Uint8Array>({
start() {
// Headers arrive, but the response body intentionally never closes.
},
}), { status: 200 });
});
vi.stubGlobal('fetch', fetchMock);
const manager = {
getStatus: () => ({ state: 'running', port: 4096 }),
getRuntimeGeneration: () => 1,
getRuntimeGenerationProvenance: () => 'fresh' as const,
restart: vi.fn(),
};
const response = createResponse();
try {
const request = handleProviderRoutes(
createRequest('POST', { accessToken: 'access-token', runtimeRefresh: 'defer' }),
response.res,
new URL('http://127.0.0.1/api/provider-accounts/import-user-model-config'),
{ opencodeManager: manager } as never,
);
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
await expect(withRuntimeConfigCoordinator(manager, async () => 'unblocked')).resolves.toBe('unblocked');
await vi.advanceTimersByTimeAsync(10_000);
await expect(request).resolves.toBe(true);
expect(fetchSignal?.aborted).toBe(true);
expect(response.statusCode).toBe(500);
await expect(isRuntimeConfigRefreshPending(manager)).resolves.toBe(false);
expect(providerServiceMock.createAccount).not.toHaveBeenCalled();
expect(providerServiceMock.updateAccount).not.toHaveBeenCalled();
} finally {
vi.useRealTimers();
}
});
});