一些检查失败了
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
114 行
4.7 KiB
TypeScript
114 行
4.7 KiB
TypeScript
import { setupDatabaseAndSynchronizer, switchClient } from '../../testing/test-utils';
|
|
import Setting from '../../models/Setting';
|
|
import { chatAvailability, embeddingAvailability, mcpAvailability, AvailabilityReason } from './availability';
|
|
|
|
describe('availability', () => {
|
|
|
|
beforeEach(async () => {
|
|
await setupDatabaseAndSynchronizer(1);
|
|
await switchClient(1);
|
|
});
|
|
|
|
test('chatAvailability reports disabled when ai.enabled is off', () => {
|
|
Setting.setValue('ai.enabled', false);
|
|
const r = chatAvailability();
|
|
expect(r.available).toBe(false);
|
|
expect(r.reason).toBe(AvailabilityReason.Disabled);
|
|
});
|
|
|
|
test('chatAvailability reports missing-api-key for anthropic without key', () => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', true);
|
|
Setting.setValue('ai.chat.providerType', 'anthropic');
|
|
Setting.setValue('ai.chat.apiKey', '');
|
|
Setting.setValue('ai.chat.model', 'claude-3-5-sonnet-latest');
|
|
expect(chatAvailability().reason).toBe(AvailabilityReason.MissingApiKey);
|
|
});
|
|
|
|
test('chatAvailability reports missing-model for openai-compatible without model', () => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', false);
|
|
Setting.setValue('ai.chat.providerType', 'openai-compatible');
|
|
Setting.setValue('ai.chat.baseUrl', 'http://localhost:11434/v1');
|
|
Setting.setValue('ai.chat.apiKey', 'unused');
|
|
Setting.setValue('ai.chat.model', '');
|
|
expect(chatAvailability().reason).toBe(AvailabilityReason.MissingModel);
|
|
});
|
|
|
|
test.each([
|
|
['http://localhost:11434/v1'],
|
|
['http://127.0.0.1:11434/v1'],
|
|
['http://192.168.1.50:11434/v1'],
|
|
['http://10.0.0.5:11434/v1'],
|
|
['http://ollama.internal:11434/v1'],
|
|
])('chatAvailability does not require an API key for %s', (baseUrl) => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', false);
|
|
Setting.setValue('ai.chat.providerType', 'openai-compatible');
|
|
Setting.setValue('ai.chat.baseUrl', baseUrl);
|
|
Setting.setValue('ai.chat.apiKey', '');
|
|
Setting.setValue('ai.chat.model', 'llama3');
|
|
expect(chatAvailability().available).toBe(true);
|
|
});
|
|
|
|
test('chatAvailability requires consent for a public endpoint that looks private', () => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', false);
|
|
Setting.setValue('ai.chat.providerType', 'openai-compatible');
|
|
// Leading zero makes this octal, so it resolves to the public 8.0.0.5.
|
|
Setting.setValue('ai.chat.baseUrl', 'http://010.0.0.5:11434/v1');
|
|
Setting.setValue('ai.chat.apiKey', 'unused');
|
|
Setting.setValue('ai.chat.model', 'llama3');
|
|
expect(chatAvailability().reason).toBe(AvailabilityReason.RemoteNotAllowed);
|
|
});
|
|
|
|
test('chatAvailability reports missing-api-key for a remote openai-compatible endpoint', () => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', true);
|
|
Setting.setValue('ai.chat.providerType', 'openai-compatible');
|
|
Setting.setValue('ai.chat.baseUrl', 'https://api.openai.com/v1');
|
|
Setting.setValue('ai.chat.apiKey', '');
|
|
Setting.setValue('ai.chat.model', 'gpt-4o');
|
|
expect(chatAvailability().reason).toBe(AvailabilityReason.MissingApiKey);
|
|
});
|
|
|
|
test('chatAvailability reports remote-not-allowed when provider is remote and switch is off', () => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', false);
|
|
Setting.setValue('ai.chat.providerType', 'anthropic');
|
|
Setting.setValue('ai.chat.apiKey', 'sk-test');
|
|
Setting.setValue('ai.chat.model', 'claude-3-5-sonnet-latest');
|
|
expect(chatAvailability().reason).toBe(AvailabilityReason.RemoteNotAllowed);
|
|
});
|
|
|
|
test('chatAvailability is true for a fully configured local provider', () => {
|
|
Setting.setValue('ai.enabled', true);
|
|
Setting.setValue('ai.allowRemote', false);
|
|
Setting.setValue('ai.chat.providerType', 'openai-compatible');
|
|
Setting.setValue('ai.chat.baseUrl', 'http://localhost:11434/v1');
|
|
Setting.setValue('ai.chat.apiKey', 'unused');
|
|
Setting.setValue('ai.chat.model', 'llama3');
|
|
const r = chatAvailability();
|
|
expect(r.available).toBe(true);
|
|
expect(r.reason).toBeUndefined();
|
|
expect(r.hint).toBeUndefined();
|
|
});
|
|
|
|
test('embeddingAvailability requires ai.enabled', () => {
|
|
Setting.setValue('ai.enabled', false);
|
|
expect(embeddingAvailability().reason).toBe(AvailabilityReason.Disabled);
|
|
});
|
|
|
|
test('mcpAvailability reports disabled when mcp.enabled is off', () => {
|
|
Setting.setValue('mcp.enabled', false);
|
|
expect(mcpAvailability().reason).toBe(AvailabilityReason.Disabled);
|
|
});
|
|
|
|
test('mcpAvailability reports web-clipper-disabled when MCP is on but clipper is off', () => {
|
|
Setting.setValue('mcp.enabled', true);
|
|
Setting.setValue('clipperServer.autoStart', false);
|
|
expect(mcpAvailability().reason).toBe(AvailabilityReason.WebClipperDisabled);
|
|
});
|
|
|
|
});
|