123 lines
4.1 KiB
Dart
123 lines
4.1 KiB
Dart
// ignore_for_file: avoid_print, unnecessary_overrides
|
|
import 'dart:io';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:kouyu_english/core/ai_service.dart';
|
|
import 'package:kouyu_english/core/models.dart';
|
|
|
|
class RealHttpOverrides extends HttpOverrides {
|
|
@override
|
|
HttpClient createHttpClient(SecurityContext? context) {
|
|
return super.createHttpClient(context);
|
|
}
|
|
}
|
|
|
|
/// 读取联网测试用的配置。密钥只来自环境变量,不写进仓库。
|
|
///
|
|
/// 运行方式:
|
|
/// KOUYU_AI_API_KEY=sk-xxx flutter test test/live_endpoint_test.dart
|
|
/// 可选:KOUYU_AI_ENDPOINT、KOUYU_AI_MODEL。
|
|
/// 未设置密钥时整组测试自动跳过,不会误报成功。
|
|
String? _env(String name) {
|
|
final value = Platform.environment[name];
|
|
return (value == null || value.trim().isEmpty) ? null : value.trim();
|
|
}
|
|
|
|
/// KOUYU_AI_ENDPOINT 可以直接填应用里配置的那个完整地址(通常带
|
|
/// /v1/responses)。这里把它归一化成服务基址,再拼出要测的两个端点,
|
|
/// 避免出现 .../v1/responses/v1/responses。
|
|
String _serviceBase(String endpoint) {
|
|
var value = endpoint.trim().replaceFirst(RegExp(r'/+$'), '');
|
|
for (final suffix in const [
|
|
'/v1/responses',
|
|
'/v1/chat/completions',
|
|
'/responses',
|
|
'/chat/completions',
|
|
'/v1',
|
|
]) {
|
|
if (value.endsWith(suffix)) {
|
|
return value.substring(0, value.length - suffix.length);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
HttpOverrides.global = RealHttpOverrides();
|
|
|
|
final apiKey = _env('KOUYU_AI_API_KEY');
|
|
final baseUrl = _serviceBase(
|
|
_env('KOUYU_AI_ENDPOINT') ?? 'https://codex.slcydia.fun/v1/responses',
|
|
);
|
|
final model = _env('KOUYU_AI_MODEL') ?? 'gemini-3.7-flash-high';
|
|
final skipReason = apiKey == null ? '未设置 KOUYU_AI_API_KEY,跳过联网测试。' : null;
|
|
if (apiKey != null) {
|
|
AiService.instance.setFallbackApiKey(apiKey);
|
|
}
|
|
|
|
// 这条不联网,只保证端点归一化不会拼出重复路径。
|
|
test('KOUYU_AI_ENDPOINT 可以直接填完整端点', () {
|
|
expect(_serviceBase('https://host/v1/responses'), 'https://host');
|
|
expect(_serviceBase('https://host/v1/chat/completions'), 'https://host');
|
|
expect(_serviceBase('https://host/v1/'), 'https://host');
|
|
expect(_serviceBase('https://host'), 'https://host');
|
|
});
|
|
|
|
test('Live test: /v1/responses endpoint testConnection', () async {
|
|
try {
|
|
final resResponses = await AiService.instance.testConnection(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: '$baseUrl/v1/responses',
|
|
model: model,
|
|
explicitApiKey: apiKey,
|
|
);
|
|
print(
|
|
'Responses API result: ok=${resResponses.ok}, msg=${resResponses.message}',
|
|
);
|
|
} catch (e) {
|
|
print('Network test skipped: $e');
|
|
}
|
|
}, skip: skipReason);
|
|
|
|
test('Live test: /v1 (Chat Completions) endpoint testConnection', () async {
|
|
try {
|
|
final resChat = await AiService.instance.testConnection(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: '$baseUrl/v1',
|
|
model: model,
|
|
explicitApiKey: apiKey,
|
|
);
|
|
print(
|
|
'Chat Completions API result: ok=${resChat.ok}, msg=${resChat.message}',
|
|
);
|
|
} catch (e) {
|
|
print('Network test skipped: $e');
|
|
}
|
|
}, skip: skipReason);
|
|
|
|
test(
|
|
'Live test: /v1/responses dialogueReply',
|
|
timeout: const Timeout(Duration(seconds: 60)),
|
|
() async {
|
|
try {
|
|
final reply = await AiService.instance.dialogueReply(
|
|
provider: AiProviderType.compatible,
|
|
endpoint: '$baseUrl/v1/responses',
|
|
model: model,
|
|
history: [
|
|
{'role': 'user', 'content': 'Hello, my name is Alex.'},
|
|
],
|
|
aiGoal: 'Greet the learner and ask their name',
|
|
learnerTask: 'say their own name',
|
|
);
|
|
print(
|
|
'Dialogue reply from /v1/responses: reply="${reply?.reply}", translation="${reply?.translation}", feedback="${reply?.feedback}"',
|
|
);
|
|
} catch (e) {
|
|
print('Network test skipped: $e');
|
|
}
|
|
},
|
|
skip: skipReason,
|
|
);
|
|
}
|