import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:kouyu_english/core/ai_service.dart'; import 'package:kouyu_english/core/models.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test( 'lexicon requests keep a stable system prefix and dynamic JSON last', () async { AiService.instance.setFallbackApiKey('test-key'); final captured = >[]; Future request(String text) async { await http.runWithClient( () => AiService.instance.temporaryDefinition( provider: AiProviderType.compatible, endpoint: 'https://api.deepseek.com', model: 'deepseek-flash', text: text, ), () => MockClient((request) async { final body = jsonDecode(request.body) as Map; captured.add(body['messages'] as List); return http.Response( jsonEncode({ 'choices': [ { 'message': {'content': '{"definition":"测试"}'}, }, ], }), 200, headers: {'content-type': 'application/json; charset=utf-8'}, ); }), ); } await request('apple'); await request('airport'); expect(captured, hasLength(2)); expect(captured[0], hasLength(2)); expect(captured[0][0]['role'], 'system'); expect(captured[0][0]['content'], captured[1][0]['content']); expect(captured[0][0]['content'], isNot(contains('apple'))); expect(jsonDecode(captured[0][1]['content'] as String), { 'text': 'apple', }); expect(jsonDecode(captured[1][1]['content'] as String), { 'text': 'airport', }); }, ); test( 'dialogue evaluation keeps learner data out of the cached prefix', () async { AiService.instance.setFallbackApiKey('test-key'); final captured = >[]; Future request({ required String learnerText, required String turnId, }) async { await http.runWithClient( () => AiService.instance.checkDialogueIntervention( provider: AiProviderType.compatible, endpoint: 'https://api.deepseek.com', model: 'deepseek-flash', partnerLine: 'How are you?', taskLabel: 'Say how you feel.', learnerText: learnerText, turnId: turnId, ), () => MockClient((request) async { final body = jsonDecode(request.body) as Map; captured.add(body['messages'] as List); return http.Response( jsonEncode({ 'choices': [ { 'message': { 'content': jsonEncode({ 'schemaVersion': 'dialogue-intervention-2', 'turnId': turnId, 'accepted': true, 'goalSatisfied': true, 'verdict': 'accepted', 'reasonCode': 'goal-met', 'suggestion': null, 'explanation': '表达自然。', }), }, }, ], }), 200, headers: {'content-type': 'application/json; charset=utf-8'}, ); }), ); } await request(learnerText: 'I am fine.', turnId: 'turn-1'); await request(learnerText: 'I feel tired.', turnId: 'turn-2'); final firstSystem = captured[0][0]['content'] as String; final secondSystem = captured[1][0]['content'] as String; expect(firstSystem, secondSystem); expect(firstSystem, isNot(contains('I am fine.'))); expect(firstSystem, isNot(contains('turn-1'))); final firstInput = jsonDecode(captured[0][1]['content'] as String); final secondInput = jsonDecode(captured[1][1]['content'] as String); expect(firstInput['learnerText'], 'I am fine.'); expect(firstInput['turnId'], 'turn-1'); expect(secondInput['learnerText'], 'I feel tired.'); expect(secondInput['turnId'], 'turn-2'); }, ); }