Files
pluriwave/test/servicios/servicio_anuncios_test.dart
T
FreeTLab 2e15d05431 fix(ads): force test ad units in release during closed testing
Closed-testing human testers cannot be registered as AdMob test
devices, so release builds serving real ad units risked invalid
traffic against an AdMob account that currently earns essentially
nothing. Add usarAnunciosDePruebaEnRelease, a single boolean switch
defaulted to true, that keeps bannerAdUnitId/interstitialAdUnitId on
Google's official test ids even in kReleaseMode. Flipping it to false
is the only change needed to go live. AndroidManifest's AdMob
application id is untouched, as it only initializes the SDK.
2026-08-28 19:49:12 +02:00

212 lines
7.4 KiB
Dart

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<bool> 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<bool>();
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<void>.delayed(const Duration(milliseconds: 5));
return true;
},
timeoutIntentoInterstitial: const Duration(milliseconds: 200),
);
expect(await servicio.intentarInterstitial(), isTrue);
});
});
group('usarAnunciosDePruebaEnRelease — interruptor de fase de pruebas', () {
test('mientras esta en true, bannerAdUnitId e interstitialAdUnitId son los '
'ids oficiales de prueba de Google', () {
expect(usarAnunciosDePruebaEnRelease, isTrue);
expect(bannerAdUnitId, equals('ca-app-pub-3940256099942544/6300978111'));
expect(
interstitialAdUnitId,
equals('ca-app-pub-3940256099942544/1033173712'),
);
});
test('los ids reales siguen presentes como constantes en el archivo (no se '
'pueden perder en un futuro edit)', () {
final source =
File('lib/servicios/servicio_anuncios.dart').readAsStringSync();
expect(
source.contains("'ca-app-pub-6038935671414339/5658618378'"),
isTrue,
reason: 'el id real del banner debe seguir presente en el archivo',
);
expect(
source.contains("'ca-app-pub-6038935671414339/4189478248'"),
isTrue,
reason:
'el id real del interstitial debe seguir presente en el '
'archivo',
);
});
});
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);
});
});
}