import 'dart:async'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/servicios/servicio_anuncios.dart'; /// Ad-display spec: session-scoped interstitial frequency cap (max 2 per /// process lifetime, >=3 min apart), suppressed entirely for premium, and /// never shown when the caller reports the alarm-cap message took /// precedence for this same tap. void main() { ServicioAnuncios construir({ required DateTime Function() ahora, required bool premium, Future Function()? mostrarInterstitialImpl, Duration? timeoutIntentoInterstitial, }) => ServicioAnuncios( ahora: ahora, esPremium: () => premium, mostrarInterstitialImpl: mostrarInterstitialImpl ?? (() async => true), timeoutIntentoInterstitial: timeoutIntentoInterstitial, ); group('intentarInterstitial — cap de frecuencia', () { test('primeros 2 intentos en la sesion se muestran', () async { var ahora = DateTime(2026, 1, 1, 10, 0); final servicio = construir(ahora: () => ahora, premium: false); expect(await servicio.intentarInterstitial(), isTrue); ahora = ahora.add(const Duration(minutes: 5)); expect(await servicio.intentarInterstitial(), isTrue); }); test('un 3er intento en la misma sesion no se muestra (cap 2)', () async { var ahora = DateTime(2026, 1, 1, 10, 0); final servicio = construir(ahora: () => ahora, premium: false); await servicio.intentarInterstitial(); ahora = ahora.add(const Duration(minutes: 5)); await servicio.intentarInterstitial(); ahora = ahora.add(const Duration(minutes: 5)); expect(await servicio.intentarInterstitial(), isFalse); }); test('menos de 3 minutos desde el ultimo: no se muestra', () async { var ahora = DateTime(2026, 1, 1, 10, 0); final servicio = construir(ahora: () => ahora, premium: false); await servicio.intentarInterstitial(); ahora = ahora.add(const Duration(minutes: 1)); expect(await servicio.intentarInterstitial(), isFalse); }); test('exactamente 3 minutos despues si se muestra', () async { var ahora = DateTime(2026, 1, 1, 10, 0); final servicio = construir(ahora: () => ahora, premium: false); await servicio.intentarInterstitial(); ahora = ahora.add(const Duration(minutes: 3)); expect(await servicio.intentarInterstitial(), isTrue); }); test('premium: nunca muestra interstitial', () async { final servicio = construir( ahora: () => DateTime(2026, 1, 1, 10, 0), premium: true, ); expect(await servicio.intentarInterstitial(), isFalse); }); test( 'el conteo/temporizador solo avanza si el ad realmente se muestra', () async { final ahora = DateTime(2026, 1, 1, 10, 0); final servicio = construir( ahora: () => ahora, premium: false, mostrarInterstitialImpl: () async => false, ); final mostrado = await servicio.intentarInterstitial(); expect(mostrado, isFalse); // Un fallo de carga (mostrarInterstitialImpl -> false) no debe // consumir el cupo de la sesion. expect(await servicio.intentarInterstitial(), isFalse); }, ); }); group('intentarInterstitial — fallo al mostrar no consume el cupo', () { test('la implementación real de AdMob distingue presentación real de ' 'fallo de renderizado (FIX 6, code review): ' 'onAdShowedFullScreenContent debe estar instrumentado y el resultado ' 'de show() no puede ser un true incondicional', () { final source = File('lib/servicios/servicio_anuncios.dart').readAsStringSync(); expect( source.contains('onAdShowedFullScreenContent'), isTrue, reason: 'debe registrar si el anuncio realmente llegó a presentarse ' '(onAdShowedFullScreenContent) para no devolver true tras un ' 'onAdFailedToShowFullScreenContent', ); expect( source.contains('await cierreCompleter.future;\n return true;'), isFalse, reason: 'el resultado de _mostrarInterstitialAdMob ya no puede ser un ' 'true incondicional tras el cierre -- debe reflejar si el ' 'anuncio realmente se presentó', ); }); }); group('intentarInterstitial — timeout acotado (FIX 2, code review)', () { test('una implementación que nunca completa resuelve false dentro del ' 'timeout inyectado y no consume el cupo', () async { final ahora = DateTime(2026, 1, 1, 10, 0); final nuncaCompleta = Completer(); addTearDown(() { if (!nuncaCompleta.isCompleted) nuncaCompleta.complete(false); }); final servicio = construir( ahora: () => ahora, premium: false, mostrarInterstitialImpl: () => nuncaCompleta.future, timeoutIntentoInterstitial: const Duration(milliseconds: 20), ); final resultado = await servicio.intentarInterstitial().timeout( const Duration(seconds: 2), ); expect(resultado, isFalse); // El cupo no se consumió: un intento posterior con una // implementación que SÍ resuelve sigue mostrando el anuncio. final servicioReal = construir(ahora: () => ahora, premium: false); expect(await servicioReal.intentarInterstitial(), isTrue); }); test('una implementación lenta pero que SÍ completa dentro del timeout ' 'sigue resolviendo con su resultado real', () async { final ahora = DateTime(2026, 1, 1, 10, 0); final servicio = construir( ahora: () => ahora, premium: false, mostrarInterstitialImpl: () async { await Future.delayed(const Duration(milliseconds: 5)); return true; }, timeoutIntentoInterstitial: const Duration(milliseconds: 200), ); expect(await servicio.intentarInterstitial(), isTrue); }); }); group('debeMostrarBanner', () { test('free: true', () { final servicio = construir( ahora: () => DateTime(2026, 1, 1), premium: false, ); expect(servicio.debeMostrarBanner, isTrue); }); test('premium: false', () { final servicio = construir( ahora: () => DateTime(2026, 1, 1), premium: true, ); expect(servicio.debeMostrarBanner, isFalse); }); }); }