WP-11: contract and failing tests for package upgrades
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / ui (push) Has been cancelled

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-02 22:24:40 +02:00
parent b6ddb8889d
commit 1f56f015a2
18 changed files with 524 additions and 2 deletions

View File

@ -3,4 +3,5 @@ mod inventory_tests;
mod jobs_tests;
mod scheduler_tests;
mod settings_tests;
mod upgrade_tests;
mod user_service_tests;

View File

@ -0,0 +1,105 @@
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use domain::DomainError;
use crate::jobs::{JobHandler, JobLog};
use crate::test_fakes::{FakeInspector, FakeUpdater, MemInventory};
use crate::{InventoryService, PackageUpgradeJob, UpgradeParams};
#[derive(Default)]
struct VecLog(Mutex<Vec<String>>);
#[async_trait]
impl JobLog for VecLog {
async fn line(&self, text: &str) {
self.0.lock().unwrap().push(text.into());
}
}
#[test]
fn params_parse_and_validate() {
assert_eq!(
UpgradeParams::from_json(None).unwrap(),
UpgradeParams::default()
);
assert_eq!(
UpgradeParams::from_json(Some("")).unwrap(),
UpgradeParams::default()
);
let p = UpgradeParams::from_json(Some(r#"{"packages":["openssl","libssl3"]}"#)).unwrap();
assert_eq!(p.packages, vec!["openssl", "libssl3"]);
assert!(matches!(
UpgradeParams::from_json(Some("{not json")).unwrap_err(),
DomainError::Validation(_)
));
let bad = UpgradeParams {
packages: vec!["rm -rf /".into()],
};
assert!(matches!(
bad.validate().unwrap_err(),
DomainError::Validation(_)
));
assert!(matches!(
UpgradeParams::from_json(Some(r#"{"packages":["../x"]}"#)).unwrap_err(),
DomainError::Validation(_)
));
}
fn job(fail: bool) -> (Arc<FakeUpdater>, Arc<MemInventory>, PackageUpgradeJob) {
let updater = Arc::new(FakeUpdater {
fail,
..Default::default()
});
let repo = Arc::new(MemInventory::default());
let inventory = Arc::new(InventoryService::new(
Arc::new(FakeInspector { fail: false }),
repo.clone(),
));
(
updater.clone(),
repo,
PackageUpgradeJob { updater, inventory },
)
}
#[tokio::test]
async fn upgrades_selected_packages_streams_output_and_refreshes_inventory() {
let (updater, repo, job) = job(false);
let log = VecLog::default();
job.run(Some(r#"{"packages":["openssl"]}"#.into()), &log)
.await
.unwrap();
assert_eq!(
updater.calls.lock().unwrap()[0],
vec!["openssl".to_string()]
);
let lines = log.0.lock().unwrap().join("\n");
assert!(lines.contains("Reading package lists"), "{lines}");
assert!(lines.contains("Upgrading 1 package"), "{lines}");
assert!(lines.contains("reboot required"), "{lines}");
assert!(
repo.0.lock().unwrap().is_some(),
"inventory refreshed after upgrade"
);
}
#[tokio::test]
async fn empty_params_upgrade_everything() {
let (updater, _, job) = job(false);
job.run(None, &VecLog::default()).await.unwrap();
assert!(updater.calls.lock().unwrap()[0].is_empty());
}
#[tokio::test]
async fn updater_failure_fails_the_job_and_keeps_output() {
let (_, _, job) = job(true);
let log = VecLog::default();
let err = job.run(None, &log).await.unwrap_err();
assert!(err.contains("status 100"));
assert!(log
.0
.lock()
.unwrap()
.iter()
.any(|l| l.contains("Reading package lists")));
}