131 lines
5.1 KiB
Dart
131 lines
5.1 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:kouyu_english/core/ai_config.dart';
|
|
import 'package:kouyu_english/core/ai_service.dart';
|
|
import 'package:kouyu_english/core/models.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
group('AiService resolveEndpointUri', () {
|
|
test('resolves OpenAI endpoint ending in /v1 to /v1/chat/completions', () {
|
|
final uri = AiService.resolveEndpointUri(
|
|
provider: AiProviderType.openAi,
|
|
endpoint: 'https://api.openai.com/v1',
|
|
model: 'gpt-4o-mini',
|
|
);
|
|
expect(uri.toString(), equals('https://api.openai.com/v1/chat/completions'));
|
|
});
|
|
|
|
test('resolves OpenAI endpoint without /v1 to /v1/chat/completions', () {
|
|
final uri = AiService.resolveEndpointUri(
|
|
provider: AiProviderType.openAi,
|
|
endpoint: 'https://api.openai.com',
|
|
model: 'gpt-4o-mini',
|
|
);
|
|
expect(uri.toString(), equals('https://api.openai.com/v1/chat/completions'));
|
|
});
|
|
|
|
test('resolves custom compatible endpoint to /v1/chat/completions', () {
|
|
final uri = AiService.resolveEndpointUri(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: 'https://codex.slcydia.fun/v1',
|
|
model: 'gpt-4o-mini',
|
|
);
|
|
expect(uri.toString(), equals('https://codex.slcydia.fun/v1/chat/completions'));
|
|
});
|
|
|
|
test('preserves endpoint already ending in /v1/chat/completions', () {
|
|
final uri = AiService.resolveEndpointUri(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: 'https://codex.slcydia.fun/v1/chat/completions',
|
|
model: 'gpt-4o-mini',
|
|
);
|
|
expect(uri.toString(), equals('https://codex.slcydia.fun/v1/chat/completions'));
|
|
});
|
|
|
|
test('preserves /responses endpoint for OpenAI Responses API support', () {
|
|
final uri = AiService.resolveEndpointUri(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: 'https://codex.slcydia.fun/v1/responses',
|
|
model: 'gpt-4o-mini',
|
|
);
|
|
expect(uri.toString(), equals('https://codex.slcydia.fun/v1/responses'));
|
|
});
|
|
|
|
test('resolves Gemini endpoint to :generateContent', () {
|
|
final uri = AiService.resolveEndpointUri(
|
|
provider: AiProviderType.gemini,
|
|
endpoint: 'https://generativelanguage.googleapis.com/v1beta',
|
|
model: 'gemini-2.5-flash',
|
|
);
|
|
expect(
|
|
uri.toString(),
|
|
equals('https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('AiConfigFile', () {
|
|
test('parses standard JSON configuration correctly', () {
|
|
const rawJson = '''{
|
|
"provider": "compatible",
|
|
"endpoint": "https://api.openai.com/v1",
|
|
"model": "gpt-4o-mini",
|
|
"apiKey": "sk-test-key-12345",
|
|
"description": "Default AI configuration"
|
|
}''';
|
|
|
|
final config = AiConfigFile.parse(rawJson);
|
|
expect(config.provider, equals(AiProviderType.compatible));
|
|
expect(config.endpoint, equals('https://api.openai.com/v1'));
|
|
expect(config.model, equals('gpt-4o-mini'));
|
|
expect(config.apiKey, equals('sk-test-key-12345'));
|
|
expect(config.description, equals('Default AI configuration'));
|
|
});
|
|
|
|
test('handles case-insensitive provider mapping and fallbacks', () {
|
|
const openAiJson = '{"provider": "openAi", "endpoint": "https://api.openai.com/v1", "model": "gpt-4o"}';
|
|
expect(AiConfigFile.parse(openAiJson).provider, equals(AiProviderType.openAi));
|
|
|
|
const geminiJson = '{"provider": "GEMINI", "endpoint": "https://generativelanguage.googleapis.com/v1beta", "model": "gemini-2.5-flash"}';
|
|
expect(AiConfigFile.parse(geminiJson).provider, equals(AiProviderType.gemini));
|
|
|
|
const mockJson = '{"provider": "mock", "endpoint": "", "model": ""}';
|
|
expect(AiConfigFile.parse(mockJson).provider, equals(AiProviderType.mock));
|
|
|
|
const unknownJson = '{"provider": "unknown_provider", "endpoint": "", "model": ""}';
|
|
expect(AiConfigFile.parse(unknownJson).provider, equals(AiProviderType.compatible));
|
|
});
|
|
|
|
test('serializes to JSON correctly', () {
|
|
const config = AiConfigFile(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: 'http://localhost:8000/v1',
|
|
model: 'llama3',
|
|
apiKey: 'sk-local',
|
|
description: 'Local proxy',
|
|
);
|
|
final json = config.toJson();
|
|
expect(json['provider'], equals('compatible'));
|
|
expect(json['endpoint'], equals('http://localhost:8000/v1'));
|
|
expect(json['model'], equals('llama3'));
|
|
expect(json['apiKey'], equals('sk-local'));
|
|
expect(json['description'], equals('Local proxy'));
|
|
});
|
|
});
|
|
|
|
group('AiService API Key Resolution', () {
|
|
test('uses explicit key over fallback key', () async {
|
|
AiService.instance.setFallbackApiKey('fallback-key');
|
|
final key = await AiService.instance.resolveApiKey('explicit-override');
|
|
expect(key, equals('explicit-override'));
|
|
});
|
|
|
|
test('falls back to fallbackApiKey when no explicit or secure key is set', () async {
|
|
AiService.instance.setFallbackApiKey('config-file-key');
|
|
final key = await AiService.instance.resolveApiKey();
|
|
expect(key, equals('config-file-key'));
|
|
});
|
|
});
|
|
}
|