import 'package:flutter_test/flutter_test.dart'; import 'package:life_helper/domain/ai/parse_response.dart'; import 'package:life_helper/domain/models/habit.dart'; void main() { test('parses 3 valid candidates', () { final r = parseFrameCandidates({ 'candidates': [ {'level': 'L2', 'framed_text': '저녁엔 무알콜 마시기', 'confidence': 0.9}, {'level': 'L3', 'framed_text': '나는 맑은 정신의 사람이다'}, {'level': 'L2', 'framed_text': '주중엔 운동 우선'}, ], }); expect(r, hasLength(3)); expect(r[0].level, FrameLevel.l2); expect(r[0].confidence, 0.9); expect(r[1].level, FrameLevel.l3); expect(r[1].confidence, 0.5); // default }); test('candidates missing → FormatException', () { expect( () => parseFrameCandidates({'foo': 'bar'}), throwsA(isA()), ); }); test('candidates not list → FormatException', () { expect( () => parseFrameCandidates({'candidates': 'oops'}), throwsA(isA()), ); }); test('empty candidates → empty list (no throw)', () { final r = parseFrameCandidates({'candidates': []}); expect(r, isEmpty); }); test('skips unknown level + length-violating items', () { final tooLong = 'a' * 121; final r = parseFrameCandidates({ 'candidates': [ {'level': 'L99', 'framed_text': '?'}, // skipped {'level': 'L2', 'framed_text': tooLong}, // skipped {'level': 'L3', 'framed_text': ' '}, // skipped (empty after trim) {'level': 'L2', 'framed_text': '유효한 후보'}, ], }); expect(r, hasLength(1)); expect(r.first.framedText, '유효한 후보'); }); test('confidence clamps and falls back to 0.5', () { final r = parseFrameCandidates({ 'candidates': [ {'level': 'L2', 'framed_text': 'a', 'confidence': -0.4}, {'level': 'L2', 'framed_text': 'b', 'confidence': 2.5}, {'level': 'L2', 'framed_text': 'c', 'confidence': 'not-a-number'}, ], }); expect(r[0].confidence, 0.0); expect(r[1].confidence, 1.0); expect(r[2].confidence, 0.5); }); test('keeps L0/L1 candidates (filtering is suggestFrame responsibility)', () { final r = parseFrameCandidates({ 'candidates': [ {'level': 'L0', 'framed_text': '술 끊기'}, {'level': 'L2', 'framed_text': '무알콜'}, ], }); expect(r, hasLength(2)); expect(r[0].level, FrameLevel.l0); }); test('source_pattern_id preserved when present', () { final r = parseFrameCandidates({ 'candidates': [ { 'level': 'L2', 'framed_text': 'foo', 'source_pattern_id': 'fp_alcohol' }, ], }); expect(r.single.sourcePatternId, 'fp_alcohol'); }); }