use super::super::*;
use crate::tests::support::create_json_durable;
use crate::tests::support::temp_dir;
use std::fs;
#[test]
fn create_only_publication_preserves_an_existing_record() {
let root = temp_dir("ic-backup-create-json");
let path = root.join("journal.json");
create_json_durable(&path, &serde_json::json!({"intent": "original"})).expect("create record");
let bytes = fs::read(&path).expect("read exact bytes");
let error = create_json_durable(&path, &serde_json::json!({"intent": "different"}))
.expect_err("existing record must not be replaced");
assert!(
matches!(error, PersistenceError::Publication(ic_host_fs::durable::NamedWriteError::BeforePublication { source: ref error, .. }) if error.kind() == io::ErrorKind::AlreadyExists)
);
assert_eq!(fs::read(&path).expect("read retained bytes"), bytes);
fs::remove_dir_all(root).expect("remove successful fixture");
}
#[cfg(unix)]
#[test]
fn record_reads_enforce_exact_byte_limits_and_reject_symlinks() {
let root = temp_dir("ic-backup-record-read");
let path = root.join("journal.json");
let value = serde_json::json!({"intent": "snapshot"});
create_json_durable(&path, &value).expect("create record");
let length = fs::metadata(&path).expect("record metadata").len();
let decoded: serde_json::Value = read_json(&path, length).expect("exact bound accepts");
assert_eq!(decoded, value);
for bound in [0, length - 1] {
assert!(
matches!(read_json::<serde_json::Value>(&path, bound), Err(PersistenceError::RecordTooLarge { limit }) if limit == bound)
);
}
let linked = root.join("linked.json");
std::os::unix::fs::symlink(&path, &linked).expect("create record link");
assert!(matches!(
read_json::<serde_json::Value>(&linked, length),
Err(PersistenceError::Io(_))
));
assert!(
matches!(read_json::<serde_json::Value>(&root, length), Err(PersistenceError::Io(ref error)) if error.kind() == io::ErrorKind::InvalidInput)
);
fs::write(&path, b"malformed").expect("write malformed record");
assert!(matches!(
read_json::<serde_json::Value>(&path, length),
Err(PersistenceError::Json(_))
));
fs::remove_dir_all(root).expect("remove successful fixture");
}
#[cfg(unix)]
#[test]
fn shared_record_reads_preserve_extreme_limits_missing_files_and_parent_aliases() {
let root = temp_dir("ic-backup-shared-record-read");
let path = root.join("record.json");
fs::write(&path, b"null").expect("write exact JSON");
assert_eq!(
read_json::<serde_json::Value>(&path, u64::MAX).expect("large declared limit"),
serde_json::Value::Null
);
assert!(matches!(
read_json::<serde_json::Value>(&path, 0),
Err(PersistenceError::RecordTooLarge { limit: 0 })
));
assert_eq!(fs::read(&path).expect("original bytes"), b"null");
let alias = root.join("parent-alias");
std::os::unix::fs::symlink(&root, &alias).expect("selected parent alias");
assert_eq!(
read_json::<serde_json::Value>(&alias.join("record.json"), 4)
.expect("parent selection remains caller owned"),
serde_json::Value::Null
);
assert!(matches!(
read_json::<serde_json::Value>(&root.join("missing"), 4),
Err(PersistenceError::Io(error)) if error.kind() == io::ErrorKind::NotFound
));
let dangling = root.join("dangling");
std::os::unix::fs::symlink("missing", &dangling).expect("dangling final link");
assert!(matches!(
read_json::<serde_json::Value>(&dangling, 4),
Err(PersistenceError::Io(error))
if error.raw_os_error() == Some(rustix::io::Errno::LOOP.raw_os_error())
));
fs::write(&path, []).expect("empty file");
assert!(matches!(
read_json::<serde_json::Value>(&path, 0),
Err(PersistenceError::Json(error)) if error.is_eof()
));
fs::remove_dir_all(root).expect("remove successful fixture");
}
#[cfg(unix)]
#[test]
fn new_machine_records_and_directories_have_private_permissions() {
use std::os::unix::fs::PermissionsExt;
let root = temp_dir("ic-backup-private-json");
let parent = root.join("private");
let path = parent.join("journal.json");
create_json_durable(&path, &serde_json::json!({"intent": "snapshot"}))
.expect("create private record");
assert_eq!(
fs::metadata(&path)
.expect("file metadata")
.permissions()
.mode()
& 0o777,
0o600
);
assert_eq!(
fs::metadata(&parent)
.expect("parent metadata")
.permissions()
.mode()
& 0o777,
0o700
);
fs::remove_dir_all(root).expect("remove successful fixture");
}