v0.3
This commit is contained in:
1 parent
28e39f7ce4
commit
b57beb83c7
34 files changed
+1037
-314
No files matched your search
+258
-56
@@ -1664,72 +1664,133 @@ mod tests {
|
||||
overview_samples: 600,
|
||||
};
|
||||
let body = serde_json::to_string(&preset).unwrap();
|
||||
let anon = router.clone().oneshot(
|
||||
request("/api/dashboard-presets").body(Body::empty()).unwrap()
|
||||
).await.unwrap();
|
||||
let anon = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(anon.status(), StatusCode::UNAUTHORIZED);
|
||||
let forbidden = router.clone().oneshot(
|
||||
request("/api/dashboard-presets/Daily").method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.clone())).unwrap()
|
||||
).await.unwrap();
|
||||
let forbidden = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets/Daily")
|
||||
.method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.clone()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(forbidden.status(), StatusCode::FORBIDDEN);
|
||||
let saved = router.clone().oneshot(
|
||||
request("/api/dashboard-presets/Daily").method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.clone())).unwrap()
|
||||
).await.unwrap();
|
||||
let saved = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets/Daily")
|
||||
.method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.clone()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(saved.status(), StatusCode::NO_CONTENT);
|
||||
let listing = router.clone().oneshot(
|
||||
request("/api/dashboard-presets")
|
||||
.header("cookie", &cookie)
|
||||
.body(Body::empty()).unwrap()
|
||||
).await.unwrap();
|
||||
let listing = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets")
|
||||
.header("cookie", &cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(listing.status(), StatusCode::OK);
|
||||
let entries = json_body(listing).await;
|
||||
assert_eq!(entries[0]["name"], "Daily");
|
||||
assert_eq!(entries[0]["overview_samples"], 600);
|
||||
assert_eq!(entries[0]["dashboard_widget_sizes"], Value::Null);
|
||||
assert_eq!(store.list_admin_dashboard_presets("owner").await.unwrap().len(), 1);
|
||||
assert!(store.list_admin_dashboard_presets("someone-else").await.unwrap().is_empty());
|
||||
let rejected = router.clone().oneshot(
|
||||
request("/api/dashboard-presets/Invalid").method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.replace("\"overview_samples\":600", "\"overview_samples\":999"))).unwrap()
|
||||
).await.unwrap();
|
||||
assert_eq!(
|
||||
store
|
||||
.list_admin_dashboard_presets("owner")
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert!(store
|
||||
.list_admin_dashboard_presets("someone-else")
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
let rejected = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets/Invalid")
|
||||
.method("PUT")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.replace(
|
||||
"\"overview_samples\":600",
|
||||
"\"overview_samples\":999",
|
||||
)))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(rejected.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
let renamed = router.clone().oneshot(
|
||||
request("/api/dashboard-presets/Daily").method("PATCH")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"name":"Nightly"}"#)).unwrap()
|
||||
).await.unwrap();
|
||||
let renamed = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets/Daily")
|
||||
.method("PATCH")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(r#"{"name":"Nightly"}"#))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(renamed.status(), StatusCode::NO_CONTENT);
|
||||
assert_eq!(store.list_admin_dashboard_presets("owner").await.unwrap()[0].0, "Nightly");
|
||||
let deleted = router.oneshot(
|
||||
request("/api/dashboard-presets/Nightly").method("DELETE")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.body(Body::empty()).unwrap()
|
||||
).await.unwrap();
|
||||
assert_eq!(
|
||||
store.list_admin_dashboard_presets("owner").await.unwrap()[0].0,
|
||||
"Nightly"
|
||||
);
|
||||
let deleted = router
|
||||
.oneshot(
|
||||
request("/api/dashboard-presets/Nightly")
|
||||
.method("DELETE")
|
||||
.header("cookie", &cookie)
|
||||
.header("origin", "http://127.0.0.1:9090")
|
||||
.header("x-rustproxy-request", "1")
|
||||
.header("x-csrf-token", &csrf)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(deleted.status(), StatusCode::NO_CONTENT);
|
||||
assert!(store.list_admin_dashboard_presets("owner").await.unwrap().is_empty());
|
||||
assert!(store
|
||||
.list_admin_dashboard_presets("owner")
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
store.close().await;
|
||||
}
|
||||
|
||||
@@ -1896,7 +1957,11 @@ mod tests {
|
||||
let mut upgraded = normalized.clone();
|
||||
for widget in DASHBOARD_WIDGETS.iter() {
|
||||
let id = json!(widget.id);
|
||||
if selected["dashboard_widgets"].as_array().unwrap().contains(&id) {
|
||||
if selected["dashboard_widgets"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.contains(&id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
upgraded["dashboard_widgets"]
|
||||
@@ -2346,6 +2411,12 @@ mod tests {
|
||||
"cookie"
|
||||
);
|
||||
assert!(spec["paths"].get("/api/config").is_some());
|
||||
assert!(spec["paths"].get("/api/config/storage").is_none());
|
||||
assert_eq!(
|
||||
spec["paths"]["/api/config"]["get"]["responses"]["200"]["content"]["application/json"]
|
||||
["schema"]["$ref"],
|
||||
"#/components/schemas/ConfigurationRead"
|
||||
);
|
||||
assert!(spec["paths"].get("/api/config/history/restore").is_some());
|
||||
let response = router
|
||||
.clone()
|
||||
@@ -3483,5 +3554,136 @@ mod tests {
|
||||
assert_eq!(result.status(), StatusCode::FORBIDDEN);
|
||||
store.close().await;
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn configuration_get_requires_authentication_and_reports_sqlite_storage() {
|
||||
let (router, store, _dir, secret, _) = fixture().await;
|
||||
let unauthenticated = router
|
||||
.clone()
|
||||
.oneshot(request("/api/config").body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(unauthenticated.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let response = router
|
||||
.clone()
|
||||
.oneshot(
|
||||
request("/api/config")
|
||||
.header("cookie", format!("{}={secret}", auth::COOKIE_NAME))
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload = json_body(response).await;
|
||||
assert!(payload["revision"].is_number());
|
||||
assert!(payload["config"].is_object());
|
||||
assert!(payload["config"].get("storage").is_none());
|
||||
assert_eq!(payload["storage"]["mode"], "sqlite");
|
||||
assert!(payload["storage"]["config_path"].is_null());
|
||||
assert!(payload["storage"]["database_path"].is_null()); // fixture omits data_dir
|
||||
|
||||
// The old GET endpoint is gone. A dynamic PUT-only section route may
|
||||
// match the path, but it must not respond to GET.
|
||||
let old_route = router
|
||||
.oneshot(
|
||||
request("/api/config/storage")
|
||||
.header("cookie", format!("{}={secret}", auth::COOKIE_NAME))
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(old_route.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
store.close().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn configuration_get_reports_explicit_json_and_private_sqlite_paths() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let config = Config::default();
|
||||
let policy = ValidationPolicy {
|
||||
tls_addr: None,
|
||||
cert_dir: None,
|
||||
data_dir: Some(dir.path().to_path_buf()),
|
||||
proxy_addr: "127.0.0.1:8080".parse().unwrap(),
|
||||
admin_addr: "127.0.0.1:9090".parse().unwrap(),
|
||||
};
|
||||
let store = Store::open(&dir.path().join("config.sqlite3"), &config)
|
||||
.await
|
||||
.unwrap();
|
||||
store
|
||||
.initialize_admin("owner", "unused-test-hash", "test")
|
||||
.await
|
||||
.unwrap();
|
||||
let auth = auth::Auth::new(store.clone(), dir.path().to_path_buf())
|
||||
.await
|
||||
.unwrap();
|
||||
let issued = store
|
||||
.create_session(1, "browser", None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let snapshot = config::compile(config, 0, &policy).unwrap();
|
||||
let state = App::new(
|
||||
store.clone(),
|
||||
snapshot,
|
||||
policy,
|
||||
auth,
|
||||
10,
|
||||
Duration::from_secs(30),
|
||||
Duration::from_secs(30),
|
||||
Duration::from_secs(300),
|
||||
);
|
||||
let file = dir.path().join("config.json");
|
||||
state.set_config_file(file.clone(), [0u8; 32]).await;
|
||||
|
||||
let response = router(state)
|
||||
.oneshot(
|
||||
request("/api/config")
|
||||
.header("cookie", format!("{}={}", auth::COOKIE_NAME, issued.secret))
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload = json_body(response).await;
|
||||
assert!(payload["revision"].is_number());
|
||||
assert!(payload["config"].is_object());
|
||||
assert!(payload["config"].get("storage").is_none());
|
||||
assert_eq!(payload["storage"]["mode"], "file");
|
||||
assert_eq!(
|
||||
payload["storage"]["config_path"],
|
||||
file.to_string_lossy().as_ref()
|
||||
);
|
||||
assert_eq!(
|
||||
payload["storage"]["database_path"],
|
||||
dir.path().join("config.sqlite3").to_string_lossy().as_ref()
|
||||
);
|
||||
store.close().await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_metadata_is_exposed_only_through_configuration_get_to_api_tokens() {
|
||||
assert!(general_api_token_allowed(&Method::GET, "/api/config", "ro"));
|
||||
assert!(general_api_token_allowed(&Method::GET, "/api/config", "rw"));
|
||||
assert!(!general_api_token_allowed(
|
||||
&Method::GET,
|
||||
"/api/config/storage",
|
||||
"ro"
|
||||
));
|
||||
assert!(!general_api_token_allowed(
|
||||
&Method::PUT,
|
||||
"/api/config/storage",
|
||||
"rw"
|
||||
));
|
||||
assert!(!general_api_token_allowed(
|
||||
&Method::POST,
|
||||
"/api/config/storage",
|
||||
"rw"
|
||||
));
|
||||
}
|
||||
|
||||
include!("preferences_tests.rs");
|
||||
}
|
||||
Reference in new issue
Block a user