fix(openapi): harden story 2.1 production lifecycle
This commit is contained in:
@@ -360,6 +360,119 @@ fn housekeeping_enforces_result_limit_after_stale_validation() {
|
||||
assert!(temp_paths.iter().all(|path| path.exists()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_temp_cleanup_round_robins_past_a_busy_early_shard() {
|
||||
let root = TestRoot::new("cleanup-round-robin");
|
||||
let store = ArtifactStore::open(&root.0).unwrap();
|
||||
let sha = root.0.join("sha256");
|
||||
let early = sha.join("00");
|
||||
let late = sha.join("ff");
|
||||
fs::create_dir(&sha).unwrap();
|
||||
fs::create_dir(&early).unwrap();
|
||||
fs::create_dir(&late).unwrap();
|
||||
for directory in [&sha, &early, &late] {
|
||||
fs::set_permissions(directory, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
}
|
||||
for index in 0..5 {
|
||||
let entry = early.join(format!("unrelated-{index}"));
|
||||
fs::write(&entry, b"not a temp").unwrap();
|
||||
fs::set_permissions(entry, fs::Permissions::from_mode(0o400)).unwrap();
|
||||
}
|
||||
let stale = late.join(".crank-artifact-tmp-v1-00000000000000000000000000000000-1-1");
|
||||
fs::write(&stale, b"partial").unwrap();
|
||||
fs::set_permissions(&stale, fs::Permissions::from_mode(0o400)).unwrap();
|
||||
|
||||
let (_, first, cursor) = store
|
||||
.scan_stale_temps_after(Duration::ZERO, None, 5, 1)
|
||||
.unwrap();
|
||||
assert!(first.is_empty());
|
||||
|
||||
let (_, second, _) = store
|
||||
.scan_stale_temps_after(Duration::ZERO, Some(cursor), 5, 1)
|
||||
.unwrap();
|
||||
assert_eq!(second.len(), 1);
|
||||
store
|
||||
.delete_stale_temp(second.into_iter().next().unwrap())
|
||||
.unwrap();
|
||||
assert!(!stale.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_temp_cleanup_resumes_inside_a_busy_shard() {
|
||||
let root = TestRoot::new("cleanup-intra-shard");
|
||||
let store = ArtifactStore::open(&root.0).unwrap();
|
||||
let sha = root.0.join("sha256");
|
||||
let shard = sha.join("00");
|
||||
fs::create_dir(&sha).unwrap();
|
||||
fs::create_dir(&shard).unwrap();
|
||||
for directory in [&sha, &shard] {
|
||||
fs::set_permissions(directory, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
}
|
||||
for index in 0..12 {
|
||||
let entry = shard.join(format!("unrelated-{index}"));
|
||||
fs::write(&entry, b"not a temp").unwrap();
|
||||
fs::set_permissions(entry, fs::Permissions::from_mode(0o400)).unwrap();
|
||||
}
|
||||
let stale = shard.join(".crank-artifact-tmp-v1-00000000000000000000000000000000-1-2");
|
||||
fs::write(&stale, b"partial").unwrap();
|
||||
fs::set_permissions(&stale, fs::Permissions::from_mode(0o400)).unwrap();
|
||||
|
||||
let mut cursor = None;
|
||||
let mut discovered = None;
|
||||
for _ in 0..4 {
|
||||
let (_, candidates, next_cursor) = store
|
||||
.scan_stale_temps_after(Duration::ZERO, cursor, 5, 1)
|
||||
.unwrap();
|
||||
if let Some(candidate) = candidates.into_iter().next() {
|
||||
discovered = Some(candidate);
|
||||
break;
|
||||
}
|
||||
cursor = Some(next_cursor);
|
||||
}
|
||||
let candidate = discovered.expect("the temp behind one scan budget is eventually discovered");
|
||||
store.delete_stale_temp(candidate).unwrap();
|
||||
assert!(!stale.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_temp_cleanup_reconsiders_candidates_beyond_each_result_page() {
|
||||
let root = TestRoot::new("cleanup-result-continuation");
|
||||
let store = ArtifactStore::open(&root.0).unwrap();
|
||||
let sha = root.0.join("sha256");
|
||||
let shard = sha.join("00");
|
||||
fs::create_dir(&sha).unwrap();
|
||||
fs::create_dir(&shard).unwrap();
|
||||
for directory in [&sha, &shard] {
|
||||
fs::set_permissions(directory, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
}
|
||||
for index in 0..20 {
|
||||
let temp = shard.join(format!(
|
||||
".crank-artifact-tmp-v1-00000000000000000000000000000000-1-{index}"
|
||||
));
|
||||
fs::write(&temp, b"partial").unwrap();
|
||||
fs::set_permissions(temp, fs::Permissions::from_mode(0o400)).unwrap();
|
||||
}
|
||||
|
||||
let mut cursor = None;
|
||||
let mut deleted = 0;
|
||||
for _ in 0..16 {
|
||||
let (scan, candidates, next_cursor) = store
|
||||
.scan_stale_temps_after(Duration::ZERO, cursor, 512, 3)
|
||||
.unwrap();
|
||||
for candidate in candidates {
|
||||
store.delete_stale_temp(candidate).unwrap();
|
||||
deleted += 1;
|
||||
}
|
||||
cursor = Some(next_cursor);
|
||||
if scan.complete {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(deleted, 20, "every stale candidate must remain reachable");
|
||||
assert!(fs::read_dir(shard).unwrap().next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn housekeeping_revalidates_subsecond_mtime() {
|
||||
let root = TestRoot::new("cleanup-subsecond");
|
||||
@@ -479,6 +592,16 @@ fn open_rejects_non_private_root_and_existing_finals_must_be_immutable() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn health_check_revalidates_the_pinned_root_permissions() {
|
||||
let root = TestRoot::new("health-check");
|
||||
let store = ArtifactStore::open(&root.0).unwrap();
|
||||
assert_eq!(store.check_health(), Ok(()));
|
||||
|
||||
fs::set_permissions(&root.0, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
assert_eq!(store.check_health(), Err(ArtifactError::UnsafeRoot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_rejects_symlinked_root() {
|
||||
let root = TestRoot::new("root-symlink-target");
|
||||
|
||||
Reference in New Issue
Block a user