273 lines
10 KiB
Rust
273 lines
10 KiB
Rust
// file: crates/knowledge-git-lib/unit_tests/backend.rs
|
|
// version: 2
|
|
|
|
#[derive(Clone)]
|
|
struct RecordingRunner {
|
|
calls: std::rc::Rc<std::cell::RefCell<Vec<super::GitCommandSpec>>>,
|
|
failure: Option<crate::GitBackendError>,
|
|
inspection_stdout: Vec<u8>,
|
|
}
|
|
|
|
impl RecordingRunner {
|
|
fn successful() -> RecordingRunner {
|
|
return RecordingRunner {
|
|
calls: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
|
|
failure: None,
|
|
inspection_stdout: b"true\n".to_vec(),
|
|
};
|
|
}
|
|
|
|
fn failing(error: crate::GitBackendError) -> RecordingRunner {
|
|
return RecordingRunner {
|
|
calls: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
|
|
failure: Some(error),
|
|
inspection_stdout: b"true\n".to_vec(),
|
|
};
|
|
}
|
|
|
|
fn with_inspection_stdout(value: &[u8]) -> RecordingRunner {
|
|
return RecordingRunner {
|
|
calls: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
|
|
failure: None,
|
|
inspection_stdout: value.to_vec(),
|
|
};
|
|
}
|
|
}
|
|
|
|
impl super::GitCommandRunner for RecordingRunner {
|
|
fn run(&self, specification: &super::GitCommandSpec) -> Result<super::GitCommandOutput, crate::GitBackendError> {
|
|
self.calls.borrow_mut().push(specification.clone());
|
|
if let Some(error) = self.failure {
|
|
return Err(error);
|
|
}
|
|
if specification.operation == crate::GitOperation::InspectMirror {
|
|
return Ok(super::GitCommandOutput { stdout: self.inspection_stdout.clone() });
|
|
}
|
|
return Ok(super::GitCommandOutput { stdout: Vec::new() });
|
|
}
|
|
}
|
|
|
|
fn source_id(value: &str) -> Option<knowledge_domain_lib::SourceId> {
|
|
let result = knowledge_domain_lib::SourceId::new(value);
|
|
match result {
|
|
Ok(source_id) => return Some(source_id),
|
|
Err(_) => return None,
|
|
}
|
|
}
|
|
|
|
fn test_root(label: &str) -> std::path::PathBuf {
|
|
let mut path = std::env::temp_dir();
|
|
path.push(format!("sasedev-knowledge-git-lib-{label}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&path);
|
|
return path;
|
|
}
|
|
|
|
#[test]
|
|
fn backend_configuration_requires_non_empty_paths() {
|
|
assert_eq!(crate::GitBackendConfig::new(std::path::PathBuf::new()), Err(crate::GitBackendError::EmptyStateRoot));
|
|
assert_eq!(
|
|
crate::GitBackendConfig::with_git_executable(test_root("empty-executable"), std::path::PathBuf::new()),
|
|
Err(crate::GitBackendError::EmptyGitExecutable)
|
|
);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn source_key_never_interpolates_raw_source_identifier_as_a_path() {
|
|
let source_result = source_id("../pgr/credential:value");
|
|
assert!(source_result.is_some());
|
|
let source_id = match source_result {
|
|
Some(value) => value,
|
|
None => return,
|
|
};
|
|
let key = crate::GitSourceKey::from_source_id(&source_id);
|
|
assert!(key.as_str().starts_with("source-"));
|
|
assert!(key.as_str().chars().all(|character| character.is_ascii_alphanumeric() || character == '-'));
|
|
assert!(!key.as_str().contains("pgr"));
|
|
assert!(!key.as_str().contains(".."));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn local_source_clone_uses_mirror_no_local_and_argument_separator() {
|
|
let root = test_root("local-arguments");
|
|
let config_result = crate::GitBackendConfig::new(root.clone());
|
|
assert!(config_result.is_ok());
|
|
let config = match config_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let runner = RecordingRunner::successful();
|
|
let backend = crate::GitBackend::with_runner(config, Box::new(runner.clone()));
|
|
let source_id_result = source_id("pgr");
|
|
assert!(source_id_result.is_some());
|
|
let source_id = match source_id_result {
|
|
Some(value) => value,
|
|
None => return,
|
|
};
|
|
let source_result = crate::GitSourceConfig::local(source_id, std::path::PathBuf::from("/controlled/source"));
|
|
assert!(source_result.is_ok());
|
|
let source = match source_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let mirror_result = backend.ensure_mirror(&source);
|
|
assert!(mirror_result.is_ok());
|
|
let calls = runner.calls.borrow();
|
|
assert_eq!(calls.len(), 2);
|
|
assert_eq!(calls[0].operation, crate::GitOperation::CloneMirror);
|
|
assert_eq!(
|
|
calls[0].arguments,
|
|
vec![
|
|
std::ffi::OsString::from("clone"),
|
|
std::ffi::OsString::from("--mirror"),
|
|
std::ffi::OsString::from("--no-local"),
|
|
std::ffi::OsString::from("--"),
|
|
std::ffi::OsString::from("/controlled/source"),
|
|
backend.repository_path(source.source_id()).into_os_string(),
|
|
]
|
|
);
|
|
assert_eq!(calls[1].operation, crate::GitOperation::InspectMirror);
|
|
assert!(backend.config().hooks_path().is_dir());
|
|
assert!(backend.config().repositories_path().is_dir());
|
|
assert!(backend.config().snapshots_path().is_dir());
|
|
let _ = std::fs::remove_dir_all(root);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn remote_source_clone_does_not_add_no_local() {
|
|
let root = test_root("remote-arguments");
|
|
let config_result = crate::GitBackendConfig::new(root.clone());
|
|
assert!(config_result.is_ok());
|
|
let config = match config_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let runner = RecordingRunner::successful();
|
|
let backend = crate::GitBackend::with_runner(config, Box::new(runner.clone()));
|
|
let source_id_result = source_id("howtos");
|
|
assert!(source_id_result.is_some());
|
|
let source_id = match source_id_result {
|
|
Some(value) => value,
|
|
None => return,
|
|
};
|
|
let source_result = crate::GitSourceConfig::remote(source_id, "ssh://git@example.invalid/howtos.git");
|
|
assert!(source_result.is_ok());
|
|
let source = match source_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
assert!(backend.ensure_mirror(&source).is_ok());
|
|
let calls = runner.calls.borrow();
|
|
assert_eq!(calls[0].operation, crate::GitOperation::CloneMirror);
|
|
assert_eq!(calls[0].arguments[0], std::ffi::OsString::from("clone"));
|
|
assert_eq!(calls[0].arguments[1], std::ffi::OsString::from("--mirror"));
|
|
assert_eq!(calls[0].arguments[2], std::ffi::OsString::from("--"));
|
|
assert!(!calls[0].arguments.contains(&std::ffi::OsString::from("--no-local")));
|
|
let _ = std::fs::remove_dir_all(root);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn process_errors_do_not_expose_remote_location_or_credentials() {
|
|
let root = test_root("redaction");
|
|
let config_result = crate::GitBackendConfig::new(root.clone());
|
|
assert!(config_result.is_ok());
|
|
let config = match config_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let runner = RecordingRunner::failing(crate::GitBackendError::GitCommandFailed { operation: crate::GitOperation::CloneMirror, exit_code: Some(128) });
|
|
let backend = crate::GitBackend::with_runner(config, Box::new(runner));
|
|
let source_id_result = source_id("snippets");
|
|
assert!(source_id_result.is_some());
|
|
let source_id = match source_id_result {
|
|
Some(value) => value,
|
|
None => return,
|
|
};
|
|
let source_result = crate::GitSourceConfig::remote(source_id, "https://user:secret@example.invalid/snippets.git");
|
|
assert!(source_result.is_ok());
|
|
let source = match source_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let result = backend.ensure_mirror(&source);
|
|
assert!(result.is_err());
|
|
let error = match result {
|
|
Ok(_) => return,
|
|
Err(value) => value,
|
|
};
|
|
let display = error.to_string();
|
|
assert!(!display.contains("secret"));
|
|
assert!(!display.contains("example.invalid"));
|
|
assert_eq!(error, crate::GitBackendError::GitCommandFailed { operation: crate::GitOperation::CloneMirror, exit_code: Some(128) });
|
|
let _ = std::fs::remove_dir_all(root);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn existing_mirror_is_inspected_without_clone() {
|
|
let root = test_root("existing-mirror");
|
|
let config_result = crate::GitBackendConfig::new(root.clone());
|
|
assert!(config_result.is_ok());
|
|
let config = match config_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let runner = RecordingRunner::successful();
|
|
let backend = crate::GitBackend::with_runner(config, Box::new(runner.clone()));
|
|
let source_result = source_id("pgr");
|
|
assert!(source_result.is_some());
|
|
let source_id = match source_result {
|
|
Some(value) => value,
|
|
None => return,
|
|
};
|
|
let repository_path = backend.repository_path(&source_id);
|
|
assert!(std::fs::create_dir_all(&repository_path).is_ok());
|
|
let source_result = crate::GitSourceConfig::remote(source_id, "https://example.invalid/pgr.git");
|
|
assert!(source_result.is_ok());
|
|
let source = match source_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let result = backend.ensure_mirror(&source);
|
|
assert!(result.is_ok());
|
|
let mirror = match result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
assert_eq!(mirror.status(), crate::GitMirrorStatus::Existing);
|
|
let calls = runner.calls.borrow();
|
|
assert_eq!(calls.len(), 1);
|
|
assert_eq!(calls[0].operation, crate::GitOperation::InspectMirror);
|
|
let _ = std::fs::remove_dir_all(root);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_existing_mirror_is_rejected_without_deleting_it() {
|
|
let root = test_root("invalid-mirror");
|
|
let config_result = crate::GitBackendConfig::new(root.clone());
|
|
assert!(config_result.is_ok());
|
|
let config = match config_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let runner = RecordingRunner::with_inspection_stdout(b"false\n");
|
|
let backend = crate::GitBackend::with_runner(config, Box::new(runner));
|
|
let source_result = source_id("pgr");
|
|
assert!(source_result.is_some());
|
|
let source_id = match source_result {
|
|
Some(value) => value,
|
|
None => return,
|
|
};
|
|
let repository_path = backend.repository_path(&source_id);
|
|
assert!(std::fs::create_dir_all(&repository_path).is_ok());
|
|
let result = backend.inspect_mirror(&source_id);
|
|
assert_eq!(result, Err(crate::GitBackendError::InvalidMirror));
|
|
assert!(repository_path.is_dir());
|
|
let _ = std::fs::remove_dir_all(root);
|
|
return;
|
|
}
|