Files
xtremflow/bin/test/asset_failure_cache_test.dart
MichaelandClaude Opus 5 b6ca9dab96 Logos, guide TV et fluidité : couper le flot de requêtes mortes
Logos absents : l'hébergeur des picons du fournisseur répond 503 sur
tout, y compris sa racine (serveur en maintenance). Le proxy relayait
ce 503 au navigateur, qui redemandait chaque logo à chaque affichage de
la grille — des centaines de requêtes sortantes mortes par seconde.
- Un statut >= 400 sur une image rend désormais un pixel transparent
  avec `Cache-Control: max-age=600`, au lieu de propager l'erreur
- Après trois échecs consécutifs, l'hôte est marqué hors service cinq
  minutes : plus aucun appel sortant, réponse servie localement
- Le corps d'erreur amont est drainé, sinon la connexion restait
  ouverte jusqu'au timeout

Guide TV très lent (4 à 8 s par chaîne dans les logs) : `player_api`
demande un appel par chaîne, et le panneau met ~4 s à répondre — une
grille de trente chaînes dépassait la minute.
- Le dump `xmltv.php` du panneau passe en premier : une requête couvre
  toutes les chaînes, réutilisée trois heures
- L'interrogation chaîne par chaîne reste le repli, puis la source
  XMLTV externe
- Une source XMLTV en échec n'est plus retéléchargée à chaque
  consultation : quinze minutes de recul
- Index borné à 48 h en avant : un dump national couvre sept jours pour
  un millier de chaînes, soit des centaines de Mo pour un guide qui
  n'affiche que le programme courant

Lecture saccadée : le relais FFmpeg -> navigateur de `/turbo.ts` ne
transmettait pas la pause du client à la source. Un lecteur plus lent
que le flux laissait FFmpeg produire à pleine vitesse et le serveur
empilait les paquets en mémoire, d'où la dérive derrière le direct. La
contre-pression est maintenant propagée (`pipeWithBackpressure`).

Le serveur tourne sur un seul isolate : le flot de requêtes mortes et
les attentes EPG de plusieurs secondes partageaient la boucle
d'événements avec les paquets vidéo.

Tests ajoutés : cache d'échecs par hôte, contre-pression du relais,
priorité des sources EPG, recul après échec XMLTV, horizon d'index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 11:57:38 +02:00

58 lines
1.9 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:test/test.dart';
import '../api/asset_failure_cache.dart';
void main() {
group('AssetFailureCache', () {
test('un hôte inconnu est considéré joignable', () {
final cache = AssetFailureCache();
expect(cache.isDown('cdn.example'), isFalse);
});
test('un échec isolé ne coupe pas l’hôte', () {
// Un 5xx passager ne doit pas priver l'utilisateur de ses logos
// pendant plusieurs minutes.
final cache = AssetFailureCache(threshold: 3);
cache.recordFailure('cdn.example');
expect(cache.isDown('cdn.example'), isFalse);
});
test('coupe l’hôte après le seuil d’échecs consécutifs', () {
final cache = AssetFailureCache(threshold: 3);
for (var i = 0; i < 3; i++) {
cache.recordFailure('cdn.example');
}
expect(cache.isDown('cdn.example'), isTrue);
});
test('un succès remet le compteur à zéro', () {
final cache = AssetFailureCache(threshold: 3);
cache.recordFailure('cdn.example');
cache.recordFailure('cdn.example');
cache.recordSuccess('cdn.example');
cache.recordFailure('cdn.example');
expect(cache.isDown('cdn.example'), isFalse);
});
test('la coupure expire après le TTL', () {
var now = DateTime(2026, 9, 19, 9);
final cache = AssetFailureCache(
threshold: 1,
ttl: const Duration(minutes: 5),
clock: () => now,
);
cache.recordFailure('cdn.example');
expect(cache.isDown('cdn.example'), isTrue);
now = now.add(const Duration(minutes: 6));
expect(cache.isDown('cdn.example'), isFalse);
});
test('les hôtes sont indépendants', () {
final cache = AssetFailureCache(threshold: 1);
cache.recordFailure('mort.example');
expect(cache.isDown('mort.example'), isTrue);
expect(cache.isDown('vivant.example'), isFalse);
});
});
}