import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:wrnexus_inbox/wrnexus_inbox.dart'; void main() { test( 'recipient transport, token refresh, writes and nullable delivery rules', () async { final calls = []; var tokens = 0; final client = InboxClient( token: () => 'recipient-${++tokens}', transport: MockClient((r) async { calls.add(r); if (r.method == 'GET') { return http.Response('{"messages":[],"unreadCount":3}', 200); } return http.Response('{"id":"d","revision":2,"replayed":false}', 200); }), ); expect((await client.messages(limit: 20)).unreadCount, 3); await client.markRead('m'); await client.archive('m'); await client.savePreferences( const PreferenceInput( category: '*', inAppEnabled: true, marketingEnabled: false, deliveryRules: DeliveryRules(timezone: 'UTC'), ), ); await client.registerMobilePush( const MobileRegistration( connectionId: 'c', installationId: 'i', name: 'Phone', platform: MobilePlatform.android, token: 'fcm', marketingConsent: false, ), ); await client.unregisterMobilePush('d'); await client.unregisterWebPush('w'); expect( calls.map((r) => r.headers['authorization']), List.generate(7, (i) => 'Bearer recipient-${i + 1}'), ); expect(calls.first.url.path, '/api/v1/inbox/messages'); expect(calls.first.url.query, 'limit=20'); expect(jsonDecode(calls[1].body), {'id': 'm', 'action': 'read'}); expect((jsonDecode(calls[3].body) as Map)['deliveryRules'], { 'timezone': 'UTC', 'quietHours': null, 'marketingDailyLimit': null, }); expect((jsonDecode(calls[4].body) as Map)['action'], 'register-mobile'); client.close(); await expectLater( client.messages(), throwsA(isA().having((e) => e.code, 'code', 'closed')), ); }, ); test('API errors stop without retry', () async { var calls = 0; final client = InboxClient( token: () => 'expired', transport: MockClient((_) async { calls++; return http.Response( '{"error":{"code":"expired-token","message":"Expired"}}', 401, ); }), ); await expectLater( client.markRead('m'), throwsA( isA() .having((e) => e.status, 'status', 401) .having((e) => e.code, 'code', 'expired-token'), ), ); expect(calls, 1); client.close(); }); test('origin, actions and frame parsing', () { for (final text in [ 'http://example.com', 'https://u:p@example.com', 'https://example.com/a', 'https://example.com?q=x', ]) { expect( () => InboxClient(endpoint: Uri.parse(text), token: () => 'r'), throwsA(isA()), ); } expect(InboxClient.safeActionUri('javascript:alert(1)'), isNull); expect(InboxClient.safeActionUri('https://u:p@example.com'), isNull); final parser = InboxEventParser(); expect(parser.line(': heartbeat'), isNull); expect(parser.line(''), isNull); parser.line('event: inbox'); parser.line('data: {"messages":[],"unreadCount":2}'); expect(parser.line('')!.unreadCount, 2); parser.line('event: inbox'); parser.line('data: broken'); expect(() => parser.line(''), throwsFormatException); }); }