import 'package:flutter_test/flutter_test.dart'; import 'package:life_helper/domain/ai/few_shot_builder.dart'; import 'package:life_helper/domain/ai/frame_candidate.dart'; import 'package:life_helper/domain/models/frame_pattern.dart'; import 'package:life_helper/domain/models/habit.dart'; final _patterns = [ const FramePatternModel( id: 'fp_alcohol', domain: 'drink', avoidanceKeyword: '술 끊기', l0Example: '술 끊기', l2Suggestion: '저녁엔 무알콜 음료', l3Identity: '나는 맑은 정신의 사람이다', ), const FramePatternModel( id: 'fp_smoke', domain: 'smoke', avoidanceKeyword: '담배 끊기', l0Example: '담배 끊기', l2Suggestion: '간식 대체', l3Identity: '나는 깨끗한 폐를 가진 사람이다', ), const FramePatternModel( id: 'fp_general', domain: 'general', avoidanceKeyword: '안 하기', l0Example: '안 하기', l2Suggestion: '대체 행동 정의', ), ]; void main() { test('matched keyword surfaces relevant pattern first', () { final p = buildFewShotPrompt( const SuggestFrameInput( rawText: '술 끊고 싶어', habitType: HabitType.breakHabit, ), _patterns, ); expect(p, contains('## 예시 1')); // The alcohol pattern (highest score) should appear before smoke/general. final idxAlc = p.indexOf('저녁엔 무알콜 음료'); final idxSmk = p.indexOf('간식 대체'); expect(idxAlc, greaterThan(-1)); expect(idxSmk == -1 || idxAlc < idxSmk, true); }); test('fallback uses first patterns when no keyword matches', () { final p = buildFewShotPrompt( const SuggestFrameInput( rawText: 'xyz unknown words', habitType: HabitType.build, ), _patterns, ); expect(p, contains('## 예시 1')); // First pattern in list is alcohol. expect(p, contains('저녁엔 무알콜 음료')); }); test('empty patterns → prompt has no few-shot section', () { final p = buildFewShotPrompt( const SuggestFrameInput( rawText: '술 끊고 싶어', habitType: HabitType.breakHabit, ), const [], ); expect(p.contains('변환 예시'), false); expect(p, contains('사용자 입력')); expect(p, contains('raw_text:')); }); test('anchor hint appears when provided', () { final p = buildFewShotPrompt( const SuggestFrameInput( rawText: '책 읽고 싶어', habitType: HabitType.build, anchorHint: '아침 양치 후', ), _patterns, ); expect(p, contains('anchor_hint: "아침 양치 후"')); }); test('habit_type rendered using dbValue', () { final pBreak = buildFewShotPrompt( const SuggestFrameInput( rawText: 'a', habitType: HabitType.breakHabit, ), const [], ); expect(pBreak, contains('habit_type: break')); final pBuild = buildFewShotPrompt( const SuggestFrameInput(rawText: 'a', habitType: HabitType.build), const [], ); expect(pBuild, contains('habit_type: build')); }); test('deterministic — same input → same prompt', () { final a = buildFewShotPrompt( const SuggestFrameInput( rawText: '술 끊기', habitType: HabitType.breakHabit, ), _patterns, ); final b = buildFewShotPrompt( const SuggestFrameInput( rawText: '술 끊기', habitType: HabitType.breakHabit, ), _patterns, ); expect(a, b); }); test('maxFewShot caps selected examples', () { final p = buildFewShotPrompt( const SuggestFrameInput(rawText: 'x', habitType: HabitType.build), _patterns, maxFewShot: 1, ); expect(p, contains('## 예시 1')); expect(p.contains('## 예시 2'), false); }); }