import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:jinzhi_flutter_app/common/router/deep_link_service.dart'; void main() { setUp(DeepLinkService.resetForTest); test('keeps FIFO events until the router is ready', () { DeepLinkService.configureTrustedHttpsHosts({'links.example.com'}); DeepLinkService.handle('jinzhi://market'); DeepLinkService.handleUri(Uri.parse('https://links.example.com/news?id=1')); expect(DeepLinkService.pendingCount, 2); final navigated = []; DeepLinkService.attachRouter((location) { navigated.add(location); return true; }); expect(navigated, ['/market', '/news?id=1']); expect(DeepLinkService.pendingCount, 0); }); test('rejects HTTPS links outside the exact trusted host allowlist', () { DeepLinkService.configureTrustedHttpsHosts({'links.example.com'}); final navigated = []; DeepLinkService.attachRouter((location) { navigated.add(location); return true; }); DeepLinkService.handleUri(Uri.parse('https://evil.example/news')); DeepLinkService.handleUri(Uri.parse('https://sub.links.example.com/news')); DeepLinkService.handleUri(Uri.parse('https://links.example.com:8443/news')); DeepLinkService.handleUri(Uri.parse('https://links.example.com/news')); expect(navigated, ['/news']); expect(DeepLinkService.pendingCount, 0); }); test('disables HTTPS deep links when no production host is configured', () { final navigated = []; DeepLinkService.attachRouter((location) { navigated.add(location); return true; }); DeepLinkService.handleUri(Uri.parse('https://links.example.com/news')); DeepLinkService.handle('jinzhi://news'); expect(navigated, ['/news']); }); test('deduplicates event ids even after the first event is consumed', () { final navigated = []; DeepLinkService.attachRouter((location) { navigated.add(location); return true; }); final payload = jsonEncode({ 'event_id': 'same-event', 'deep_link': 'jinzhi://assets', }); DeepLinkService.handle(payload); DeepLinkService.handle(payload); expect(navigated, ['/assets']); }); test('uses bounded queue and discards expired events', () { final old = DateTime.now().subtract(DeepLinkService.expiry * 2); DeepLinkService.enqueue( 'jinzhi://assets', source: DeepLinkSource.appLink, receivedAt: old, ); for (var index = 0; index < DeepLinkService.maxPending + 5; index++) { DeepLinkService.enqueue( 'jinzhi://news?event_id=$index', source: DeepLinkSource.push, ); } expect(DeepLinkService.pendingCount, DeepLinkService.maxPending); final navigated = []; DeepLinkService.attachRouter((location) { navigated.add(location); return true; }); expect(navigated, hasLength(DeepLinkService.maxPending)); expect(navigated, isNot(contains('/assets'))); }); test('auth guard leaves the head pending until auth changes', () { var authenticated = false; final navigated = []; DeepLinkService.attachRouter((location) { navigated.add(location); return true; }, authGuard: (_) => authenticated); DeepLinkService.handle('jinzhi://profile'); expect(DeepLinkService.pendingCount, 1); authenticated = true; DeepLinkService.notifyAuthChanged(); expect(navigated, ['/profile']); }); }