EstadoAlarmas, EstadoGrabacion and EstadoRadio defaulted `esPremium` to `() => true`, so any construction site that forgot to wire entitlement compiled fine and silently ran ungated — failing OPEN to premium and disabling the paywall with no test able to catch it. The parameter is now required with no default. Production wiring in app.dart was already correct and is unchanged; the 184 pre-existing test call sites now pass `() => true` explicitly, which is exactly the old implicit default, so every assertion is untouched. EstadoRadio has no gate of its own but constructs EstadoGrabacion, so it inherits the same contract. The one test that existed to pin the old default is renamed to describe what it still covers (the premium path through iniciar() with no duracion); its assertions are unchanged.
100 lines
3.0 KiB
Dart
100 lines
3.0 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/estado/estado_grabacion.dart';
|
|
import 'package:pluriwave/modelos/emisora.dart';
|
|
import 'package:pluriwave/servicios/servicio_grabacion_radio.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
|
|
/// Freemium gating (freemium-gating spec "Recording Start Gated, Management
|
|
/// Stays Free"): starting a NEW recording requires premium; management of
|
|
/// already-existing recordings (listing/playing/deleting — untouched by
|
|
/// this file) stays free regardless.
|
|
void main() {
|
|
test(
|
|
'free tier: iniciar() no llama al servicio y reporta requierePremium',
|
|
() async {
|
|
final servicio = _ServicioGrabacionControlado();
|
|
final emisora = emisoraDemo(uuid: 'rec-1', nombre: 'Grabable');
|
|
final estado = EstadoGrabacion(
|
|
servicio: servicio,
|
|
emisoraActual: () => emisora,
|
|
esPremium: () => false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
final resultado = await estado.iniciar();
|
|
|
|
expect(resultado, ResultadoIniciarGrabacion.requierePremium);
|
|
expect(servicio.inicios, 0);
|
|
},
|
|
);
|
|
|
|
test('premium: iniciar() delega en el servicio normalmente', () async {
|
|
final servicio = _ServicioGrabacionControlado();
|
|
final emisora = emisoraDemo(uuid: 'rec-2', nombre: 'Grabable');
|
|
final estado = EstadoGrabacion(
|
|
servicio: servicio,
|
|
emisoraActual: () => emisora,
|
|
esPremium: () => true,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
final resultado = await estado.iniciar(
|
|
duracion: const Duration(minutes: 1),
|
|
);
|
|
|
|
expect(resultado, ResultadoIniciarGrabacion.iniciada);
|
|
expect(servicio.inicios, 1);
|
|
});
|
|
|
|
// `esPremium` is a required parameter, so "no entitlement callback" is no
|
|
// longer a reachable state to test; what stays worth covering is the
|
|
// premium path through `iniciar()` with no explicit `duracion`.
|
|
test('premium: iniciar() sin duracion tambien delega', () async {
|
|
final servicio = _ServicioGrabacionControlado();
|
|
final emisora = emisoraDemo(uuid: 'rec-3', nombre: 'Grabable');
|
|
final estado = EstadoGrabacion(
|
|
esPremium: () => true,
|
|
servicio: servicio,
|
|
emisoraActual: () => emisora,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
final resultado = await estado.iniciar();
|
|
|
|
expect(resultado, ResultadoIniciarGrabacion.iniciada);
|
|
expect(servicio.inicios, 1);
|
|
});
|
|
}
|
|
|
|
class _ServicioGrabacionControlado extends ServicioGrabacionRadio {
|
|
final _controller = StreamController<EstadoGrabacionRadio>.broadcast();
|
|
final EstadoGrabacionRadio _estadoActual =
|
|
const EstadoGrabacionRadio.inactiva();
|
|
|
|
int inicios = 0;
|
|
|
|
@override
|
|
EstadoGrabacionRadio get estado => _estadoActual;
|
|
|
|
@override
|
|
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
|
|
|
|
@override
|
|
Future<void> inicializar() async {}
|
|
|
|
@override
|
|
Future<void> iniciar(
|
|
Emisora emisora, {
|
|
Duration? duracion,
|
|
String? directorio,
|
|
}) async {
|
|
inicios++;
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() => _controller.close();
|
|
}
|