feat(quality): harden lint rules and add quality-gate tests
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user