import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:reelbox/data/api_service.dart'; import 'package:reelbox/data/home_banners.dart'; class BannerApi extends ApiService { String? requested; dynamic response; bool fail = false; @override Future get(String path, {Map? query}) async { requested = path; if (fail) throw const ApiException('Offline'); return response; } } void main() { test('real trusted transport preserves configured ads without restoring vendor feeds', () async { final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); addTearDown(() => server.close(force: true)); final paths = []; server.listen((request) async { paths.add(request.uri.path); request.response.headers.contentType = ContentType.json; request.response.write( jsonEncode({ 'banners': [ { 'title_en': 'Fixture sponsor', 'target_url': 'https://sponsor.example/offer', }, { 'title_en': 'Melodybox fixture', 'target_url': 'https://vendor.example/', }, { 'title_en': 'Blocked link', 'target_url': 'https://t.me/OtherChannel', }, ], }), ); await request.response.close(); }); final api = ApiService(baseUrl: 'http://127.0.0.1:${server.port}/api/'); final banners = await HomeBannerService(api).load(); expect(paths, ['/api/banners/']); expect(banners, hasLength(2)); expect(banners.first, HomeBanner.telegram); expect(banners.last.title(false), 'Fixture sponsor'); expect(banners.last.isAdvertisement, isTrue); }); test( 'unsafe ads are skipped independently and config work is bounded', () async { final api = BannerApi(); final valid = { 'title_en': 'Fixture sponsor', 'target_url': 'https://sponsor.example/', }; for (final target in [ 'http://sponsor.example/', 'javascript:alert(1)', '//sponsor.example/', 'https://user:pass@sponsor.example/', 'https://sponsor.example:444/', 'https://sponsor.example/ bad', 'https://sponsor.example/\\\\evil', 'https://', 'https://t.me/OtherChannel', 'https://sponsor.example/app.apk', 'https://sponsor.example/%0aevil', 'https://-bad.example/', 'https://bad..example/', 'https://bad.example./', 'https://sponsor.example/%zz', 'https://sponsor.example/${'x' * 2048}', ]) { api.response = { 'banners': [ {...valid, 'target_url': target}, valid, ], }; final banners = await HomeBannerService(api).load(); expect(banners, hasLength(2), reason: target); expect(banners.last.target.toString(), 'https://sponsor.example/'); } for (final invalid in [ null, true, 4, [], {}, {'banners': {}}, { 'banners': [null, 7, true, {}], }, ]) { api.response = invalid; expect(await HomeBannerService(api).load(), [HomeBanner.telegram]); } api.response = { 'banners': [ {...valid, 'title_en': 'x' * 81}, {...valid, 'title_en': 'bad\u202etext'}, {...valid, 'subtitle_en': 'x' * 181}, {...valid, 'title_en': 5}, {...valid, 'image_url': 'http://sponsor.example/image.jpg'}, valid, ], }; expect(await HomeBannerService(api).load(), hasLength(2)); api.response = {'banners': List.filled(1000, valid)}; expect(await HomeBannerService(api).load(), hasLength(6)); api.response = { 'banners': [...List.filled(20, {}), valid], }; expect(await HomeBannerService(api).load(), [HomeBanner.telegram]); }, ); test( 'configured bilingual ads append after Telegram with optional image', () async { final api = BannerApi() ..response = { 'banners': [ { 'title_en': 'Test sponsor', 'title_fa': 'حامی آزمایشی', 'subtitle_en': 'Fixture only', 'subtitle_fa': 'فقط دادهٔ آزمایشی', 'target_url': 'https://sponsor.example/offer?ref=reelbox', 'image_url': 'https://sponsor.example/banner.jpg', }, { 'title_en': 'Text sponsor', 'target_url': 'https://sponsor.example/text', }, ], }; final banners = await HomeBannerService(api).load(); expect(banners, hasLength(3)); expect(banners.first, same(HomeBanner.telegram)); expect(banners[1].isAdvertisement, isTrue); expect(banners[1].title(true), 'حامی آزمایشی'); expect(banners[1].subtitle(true), 'فقط دادهٔ آزمایشی'); expect(banners[1].image.toString(), 'https://sponsor.example/banner.jpg'); expect(banners.last.title(true), 'Text sponsor'); expect(banners.last.image, isNull); }, ); test( 'default Telegram banner survives missing config and API failures', () async { final api = BannerApi(); final service = HomeBannerService(api); for (final fail in [false, true]) { api.fail = fail; final banners = await service.load(); expect(banners, hasLength(1)); expect(banners.single.target.toString(), 'https://t.me/ReelBoxApp'); expect(banners.single.title(false), 'ReelBox updates'); expect(banners.single.subtitle(false), 'Updates and announcements'); expect(banners.single.title(true), 'تازه‌های ریل‌باکس'); expect(banners.single.isAdvertisement, isFalse); } expect(api.requested, 'banners/'); }, ); }