DataSourceConfig refactored to enum: External/Clock/StaticText. Clock generates formatted time via chrono, static text emits configured string. ESP32: connection status indicator (green/red dot bottom-right), per-widget clear before redraw, RenderEvent enum for local + server messages. Polling uses DataUpdate instead of ScreenUpdate to avoid wiping widget state. Empty mappings passthrough raw source data for internal sources.
87 lines
2.5 KiB
Rust
87 lines
2.5 KiB
Rust
use domain::{DataSource, DataSourceConfig, DataSourcePort, DataSourceType, Value};
|
|
use media_adapter::MediaAdapter;
|
|
use std::time::Duration;
|
|
|
|
fn subsonic_response(playing: bool) -> serde_json::Value {
|
|
if playing {
|
|
serde_json::json!({
|
|
"subsonic-response": {
|
|
"status": "ok",
|
|
"nowPlaying": {
|
|
"entry": [{
|
|
"title": "Believer",
|
|
"artist": "Imagine Dragons",
|
|
"album": "Evolve",
|
|
"duration": 204
|
|
}]
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
serde_json::json!({
|
|
"subsonic-response": {
|
|
"status": "ok",
|
|
"nowPlaying": {}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
async fn start_fake_subsonic(playing: bool) -> String {
|
|
let app = axum::Router::new().route(
|
|
"/rest/getNowPlaying.view",
|
|
axum::routing::get(move || async move { axum::response::Json(subsonic_response(playing)) }),
|
|
);
|
|
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
|
format!("http://{addr}")
|
|
}
|
|
|
|
fn make_source(url: String) -> DataSource {
|
|
DataSource {
|
|
id: 1,
|
|
name: "navidrome".into(),
|
|
source_type: DataSourceType::Media,
|
|
poll_interval: Duration::from_secs(5),
|
|
config: DataSourceConfig::External {
|
|
url: Some(url),
|
|
headers: vec![
|
|
("username".into(), "test".into()),
|
|
("password".into(), "testpass".into()),
|
|
],
|
|
api_key: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn returns_now_playing_info() {
|
|
let base = start_fake_subsonic(true).await;
|
|
let adapter = MediaAdapter::new();
|
|
let source = make_source(base);
|
|
|
|
let result = adapter.poll(&source).await.unwrap();
|
|
|
|
assert_eq!(result.get_path("$.playing"), Some(&Value::Bool(true)));
|
|
assert_eq!(
|
|
result.get_path("$.title"),
|
|
Some(&Value::String("Believer".into()))
|
|
);
|
|
assert_eq!(
|
|
result.get_path("$.artist"),
|
|
Some(&Value::String("Imagine Dragons".into()))
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn returns_not_playing_when_empty() {
|
|
let base = start_fake_subsonic(false).await;
|
|
let adapter = MediaAdapter::new();
|
|
let source = make_source(base);
|
|
|
|
let result = adapter.poll(&source).await.unwrap();
|
|
assert_eq!(result.get_path("$.playing"), Some(&Value::Bool(false)));
|
|
}
|