feat: ship HTTP dashboard and harden daemon/API flows
Add the static HTTP dashboard example and wire in the recent daemon/API polish: CORS-aware API routing, service-install behavior cleanup, safer systemd unit ExecStart quoting, and friendly-name validation for path-safe targeting. Also refresh README/API/roadmap docs, remove the temporary claude observations file, and include the related tests for API/status and daemon validation.
This commit is contained in:
+54
-2
@@ -59,6 +59,8 @@ impl TestDaemon {
|
||||
http_enabled: true,
|
||||
http_port: port,
|
||||
http_host: "127.0.0.1".to_string(),
|
||||
cors_enabled: false,
|
||||
cors_allowed_origins: Vec::new(),
|
||||
log_level: "info".to_string(),
|
||||
},
|
||||
discovery: tvctl::daemon::config::DiscoveryConfig {
|
||||
@@ -153,6 +155,10 @@ impl Drop for TestDaemon {
|
||||
|
||||
impl InProcessApi {
|
||||
async fn start() -> Self {
|
||||
Self::start_with_daemon_config(DaemonConfig::default()).await
|
||||
}
|
||||
|
||||
async fn start_with_daemon_config(daemon_config: DaemonConfig) -> Self {
|
||||
let temp_dir = tempfile::tempdir().expect("temp dir should exist");
|
||||
let root = temp_dir.path();
|
||||
let config_home = root.join("config");
|
||||
@@ -176,7 +182,9 @@ impl InProcessApi {
|
||||
http_enabled: true,
|
||||
http_port: port,
|
||||
http_host: "127.0.0.1".to_string(),
|
||||
log_level: "info".to_string(),
|
||||
cors_enabled: daemon_config.cors_enabled,
|
||||
cors_allowed_origins: daemon_config.cors_allowed_origins,
|
||||
log_level: daemon_config.log_level,
|
||||
},
|
||||
discovery: DiscoveryConfig {
|
||||
auto_discover: false,
|
||||
@@ -233,6 +241,7 @@ impl InProcessApi {
|
||||
} else {
|
||||
registry.ensure_default();
|
||||
}
|
||||
let daemon_http_config = config.daemon.clone();
|
||||
let daemon: SharedDaemon = Arc::new(Mutex::new(Daemon {
|
||||
config,
|
||||
paths: paths.clone(),
|
||||
@@ -243,7 +252,7 @@ impl InProcessApi {
|
||||
discovery: DiscoveryService::new(adapters),
|
||||
}));
|
||||
|
||||
let app = tvctl::api::router(daemon);
|
||||
let app = tvctl::api::router_with_config(daemon, &daemon_http_config);
|
||||
let server = tokio::spawn(async move {
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
@@ -397,3 +406,46 @@ async fn http_api_routes_requests_without_unix_socket_loopback() {
|
||||
assert_eq!(devices_json["ok"], true);
|
||||
assert_eq!(devices_json["data"][0]["id"], api.device.id.to_string());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_api_cors_is_disabled_by_default() {
|
||||
let api = InProcessApi::start().await;
|
||||
let client = Client::new();
|
||||
let response = client
|
||||
.get(format!("{}/devices", api.base_url))
|
||||
.header("Origin", "http://127.0.0.1:8080")
|
||||
.send()
|
||||
.await
|
||||
.expect("cors default response should arrive");
|
||||
assert!(
|
||||
response
|
||||
.headers()
|
||||
.get("access-control-allow-origin")
|
||||
.is_none(),
|
||||
"CORS headers should be absent by default"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_api_cors_allows_configured_loopback_origin() {
|
||||
let api = InProcessApi::start_with_daemon_config(DaemonConfig {
|
||||
cors_enabled: true,
|
||||
cors_allowed_origins: vec!["http://127.0.0.1:8080".to_string()],
|
||||
..DaemonConfig::default()
|
||||
})
|
||||
.await;
|
||||
let client = Client::new();
|
||||
let response = client
|
||||
.get(format!("{}/devices", api.base_url))
|
||||
.header("Origin", "http://127.0.0.1:8080")
|
||||
.send()
|
||||
.await
|
||||
.expect("cors allowed response should arrive");
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get("access-control-allow-origin")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("http://127.0.0.1:8080")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user