// routing_profile_model_test.dart — RoutingProfile/RoutingRule JSON 无损往返。 import 'package:flutter_test/flutter_test.dart'; import 'package:pangolin_vpn/models/routing_profile.dart'; void main() { test('RoutingProfile round-trips json', () { const j = { 'mode': 'rule', 'builtin': {'china_direct': true, 'lan_direct': true, 'private_via_tunnel': true}, 'rules': [ {'type': 'domain_suffix', 'value': 'x.com', 'action': 'direct', 'note': 'ci', 'enabled': true} ], 'final': 'proxy', }; final p = RoutingProfile.fromJson(j); expect(p.mode, 'rule'); expect(p.rules.single.value, 'x.com'); expect(p.toJson(), j); // 无损往返 }); test('RoutingRule enabled 缺省容错默认 true', () { final r = RoutingRule.fromJson({'type': 'domain', 'value': 'a.com', 'action': 'proxy'}); expect(r.enabled, isTrue); }); test('RoutingRule 构造默认 enabled=true 且 toJson 恒输出 enabled 字段', () { const r = RoutingRule(type: 'domain', value: 'a.com', action: 'proxy'); expect(r.enabled, isTrue); expect(r.toJson().containsKey('enabled'), isTrue); expect(r.toJson()['enabled'], isTrue); }); test('RoutingProfile.fromJson 无档案默认(builtin 缺省)容错', () { final p = RoutingProfile.fromJson({'mode': 'global', 'rules': [], 'final': 'direct'}); expect(p.builtin.chinaDirect, isTrue); expect(p.builtin.lanDirect, isTrue); expect(p.builtin.privateViaTunnel, isTrue); }); test('systemLockedDomains:fromJson 读取,默认空,toJson 不输出(只读字段)', () { final withLocks = RoutingProfile.fromJson({ 'mode': 'rule', 'rules': [], 'final': 'proxy', 'system_locked_domains': ['git.yanmeiai.com', 'admin.yanmeiai.com'], }); expect(withLocks.systemLockedDomains, ['git.yanmeiai.com', 'admin.yanmeiai.com']); expect(withLocks.toJson().containsKey('system_locked_domains'), isFalse); final withoutLocks = RoutingProfile.fromJson({'mode': 'rule', 'rules': [], 'final': 'proxy'}); expect(withoutLocks.systemLockedDomains, isEmpty); }); test('RoutingProfile.defaults() 对齐服务端 routing.Default()', () { final d = RoutingProfile.defaults(); expect(d.mode, 'rule'); expect(d.builtin.chinaDirect, isTrue); expect(d.builtin.lanDirect, isTrue); expect(d.builtin.privateViaTunnel, isTrue); expect(d.rules, isEmpty); expect(d.finalAction, 'proxy'); }); test('RoutingRule.copyWith 用哨兵让 note 可清空为 null', () { const r = RoutingRule(type: 'domain', value: 'a.com', action: 'proxy', note: 'ci'); final cleared = r.copyWith(note: null); expect(cleared.note, isNull, reason: '显式传 null 应清空 note,而不是被 ?? 吞掉'); final unchanged = r.copyWith(value: 'b.com'); expect(unchanged.note, 'ci', reason: '不传 note 时应保留原值'); }); test('RoutingRule / Builtin / RoutingProfile 值相等(operator==)', () { const r1 = RoutingRule(type: 'domain', value: 'a.com', action: 'proxy', note: 'x'); const r2 = RoutingRule(type: 'domain', value: 'a.com', action: 'proxy', note: 'x'); const r3 = RoutingRule(type: 'domain', value: 'a.com', action: 'direct', note: 'x'); expect(r1, r2); expect(r1.hashCode, r2.hashCode); expect(r1 == r3, isFalse); const b1 = Builtin(chinaDirect: true, lanDirect: false, privateViaTunnel: true); const b2 = Builtin(chinaDirect: true, lanDirect: false, privateViaTunnel: true); const b3 = Builtin(); expect(b1, b2); expect(b1.hashCode, b2.hashCode); expect(b1 == b3, isFalse); const p1 = RoutingProfile(mode: 'rule', rules: [r1], finalAction: 'proxy'); const p2 = RoutingProfile(mode: 'rule', rules: [r2], finalAction: 'proxy'); final p3 = RoutingProfile.fromJson({ 'mode': 'rule', 'rules': [ {'type': 'domain', 'value': 'a.com', 'action': 'proxy', 'note': 'x', 'enabled': true} ], 'final': 'proxy', 'system_locked_domains': ['git.yanmeiai.com'], }); expect(p1, p2); expect(p1.hashCode, p2.hashCode); expect(p1 == p3, isFalse, reason: 'systemLockedDomains 不同应视为不相等'); }); }