一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
65 行
1.8 KiB
TypeScript
65 行
1.8 KiB
TypeScript
import { mockFetch, setupDatabaseAndSynchronizer, switchClient } from '../../../testing/test-utils';
|
|
import OpenAiCompatibleProvider from './OpenAiCompatible';
|
|
import { ChatRole } from '../types';
|
|
|
|
const mockChatResponse = (message: Record<string, unknown>, finishReason: string|undefined) => {
|
|
return mockFetch(() => {
|
|
return new Response(JSON.stringify({
|
|
choices: [{ message, finish_reason: finishReason }],
|
|
usage: { prompt_tokens: 10, completion_tokens: 20 },
|
|
}), { status: 200 });
|
|
});
|
|
};
|
|
|
|
const newProvider = () => {
|
|
return new OpenAiCompatibleProvider({
|
|
baseUrl: 'http://localhost:1234/v1',
|
|
apiKey: 'test-key',
|
|
model: 'test-model',
|
|
classification: 'local',
|
|
});
|
|
};
|
|
|
|
const chat = async () => {
|
|
return newProvider().chat([{ role: ChatRole.User, content: 'Hello' }]);
|
|
};
|
|
|
|
describe('ai/providers/OpenAiCompatible', () => {
|
|
|
|
beforeEach(async () => {
|
|
await setupDatabaseAndSynchronizer(0);
|
|
await switchClient(0);
|
|
});
|
|
|
|
test.each([
|
|
['length', 'length'],
|
|
['max_tokens', 'length'],
|
|
['MAX_TOKENS', 'length'],
|
|
['stop', 'stop'],
|
|
['tool_calls', 'tool_calls'],
|
|
['content_filter', 'other'],
|
|
[undefined, undefined],
|
|
])('maps finish_reason %s to %s', async (finishReason, expected) => {
|
|
mockChatResponse({ content: 'OK' }, finishReason);
|
|
const result = await chat();
|
|
expect(result.finishReason).toBe(expected);
|
|
});
|
|
|
|
test.each([
|
|
['reasoning_content'],
|
|
['reasoning'],
|
|
])('surfaces the reasoning trace from %s', async (field) => {
|
|
mockChatResponse({ content: '', [field]: 'Let me think...' }, 'length');
|
|
const result = await chat();
|
|
expect(result.text).toBe('');
|
|
expect(result.reasoningText).toBe('Let me think...');
|
|
});
|
|
|
|
it('leaves reasoningText undefined when the provider reports none', async () => {
|
|
mockChatResponse({ content: 'OK' }, 'stop');
|
|
const result = await chat();
|
|
expect(result.reasoningText).toBeUndefined();
|
|
});
|
|
|
|
});
|