Files
pluriwave/test/servicios/servicio_audio_source_switch_test.dart
T

118 lines
3.8 KiB
Dart

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/servicios/controlador_reconexion.dart';
import 'package:pluriwave/servicios/servicio_audio.dart';
/// S6-R3 — Source-revision guard: rapid source switches must not let a stale
/// error from an earlier source bleed through to the final active source.
///
/// [PluriWaveAudioHandler] uses a monotonically-increasing [_revisionFuente]
/// counter; each [playMediaItem] call increments it and checks `revision ==
/// _revisionFuente` before every async step in [_cambiarFuente]. We cannot
/// instantiate the handler in unit tests (MethodChannels), so we exercise the
/// guard invariants through:
/// (a) the static buffer-configuration constant (already fully testable), and
/// (b) [ControladorReconexion.restablecer], which [playMediaItem] calls on
/// every fresh user switch to discard any in-flight backoff for the
/// previous source — the core "no stale error bleeds through" contract.
void main() {
group('Source-switch guard (S6-R3)', () {
test(
'rapid switch resets the reconnect controller so old backoff is discarded',
() {
final temporizadores = <_FakeTimer>[];
final controlador = ControladorReconexion(
crearTemporizador: (duracion, cb) {
final t = _FakeTimer(duracion, cb);
temporizadores.add(t);
return t;
},
);
// Simulate source A failing and scheduling a retry.
controlador.registrarFallo(
intencionReproducir: true,
alReintentar: () {},
);
expect(controlador.reintentoPendiente, isTrue);
// User rapidly switches to source B — playMediaItem calls restablecer().
controlador.restablecer();
expect(
controlador.reintentoPendiente,
isFalse,
reason: 'stale source-A retry must not fire after switching to B',
);
expect(
controlador.intentos,
0,
reason:
'backoff counter resets so source B starts with a clean slate',
);
expect(
temporizadores.single.cancelado,
isTrue,
reason: 'the pending timer for source A was cancelled',
);
},
);
test(
'three rapid switches all reset backoff independently (A→B→C keeps C clean)',
() {
final controlador = ControladorReconexion();
// A fails, schedule retry.
controlador.registrarFallo(
intencionReproducir: true,
alReintentar: () {},
);
// Switch to B — resets.
controlador.restablecer();
// B fails immediately too.
controlador.registrarFallo(
intencionReproducir: true,
alReintentar: () {},
);
// Switch to C — resets again.
controlador.restablecer();
expect(controlador.reintentoPendiente, isFalse);
expect(controlador.intentos, 0);
},
);
test(
'buffer config constant carries the live-stream guard values (S7-R1)',
() {
// Ensures the static config used when recreating the player for a
// source switch carries the correct live-stream buffer parameters.
const config = PluriWaveAudioHandler.configuracionCargaAndroid;
final control = config.androidLoadControl;
expect(control, isNotNull);
expect(control!.minBufferDuration, const Duration(seconds: 15));
expect(control.maxBufferDuration, const Duration(seconds: 50));
},
);
});
}
class _FakeTimer implements Timer {
_FakeTimer(this.duracion, this.callback);
final Duration duracion;
final void Function() callback;
bool cancelado = false;
@override
void cancel() => cancelado = true;
@override
bool get isActive => !cancelado;
@override
int get tick => 0;
}