Files
depot/src/db/repo/tests.rs
T

912 lines
30 KiB
Rust

use super::*;
#[test]
fn test_init_repo_schema() {
let mut conn = Connection::open_in_memory().unwrap();
let manager = RepoManager::new(PathBuf::from("."));
manager.init_repo_schema(&mut conn).unwrap();
// Check if table exists
let exists: bool = conn
.query_row(
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='packages'",
[],
|r| r.get(0),
)
.unwrap();
assert!(exists);
let has_sha512: bool = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'sha512'",
[],
|r| {
let n: i64 = r.get(0)?;
Ok(n > 0)
},
)
.unwrap();
assert!(has_sha512);
let has_completed_at: bool = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'completed_at'",
[],
|r| {
let n: i64 = r.get(0)?;
Ok(n > 0)
},
)
.unwrap();
assert!(has_completed_at);
}
#[test]
fn test_index_package() {
let tmp = tempfile::tempdir().unwrap();
let repo_dir = tmp.path();
let pkg_path = repo_dir.join("test-1.0-1-x86_64.depot.pkg.tar.zst");
// Create a valid .tar.zst with .metadata.toml
let file = fs::File::create(&pkg_path).unwrap();
let encoder = zstd::stream::write::Encoder::new(file, 3).unwrap();
let mut tar = tar::Builder::new(encoder);
let metadata = r#"
name = "test"
real_name = "icu"
version = "1.0"
revision = 1
abi_breaking = true
built_against = ["icu78"]
description = "test description"
homepage = "https://example.com"
license = "MIT"
completed_at = "2026-03-10T12:34:56Z"
provides = ["test-feature"]
[dependencies]
runtime = []
optional = []
"#;
let mut header = tar::Header::new_gnu();
header.set_path(".metadata.toml").unwrap();
header.set_size(metadata.len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append(&header, metadata.as_bytes()).unwrap();
let encoder = tar.into_inner().unwrap();
encoder.finish().unwrap();
filetime::set_file_mtime(
&pkg_path,
filetime::FileTime::from_unix_time(1_700_000_000, 0),
)
.unwrap();
let mut conn = Connection::open_in_memory().unwrap();
let manager = RepoManager::new(repo_dir.to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
let indexed = manager.read_indexed_package(&pkg_path).unwrap();
manager.insert_indexed_package(&mut conn, indexed).unwrap();
type PackageRow = (
String,
Option<String>,
String,
i64,
i64,
String,
Option<String>,
Option<String>,
Option<String>,
String,
String,
);
let (
name,
real_name,
version,
revision,
abi_breaking,
built_against,
desc,
home,
lic,
sha256,
sha512,
): PackageRow = conn
.query_row(
"SELECT name, real_name, version, revision, abi_breaking, built_against, description, homepage, license, sha256, sha512 FROM packages",
[],
|r| {
Ok((
r.get(0)?,
r.get(1)?,
r.get(2)?,
r.get(3)?,
r.get(4)?,
r.get(5)?,
r.get(6)?,
r.get(7)?,
r.get(8)?,
r.get(9)?,
r.get(10)?,
))
},
)
.unwrap();
assert_eq!(name, "test");
assert_eq!(real_name, Some("icu".to_string()));
assert_eq!(version, "1.0");
assert_eq!(revision, 1);
assert_eq!(abi_breaking, 1);
assert_eq!(built_against, "icu78");
assert_eq!(desc, Some("test description".to_string()));
assert_eq!(home, Some("https://example.com".to_string()));
assert_eq!(lic, Some("MIT".to_string()));
assert_eq!(sha256.len(), 64);
assert_eq!(sha512.len(), 128);
let completed_at: Option<i64> = conn
.query_row("SELECT completed_at FROM packages", [], |r| r.get(0))
.unwrap();
assert_eq!(completed_at, Some(1_773_146_096));
let provides_count: i64 = conn
.query_row("SELECT count(*) FROM provides", [], |r| r.get(0))
.unwrap();
assert_eq!(provides_count, 1);
}
#[test]
fn test_index_package_with_multiple_licenses() {
let tmp = tempfile::tempdir().unwrap();
let repo_dir = tmp.path();
let pkg_path = repo_dir.join("test-1.0-1-x86_64.depot.pkg.tar.zst");
let file = fs::File::create(&pkg_path).unwrap();
let encoder = zstd::stream::write::Encoder::new(file, 3).unwrap();
let mut tar = tar::Builder::new(encoder);
let metadata = r#"
name = "test"
version = "1.0"
revision = 1
license = ["MIT", "Apache-2.0"]
"#;
let mut header = tar::Header::new_gnu();
header.set_path(".metadata.toml").unwrap();
header.set_size(metadata.len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append(&header, metadata.as_bytes()).unwrap();
let encoder = tar.into_inner().unwrap();
encoder.finish().unwrap();
let mut conn = Connection::open_in_memory().unwrap();
let manager = RepoManager::new(repo_dir.to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
let indexed = manager.read_indexed_package(&pkg_path).unwrap();
manager.insert_indexed_package(&mut conn, indexed).unwrap();
let lic: Option<String> = conn
.query_row("SELECT license FROM packages", [], |r| r.get(0))
.unwrap();
assert_eq!(lic, Some("MIT, Apache-2.0".to_string()));
}
#[test]
fn test_index_package_records_symlink_paths_for_repo_owns() {
let tmp = tempfile::tempdir().unwrap();
let repo_dir = tmp.path();
let pkg_path = repo_dir.join("test-1.0-1-x86_64.depot.pkg.tar.zst");
let file = fs::File::create(&pkg_path).unwrap();
let encoder = zstd::stream::write::Encoder::new(file, 3).unwrap();
let mut tar = tar::Builder::new(encoder);
let metadata = r#"
name = "test"
version = "1.0"
revision = 1
"#;
let mut header = tar::Header::new_gnu();
header.set_path(".metadata.toml").unwrap();
header.set_size(metadata.len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append(&header, metadata.as_bytes()).unwrap();
let mut file_header = tar::Header::new_gnu();
file_header.set_path("usr/bin/coreutils").unwrap();
file_header.set_size(4);
file_header.set_mode(0o755);
file_header.set_cksum();
tar.append(&file_header, &b"test"[..]).unwrap();
let mut link_header = tar::Header::new_gnu();
link_header.set_entry_type(tar::EntryType::Symlink);
link_header.set_path("usr/bin/ls").unwrap();
link_header.set_link_name("coreutils").unwrap();
link_header.set_size(0);
link_header.set_mode(0o777);
link_header.set_cksum();
tar.append(&link_header, std::io::empty()).unwrap();
let encoder = tar.into_inner().unwrap();
encoder.finish().unwrap();
let mut conn = Connection::open_in_memory().unwrap();
let manager = RepoManager::new(repo_dir.to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
let indexed = manager.read_indexed_package(&pkg_path).unwrap();
manager.insert_indexed_package(&mut conn, indexed).unwrap();
let db_path = repo_dir.join("repo.db");
let mut file_conn = Connection::open(&db_path).unwrap();
manager.init_repo_schema(&mut file_conn).unwrap();
let indexed = manager.read_indexed_package(&pkg_path).unwrap();
manager
.insert_indexed_package(&mut file_conn, indexed)
.unwrap();
drop(file_conn);
let hits = cached_binary_repo_owns_path("repo", &db_path, "usr/bin/ls").unwrap();
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].package_name, "test");
assert_eq!(hits[0].path, "usr/bin/ls");
}
#[test]
fn test_search_cached_binary_repo_db_matches_name_and_provides() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("repo.db");
let mut conn = Connection::open(&db_path).unwrap();
let manager = RepoManager::new(tmp.path().to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
conn.execute(
"INSERT INTO packages (id, name, version, revision, description, homepage, license, filename, size, sha256, sha512)
VALUES (1, 'foo', '1.2.3', 1, 'Foo package', 'https://example.test', 'MIT', 'foo-1.2.3-1-x86_64.depot.pkg.tar.zst', 1234, 'a', 'b')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO provides (package_id, name) VALUES (1, 'libfoo.so')",
[],
)
.unwrap();
drop(conn);
let name_hits = search_cached_binary_repo_db("testrepo", &db_path, "foo").unwrap();
assert_eq!(name_hits.len(), 1);
assert_eq!(name_hits[0].name, "foo");
assert_eq!(name_hits[0].repo_name, "testrepo");
assert!(name_hits[0].provides.iter().any(|p| p == "libfoo.so"));
let provide_hits = search_cached_binary_repo_db("testrepo", &db_path, "libfoo").unwrap();
assert_eq!(provide_hits.len(), 1);
assert_eq!(provide_hits[0].name, "foo");
}
#[test]
fn test_find_cached_binary_repo_packages_by_group() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("repo.db");
let mut conn = Connection::open(&db_path).unwrap();
let manager = RepoManager::new(tmp.path().to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
conn.execute(
"INSERT INTO packages (id, name, version, revision, description, homepage, license, filename, size, sha256, sha512)
VALUES (1, 'foo', '1.2.3', 1, 'Foo package', 'https://example.test', 'MIT', 'foo-1.2.3-1-x86_64.depot.pkg.tar.zst', 1234, 'a', 'b')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO groups (package_id, name) VALUES (1, 'base')",
[],
)
.unwrap();
drop(conn);
let hits = find_cached_binary_repo_packages_by_group("testrepo", &db_path, "base").unwrap();
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].name, "foo");
assert_eq!(hits[0].groups, vec!["base".to_string()]);
}
#[test]
#[cfg(unix)]
fn test_repo_owns_query_candidates_follow_rootfs_symlink_targets() {
let rootfs = tempfile::tempdir().unwrap();
let usr_bin = rootfs.path().join("usr/bin");
fs::create_dir_all(&usr_bin).unwrap();
fs::write(usr_bin.join("coreutils"), b"payload").unwrap();
std::os::unix::fs::symlink("coreutils", usr_bin.join("ls")).unwrap();
std::os::unix::fs::symlink("usr/bin", rootfs.path().join("bin")).unwrap();
let ls_candidates = repo_owns_query_candidates(rootfs.path(), "/usr/bin/ls");
assert!(
ls_candidates
.iter()
.any(|candidate| candidate == "usr/bin/ls")
);
assert!(
ls_candidates
.iter()
.any(|candidate| candidate == "usr/bin/coreutils")
);
let bin_candidates = repo_owns_query_candidates(rootfs.path(), "/bin/ls");
assert!(bin_candidates.iter().any(|candidate| candidate == "bin/ls"));
assert!(
bin_candidates
.iter()
.any(|candidate| candidate == "usr/bin/coreutils")
);
}
#[test]
fn test_find_cached_binary_repo_package_prefers_exact_name() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("repo.db");
let mut conn = Connection::open(&db_path).unwrap();
let manager = RepoManager::new(tmp.path().to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
conn.execute(
"INSERT INTO packages (id, name, version, revision, description, homepage, license, filename, size, sha256, sha512)
VALUES (1, 'foo', '1.0', 1, NULL, NULL, NULL, 'foo-1.0-1.depot.pkg.tar.zst', 10, 'aa', 'bb')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO packages (id, name, version, revision, description, homepage, license, filename, size, sha256, sha512)
VALUES (2, 'bar', '1.0', 1, NULL, NULL, NULL, 'bar-1.0-1.depot.pkg.tar.zst', 10, 'cc', 'dd')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO provides (package_id, name) VALUES (2, 'foo')",
[],
)
.unwrap();
drop(conn);
let recs = find_cached_binary_repo_packages("repo", &db_path, "foo").unwrap();
let rec = recs.first().expect("expected a match");
assert_eq!(rec.name, "foo");
assert_eq!(rec.filename, "foo-1.0-1.depot.pkg.tar.zst");
}
#[test]
fn test_find_cached_binary_repo_packages_matches_real_name_and_built_against() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("repo.db");
let mut conn = Connection::open(&db_path).unwrap();
let manager = RepoManager::new(tmp.path().to_path_buf());
manager.init_repo_schema(&mut conn).unwrap();
conn.execute(
"INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, description, homepage, license, filename, size, sha256, sha512)
VALUES (1, 'icu78', 'icu', '78.1', 1, 0, '', NULL, NULL, NULL, 'icu78.pkg', 10, 'aa', 'bb')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO packages (id, name, real_name, version, revision, abi_breaking, built_against, description, homepage, license, filename, size, sha256, sha512)
VALUES (2, 'app', NULL, '1.0', 1, 0, 'icu78', NULL, NULL, NULL, 'app.pkg', 10, 'cc', 'dd')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO dependencies (package_id, kind, name) VALUES (2, 'runtime', 'icu')",
[],
)
.unwrap();
drop(conn);
let icu_matches = find_cached_binary_repo_packages("repo", &db_path, "icu").unwrap();
assert_eq!(icu_matches.len(), 1);
assert_eq!(icu_matches[0].name, "icu78");
assert_eq!(icu_matches[0].real_name.as_deref(), Some("icu"));
let app = find_cached_binary_repo_packages("repo", &db_path, "app")
.unwrap()
.pop()
.unwrap();
assert_eq!(app.runtime_dependencies, vec!["icu".to_string()]);
assert_eq!(app.built_against, vec!["icu78".to_string()]);
}
#[test]
fn test_verify_binary_package_record_checksums_accepts_valid_hashes() {
use sha2::{Digest, Sha512};
let tmp = tempfile::tempdir().unwrap();
let pkg = tmp.path().join("pkg.depot.pkg.tar.zst");
fs::write(&pkg, b"payload").unwrap();
let sha512 = {
let mut h = Sha512::new();
h.update(b"payload");
crate::hex::encode_lower(h.finalize())
};
let rec = BinaryRepoPackageRecord {
repo_name: "repo".into(),
name: "pkg".into(),
real_name: None,
version: "1.0".into(),
revision: 1,
abi_breaking: false,
built_against: Vec::new(),
completed_at: None,
filename: "pkg.depot.pkg.tar.zst".into(),
size: 7,
sha512,
description: None,
homepage: None,
license: None,
provides: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
groups: Vec::new(),
};
verify_binary_package_record_checksums(&pkg, &rec).unwrap();
}
#[test]
fn test_verify_binary_package_record_checksums_requires_valid_sha512() {
use sha2::{Digest, Sha512};
let tmp = tempfile::tempdir().unwrap();
let pkg = tmp.path().join("pkg.depot.pkg.tar.zst");
fs::write(&pkg, b"payload").unwrap();
let mut rec = test_record_for_payload("pkg.depot.pkg.tar.zst", b"payload");
verify_binary_package_record_checksums(&pkg, &rec).unwrap();
let mut wrong_sha512 = Sha512::new();
wrong_sha512.update(b"different payload");
rec.sha512 = crate::hex::encode_lower(wrong_sha512.finalize());
let err = verify_binary_package_record_checksums(&pkg, &rec).unwrap_err();
assert!(err.to_string().contains("SHA-512 mismatch"));
}
fn test_record_for_payload(filename: &str, payload: &[u8]) -> BinaryRepoPackageRecord {
use sha2::{Digest, Sha512};
let sha512 = {
let mut h = Sha512::new();
h.update(payload);
crate::hex::encode_lower(h.finalize())
};
BinaryRepoPackageRecord {
repo_name: "repo".into(),
name: "pkg".into(),
real_name: None,
version: "1.0".into(),
revision: 1,
abi_breaking: false,
built_against: Vec::new(),
completed_at: None,
filename: filename.to_string(),
size: payload.len() as u64,
sha512,
description: None,
homepage: None,
license: None,
provides: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
groups: Vec::new(),
}
}
#[test]
fn test_fetch_binary_package_archive_requires_signature_when_unsigned_disallowed() {
let rootfs = tempfile::tempdir().unwrap();
let repo_dir = tempfile::tempdir().unwrap();
let cache_dir = tempfile::tempdir().unwrap();
let filename = "pkg-1.0-1-x86_64.depot.pkg.tar.zst";
let payload = b"package payload";
std::fs::write(repo_dir.path().join(filename), payload).unwrap();
let rec = test_record_for_payload(filename, payload);
let repo_url = url::Url::from_directory_path(repo_dir.path())
.expect("file URL")
.to_string();
let repo_cfg = crate::config::BinaryRepo {
url: repo_url,
allow_unsigned: false,
..Default::default()
};
let err =
fetch_binary_package_archive("repo", &repo_cfg, rootfs.path(), &rec, cache_dir.path())
.expect_err("missing detached signature should fail");
assert!(err.to_string().to_ascii_lowercase().contains("signature"));
}
#[test]
fn test_fetch_binary_package_archive_verifies_signature_and_checksum() {
let rootfs = tempfile::tempdir().unwrap();
let repo_dir = tempfile::tempdir().unwrap();
let cache_dir = tempfile::tempdir().unwrap();
let trusted_dir = crate::signing::trusted_public_keys_dir(rootfs.path());
std::fs::create_dir_all(&trusted_dir).unwrap();
let keypair = minisign::KeyPair::generate_unencrypted_keypair().unwrap();
std::fs::write(
trusted_dir.join("repo.pub"),
keypair.pk.to_box().unwrap().to_bytes(),
)
.unwrap();
let filename = "pkg-1.0-1-x86_64.depot.pkg.tar.zst";
let payload = b"signed package payload";
let package_path = repo_dir.path().join(filename);
std::fs::write(&package_path, payload).unwrap();
let sig = minisign::sign(
Some(&keypair.pk),
&keypair.sk,
std::fs::File::open(&package_path).unwrap(),
None,
Some("test signature"),
)
.unwrap();
std::fs::write(format!("{}.sig", package_path.display()), sig.to_bytes()).unwrap();
let rec = test_record_for_payload(filename, payload);
let repo_url = url::Url::from_directory_path(repo_dir.path())
.expect("file URL")
.to_string();
let repo_cfg = crate::config::BinaryRepo {
url: repo_url,
allow_unsigned: false,
..Default::default()
};
let fetched =
fetch_binary_package_archive("repo", &repo_cfg, rootfs.path(), &rec, cache_dir.path())
.unwrap();
assert_eq!(std::fs::read(&fetched).unwrap(), payload);
assert!(PathBuf::from(format!("{}.sig", fetched.display())).exists());
}
#[test]
fn test_cache_binary_package_archive_supports_combined_integrity_verification() {
let rootfs = tempfile::tempdir().unwrap();
let repo_dir = tempfile::tempdir().unwrap();
let cache_dir = tempfile::tempdir().unwrap();
let trusted_dir = crate::signing::trusted_public_keys_dir(rootfs.path());
std::fs::create_dir_all(&trusted_dir).unwrap();
let keypair = minisign::KeyPair::generate_unencrypted_keypair().unwrap();
std::fs::write(
trusted_dir.join("repo.pub"),
keypair.pk.to_box().unwrap().to_bytes(),
)
.unwrap();
let filename = "pkg-1.0-1-x86_64.depot.pkg.tar.zst";
let payload = b"staged verification payload";
let package_path = repo_dir.path().join(filename);
std::fs::write(&package_path, payload).unwrap();
let sig = minisign::sign(
Some(&keypair.pk),
&keypair.sk,
std::fs::File::open(&package_path).unwrap(),
None,
Some("test signature"),
)
.unwrap();
std::fs::write(format!("{}.sig", package_path.display()), sig.to_bytes()).unwrap();
let rec = test_record_for_payload(filename, payload);
let repo_url = url::Url::from_directory_path(repo_dir.path())
.expect("file URL")
.to_string();
let repo_cfg = crate::config::BinaryRepo {
url: repo_url,
allow_unsigned: false,
..Default::default()
};
let cached = cache_binary_package_archive("repo", &repo_cfg, &rec, cache_dir.path())
.expect("cache should succeed");
assert!(cached.package_path.exists());
assert!(cached.signature_path.exists());
verify_binary_package_archive_checksums(&cached.package_path, &rec)
.expect("checksum verification should succeed");
let trusted_keys = crate::signing::load_trusted_public_keys(rootfs.path()).unwrap();
verify_binary_package_archive_integrity_with_trusted_keys(
"repo",
&repo_cfg,
&rec,
&cached.package_path,
&cached.signature_path,
&trusted_keys,
)
.expect("combined integrity verification should succeed");
let mut wrong_record = rec.clone();
wrong_record.sha512 = crate::hex::encode_lower(Sha512::digest(b"wrong payload"));
let error = verify_binary_package_archive_integrity_with_trusted_keys(
"repo",
&repo_cfg,
&wrong_record,
&cached.package_path,
&cached.signature_path,
&trusted_keys,
)
.expect_err("combined verification must reject a checksum mismatch");
assert!(error.to_string().contains("SHA-512 mismatch"));
}
#[test]
fn test_fetch_binary_package_archive_allows_missing_signature_when_configured() {
let rootfs = tempfile::tempdir().unwrap();
let repo_dir = tempfile::tempdir().unwrap();
let cache_dir = tempfile::tempdir().unwrap();
let filename = "pkg-1.0-1-x86_64.depot.pkg.tar.zst";
let payload = b"unsigned package payload";
std::fs::write(repo_dir.path().join(filename), payload).unwrap();
let rec = test_record_for_payload(filename, payload);
let repo_url = url::Url::from_directory_path(repo_dir.path())
.expect("file URL")
.to_string();
let repo_cfg = crate::config::BinaryRepo {
url: repo_url,
allow_unsigned: true,
..Default::default()
};
let fetched =
fetch_binary_package_archive("repo", &repo_cfg, rootfs.path(), &rec, cache_dir.path())
.unwrap();
assert_eq!(std::fs::read(&fetched).unwrap(), payload);
assert!(!PathBuf::from(format!("{}.sig", fetched.display())).exists());
}
#[test]
fn test_copy_file_url_to_path_supports_file_scheme() {
let tmp = tempfile::tempdir().unwrap();
let src = tmp.path().join("repo.db.zst");
let dst = tmp.path().join("copy.zst");
fs::write(&src, b"repo-db").unwrap();
let url = format!("file://{}", src.display());
let outcome = copy_file_url_to_path(&url, &dst).unwrap();
assert_eq!(outcome, FileUrlCopyOutcome::Copied);
assert_eq!(fs::read(&dst).unwrap(), b"repo-db");
}
#[test]
fn test_copy_file_url_to_path_reports_missing_file() {
let tmp = tempfile::tempdir().unwrap();
let missing = tmp.path().join("missing.db.zst");
let dst = tmp.path().join("copy.zst");
let url = format!("file://{}", missing.display());
let outcome = copy_file_url_to_path(&url, &dst).unwrap();
assert_eq!(outcome, FileUrlCopyOutcome::Missing);
assert!(!dst.exists());
}
#[test]
fn test_repo_db_fetch_cache_roundtrip_and_prunes_stale_entry() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("repo.db");
fs::write(&db_path, b"db").unwrap();
let key = RepoDbFetchCacheKey {
repo_name: "core".to_string(),
base_url: "https://repo.example.test/core".to_string(),
repo_db_rel: "repo.db.zst".to_string(),
rootfs: PathBuf::from("/tmp/rootfs-test"),
package_cache_dir: PathBuf::from("/tmp/pkg-cache-test"),
};
remember_repo_db_path(key.clone(), db_path.clone());
assert_eq!(get_cached_repo_db_path(&key), Some(db_path.clone()));
fs::remove_file(&db_path).unwrap();
assert_eq!(get_cached_repo_db_path(&key), None);
}
#[test]
fn test_extract_html_href_targets_parses_common_forms() {
let html = r#"
<html><body>
<a href="alpha.pub">alpha</a>
<a HREF='nested/beta.pub'>beta</a>
<a href=gamma.pub>gamma</a>
<a href="../">parent</a>
</body></html>
"#;
let hrefs = extract_html_href_targets(html);
assert!(hrefs.iter().any(|h| h == "alpha.pub"));
assert!(hrefs.iter().any(|h| h == "nested/beta.pub"));
assert!(hrefs.iter().any(|h| h == "gamma.pub"));
assert!(hrefs.iter().any(|h| h == "../"));
}
#[test]
fn test_list_repo_public_key_urls_reads_file_repo_keys_dir() {
let tmp = tempfile::tempdir().unwrap();
let repo_dir = tmp.path().join("repo");
let keys_dir = repo_dir.join("keys");
fs::create_dir_all(&keys_dir).unwrap();
fs::write(keys_dir.join("repo.pub"), b"pubkey").unwrap();
fs::write(keys_dir.join("ignore.txt"), b"nope").unwrap();
fs::create_dir_all(keys_dir.join("subdir")).unwrap();
let base_url = url::Url::from_directory_path(&repo_dir)
.expect("file URL")
.to_string();
let client = reqwest::blocking::Client::builder().build().unwrap();
let keys = list_repo_public_key_urls(&base_url, &client).unwrap();
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].0, "repo.pub");
assert!(keys[0].1.ends_with("/repo.pub"));
}
#[test]
fn test_list_repo_public_key_urls_probes_common_names_when_index_missing() {
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use std::thread;
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let server = thread::spawn(move || {
for _ in 0..7 {
let (mut stream, _) = listener.accept().unwrap();
let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
loop {
let mut line = String::new();
reader.read_line(&mut line).unwrap();
if line == "\r\n" || line.is_empty() {
break;
}
}
let path = request_line
.split_whitespace()
.nth(1)
.unwrap_or_default()
.to_string();
let (status, body) = if path == "/core/keys/vertex.pub" {
("200 OK", "trusted-key")
} else {
("404 Not Found", "missing")
};
write!(
stream,
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
)
.unwrap();
stream.flush().unwrap();
}
});
let base_url = format!("http://{}/core", addr);
let client = reqwest::blocking::Client::builder().build().unwrap();
let keys = list_repo_public_key_urls(&base_url, &client).unwrap();
server.join().unwrap();
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].0, "vertex.pub");
assert!(keys[0].1.ends_with("/core/keys/vertex.pub"));
}
#[test]
fn test_fetch_binary_repo_db_can_recover_from_stale_trusted_key() {
use std::io::Write;
struct AssumeYesReset;
impl Drop for AssumeYesReset {
fn drop(&mut self) {
crate::ui::set_assume_yes(false);
}
}
crate::ui::set_assume_yes(true);
let _reset = AssumeYesReset;
let rootfs = tempfile::tempdir().unwrap();
let repo_dir = tempfile::tempdir().unwrap();
let cache_dir = tempfile::tempdir().unwrap();
let stale_keypair = minisign::KeyPair::generate_unencrypted_keypair().unwrap();
let repo_keypair = minisign::KeyPair::generate_unencrypted_keypair().unwrap();
let trusted_dir = crate::signing::trusted_public_keys_dir(rootfs.path());
fs::create_dir_all(&trusted_dir).unwrap();
fs::write(
trusted_dir.join("vertex.pub"),
stale_keypair.pk.to_box().unwrap().to_bytes(),
)
.unwrap();
let repo_keys_dir = repo_dir.path().join("keys");
fs::create_dir_all(&repo_keys_dir).unwrap();
fs::write(
repo_keys_dir.join("vertex.pub"),
repo_keypair.pk.to_box().unwrap().to_bytes(),
)
.unwrap();
let repo_db_path = repo_dir.path().join("repo.db.zst");
let mut encoder =
zstd::stream::write::Encoder::new(fs::File::create(&repo_db_path).unwrap(), 3).unwrap();
encoder.write_all(b"repo-db-content").unwrap();
encoder.finish().unwrap();
let sig = minisign::sign(
Some(&repo_keypair.pk),
&repo_keypair.sk,
fs::File::open(&repo_db_path).unwrap(),
None,
Some("repo db signature"),
)
.unwrap();
fs::write(repo_dir.path().join("repo.db.zst.sig"), sig.to_bytes()).unwrap();
let repo_cfg = crate::config::BinaryRepo {
url: url::Url::from_directory_path(repo_dir.path())
.expect("file URL")
.to_string(),
allow_unsigned: false,
..Default::default()
};
let sqlite_db =
fetch_binary_repo_db("core", &repo_cfg, rootfs.path(), cache_dir.path()).unwrap();
assert_eq!(fs::read(sqlite_db).unwrap(), b"repo-db-content");
let installed_key = trusted_dir.join("core-vertex.pub");
assert!(installed_key.exists());
assert_eq!(
fs::read(installed_key).unwrap(),
repo_keypair.pk.to_box().unwrap().to_bytes()
);
}
#[test]
fn test_normalize_git_mirror_url_converts_file_scheme() {
let tmp = tempfile::tempdir().unwrap();
let repo_dir = tmp.path().join("repo.git");
fs::create_dir_all(&repo_dir).unwrap();
let url = format!("file://{}", repo_dir.display());
let normalized = normalize_git_mirror_url(&url).unwrap();
assert_eq!(normalized, repo_dir.to_string_lossy());
}