v0.5.4: code-cleanup pass (AppError, figment config, encoding dedup, typed status, deps)
Final cleanup before hardware testing. No behaviour changes; 248 tests green, clippy clean. #1 AppError newtype (http-api/src/error.rs) with one IntoResponse mapping (NotFound→404, Invalid→400, _→500) + From<core::Error>/From<io::Error>. Converted the clearly-safe handlers (sso_put, unattended_upload, branding_clear) to `?`; intentionally left handlers with bespoke status semantics (Invalid→404 on category, 409 on duplicate share / open upload) explicit so no asserted status changes. #2 figment-based Config::load (defaults → TOML → env). Keeps the historical flat OPENPXE_* names (Unraid/entrypoint compatible) AND adds the nested OPENPXE_SECTION__FIELD form; now covers every field (apply_env had silently skipped unattended_dir + bind addrs). 6 Jail tests prove backward-compat. Removed the hand-rolled apply_env. #3 thiserror 1→2; dropped unused mime/mime_guess/once_cell deps. #4 Re-evaluated: Duration::from_hours/from_mins are stable on the pinned 1.95 toolchain and clippy prefers them — kept the readable form (the "unstable" premise didn't hold; MSRV is intentionally 1.95). #5 insta snapshot of the rendered iPXE menu (version-filtered) + wiremock coverage of the SAML metadata-URL fetch (200 + non-2xx). #6 api_status → typed StatusResponse struct (was a 25-key json! blob) with a full_flow guard test asserting every UI key + the started_at string shape. Deferred the /api/docs typed conversion (lowest value, highest churn, zero functional benefit). #7 pct_encode/xml_escape de-duplicated into openpxe_core::encoding (were copied across app.rs + the SAML modules). No new crates. #8 UploadSessions registry → parking_lot::RwLock (sync, never held across .await); per-session lock stays tokio::Mutex. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7358013093
commit
674a69f93b
@@ -1536,6 +1536,60 @@ async fn status_exposes_custom_logo_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
/// v0.5.4 guard: the typed `StatusResponse` must keep every key the WebUI
|
||||
/// (`crates/webui/src/app.js`) reads off `/api/status`. If a refactor drops
|
||||
/// or renames one, the dashboard silently breaks — this catches it.
|
||||
#[tokio::test]
|
||||
async fn status_contract_has_all_ui_keys() {
|
||||
let (state, _dir) = build_state().await;
|
||||
let app = build_router(state);
|
||||
let (s, body) = get(&app, "/api/status").await;
|
||||
assert_eq!(s, StatusCode::OK);
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
for key in [
|
||||
"version",
|
||||
"public_base_url",
|
||||
"iso_count",
|
||||
"client_count",
|
||||
"queue_count",
|
||||
"imaging_count",
|
||||
"waiting_count",
|
||||
"ipxe_assets",
|
||||
"settings",
|
||||
"smb_share_count",
|
||||
"smb_share_reachable",
|
||||
"nfs_share_count",
|
||||
"nfs_share_reachable",
|
||||
"host_bindings",
|
||||
"custom_logo",
|
||||
"branding",
|
||||
"unattended_count",
|
||||
"uptime_secs",
|
||||
"started_at",
|
||||
"nic_name",
|
||||
"subnet_mask",
|
||||
"gateway",
|
||||
] {
|
||||
assert!(
|
||||
v.get(key).is_some(),
|
||||
"/api/status missing UI key '{key}': {v}"
|
||||
);
|
||||
}
|
||||
// Nested branding presence the Settings tab reads.
|
||||
for key in ["light", "dark", "client", "rev"] {
|
||||
assert!(
|
||||
v["branding"].get(key).is_some(),
|
||||
"/api/status branding missing '{key}': {v}"
|
||||
);
|
||||
}
|
||||
// started_at must remain an RFC3339 string (the UI does fmtUptime on
|
||||
// uptime_secs but renders started_at as text), not a serialized struct.
|
||||
assert!(
|
||||
v["started_at"].is_string(),
|
||||
"started_at should serialize as a string: {v}"
|
||||
);
|
||||
}
|
||||
|
||||
async fn put_json(router: &axum::Router, path: &str, body: &str) -> (StatusCode, Vec<u8>) {
|
||||
let res = router
|
||||
.clone()
|
||||
|
||||
Reference in New Issue
Block a user