- React SPA: dashboard, data sources CRUD, widgets CRUD, layout builder, presets. TanStack Router + Query, shadcn/ui, Vite proxy to :3000 - wire media + rss adapters into polling loop, remove xtb source type - media adapter: read username/password from headers, proper subsonic auth - event handler: subscribe to LayoutChanged, push screen update to clients - fix clippy warnings across workspace (Default impls, collapsible ifs, redundant closures, is_none_or, unused imports)
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 {
|
|
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 {
|
|
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());
|
|
}
|