// 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); }