Files
pmomusic/pmoradiofrance/src/models.rs

365 lines
10 KiB
Rust
Raw Normal View History

//! Data models for Radio France API responses
//!
//! This module contains all the structures needed to deserialize
//! responses from Radio France's public APIs.
use serde::{Deserialize, Serialize};
// ============================================================================
// Station Discovery Models
// ============================================================================
/// A discovered Radio France station
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
///
/// Simplifié: juste slug + name, plus de distinction de type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Station {
/// Unique slug identifier (e.g., "franceculture", "fip_rock")
pub slug: String,
/// Human-readable name (e.g., "France Culture", "FIP Rock")
pub name: String,
}
impl Station {
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
/// Create a new station
pub fn new(slug: impl Into<String>, name: impl Into<String>) -> Self {
Self {
slug: slug.into(),
name: name.into(),
}
}
}
// ============================================================================
// Live API Response Models
// ============================================================================
/// Response from the /api/live? endpoint
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveResponse {
/// Station name (slug)
pub station_name: String,
/// Recommended delay before next refresh (milliseconds)
pub delay_to_refresh: u64,
/// Whether station has been migrated to new system
#[serde(default)]
pub migrated: bool,
/// Current show/track metadata
pub now: ShowMetadata,
/// Next show/track metadata (if available)
pub next: Option<ShowMetadata>,
}
impl LiveResponse {
/// Get local radios (France Bleu only) - convenience accessor
pub fn local_radios(&self) -> Option<&Vec<LocalRadio>> {
self.now.local_radios.as_ref()
}
}
/// Metadata for a show or track currently playing
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ShowMetadata {
/// Whether to display music program info
#[serde(default)]
pub print_prog_music: bool,
/// Start time (Unix timestamp)
pub start_time: Option<u64>,
/// End time (Unix timestamp)
pub end_time: Option<u64>,
/// Producer name
pub producer: Option<String>,
/// First line (usually show title)
#[serde(default)]
pub first_line: Line,
/// Second line (usually episode/track title)
#[serde(default)]
pub second_line: Line,
/// Third line (optional subtitle)
pub third_line: Option<Line>,
/// Show description/intro
pub intro: Option<String>,
/// React availability flag
#[serde(default)]
pub react_available: bool,
/// Background visual
pub visual_background: Option<EmbedImage>,
/// Song info (for music stations like FIP, France Musique)
pub song: Option<Song>,
/// Available media streams
#[serde(default)]
pub media: Media,
/// Visual assets (card, player)
pub visuals: Option<Visuals>,
/// Local radios list (France Bleu only)
#[serde(default)]
pub local_radios: Option<Vec<LocalRadio>>,
}
/// A line of text with optional link
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct Line {
/// Text content
pub title: Option<String>,
/// UUID of the referenced object
pub id: Option<String>,
/// URL path to the referenced page
pub path: Option<String>,
}
impl Line {
/// Get the title or an empty string
pub fn title_or_default(&self) -> &str {
self.title.as_deref().unwrap_or("")
}
}
/// Song information (for music stations)
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Song {
/// Song UUID
pub id: String,
/// Release year
pub year: Option<u32>,
/// Artist names
#[serde(default)]
pub interpreters: Vec<String>,
/// Album/release information
#[serde(default)]
pub release: Release,
}
impl Song {
/// Get artists as a comma-separated string
pub fn artists_display(&self) -> String {
self.interpreters.join(", ")
}
}
/// Album/release information
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct Release {
/// Record label
pub label: Option<String>,
/// Album title
pub title: Option<String>,
/// Catalog reference
pub reference: Option<String>,
}
/// Available media streams
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct Media {
/// List of available stream sources
#[serde(default)]
pub sources: Vec<StreamSource>,
}
impl Media {
/// Find the best HiFi stream (AAC ou MP3, bitrate maximum, jamais HLS)
pub fn best_hifi_stream(&self) -> Option<&StreamSource> {
// Priority: AAC 192 kbps > AAC autre bitrate > MP3 bitrate max > autre
// JAMAIS HLS (incompatible avec beaucoup de lecteurs)
self.sources
.iter()
.filter(|s| s.broadcast_type == BroadcastType::Live && s.format != StreamFormat::Hls)
.max_by_key(|s| {
// Priorité: format puis bitrate
let format_priority = match s.format {
StreamFormat::Aac => 1000,
StreamFormat::Mp3 => 500,
StreamFormat::Hls => 0, // Filtré de toute façon
};
format_priority + s.bitrate
})
}
/// Find a stream by format and broadcast type
pub fn find_stream(
&self,
format: StreamFormat,
broadcast_type: BroadcastType,
) -> Option<&StreamSource> {
self.sources
.iter()
.find(|s| s.format == format && s.broadcast_type == broadcast_type)
}
/// Get all live streams
pub fn live_streams(&self) -> impl Iterator<Item = &StreamSource> {
self.sources
.iter()
.filter(|s| s.broadcast_type == BroadcastType::Live)
}
}
/// A stream source with URL and format info
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamSource {
/// Stream URL
pub url: String,
/// Broadcast type (live or timeshift)
pub broadcast_type: BroadcastType,
/// Stream format
pub format: StreamFormat,
/// Bitrate in kbps (0 for HLS adaptive)
pub bitrate: u32,
}
/// Type of broadcast
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum BroadcastType {
/// Live stream
Live,
/// Timeshift (replay) stream
Timeshift,
}
/// Stream format
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum StreamFormat {
/// MP3 format
Mp3,
/// AAC format
Aac,
/// HLS adaptive streaming
Hls,
}
impl StreamFormat {
/// Get the MIME type for this format
pub fn mime_type(&self) -> &'static str {
match self {
StreamFormat::Mp3 => "audio/mpeg",
StreamFormat::Aac => "audio/aac",
StreamFormat::Hls => "application/vnd.apple.mpegurl",
}
}
}
/// An embedded image
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EmbedImage {
/// Model type (usually "EmbedImage")
#[serde(default)]
pub model: String,
/// Image URL or path
pub src: String,
/// Image width
pub width: Option<u32>,
/// Image height
pub height: Option<u32>,
/// Dominant color (hex)
pub dominant: Option<String>,
/// Copyright notice
pub copyright: Option<String>,
}
impl EmbedImage {
/// Extract the UUID from the image URL
///
/// Pikapi URLs are in format: https://www.radiofrance.fr/pikapi/images/{uuid}[/size]
pub fn extract_uuid(&self) -> Option<String> {
let re = regex::Regex::new(r"/pikapi/images/([a-f0-9-]+)").ok()?;
re.captures(&self.src)
.and_then(|cap| cap.get(1))
.map(|m| m.as_str().to_string())
}
}
/// Visual assets for different display contexts
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Visuals {
/// Card-sized image
pub card: Option<EmbedImage>,
/// Player-sized image
pub player: Option<EmbedImage>,
}
/// A local France Bleu radio station
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalRadio {
/// Internal ID
pub id: u32,
/// Display title (e.g., "ICI Alsace")
pub title: String,
/// Technical name (e.g., "francebleu_alsace")
pub name: String,
/// Whether the station is currently on air
#[serde(default)]
pub is_on_air: bool,
}
// ============================================================================
// Image Size Helpers
// ============================================================================
/// Available image sizes from Pikapi
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageSize {
/// 88x88 pixels
Tiny,
/// 200x200 pixels
Small,
/// 420x720 pixels (portrait)
Medium,
/// 560x960 pixels (portrait)
Large,
/// 1200x680 pixels (landscape)
XLarge,
/// Original size
Raw,
}
impl ImageSize {
/// Get the size string for Pikapi URLs
pub fn as_str(&self) -> &'static str {
match self {
ImageSize::Tiny => "88x88",
ImageSize::Small => "200x200",
ImageSize::Medium => "420x720",
ImageSize::Large => "560x960",
ImageSize::XLarge => "1200x680",
ImageSize::Raw => "raw",
}
}
/// Build a Pikapi image URL
pub fn build_url(&self, uuid: &str) -> String {
format!(
"https://www.radiofrance.fr/pikapi/images/{}/{}",
uuid,
self.as_str()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_station_creation() {
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
let station = Station::new("franceculture", "France Culture");
assert_eq!(station.slug, "franceculture");
assert_eq!(station.name, "France Culture");
}
#[test]
fn test_image_size() {
let uuid = "436430f7-5b2b-43f2-9f3c-28f2ad6cae39";
let url = ImageSize::Small.build_url(uuid);
assert_eq!(
url,
"https://www.radiofrance.fr/pikapi/images/436430f7-5b2b-43f2-9f3c-28f2ad6cae39/200x200"
);
}
}