// Copyright (c) [2025] SUSE LLC // // All Rights Reserved. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 2 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, contact SUSE LLC. // // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. use agama_security as security; use agama_software::state::{Repository as StateRepository, SoftwareState}; use agama_software::zypp_server::{SoftwareAction, ZyppServer, ZyppServerResult}; use agama_software::WriteIssues; use agama_utils::{ actor, api::{ event::Event, question::{Answer, AnswerRule, Config}, }, progress, question, }; use camino::{Utf8Path, Utf8PathBuf}; use glob::glob; use std::fs; use std::path::Path; use std::result::Result; use tokio::sync::{broadcast, oneshot}; fn cleanup_past_leftovers(root_dir: &Path) { remove_repos(root_dir); remove_rpmdb(root_dir); } fn remove_repos(root_dir: &Path) { let repo_dir = root_dir.join("etc/zypp/repos.d/"); for path in glob(&format!("{}/*.repo", repo_dir.display())) // unwrap OK: literal pattern syntax is correct .unwrap() .filter_map(Result::ok) { fs::remove_file(path).expect("failed to remove repo file"); } } fn remove_rpmdb(root_dir: &Path) { let rpmdb_dir = root_dir.join("usr/lib/sysimage/rpm/"); if fs::exists(&rpmdb_dir).unwrap_or(false) { fs::remove_dir_all(rpmdb_dir).expect("removing RPM data failed"); } } #[tokio::test] async fn test_start_zypp_server() { let _ = tracing_subscriber::fmt::try_init(); let root_dir = Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../zypp-agama/fixtures/zypp_repos_root"); cleanup_past_leftovers(root_dir.as_std_path()); let zypp_root = Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../zypp-agama/fixtures/zypp_root_tmp"); let install_dir = Utf8PathBuf::from("/mnt"); let cmdline = Default::default(); let client = ZyppServer::start(&zypp_root, &install_dir, &cmdline).expect("starting zypp server failed"); // Setup event broadcast channel let (event_tx, _event_rx) = broadcast::channel::(100); // Buffer size 100 // Spawn progress service let progress_service_starter = progress::service::Starter::new(event_tx.clone()); let progress_handler = progress_service_starter.start(); // Spawn question service let question_service = question::service::Service::new(event_tx.clone()); let question_handler = actor::spawn(question_service); // Spawn the security service let security_service_starter = security::service::Starter::new(question_handler.clone()); let security_handler = security_service_starter.start().unwrap(); // Pre-configure the answer to the GPG key question let answer = Answer { action: "Trust".to_string(), value: None, }; let rule = AnswerRule { class: Some("software.import_gpg".to_string()), text: None, data: None, answer, }; let config = Config { policy: None, answers: Some(vec![rule]), }; question_handler .call(question::message::SetConfig::new(Some(config))) .await .unwrap(); let (tx, rx) = oneshot::channel(); let repo_s = StateRepository { name: "signed_repo".to_string(), alias: "signed_repo".to_string(), url: root_dir.join("usr/share/signed_repo").to_string(), enabled: true, priority: None, }; let mut software_state = SoftwareState::new("test_product"); software_state.repositories = vec![repo_s]; client .send(SoftwareAction::Write { state: software_state, progress: progress_handler, l10n: None, question: question_handler.clone(), security: security_handler, tx, }) .expect("Failed to send SoftwareAction::Write"); let result: ZyppServerResult = rx.await.expect("Failed to receive response from server"); assert!( result.is_ok(), "SoftwareAction::Write failed: {:?}", result.unwrap_err() ); let issues = result.unwrap(); assert_eq!( issues.software.len(), 1, "There are unexpected issues size {issues:#?}" ); assert_eq!(issues.software[0].class, "missing_product"); let questions = question_handler .call(question::message::Get) .await .expect("Failed to get questions"); assert!(questions.is_empty()); // NOTE: here we drop sender channel, which result in exit of zypp thread due to closed channel }