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.
104 lines
2.9 KiB
Rust
104 lines
2.9 KiB
Rust
use axum::{Router, response::Json, routing::get};
|
|
use domain::{DataSource, DataSourceConfig, DataSourcePort, DataSourceType, Value};
|
|
use http_json::HttpJsonAdapter;
|
|
use std::time::Duration;
|
|
|
|
async fn start_fake_api() -> String {
|
|
let app = Router::new()
|
|
.route(
|
|
"/weather",
|
|
get(|| async {
|
|
Json(serde_json::json!({
|
|
"main": {"temp": 5.4, "humidity": 80},
|
|
"weather": [{"icon": "cloud_rain"}]
|
|
}))
|
|
}),
|
|
)
|
|
.route(
|
|
"/simple",
|
|
get(|| async { Json(serde_json::json!({"value": "hello"})) }),
|
|
)
|
|
.route("/not-json", get(|| async { "plain text" }));
|
|
|
|
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: "test".into(),
|
|
source_type: DataSourceType::HttpJson,
|
|
poll_interval: Duration::from_secs(60),
|
|
config: DataSourceConfig::External {
|
|
url: Some(url),
|
|
headers: vec![],
|
|
api_key: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn polls_url_and_returns_nested_json_as_value() {
|
|
let base = start_fake_api().await;
|
|
let adapter = HttpJsonAdapter::new();
|
|
let source = make_source(format!("{base}/weather"));
|
|
|
|
let result = adapter.poll(&source).await.unwrap();
|
|
|
|
assert_eq!(result.get_path("$.main.temp"), Some(&Value::Number(5.4)));
|
|
assert_eq!(
|
|
result.get_path("$.main.humidity"),
|
|
Some(&Value::Number(80.0))
|
|
);
|
|
assert_eq!(
|
|
result.get_path("$.weather[0].icon"),
|
|
Some(&Value::String("cloud_rain".into()))
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn polls_simple_json() {
|
|
let base = start_fake_api().await;
|
|
let adapter = HttpJsonAdapter::new();
|
|
let source = make_source(format!("{base}/simple"));
|
|
|
|
let result = adapter.poll(&source).await.unwrap();
|
|
assert_eq!(
|
|
result.get_path("$.value"),
|
|
Some(&Value::String("hello".into()))
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn returns_error_when_no_url() {
|
|
let adapter = HttpJsonAdapter::new();
|
|
let source = DataSource {
|
|
id: 1,
|
|
name: "bad".into(),
|
|
source_type: DataSourceType::HttpJson,
|
|
poll_interval: Duration::from_secs(60),
|
|
config: DataSourceConfig::External {
|
|
url: None,
|
|
headers: vec![],
|
|
api_key: None,
|
|
},
|
|
};
|
|
|
|
let result = adapter.poll(&source).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn returns_error_on_connection_refused() {
|
|
let adapter = HttpJsonAdapter::new();
|
|
let source = make_source("http://127.0.0.1:1".into());
|
|
|
|
let result = adapter.poll(&source).await;
|
|
assert!(result.is_err());
|
|
}
|