use dnf_repofile::*;
use std::collections::HashMap;
mod official_parity {
use super::*;
#[test]
fn whitespace_only_lines_do_not_break_parsing() {
let input = "\
[main]
gpgcheck=0
[testingrepo]
gpgcheck=1
baseurl=http://some.url/
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("testingrepo").unwrap()).unwrap();
assert_eq!(block.data.gpgcheck, Some(DnfBool::True));
assert_eq!(block.data.baseurl[0].as_str(), "http://some.url/");
}
#[test]
fn repos_defined_in_main_config_file() {
let input = "\
[main]
gpgcheck=0
[repo-A]
name=Repository A
baseurl=http://a.example.com/
enabled=1
[repo-B]
name=Repository B
baseurl=http://b.example.com/
enabled=0
[repo-C]
name=Repository C
baseurl=http://c.example.com/
enabled=1
";
let rf = RepoFile::parse(input).unwrap();
assert_eq!(rf.len(), 3);
let a = rf.get(&RepoId::try_new("repo-A").unwrap()).unwrap();
let b = rf.get(&RepoId::try_new("repo-B").unwrap()).unwrap();
let c = rf.get(&RepoId::try_new("repo-C").unwrap()).unwrap();
assert_eq!(a.data.enabled, Some(DnfBool::True));
assert_eq!(b.data.enabled, Some(DnfBool::False));
assert_eq!(c.data.enabled, Some(DnfBool::True));
}
#[test]
fn variable_substitution_in_values_not_section_ids() {
let input = "\
[test-distrib]
name=Test ${distrib} repository
baseurl=http://example.com/${distrib}/
";
let rf = RepoFile::parse(input).unwrap();
let id = RepoId::try_new("test-distrib").unwrap();
let block = rf.get(&id).unwrap();
assert_eq!(
block.data.name.as_ref().unwrap().as_ref(),
"Test ${distrib} repository"
);
assert!(
block.data.baseurl[0].as_str().contains("distrib"),
"baseurl should contain distrib: {}",
block.data.baseurl[0].as_str()
);
}
#[test]
fn variable_in_section_id_is_rejected() {
let input = "[test-$distrib]\nname=Test\nbaseurl=http://example.com/\n";
assert!(RepoFile::parse(input).is_err());
}
#[test]
fn expand_variables_resolves_simple_var() {
let mut vars = HashMap::new();
vars.insert("releasever".into(), "9".into());
vars.insert("basearch".into(), "x86_64".into());
let result =
expand_variables("http://example.com/$releasever/$basearch/os/", &vars).unwrap();
assert_eq!(result, "http://example.com/9/x86_64/os/");
}
#[test]
fn parse_error_on_invalid_section_header() {
assert!(RepoFile::parse("[invalid section]\nname=Test\n").is_err());
assert!(RepoFile::parse("[invalid@char]\nname=Test\n").is_err());
}
#[test]
fn parse_error_on_missing_equals_sign() {
assert!(RepoFile::parse("[repo]\nbadline\n").is_err());
}
#[test]
fn parse_error_on_empty_section_name() {
assert!(RepoFile::parse("[]\nname=Test\n").is_err());
}
#[test]
fn gpgkey_with_multiple_values() {
let input = "\
[repo]
name=Test
baseurl=http://example.com/
gpgkey=http://example.com/key1
gpgkey=http://example.com/key2
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-local
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.gpgkey.len(), 3);
assert!(block
.data
.gpgkey
.contains(&"http://example.com/key1".to_string()));
assert!(block
.data
.gpgkey
.contains(&"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-local".to_string()));
}
#[test]
fn proxy_with_authentication() {
let input = "\
[repo]
name=Test
baseurl=http://example.com/
proxy=http://proxy.example.com:8080
proxy_username=myuser
proxy_password=mypass
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
match &block.data.proxy {
ProxySetting::Url(url) => {
assert!(
url.as_str().starts_with("http://proxy.example.com:8080"),
"expected proxy URL to start with http://proxy.example.com:8080, got: {}",
url.as_str()
);
}
_ => panic!("expected ProxySetting::Url"),
}
assert_eq!(
block.data.proxy_username.as_ref().unwrap().as_ref(),
"myuser"
);
assert_eq!(
block.data.proxy_password.as_ref().unwrap().as_ref(),
"mypass"
);
}
#[test]
fn ssl_client_certificate_configuration() {
let input = "\
[repo]
name=Test
baseurl=https://example.com/
sslverify=1
sslclientcert=/etc/pki/client/cert.pem
sslclientkey=/etc/pki/client/key.pem
sslcacert=/etc/pki/ca/ca.pem
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.sslverify, Some(DnfBool::True));
assert!(block.data.sslclientcert.is_some());
assert!(block.data.sslclientkey.is_some());
assert!(block.data.sslcacert.is_some());
}
#[test]
fn priority_and_cost_configuration() {
let input = "\
[high-prio]
name=High Priority
baseurl=http://example.com/
priority=1
cost=500
[low-prio]
name=Low Priority
baseurl=http://example.com/
priority=99
cost=2000
";
let rf = RepoFile::parse(input).unwrap();
let high = rf.get(&RepoId::try_new("high-prio").unwrap()).unwrap();
let low = rf.get(&RepoId::try_new("low-prio").unwrap()).unwrap();
assert_eq!(high.data.priority.unwrap().to_string(), "1");
assert_eq!(low.data.priority.unwrap().to_string(), "99");
assert_eq!(high.data.cost.unwrap().to_string(), "500");
assert_eq!(low.data.cost.unwrap().to_string(), "2000");
}
#[test]
fn option_values_distributed_across_multiple_lines() {
let input = "\
[repo]
name=Test Repository
baseurl=http://example.com/
excludepkgs=kernel-debug
excludepkgs=kernel-debug-devel
includepkgs=firefox
includepkgs=thunderbird
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.excludepkgs.len(), 2);
assert_eq!(block.data.includepkgs.len(), 2);
}
#[test]
fn repo_id_with_trailing_whitespace_in_section_header() {
let input = "[ myrepo ]\nname=Test\nbaseurl=http://example.com/\n";
assert!(RepoFile::parse(input).is_ok());
}
#[test]
fn metadata_expire_variants() {
let input = "[repo]\nname=T\nbaseurl=http://x.com/\nmetadata_expire=3600\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(
block.data.metadata_expire,
Some(MetadataExpire::Duration(3600))
);
let input = "[repo]\nname=T\nbaseurl=http://x.com/\nmetadata_expire=never\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.metadata_expire, Some(MetadataExpire::Never));
let input = "[repo]\nname=T\nbaseurl=http://x.com/\nmetadata_expire=-1\n";
let result = RepoFile::parse(input);
if let Ok(rf) = result {
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
let _ = block.data.metadata_expire.is_some()
|| block.data.extras.get("metadata_expire").is_some();
}
}
#[test]
fn countme_option() {
let input = "\
[repo]
name=Test
baseurl=http://example.com/
countme=1
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.countme, Some(DnfBool::True));
}
}
mod extended_edge_cases {
use super::*;
#[test]
fn crlf_line_endings() {
let input = "[repo]\r\nname=Test\r\nbaseurl=http://example.com/\r\nenabled=1\r\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.name.as_ref().unwrap().as_ref(), "Test");
assert_eq!(block.data.baseurl[0].as_str(), "http://example.com/");
}
#[test]
fn mixed_line_endings() {
let input = "[repo]\nname=Test\r\nbaseurl=http://example.com/\nenabled=1\r\n";
let rf = RepoFile::parse(input).unwrap();
assert_eq!(rf.len(), 1);
}
#[test]
fn utf8_in_repo_name() {
let input = "[repo]\nname=Réseau d'Entreprise™\nbaseurl=http://example.com/\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(
block.data.name.as_ref().unwrap().as_ref(),
"Réseau d'Entreprise™"
);
}
#[test]
fn utf8_in_comment() {
let input = "# Café comment\n[repo]\nname=Test\nbaseurl=http://example.com/\n";
let rf = RepoFile::parse(input).unwrap();
assert!(rf.preamble[0].contains("Café"));
}
#[test]
fn empty_value_for_optional_field() {
let input = "[repo]\nname=Test\nbaseurl=http://example.com/\nmediaid=\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.mediaid.as_deref(), Some(""));
}
#[test]
fn key_with_trailing_whitespace_before_equals() {
let input = "[repo]\nname =Test\nbaseurl=http://example.com/\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
assert_eq!(block.data.name.as_ref().unwrap().as_ref(), "Test");
}
#[test]
fn duplicate_section_ids_last_wins() {
let input = "\
[dupe]
name=First
baseurl=http://first.example.com/
priority=10
[dupe]
name=Second
baseurl=http://second.example.com/
priority=20
";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("dupe").unwrap()).unwrap();
assert_eq!(block.data.name.as_ref().unwrap().as_ref(), "Second");
assert_eq!(block.data.priority.unwrap().to_string(), "20");
}
#[test]
fn section_header_parsing_handles_trailing_chars() {
let input = "[repo] \nname=Test\nbaseurl=http://example.com/\n";
let rf = RepoFile::parse(input).unwrap();
assert!(rf.contains(&RepoId::try_new("repo").unwrap()));
}
#[test]
fn numeric_boundary_values() {
assert!(Priority::try_new(1).is_ok());
assert!(Priority::try_new(99).is_ok());
assert!(Priority::try_new(0).is_err());
assert!(Priority::try_new(100).is_err());
assert!(MaxParallelDownloads::try_new(0).is_ok());
assert!(MaxParallelDownloads::try_new(20).is_ok());
assert!(MaxParallelDownloads::try_new(21).is_err());
assert!(Retries::try_new(0).is_ok());
assert_eq!(*Retries::try_new(0).unwrap(), 0);
assert!(Cost::try_new(0).is_ok());
}
#[test]
fn storage_size_unit_parsing() {
let cases = vec![
(
"[r]\nname=T\nbaseurl=http://x.com/\nbandwidth=1G\n",
1_073_741_824,
),
(
"[r]\nname=T\nbaseurl=http://x.com/\nbandwidth=500M\n",
524_288_000,
),
(
"[r]\nname=T\nbaseurl=http://x.com/\nbandwidth=100K\n",
102_400,
),
("[r]\nname=T\nbaseurl=http://x.com/\nbandwidth=1024\n", 1024),
];
for (input, expected_bytes) in cases {
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
assert_eq!(
block.data.bandwidth.unwrap().0,
expected_bytes,
"failed for input: {input}"
);
}
}
#[test]
fn throttle_percentage_and_absolute_values() {
let input = "[r]\nn=T\nb=http://x.com/\nthrottle=100K\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
assert!(block.data.throttle.is_some());
}
#[test]
fn ip_resolve_all_variants() {
for (val, expected) in &[
("4", IpResolve::V4),
("IPv4", IpResolve::V4),
("ipv4", IpResolve::V4),
("6", IpResolve::V6),
("IPv6", IpResolve::V6),
("ipv6", IpResolve::V6),
] {
let input = format!("[r]\nn=T\nb=http://x.com/\nip_resolve={val}\n");
let rf = RepoFile::parse(&input).unwrap();
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
assert_eq!(block.data.ip_resolve, Some(*expected), "failed for {val}");
}
}
#[test]
fn proxy_auth_method_all_variants() {
let all_methods = vec![
("any", ProxyAuthMethod::Any),
("none", ProxyAuthMethod::None_),
("basic", ProxyAuthMethod::Basic),
("digest", ProxyAuthMethod::Digest),
("negotiate", ProxyAuthMethod::Negotiate),
("ntlm", ProxyAuthMethod::Ntlm),
("digest_ie", ProxyAuthMethod::DigestIe),
("ntlm_wb", ProxyAuthMethod::NtlmWb),
];
for (val, expected) in all_methods {
let input = format!("[r]\nn=T\nb=http://x.com/\nproxy_auth_method={val}\n");
let rf = RepoFile::parse(&input).unwrap();
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
assert_eq!(
block.data.proxy_auth_method,
Some(expected),
"failed for {val}"
);
}
}
#[test]
fn tsflags_parsing() {
let input = "\
[main]
tsflags=nodocs
tsflags=test
";
let rf = RepoFile::parse(input).unwrap();
let main = rf.main().unwrap();
assert_eq!(main.data.tsflags.len(), 2);
assert!(main.data.tsflags.contains(&TsFlag::NoDocs));
assert!(main.data.tsflags.contains(&TsFlag::Test));
}
#[test]
fn rpmverbosity_all_variants() {
for verb in &["critical", "emergency", "error", "warn", "info", "debug"] {
let input = format!("[main]\nrpmverbosity={verb}\n");
let rf = RepoFile::parse(&input).unwrap();
let main = rf.main().unwrap();
assert!(main.data.rpmverbosity.is_some(), "failed for {verb}");
}
}
#[test]
fn multilib_policy_variants() {
for policy in &["best", "all"] {
let input = format!("[main]\nmultilib_policy={policy}\n");
let rf = RepoFile::parse(&input).unwrap();
let main = rf.main().unwrap();
assert!(main.data.multilib_policy.is_some(), "failed for {policy}");
}
}
#[test]
fn persistence_variants() {
for p in &["auto", "transient", "persist"] {
let input = format!("[main]\npersistence={p}\n");
let rf = RepoFile::parse(&input).unwrap();
let main = rf.main().unwrap();
assert!(main.data.persistence.is_some(), "failed for {p}");
}
}
#[test]
fn all_repo_boolean_fields_parse_correctly() {
let bool_fields = vec![
"enabled",
"gpgcheck",
"repo_gpgcheck",
"localpkg_gpgcheck",
"skip_if_unavailable",
"module_hotfixes",
"deltarpm",
"enablegroups",
"fastestmirror",
"countme",
"sslverify",
"sslverifystatus",
"proxy_sslverify",
];
let mut input = String::from("[repo]\nname=T\nbaseurl=http://x.com/\n");
for field in &bool_fields {
input.push_str(&format!("{field}=1\n"));
}
let rf = RepoFile::parse(&input).unwrap();
let block = rf.get(&RepoId::try_new("repo").unwrap()).unwrap();
for &field in &bool_fields {
match field {
"enabled" => assert_eq!(block.data.enabled, Some(DnfBool::True)),
"gpgcheck" => assert_eq!(block.data.gpgcheck, Some(DnfBool::True)),
"repo_gpgcheck" => assert_eq!(block.data.repo_gpgcheck, Some(DnfBool::True)),
"localpkg_gpgcheck" => {
assert_eq!(block.data.localpkg_gpgcheck, Some(DnfBool::True))
}
"skip_if_unavailable" => {
assert_eq!(block.data.skip_if_unavailable, Some(DnfBool::True))
}
"module_hotfixes" => assert_eq!(block.data.module_hotfixes, Some(DnfBool::True)),
"deltarpm" => assert_eq!(block.data.deltarpm, Some(DnfBool::True)),
"enablegroups" => assert_eq!(block.data.enablegroups, Some(DnfBool::True)),
"fastestmirror" => assert_eq!(block.data.fastestmirror, Some(DnfBool::True)),
"countme" => assert_eq!(block.data.countme, Some(DnfBool::True)),
"sslverify" => assert_eq!(block.data.sslverify, Some(DnfBool::True)),
"sslverifystatus" => assert_eq!(block.data.sslverifystatus, Some(DnfBool::True)),
"proxy_sslverify" => assert_eq!(block.data.proxy_sslverify, Some(DnfBool::True)),
_ => panic!("unexpected field: {field}"),
}
}
}
#[test]
fn all_public_types_are_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<RepoFile>();
assert_send_sync::<Repo>();
assert_send_sync::<MainConfig>();
assert_send_sync::<RepoBuilder>();
assert_send_sync::<ValidationReport>();
assert_send_sync::<RepoId>();
assert_send_sync::<DnfBool>();
}
#[test]
fn very_long_baseurl_value() {
let long_path = "a".repeat(500);
let input = format!("[r]\nn=T\nbaseurl=http://example.com/{long_path}/\n");
let rf = RepoFile::parse(&input).unwrap();
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
assert_eq!(block.data.baseurl.len(), 1);
}
}
mod api_coverage {
use super::*;
#[test]
fn repofile_new_empty() {
let rf = RepoFile::new();
assert!(rf.is_empty());
assert_eq!(rf.len(), 0);
assert!(rf.main().is_none());
}
#[test]
fn repofile_add_remove_contains_len() {
let mut rf = RepoFile::new();
assert!(rf.is_empty());
let repo = RepoBuilder::new(RepoId::try_new("r1").unwrap())
.name(RepoName::try_new("R1").unwrap())
.baseurl("http://x.com/".parse().unwrap())
.build();
rf.add(repo).unwrap();
assert!(!rf.is_empty());
assert_eq!(rf.len(), 1);
assert!(rf.contains(&RepoId::try_new("r1").unwrap()));
assert!(!rf.contains(&RepoId::try_new("nonexistent").unwrap()));
let removed = rf.remove(&RepoId::try_new("r1").unwrap());
assert!(removed.is_some());
assert!(rf.is_empty());
}
#[test]
fn repofile_add_duplicate_id_errors() {
let mut rf = RepoFile::new();
let repo1 = RepoBuilder::new(RepoId::try_new("dup").unwrap())
.name(RepoName::try_new("First").unwrap())
.baseurl("http://a.com/".parse().unwrap())
.build();
rf.add(repo1).unwrap();
let repo2 = RepoBuilder::new(RepoId::try_new("dup").unwrap())
.name(RepoName::try_new("Second").unwrap())
.baseurl("http://b.com/".parse().unwrap())
.build();
assert!(rf.add(repo2).is_err());
assert_eq!(rf.len(), 1);
}
#[test]
fn repofile_set_overwrites() {
let mut rf = RepoFile::new();
let repo = RepoBuilder::new(RepoId::try_new("r").unwrap())
.name(RepoName::try_new("Original").unwrap())
.baseurl("http://a.com/".parse().unwrap())
.build();
rf.set(repo); assert_eq!(rf.len(), 1);
let updated = RepoBuilder::new(RepoId::try_new("r").unwrap())
.name(RepoName::try_new("Updated").unwrap())
.baseurl("http://b.com/".parse().unwrap())
.build();
rf.set(updated); assert_eq!(rf.len(), 1);
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
assert_eq!(block.data.name.as_ref().unwrap().as_ref(), "Updated");
}
#[test]
fn repofile_main_get_set_remove() {
let mut rf = RepoFile::new();
assert!(rf.main().is_none());
let mc = MainConfig::default();
rf.set_main(mc);
assert!(rf.main().is_some());
rf.remove_main();
assert!(rf.main().is_none());
}
#[test]
fn repofile_main_mut_modification() {
let mut rf = RepoFile::new();
rf.set_main(MainConfig::default());
{
let main_block = rf.main_mut().unwrap();
main_block.data.debuglevel = Some(DebugLevel::try_new(5).unwrap());
}
assert_eq!(rf.main().unwrap().data.debuglevel.unwrap().to_string(), "5");
}
#[test]
fn repofile_iter_and_repo_ids() {
let input = "\
[r1]\nn=R1\nb=http://1.com/\n
[r2]\nn=R2\nb=http://2.com/\n
[r3]\nn=R3\nb=http://3.com/\n
";
let rf = RepoFile::parse(input).unwrap();
let ids: Vec<String> = rf.repo_ids().map(|id| id.to_string()).collect();
assert_eq!(ids, vec!["r1", "r2", "r3"]);
let mut count = 0;
for (_id, _block) in rf.iter() {
count += 1;
}
assert_eq!(count, 3);
}
#[test]
fn repofile_merge_combines_repos() {
let mut a = RepoFile::new();
a.add(
RepoBuilder::new(RepoId::try_new("r1").unwrap())
.name(RepoName::try_new("R1").unwrap())
.baseurl("http://1.com/".parse().unwrap())
.build(),
)
.unwrap();
let mut b = RepoFile::new();
b.add(
RepoBuilder::new(RepoId::try_new("r2").unwrap())
.name(RepoName::try_new("R2").unwrap())
.baseurl("http://2.com/".parse().unwrap())
.build(),
)
.unwrap();
a.merge(b);
assert_eq!(a.len(), 2);
assert!(a.contains(&RepoId::try_new("r1").unwrap()));
assert!(a.contains(&RepoId::try_new("r2").unwrap()));
}
#[test]
fn repofile_parse_then_render_produces_parseable_output() {
let input = "[test]\nname=Test\nbaseurl=http://example.com/\nenabled=1\ngpgcheck=1\n";
let rf = RepoFile::parse(input).unwrap();
let rendered = rf.render();
let parsed = RepoFile::parse(&rendered).unwrap();
assert_eq!(parsed.len(), 1);
let block = parsed.get(&RepoId::try_new("test").unwrap()).unwrap();
assert_eq!(block.data.name.as_ref().unwrap().as_ref(), "Test");
assert_eq!(block.data.baseurl[0].as_str(), "http://example.com/");
}
#[test]
fn diff_empty_files_no_changes() {
let a = RepoFile::new();
let b = RepoFile::new();
let d = diff_files(&a, &b);
assert!(!d.has_changes);
}
#[test]
#[allow(clippy::field_reassign_with_default)]
fn diff_file_with_main_changes() {
let mut a = RepoFile::new();
a.set_main(MainConfig::default());
let mut b = RepoFile::new();
let mut mc = MainConfig::default();
mc.debuglevel = Some(DebugLevel::try_new(5).unwrap());
b.set_main(mc);
let d = diff_files(&a, &b);
assert!(d.has_changes);
}
#[test]
fn diff_identical_repos_no_changes() {
let repo = RepoBuilder::new(RepoId::try_new("r").unwrap())
.name(RepoName::try_new("R").unwrap())
.baseurl("http://x.com/".parse().unwrap())
.build();
let d = diff_repos(&repo, &repo);
assert!(!d.has_changes);
}
#[test]
fn validation_report_combines_errors_and_warnings() {
let mut report = ValidationReport::new();
assert!(report.is_ok());
assert!(!report.has_issues());
report.errors.push(ValidationIssue {
level: IssueLevel::Error,
location: IssueLocation::Main,
field: None,
message: "test error".into(),
});
assert!(!report.is_ok());
assert!(report.has_issues());
}
#[test]
fn validate_repo_with_metalink_passes() {
let mut repo = Repo::new(RepoId::try_new("r").unwrap());
repo.metalink = Some("https://example.com/metalink".parse().unwrap());
assert!(repo.validate().is_ok());
}
#[test]
fn validate_mainconfig_always_passes() {
let mc = MainConfig::default();
let report = mc.validate();
assert!(report.is_ok());
}
#[test]
fn expand_empty_string() {
let vars = HashMap::new();
assert_eq!(expand_variables("", &vars).unwrap(), "");
}
#[test]
fn expand_no_variables() {
let vars = HashMap::new();
assert_eq!(
expand_variables("http://example.com/path", &vars).unwrap(),
"http://example.com/path"
);
}
#[test]
fn expand_escaped_dollar_sign() {
let vars = HashMap::new();
assert_eq!(
expand_variables(r"price is \$100", &vars).unwrap(),
"price is $100"
);
}
#[test]
fn detect_variables_empty_input() {
assert!(detect_variables("").is_empty());
assert!(detect_variables("no vars here").is_empty());
}
#[test]
fn builder_from_preserves_unmodified_fields() {
let original = RepoBuilder::new(RepoId::try_new("r").unwrap())
.name(RepoName::try_new("Original").unwrap())
.baseurl("http://example.com/".parse().unwrap())
.enabled(DnfBool::True)
.gpgcheck(DnfBool::True)
.priority(Priority::try_new(50).unwrap())
.build();
let modified = RepoBuilder::from(&original).enabled(DnfBool::False).build();
assert_eq!(modified.name.as_ref().unwrap().as_ref(), "Original");
assert_eq!(modified.priority.unwrap().to_string(), "50");
assert_eq!(modified.gpgcheck, Some(DnfBool::True));
assert_eq!(modified.enabled, Some(DnfBool::False));
}
#[test]
fn reposdir_file_names() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(dir.path().join("a.repo"), "[r]\nn=R\nb=http://x.com/\n").unwrap();
std::fs::write(dir.path().join("b.repo"), "[r]\nn=R\nb=http://x.com/\n").unwrap();
std::fs::write(dir.path().join("not-repo.txt"), "hello").unwrap();
let rd = ReposDir::load(dir.path()).unwrap();
let names = rd.file_names();
assert_eq!(names.len(), 2);
assert!(names.iter().all(|n| n.ends_with(".repo")));
}
#[test]
fn reposdir_iter_repos() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join("multi.repo"),
"[r1]\nn=R1\nb=http://1.com/\n[r2]\nn=R2\nb=http://2.com/\n",
)
.unwrap();
let rd = ReposDir::load(dir.path()).unwrap();
let repos: Vec<(&str, &Repo)> = rd.iter_repos().collect();
assert_eq!(repos.len(), 2);
}
#[test]
fn reposdir_create_file() {
let dir = tempfile::TempDir::new().unwrap();
let mut rd = ReposDir::load(dir.path()).unwrap();
assert_eq!(rd.file_names().len(), 0);
let rf = rd.create_file("new.repo");
rf.add(
RepoBuilder::new(RepoId::try_new("new").unwrap())
.name(RepoName::try_new("New").unwrap())
.baseurl("http://new.example.com/".parse().unwrap())
.build(),
)
.unwrap();
assert_eq!(rd.file_names().len(), 1);
assert!(rd
.get_file("new.repo")
.unwrap()
.contains(&RepoId::try_new("new").unwrap()));
}
#[test]
fn url_source_metalink() {
let mut repo = Repo::new(RepoId::try_new("r").unwrap());
repo.metalink = Some("https://example.com/metalink.xml".parse().unwrap());
match repo.url_source() {
Some(UrlSource::Metalink(url)) => {
assert_eq!(url.as_str(), "https://example.com/metalink.xml")
}
other => panic!("expected Metalink, got {:?}", other),
}
}
#[test]
fn section_block_captures_unknown_keys() {
let input = "[r]\nname=Test\nbaseurl=http://x.com/\ncustom_opt=custom_val\n";
let rf = RepoFile::parse(input).unwrap();
let block = rf.get(&RepoId::try_new("r").unwrap()).unwrap();
let custom_entry = block.raw_entries.iter().find(|e| e.key == "custom_opt");
assert!(custom_entry.is_some());
assert_eq!(custom_entry.unwrap().value, "custom_val");
}
#[test]
fn round_trip_all_option_types() {
let input = "\
# Full-featured repo file
[main]
gpgcheck=1
debuglevel=5
max_parallel_downloads=10
[full-repo]
name=Full Featured Repository
baseurl=https://mirror.example.com/repo/$releasever/$basearch/
mirrorlist=https://mirrors.example.com/mirrorlist
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://example.com/RPM-GPG-KEY
priority=50
cost=500
module_hotfixes=0
metadata_expire=3600
timeout=60
retries=5
max_parallel_downloads=8
ip_resolve=4
sslverify=1
sslclientcert=/etc/pki/cert.pem
sslclientkey=/etc/pki/key.pem
proxy=http://proxy.example.com:8080
proxy_username=proxyuser
proxy_auth_method=basic
username=myuser
bandwidth=50M
minrate=1K
countme=1
";
let rf = RepoFile::parse(input).unwrap();
let output = rf.render();
let rf2 = RepoFile::parse(&output).unwrap();
let block = rf2.get(&RepoId::try_new("full-repo").unwrap()).unwrap();
assert_eq!(
block.data.name.as_ref().unwrap().as_ref(),
"Full Featured Repository"
);
assert_eq!(block.data.baseurl.len(), 1);
let mirrorlist_url = block.data.mirrorlist.as_ref().unwrap().as_str();
assert!(
mirrorlist_url.contains("mirrors.example.com/mirrorlist"),
"mirrorlist URL = {mirrorlist_url}"
);
assert_eq!(block.data.enabled, Some(DnfBool::True));
assert_eq!(block.data.priority.unwrap().to_string(), "50");
assert_eq!(block.data.cost.unwrap().to_string(), "500");
assert_eq!(block.data.timeout.unwrap().to_string(), "60");
assert_eq!(block.data.retries.unwrap().to_string(), "5");
assert_eq!(block.data.ip_resolve, Some(IpResolve::V4));
assert_eq!(block.data.sslverify, Some(DnfBool::True));
assert_eq!(block.data.countme, Some(DnfBool::True));
}
#[test]
fn mainconfig_all_boolean_fields_round_trip() {
let input = "\
[main]
best=1
cacheonly=0
check_config_file_age=yes
clean_requirements_on_remove=true
debug_solver=on
diskspacecheck=1
exit_on_lock=0
gpgkey_dns_verification=no
ignorearch=false
install_weak_deps=1
keepcache=off
log_compress=1
module_obsoletes=0
module_stream_switch=no
obsoletes=true
plugins=1
protect_running_kernel=1
strict=on
upgrade_group_objects_upgrade=0
zchunk=1
";
let rf = RepoFile::parse(input).unwrap();
let main = rf.main().unwrap();
assert_eq!(main.data.best, Some(DnfBool::True));
assert_eq!(main.data.cacheonly, Some(DnfBool::False));
assert_eq!(main.data.check_config_file_age, Some(DnfBool::True));
assert_eq!(main.data.clean_requirements_on_remove, Some(DnfBool::True));
assert_eq!(main.data.debug_solver, Some(DnfBool::True));
assert_eq!(main.data.diskspacecheck, Some(DnfBool::True));
assert_eq!(main.data.exit_on_lock, Some(DnfBool::False));
assert_eq!(main.data.gpgkey_dns_verification, Some(DnfBool::False));
assert_eq!(main.data.ignorearch, Some(DnfBool::False));
assert_eq!(main.data.install_weak_deps, Some(DnfBool::True));
assert_eq!(main.data.keepcache, Some(DnfBool::False));
assert_eq!(main.data.log_compress, Some(DnfBool::True));
assert_eq!(main.data.module_obsoletes, Some(DnfBool::False));
assert_eq!(main.data.module_stream_switch, Some(DnfBool::False));
assert_eq!(main.data.obsoletes, Some(DnfBool::True));
assert_eq!(main.data.plugins, Some(DnfBool::True));
assert_eq!(main.data.protect_running_kernel, Some(DnfBool::True));
assert_eq!(main.data.strict, Some(DnfBool::True));
assert_eq!(
main.data.upgrade_group_objects_upgrade,
Some(DnfBool::False)
);
assert_eq!(main.data.zchunk, Some(DnfBool::True));
}
#[test]
fn mainconfig_numeric_fields_round_trip() {
let input = "\
[main]
debuglevel=7
logfilelevel=5
log_rotate=2
log_size=10M
installonly_limit=3
errorlevel=5
metadata_timer_sync=7200
";
let rf = RepoFile::parse(input).unwrap();
let main = rf.main().unwrap();
assert_eq!(main.data.debuglevel.unwrap().to_string(), "7");
assert_eq!(main.data.logfilelevel.unwrap().to_string(), "5");
assert_eq!(main.data.log_rotate.unwrap().to_string(), "2");
assert_eq!(main.data.installonly_limit.unwrap().to_string(), "3");
assert_eq!(main.data.errorlevel.unwrap().to_string(), "5");
assert_eq!(main.data.metadata_timer_sync.unwrap().to_string(), "7200");
}
}