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)?;
|
||||
let selected = options.selection.select(&catalog)?;
|
||||
for game in &selected {
|
||||
package::validate_package_version(
|
||||
package::preflight_package_version(
|
||||
&options.packages_dir.join(&game.game_id),
|
||||
&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]
|
||||
async fn incremental_generation_requires_an_existing_complete_indexed_corpus() {
|
||||
let temp = TempDir::new();
|
||||
@@ -894,15 +939,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn package_version_mismatch_is_rejected() {
|
||||
const OBSERVED: &str = "package-controlled-secret-20240101";
|
||||
|
||||
let temp = TempDir::new();
|
||||
let root = simple_package(&temp, "g", "20240101");
|
||||
let root = simple_package(&temp, "g", OBSERVED);
|
||||
let game = CatalogGame {
|
||||
game_id: "g".to_owned(),
|
||||
game_version: "20240102".to_owned(),
|
||||
};
|
||||
let error = build_manifest_from_package(&game, &root, Path::new("missing-unrar"))
|
||||
.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]
|
||||
|
||||
@@ -122,6 +122,14 @@ pub(super) fn validate_manifest_destination(path: &Path) -> eyre::Result<()> {
|
||||
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<()> {
|
||||
let path = package_root.join("version.ini");
|
||||
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")?
|
||||
.trim();
|
||||
if version != expected {
|
||||
eyre::bail!(
|
||||
"package version mismatch: game.db expects {expected}, version.ini contains {version:?}"
|
||||
);
|
||||
eyre::bail!("package version mismatch: game.db expects {expected}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user