fix(catalog): validate package roots before preflight
Require each selected package root to be a regular non-link directory before reading its bounded version.ini, and omit package-controlled observed contents from mismatch diagnostics. Full-selection preflight still completes before archive work or publication. Test Plan: - just test - just clippy - symlink-root and diagnostic-redaction regressions - git diff --check
This commit is contained in:
@@ -122,7 +122,7 @@ pub async fn generate_catalog_manifests(
|
|||||||
reject_incomplete_publication(&options.manifests_dir)?;
|
reject_incomplete_publication(&options.manifests_dir)?;
|
||||||
let selected = options.selection.select(&catalog)?;
|
let selected = options.selection.select(&catalog)?;
|
||||||
for game in &selected {
|
for game in &selected {
|
||||||
package::validate_package_version(
|
package::preflight_package_version(
|
||||||
&options.packages_dir.join(&game.game_id),
|
&options.packages_dir.join(&game.game_id),
|
||||||
&game.game_version,
|
&game.game_version,
|
||||||
)
|
)
|
||||||
@@ -820,6 +820,51 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn symlink_package_root_is_rejected_during_full_selection_preflight() {
|
||||||
|
use std::os::unix::fs::symlink;
|
||||||
|
|
||||||
|
let temp = TempDir::new();
|
||||||
|
let catalog_db = temp.0.join("game.db");
|
||||||
|
create_catalog(&catalog_db, &[("a", "20240101"), ("b", "20240102")]).await;
|
||||||
|
let a_package = simple_package(&temp, "a", "20240101");
|
||||||
|
fs::write(a_package.join("a.eti"), b"not an archive")
|
||||||
|
.expect("archive-shaped input should be written");
|
||||||
|
let outside = temp.0.join("outside-b");
|
||||||
|
fs::create_dir(&outside).expect("outside package should be created");
|
||||||
|
fs::write(outside.join("version.ini"), "20240102")
|
||||||
|
.expect("outside version should be written");
|
||||||
|
symlink(&outside, temp.0.join("packages/b"))
|
||||||
|
.expect("package root symlink should be created");
|
||||||
|
let manifests_dir = temp.0.join("manifests");
|
||||||
|
fs::create_dir(&manifests_dir).expect("manifest directory should be created");
|
||||||
|
let existing = manifests_dir.join("a.json");
|
||||||
|
fs::write(&existing, b"existing artifact").expect("existing artifact should be seeded");
|
||||||
|
|
||||||
|
let error = generate_catalog_manifests(&GenerateOptions {
|
||||||
|
catalog_db,
|
||||||
|
packages_dir: temp.0.join("packages"),
|
||||||
|
manifests_dir: manifests_dir.clone(),
|
||||||
|
unrar: PathBuf::from("missing-unrar"),
|
||||||
|
selection: CatalogSelection::All,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect_err("package root links must fail during version preflight");
|
||||||
|
let rendered = format!("{error:#}");
|
||||||
|
|
||||||
|
assert!(rendered.contains("failed to preflight package version for b"));
|
||||||
|
assert!(rendered.contains("expected a regular non-link directory"));
|
||||||
|
assert_eq!(
|
||||||
|
fs::read(existing).expect("existing artifact should remain readable"),
|
||||||
|
b"existing artifact"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!manifests_dir.join(CATALOG_PUBLICATION_MARKER_NAME).exists(),
|
||||||
|
"root validation must fail before archive work or publication"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn incremental_generation_requires_an_existing_complete_indexed_corpus() {
|
async fn incremental_generation_requires_an_existing_complete_indexed_corpus() {
|
||||||
let temp = TempDir::new();
|
let temp = TempDir::new();
|
||||||
@@ -894,15 +939,23 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn package_version_mismatch_is_rejected() {
|
fn package_version_mismatch_is_rejected() {
|
||||||
|
const OBSERVED: &str = "package-controlled-secret-20240101";
|
||||||
|
|
||||||
let temp = TempDir::new();
|
let temp = TempDir::new();
|
||||||
let root = simple_package(&temp, "g", "20240101");
|
let root = simple_package(&temp, "g", OBSERVED);
|
||||||
let game = CatalogGame {
|
let game = CatalogGame {
|
||||||
game_id: "g".to_owned(),
|
game_id: "g".to_owned(),
|
||||||
game_version: "20240102".to_owned(),
|
game_version: "20240102".to_owned(),
|
||||||
};
|
};
|
||||||
let error = build_manifest_from_package(&game, &root, Path::new("missing-unrar"))
|
let error = build_manifest_from_package(&game, &root, Path::new("missing-unrar"))
|
||||||
.expect_err("version skew should fail");
|
.expect_err("version skew should fail");
|
||||||
assert!(error.to_string().contains("package version mismatch"));
|
let rendered = format!("{error:#}");
|
||||||
|
assert!(rendered.contains("package version mismatch"));
|
||||||
|
assert!(rendered.contains("game.db expects 20240102"));
|
||||||
|
assert!(
|
||||||
|
!rendered.contains(OBSERVED),
|
||||||
|
"package-controlled version contents must not enter diagnostics"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -122,6 +122,14 @@ pub(super) fn validate_manifest_destination(path: &Path) -> eyre::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn preflight_package_version(
|
||||||
|
package_root: &Path,
|
||||||
|
expected: &str,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
validate_regular_directory(package_root)?;
|
||||||
|
validate_package_version(package_root, expected)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn validate_package_version(package_root: &Path, expected: &str) -> eyre::Result<()> {
|
pub(super) fn validate_package_version(package_root: &Path, expected: &str) -> eyre::Result<()> {
|
||||||
let path = package_root.join("version.ini");
|
let path = package_root.join("version.ini");
|
||||||
let bytes = read_bounded_regular_file(&path, MAX_VERSION_INI_BYTES)?;
|
let bytes = read_bounded_regular_file(&path, MAX_VERSION_INI_BYTES)?;
|
||||||
@@ -129,9 +137,7 @@ pub(super) fn validate_package_version(package_root: &Path, expected: &str) -> e
|
|||||||
.wrap_err("version.ini is not valid UTF-8")?
|
.wrap_err("version.ini is not valid UTF-8")?
|
||||||
.trim();
|
.trim();
|
||||||
if version != expected {
|
if version != expected {
|
||||||
eyre::bail!(
|
eyre::bail!("package version mismatch: game.db expects {expected}");
|
||||||
"package version mismatch: game.db expects {expected}, version.ini contains {version:?}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user