ipc: support pakker.json; fall back to dir-path hash when parentLockHash absent
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I48d58165d306901dfaeb233310ae93846a6a6964
This commit is contained in:
parent
45d5f7e99b
commit
86b598b431
1 changed files with 96 additions and 113 deletions
207
src/ipc.rs
207
src/ipc.rs
|
|
@ -106,42 +106,51 @@ impl IpcCoordinator {
|
|||
}
|
||||
|
||||
/// Extract modpack hash from pakku.json's parentLockHash field.
|
||||
/// This is the authoritative content hash for the modpack (Nix-style).
|
||||
/// This is the authoritative content hash for the modpack. If you've used Nix
|
||||
/// the model might seem familiar.
|
||||
fn get_modpack_hash(working_dir: &Path) -> Result<String, IpcError> {
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
let pakker_path = working_dir.join("pakker.json");
|
||||
let pakku_path = working_dir.join("pakku.json");
|
||||
|
||||
if !pakku_path.exists() {
|
||||
let config_path = if pakker_path.exists() {
|
||||
pakker_path
|
||||
} else if pakku_path.exists() {
|
||||
pakku_path
|
||||
} else {
|
||||
return Err(IpcError::PakkuJsonReadFailed(
|
||||
"pakku.json not found in working directory".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let content = fs::read_to_string(&pakku_path)
|
||||
let content = fs::read_to_string(&config_path)
|
||||
.map_err(|e| IpcError::PakkuJsonReadFailed(e.to_string()))?;
|
||||
|
||||
// Parse pakku.json and extract parentLockHash
|
||||
// Parse config and try to extract parentLockHash (fork modpacks have this)
|
||||
let pakku: serde_json::Value = serde_json::from_str(&content)
|
||||
.map_err(|e| IpcError::PakkuJsonReadFailed(e.to_string()))?;
|
||||
|
||||
let hash = pakku
|
||||
let candidate = pakku
|
||||
.get("pakku")
|
||||
.and_then(|p| p.get("parentLockHash"))
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| {
|
||||
IpcError::PakkuJsonReadFailed(
|
||||
"parentLockHash not found in pakku.json".to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
.map(std::string::ToString::to_string);
|
||||
|
||||
// Validate it's a valid hex string (SHA256 = 64 chars)
|
||||
if hash.len() != 64 || !hash.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(IpcError::PakkuJsonReadFailed(
|
||||
"parentLockHash is not a valid SHA256 hash".to_string(),
|
||||
));
|
||||
if let Some(hash) = candidate {
|
||||
// Validate it's a valid hex string (SHA256 = 64 chars)
|
||||
if hash.len() == 64 && hash.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Ok(hash);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash)
|
||||
// Hash the working directory path for non-fork modpacks as a fallback.
|
||||
let dir_str = working_dir.to_string_lossy();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(dir_str.as_bytes());
|
||||
Ok(crate::utils::hash::hash_to_hex(
|
||||
hasher.finalize().as_slice(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new IPC coordinator for the given modpack directory.
|
||||
|
|
@ -470,6 +479,7 @@ impl Drop for OperationGuard {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![expect(clippy::expect_used, reason = "Fine in tests")]
|
||||
use std::fs;
|
||||
|
||||
use tempfile::TempDir;
|
||||
|
|
@ -495,7 +505,7 @@ mod tests {
|
|||
|
||||
// Write pakku.json with unique parentLockHash (nested under "pakku" key)
|
||||
let pakku_content =
|
||||
format!(r#"{{"pakku": {{"parentLockHash": "{}"}}}}"#, unique_hash);
|
||||
format!(r#"{{"pakku": {{"parentLockHash": "{unique_hash}"}}}}"#);
|
||||
fs::write(temp_dir.path().join("pakku.json"), pakku_content).unwrap();
|
||||
|
||||
temp_dir
|
||||
|
|
@ -520,7 +530,7 @@ mod tests {
|
|||
|
||||
// Write pakku.json with specified parentLockHash (nested under "pakku" key)
|
||||
let pakku_content =
|
||||
format!(r#"{{"pakku": {{"parentLockHash": "{}"}}}}"#, hash);
|
||||
format!(r#"{{"pakku": {{"parentLockHash": "{hash}"}}}}"#);
|
||||
fs::write(temp_dir.path().join("pakku.json"), pakku_content).unwrap();
|
||||
|
||||
temp_dir
|
||||
|
|
@ -539,8 +549,7 @@ mod tests {
|
|||
"cfe85e0e7e7aa0922d30d8faad071e3a4126cb78b5f0f792f191e90a295aa2c7",
|
||||
);
|
||||
|
||||
let hash =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let hash = IpcCoordinator::get_modpack_hash(temp_dir.path()).unwrap();
|
||||
assert_eq!(
|
||||
hash,
|
||||
"cfe85e0e7e7aa0922d30d8faad071e3a4126cb78b5f0f792f191e90a295aa2c7"
|
||||
|
|
@ -554,13 +563,13 @@ mod tests {
|
|||
.tempdir()
|
||||
.unwrap();
|
||||
|
||||
let result =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf());
|
||||
let result = IpcCoordinator::get_modpack_hash(temp_dir.path());
|
||||
assert!(matches!(result, Err(IpcError::PakkuJsonReadFailed(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_modpack_hash_missing_parent_lock_hash() {
|
||||
// Non-fork modpacks have no parentLockHash; should fall back to dir hash
|
||||
let temp_dir = tempfile::Builder::new()
|
||||
.prefix("pakker-ipc-test-")
|
||||
.tempdir()
|
||||
|
|
@ -569,13 +578,15 @@ mod tests {
|
|||
fs::write(temp_dir.path().join("pakku.json"), r#"{"other": "field"}"#)
|
||||
.unwrap();
|
||||
|
||||
let result =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf());
|
||||
assert!(matches!(result, Err(IpcError::PakkuJsonReadFailed(_))));
|
||||
let result = IpcCoordinator::get_modpack_hash(temp_dir.path());
|
||||
let hash = result.unwrap();
|
||||
assert_eq!(hash.len(), 64);
|
||||
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_modpack_hash_invalid_hash() {
|
||||
// Root-level parentLockHash (wrong location) falls back to dir hash
|
||||
let temp_dir = tempfile::Builder::new()
|
||||
.prefix("pakker-ipc-test-")
|
||||
.tempdir()
|
||||
|
|
@ -587,9 +598,10 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let result =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf());
|
||||
assert!(matches!(result, Err(IpcError::PakkuJsonReadFailed(_))));
|
||||
let result = IpcCoordinator::get_modpack_hash(temp_dir.path());
|
||||
let hash = result.unwrap();
|
||||
assert_eq!(hash.len(), 64);
|
||||
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -607,8 +619,8 @@ mod tests {
|
|||
shared_hash,
|
||||
);
|
||||
|
||||
let coord1 = IpcCoordinator::new(&temp_dir1.path().to_path_buf()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(&temp_dir2.path().to_path_buf()).unwrap();
|
||||
let coord1 = IpcCoordinator::new(temp_dir1.path()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(temp_dir2.path()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
coord1.ops_file, coord2.ops_file,
|
||||
|
|
@ -624,8 +636,8 @@ mod tests {
|
|||
let temp_dir1 = create_test_modpack(&[("mod1.jar", "content")]);
|
||||
let temp_dir2 = create_test_modpack(&[("mod1.jar", "content")]);
|
||||
|
||||
let coord1 = IpcCoordinator::new(&temp_dir1.path().to_path_buf()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(&temp_dir2.path().to_path_buf()).unwrap();
|
||||
let coord1 = IpcCoordinator::new(temp_dir1.path()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(temp_dir2.path()).unwrap();
|
||||
|
||||
assert_ne!(
|
||||
coord1.ops_file, coord2.ops_file,
|
||||
|
|
@ -636,8 +648,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_ipc_coordinator_new_creates_dir() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Check that the parent directory (ipc_dir) exists
|
||||
assert!(coordinator.ops_file.parent().unwrap().exists());
|
||||
|
|
@ -646,8 +657,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_load_operations_empty() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let operations = coordinator.load_operations().unwrap();
|
||||
assert!(operations.is_empty());
|
||||
|
|
@ -656,8 +666,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_register_operation() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let id = coordinator
|
||||
.register_operation(OperationType::Fetch)
|
||||
|
|
@ -670,8 +679,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_register_multiple_operations_different_types() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let fetch_id = coordinator
|
||||
.register_operation(OperationType::Fetch)
|
||||
|
|
@ -689,8 +697,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_register_conflicting_operation_same_type() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let _id1 = coordinator
|
||||
.register_operation(OperationType::Fetch)
|
||||
|
|
@ -703,8 +710,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_complete_operation() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let id = coordinator
|
||||
.register_operation(OperationType::Fetch)
|
||||
|
|
@ -719,8 +725,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_complete_nonexistent_operation() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let result = coordinator.complete_operation("nonexistent-id");
|
||||
assert!(matches!(result, Err(IpcError::OperationNotFound(_))));
|
||||
|
|
@ -729,8 +734,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_has_running_operation() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
assert!(!coordinator.has_running_operation(OperationType::Fetch));
|
||||
|
||||
|
|
@ -747,8 +751,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_get_running_operations() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let operations = coordinator.get_running_operations(OperationType::Fetch);
|
||||
assert!(operations.is_empty());
|
||||
|
|
@ -764,8 +767,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn test_wait_for_conflicts_no_conflicts() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let result = coordinator
|
||||
.wait_for_conflicts(OperationType::Fetch, Duration::from_secs(1))
|
||||
|
|
@ -776,8 +778,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn test_wait_for_conflicts_with_completion() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Register an operation
|
||||
let id = coordinator
|
||||
|
|
@ -801,8 +802,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn test_wait_for_conflicts_timeout() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Register a long-running operation (we won't complete it)
|
||||
let _id = coordinator
|
||||
|
|
@ -819,8 +819,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_operation_guard_completes_on_drop() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let id = coordinator
|
||||
.register_operation(OperationType::Fetch)
|
||||
|
|
@ -842,8 +841,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_operation_guard_manual_complete() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let id = coordinator
|
||||
.register_operation(OperationType::Export)
|
||||
|
|
@ -862,8 +860,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_stale_operation_cleanup() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Manually add a stale operation (started 15 minutes ago)
|
||||
let now = SystemTime::now()
|
||||
|
|
@ -896,8 +893,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_multiple_operations_same_process() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let id1 = coordinator
|
||||
.register_operation(OperationType::Fetch)
|
||||
|
|
@ -929,8 +925,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_operation_id_format() {
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let id = coordinator
|
||||
.register_operation(OperationType::Export)
|
||||
|
|
@ -971,8 +966,8 @@ mod tests {
|
|||
fs::write(temp_dir1.path().join("file1.txt"), "content1").unwrap();
|
||||
fs::write(temp_dir2.path().join("file2.txt"), "content2").unwrap();
|
||||
|
||||
let coord1 = IpcCoordinator::new(&temp_dir1.path().to_path_buf()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(&temp_dir2.path().to_path_buf()).unwrap();
|
||||
let coord1 = IpcCoordinator::new(temp_dir1.path()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(temp_dir2.path()).unwrap();
|
||||
|
||||
// Both should point to SAME ops file despite different paths
|
||||
assert_eq!(coord1.ops_file, coord2.ops_file);
|
||||
|
|
@ -982,8 +977,7 @@ mod tests {
|
|||
fn test_corrupted_ops_json_trailing_bracket() {
|
||||
// Test handling of corrupted ops.json with trailing characters
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Register an operation to create ops.json
|
||||
let _id = coordinator
|
||||
|
|
@ -992,7 +986,7 @@ mod tests {
|
|||
|
||||
// Manually corrupt the ops.json by appending extra bracket
|
||||
let ops_content = fs::read_to_string(&coordinator.ops_file).unwrap();
|
||||
fs::write(&coordinator.ops_file, format!("{}]", ops_content)).unwrap();
|
||||
fs::write(&coordinator.ops_file, format!("{ops_content}]")).unwrap();
|
||||
|
||||
// Loading should fail with InvalidFormat error
|
||||
let result = coordinator.load_operations();
|
||||
|
|
@ -1003,8 +997,7 @@ mod tests {
|
|||
fn test_corrupted_ops_json_invalid_json() {
|
||||
// Test handling of completely invalid JSON
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Write invalid JSON to ops.json
|
||||
fs::write(&coordinator.ops_file, "not valid json {[}").unwrap();
|
||||
|
|
@ -1017,8 +1010,7 @@ mod tests {
|
|||
fn test_corrupted_ops_json_missing_fields() {
|
||||
// Test handling of JSON with missing required fields
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Write JSON with missing fields
|
||||
fs::write(&coordinator.ops_file, r#"[{"id": "test"}]"#).unwrap();
|
||||
|
|
@ -1031,8 +1023,7 @@ mod tests {
|
|||
fn test_empty_ops_file() {
|
||||
// Test handling of empty ops.json file
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Create empty ops.json
|
||||
fs::write(&coordinator.ops_file, "").unwrap();
|
||||
|
|
@ -1045,8 +1036,7 @@ mod tests {
|
|||
fn test_whitespace_only_ops_file() {
|
||||
// Test handling of whitespace-only ops.json
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
fs::write(&coordinator.ops_file, " \n\t \n ").unwrap();
|
||||
|
||||
|
|
@ -1074,8 +1064,7 @@ mod tests {
|
|||
}"#;
|
||||
fs::write(temp_dir.path().join("pakku.json"), pakku_content).unwrap();
|
||||
|
||||
let hash =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let hash = IpcCoordinator::get_modpack_hash(temp_dir.path()).unwrap();
|
||||
assert_eq!(
|
||||
hash,
|
||||
"abc123def456789012345678901234567890abcd123456789012345678901234"
|
||||
|
|
@ -1096,12 +1085,11 @@ mod tests {
|
|||
}"#;
|
||||
fs::write(temp_dir.path().join("pakku.json"), old_format).unwrap();
|
||||
|
||||
let result =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf());
|
||||
assert!(
|
||||
matches!(result, Err(IpcError::PakkuJsonReadFailed(_))),
|
||||
"Old format should be rejected"
|
||||
);
|
||||
// Old format (root-level parentLockHash, not in pakku section) falls back
|
||||
// to dir-path hash
|
||||
let hash = IpcCoordinator::get_modpack_hash(temp_dir.path()).unwrap();
|
||||
assert_eq!(hash.len(), 64);
|
||||
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1111,12 +1099,13 @@ mod tests {
|
|||
.tempdir()
|
||||
.unwrap();
|
||||
|
||||
// Too-short hash falls back to dir-path hash
|
||||
let pakku_content = r#"{"pakku": {"parentLockHash": "tooshort"}}"#;
|
||||
fs::write(temp_dir.path().join("pakku.json"), pakku_content).unwrap();
|
||||
|
||||
let result =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf());
|
||||
assert!(matches!(result, Err(IpcError::PakkuJsonReadFailed(_))));
|
||||
let hash = IpcCoordinator::get_modpack_hash(temp_dir.path()).unwrap();
|
||||
assert_eq!(hash.len(), 64);
|
||||
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1126,13 +1115,13 @@ mod tests {
|
|||
.tempdir()
|
||||
.unwrap();
|
||||
|
||||
// 64 chars but not all hex
|
||||
// 64 chars but not all hex falls back to dir-path hash
|
||||
let pakku_content = r#"{"pakku": {"parentLockHash": "gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"}}"#;
|
||||
fs::write(temp_dir.path().join("pakku.json"), pakku_content).unwrap();
|
||||
|
||||
let result =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf());
|
||||
assert!(matches!(result, Err(IpcError::PakkuJsonReadFailed(_))));
|
||||
let hash = IpcCoordinator::get_modpack_hash(temp_dir.path()).unwrap();
|
||||
assert_eq!(hash.len(), 64);
|
||||
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1146,8 +1135,7 @@ mod tests {
|
|||
let pakku_content = r#"{"pakku": {"parentLockHash": "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789"}}"#;
|
||||
fs::write(temp_dir.path().join("pakku.json"), pakku_content).unwrap();
|
||||
|
||||
let hash =
|
||||
IpcCoordinator::get_modpack_hash(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let hash = IpcCoordinator::get_modpack_hash(temp_dir.path()).unwrap();
|
||||
assert_eq!(
|
||||
hash,
|
||||
"ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789"
|
||||
|
|
@ -1158,8 +1146,7 @@ mod tests {
|
|||
fn test_concurrent_registration_race_condition() {
|
||||
// Test that file locking prevents race conditions
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// First registration should succeed
|
||||
let id1 = coordinator
|
||||
|
|
@ -1182,8 +1169,7 @@ mod tests {
|
|||
fn test_file_permissions_ipc_dir() {
|
||||
// Test that IPC directory has correct permissions (700 - owner only)
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let ipc_dir = coordinator.ops_file.parent().unwrap();
|
||||
let metadata = fs::metadata(ipc_dir).unwrap();
|
||||
|
|
@ -1195,8 +1181,7 @@ mod tests {
|
|||
fn test_file_permissions_ops_file() {
|
||||
// Test that ops.json has correct permissions (600 - owner read/write only)
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Register operation to create ops.json
|
||||
let _id = coordinator
|
||||
|
|
@ -1215,21 +1200,21 @@ mod tests {
|
|||
let unique_hash = format!(
|
||||
"{:064x}",
|
||||
rand::random::<u128>()
|
||||
^ (std::time::SystemTime::now()
|
||||
^ std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u128)
|
||||
.as_nanos()
|
||||
);
|
||||
let temp_dir = create_test_modpack_with_hash(
|
||||
&[("test.txt", "test")],
|
||||
&unique_hash[..64],
|
||||
);
|
||||
|
||||
let coord1 = IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coord1 = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
let id = coord1.register_operation(OperationType::Fetch).unwrap();
|
||||
|
||||
// Create new coordinator instance
|
||||
let coord2 = IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coord2 = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
let operations = coord2.load_operations().unwrap();
|
||||
|
||||
assert_eq!(operations.len(), 1);
|
||||
|
|
@ -1245,8 +1230,7 @@ mod tests {
|
|||
// Test that stale cleanup only removes running operations, not completed
|
||||
// ones
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
|
|
@ -1292,8 +1276,7 @@ mod tests {
|
|||
async fn test_wait_for_conflicts_multiple_types() {
|
||||
// Test that wait_for_conflicts only waits for matching operation types
|
||||
let temp_dir = create_test_modpack(&[("test.txt", "test")]);
|
||||
let coordinator =
|
||||
IpcCoordinator::new(&temp_dir.path().to_path_buf()).unwrap();
|
||||
let coordinator = IpcCoordinator::new(temp_dir.path()).unwrap();
|
||||
|
||||
// Register Export operation (different type)
|
||||
let _export_id = coordinator
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue