use super::*;
use crate::test_support::{FakeRepository, service};
use std::sync::mpsc;
use std::time::Duration;
fn list_context(generation: u64) -> ListRequestContext {
ListRequestContext {
connection: ConnectionType::System,
generation,
}
}
#[test]
fn lists_sorts_and_deduplicates_runtime_and_file_units() {
let fake = FakeRepository::with_services(
vec![
service("zeta.service", "active", "enabled"),
service("alpha.service", "active", "enabled"),
],
vec![
service("alpha.service", "inactive", "disabled"),
service("beta.service", "inactive", "disabled"),
],
);
let manager = ServicesManager::new(Box::new(fake));
let (sender, receiver) = mpsc::channel();
let services = manager
.list_services(true, list_context(1), Arc::new(sender))
.unwrap();
let names: Vec<_> = services.iter().map(Service::name).collect();
assert_eq!(names, ["alpha.service", "beta.service", "zeta.service"]);
let states = match receiver
.recv_timeout(Duration::from_secs(1))
.expect("unit file states were not returned")
{
QueryUnitFile::Finished(context, states) => {
assert_eq!(context, list_context(1));
states
}
QueryUnitFile::Error(_, error) => panic!("unit file lookup failed: {error}"),
};
assert_eq!(
states.get("alpha.service").map(String::as_str),
Some("enabled")
);
assert_eq!(
states.get("zeta.service").map(String::as_str),
Some("enabled")
);
}
#[test]
fn background_state_errors_are_returned_to_the_caller() {
let fake =
FakeRepository::with_services(vec![service("demo.service", "active", "enabled")], vec![]);
fake.fail("states");
let manager = ServicesManager::new(Box::new(fake));
let (sender, receiver) = mpsc::channel();
manager
.list_services(false, list_context(2), Arc::new(sender))
.unwrap();
assert!(matches!(
receiver.recv_timeout(Duration::from_secs(1)),
Ok(QueryUnitFile::Error(context, error))
if context == list_context(2) && error == "states failed"
));
}
#[test]
fn unit_file_lookup_reuses_the_services_from_the_foreground_query() {
let fake =
FakeRepository::with_services(vec![service("demo.service", "active", "enabled")], vec![]);
let observer = fake.clone();
let manager = ServicesManager::new(Box::new(fake));
let (sender, receiver) = mpsc::channel();
manager
.list_services(false, list_context(6), Arc::new(sender))
.unwrap();
assert!(matches!(
receiver.recv_timeout(Duration::from_secs(1)),
Ok(QueryUnitFile::Finished(context, _)) if context == list_context(6)
));
assert_eq!(observer.calls(), ["list:false", "states:1"]);
}
#[test]
fn service_only_listing_does_not_add_unit_files() {
let fake = FakeRepository::with_services(
vec![service("runtime.service", "active", "enabled")],
vec![service("file-only.service", "inactive", "disabled")],
);
let manager = ServicesManager::new(Box::new(fake));
let (sender, _receiver) = mpsc::channel();
let services = manager
.list_services(false, list_context(3), Arc::new(sender))
.unwrap();
assert_eq!(services.len(), 1);
assert_eq!(services[0].name(), "runtime.service");
}
#[test]
fn enable_and_disable_reload_the_daemon() {
let fake = FakeRepository::default();
let observer = fake.clone();
let manager = ServicesManager::new(Box::new(fake));
let unit = service("demo.service", "inactive", "disabled");
manager.enable_service(&unit).unwrap();
manager.disable_service(&unit).unwrap();
assert_eq!(
observer.calls(),
[
"enable:demo.service",
"reload:",
"disable:demo.service",
"reload:",
]
);
}
#[test]
fn delegates_all_lifecycle_operations() {
let fake = FakeRepository::default();
let observer = fake.clone();
let manager = ServicesManager::new(Box::new(fake));
let unit = service("demo.service", "inactive", "disabled");
manager.start_service(&unit).unwrap();
manager.stop_service(&unit).unwrap();
manager.restart_service(&unit).unwrap();
manager.mask_service(&unit).unwrap();
manager.unmask_service(&unit).unwrap();
assert_eq!(
observer.calls(),
[
"start:demo.service",
"stop:demo.service",
"restart:demo.service",
"mask:demo.service",
"unmask:demo.service",
]
);
}
#[test]
fn propagates_repository_errors_without_reloading() {
let fake = FakeRepository::default();
fake.fail("enable");
let observer = fake.clone();
let manager = ServicesManager::new(Box::new(fake));
let unit = service("broken.service", "inactive", "disabled");
let error = manager.enable_service(&unit).unwrap_err();
assert_eq!(error.to_string(), "enable failed");
assert_eq!(observer.calls(), ["enable:broken.service"]);
}
#[test]
fn delegates_log_unit_file_timestamp_and_connection() {
let fake = FakeRepository::with_content("journal output", "[Service]\nExecStart=/bin/true", 42);
let observer = fake.clone();
let mut manager = ServicesManager::new(Box::new(fake));
let unit = service("demo.service", "active", "enabled");
assert_eq!(manager.get_log(unit.name()).unwrap(), "journal output");
assert!(manager.systemctl_cat(&unit).unwrap().contains("ExecStart"));
manager
.change_repository_connection(ConnectionType::Session)
.unwrap();
assert_eq!(
manager
.repository_handle()
.lock()
.unwrap()
.get_active_enter_timestamp(unit.name())
.unwrap(),
42
);
assert_eq!(
observer.calls(),
[
"log:demo.service",
"cat:demo.service",
"connection:session",
"timestamp:demo.service"
]
);
}
#[test]
fn list_failure_is_returned_without_attempting_file_lookup() {
let fake = FakeRepository::default();
fake.fail("list");
let observer = fake.clone();
let manager = ServicesManager::new(Box::new(fake));
let (sender, _receiver) = mpsc::channel();
let error = manager
.list_services(true, list_context(4), Arc::new(sender))
.unwrap_err();
assert_eq!(error.to_string(), "list failed");
assert_eq!(observer.calls(), ["list:true"]);
}
#[test]
fn daemon_reload_failure_is_propagated_after_enable() {
let fake = FakeRepository::default();
fake.fail("reload");
let observer = fake.clone();
let manager = ServicesManager::new(Box::new(fake));
let unit = service("demo.service", "inactive", "disabled");
let error = manager.enable_service(&unit).unwrap_err();
assert_eq!(error.to_string(), "reload failed");
assert_eq!(observer.calls(), ["enable:demo.service", "reload:"]);
}
#[test]
fn log_and_unit_file_failures_are_propagated() {
let fake = FakeRepository::default();
fake.fail("log");
fake.fail("cat");
let manager = ServicesManager::new(Box::new(fake));
let unit = service("broken.service", "active", "enabled");
assert_eq!(
manager.get_log(unit.name()).unwrap_err().to_string(),
"log failed"
);
assert_eq!(
manager.systemctl_cat(&unit).unwrap_err().to_string(),
"cat failed"
);
}
#[test]
fn service_sorting_is_case_insensitive() {
let fake = FakeRepository::with_services(
vec![
service("Zulu.service", "active", "enabled"),
service("alpha.service", "active", "enabled"),
service("Beta.service", "active", "enabled"),
],
vec![],
);
let manager = ServicesManager::new(Box::new(fake));
let (sender, _receiver) = mpsc::channel();
let services = manager
.list_services(false, list_context(5), Arc::new(sender))
.unwrap();
let names: Vec<_> = services.iter().map(Service::name).collect();
assert_eq!(names, ["alpha.service", "Beta.service", "Zulu.service"]);
}