197 lines
9.2 KiB
Rust
197 lines
9.2 KiB
Rust
// file: crates/knowledge-git-api/unit_tests/lib.rs
|
|
// version: 4
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
struct FakeGitError;
|
|
|
|
struct FakeGitRepository;
|
|
|
|
impl crate::GitRepository for FakeGitRepository {
|
|
type Error = FakeGitError;
|
|
|
|
fn refresh(&mut self, _source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error> {
|
|
return Ok(());
|
|
}
|
|
|
|
fn resolve_revision(&self, source_id: &knowledge_domain_lib::SourceId, reference: &crate::GitReference) -> Result<crate::GitRevision, Self::Error> {
|
|
return Ok(crate::GitRevision::new(&format!("{}:{}", source_id.as_str(), reference.as_str())));
|
|
}
|
|
|
|
fn prepare_snapshot(&self, _source_id: &knowledge_domain_lib::SourceId, revision: &crate::GitRevision) -> Result<crate::GitSnapshot, Self::Error> {
|
|
return Ok(crate::GitSnapshot::new(revision.clone(), std::path::PathBuf::from("/controlled/snapshot")));
|
|
}
|
|
}
|
|
|
|
struct FakeGitWriter;
|
|
|
|
impl crate::GitRepositoryWriter for FakeGitWriter {
|
|
type Error = FakeGitError;
|
|
|
|
fn create_candidate(&mut self, _source_id: &knowledge_domain_lib::SourceId, request: &crate::GitWriteRequest) -> Result<crate::GitRevision, Self::Error> {
|
|
return Ok(crate::GitRevision::new(&format!("candidate:{}", request.base_revision().as_str())));
|
|
}
|
|
|
|
fn push_candidate(&mut self, _source_id: &knowledge_domain_lib::SourceId, request: &crate::GitPushRequest) -> Result<crate::GitPushOutcome, Self::Error> {
|
|
return Ok(crate::GitPushOutcome::Updated(request.candidate_revision().clone()));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn generic_repository_contract_does_not_require_a_forge() {
|
|
let source_result = knowledge_domain_lib::SourceId::new("local-pgr");
|
|
assert!(source_result.is_ok());
|
|
let source_id = match source_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let reference = crate::GitReference::new("refs/heads/master");
|
|
let mut repository = FakeGitRepository;
|
|
assert!(crate::GitRepository::refresh(&mut repository, &source_id).is_ok());
|
|
let revision_result = crate::GitRepository::resolve_revision(&repository, &source_id, &reference);
|
|
assert_eq!(revision_result, Ok(crate::GitRevision::new("local-pgr:refs/heads/master")));
|
|
let revision = match revision_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let snapshot_result = crate::GitRepository::prepare_snapshot(&repository, &source_id, &revision);
|
|
assert_eq!(snapshot_result, Ok(crate::GitSnapshot::new(revision, std::path::PathBuf::from("/controlled/snapshot"))));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn mutation_paths_are_relative_normalized_and_protect_git_metadata() {
|
|
let valid = crate::GitMutationPath::new("docs/rules/200-GOVERNANCE.md");
|
|
assert_eq!(valid.as_ref().map(crate::GitMutationPath::as_str), Ok("docs/rules/200-GOVERNANCE.md"));
|
|
assert_eq!(crate::GitMutationPath::new(""), Err(crate::GitWriteInputError::EmptyMutationPath));
|
|
assert_eq!(crate::GitMutationPath::new("/etc/passwd"), Err(crate::GitWriteInputError::AbsoluteMutationPath));
|
|
assert_eq!(crate::GitMutationPath::new("C:/outside.txt"), Err(crate::GitWriteInputError::AbsoluteMutationPath));
|
|
assert_eq!(crate::GitMutationPath::new("docs\\rules.md"), Err(crate::GitWriteInputError::AbsoluteMutationPath));
|
|
assert_eq!(crate::GitMutationPath::new("docs/../RULES.md"), Err(crate::GitWriteInputError::UnnormalizedMutationPath));
|
|
assert_eq!(crate::GitMutationPath::new("docs/./RULES.md"), Err(crate::GitWriteInputError::UnnormalizedMutationPath));
|
|
assert_eq!(crate::GitMutationPath::new(".git/config"), Err(crate::GitWriteInputError::ProtectedGitPath));
|
|
assert_eq!(crate::GitMutationPath::new("nested/.GIT/config"), Err(crate::GitWriteInputError::ProtectedGitPath));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn commit_identity_is_explicit_and_rejects_line_breaks() {
|
|
let identity = crate::GitCommitIdentity::new("Knowledge Publisher", "knowledge@example.invalid");
|
|
assert_eq!(identity.as_ref().map(crate::GitCommitIdentity::name), Ok("Knowledge Publisher"));
|
|
assert_eq!(identity.as_ref().map(crate::GitCommitIdentity::email), Ok("knowledge@example.invalid"));
|
|
assert_eq!(crate::GitCommitIdentity::new(" ", "knowledge@example.invalid"), Err(crate::GitWriteInputError::InvalidCommitIdentityName));
|
|
assert_eq!(
|
|
crate::GitCommitIdentity::new("Knowledge\nPublisher", "knowledge@example.invalid"),
|
|
Err(crate::GitWriteInputError::InvalidCommitIdentityName)
|
|
);
|
|
assert_eq!(crate::GitCommitIdentity::new("Knowledge Publisher", " "), Err(crate::GitWriteInputError::InvalidCommitIdentityEmail));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn target_branch_is_limited_to_valid_heads_references() {
|
|
let branch = crate::GitBranch::new("refs/heads/knowledge/proposal-42");
|
|
assert_eq!(branch.as_ref().map(crate::GitBranch::as_str), Ok("refs/heads/knowledge/proposal-42"));
|
|
for invalid in [
|
|
"main",
|
|
"refs/tags/v1",
|
|
"refs/heads/",
|
|
"refs/heads/.hidden",
|
|
"refs/heads/topic..next",
|
|
"refs/heads/topic.lock",
|
|
"refs/heads/topic@{next",
|
|
"refs/heads/topic\\next",
|
|
"refs/heads/topic//next",
|
|
] {
|
|
assert_eq!(crate::GitBranch::new(invalid), Err(crate::GitWriteInputError::InvalidBranch), "{invalid}");
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn write_request_requires_mutations_unique_paths_and_message() {
|
|
let identity_result = crate::GitCommitIdentity::new("Knowledge Publisher", "knowledge@example.invalid");
|
|
let identity = match identity_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let empty = crate::GitWriteRequest::new(crate::GitRevision::new("base"), Vec::new(), identity.clone(), "candidate");
|
|
assert_eq!(empty, Err(crate::GitWriteInputError::NoMutations));
|
|
let path_result = crate::GitMutationPath::new("content.md");
|
|
let path = match path_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let duplicates =
|
|
vec![crate::GitFileMutation::Write { path: path.clone(), content: b"one".to_vec() }, crate::GitFileMutation::Delete { path: path.clone() }];
|
|
let duplicate_request = crate::GitWriteRequest::new(crate::GitRevision::new("base"), duplicates, identity.clone(), "candidate");
|
|
assert_eq!(duplicate_request, Err(crate::GitWriteInputError::DuplicateMutationPath));
|
|
let mutation = crate::GitFileMutation::Write { path, content: b"content".to_vec() };
|
|
let invalid_message = crate::GitWriteRequest::new(crate::GitRevision::new("base"), vec![mutation.clone()], identity.clone(), " ");
|
|
assert_eq!(invalid_message, Err(crate::GitWriteInputError::InvalidCommitMessage));
|
|
let request = crate::GitWriteRequest::new(crate::GitRevision::new("base"), vec![mutation], identity, "candidate");
|
|
assert!(request.is_ok());
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn push_request_models_absent_or_revision_remote_state() {
|
|
let branch_result = crate::GitBranch::new("refs/heads/proposals/example");
|
|
let branch = match branch_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let candidate = crate::GitRevision::new("candidate");
|
|
let create = crate::GitPushRequest::new(branch.clone(), crate::GitRemoteState::Absent, candidate.clone());
|
|
assert_eq!(create.target_branch(), &branch);
|
|
assert_eq!(create.expected_remote(), &crate::GitRemoteState::Absent);
|
|
assert_eq!(create.candidate_revision(), &candidate);
|
|
let existing = crate::GitPushRequest::new(branch, crate::GitRemoteState::Revision(crate::GitRevision::new("old")), candidate);
|
|
assert_eq!(existing.expected_remote(), &crate::GitRemoteState::Revision(crate::GitRevision::new("old")));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn write_contract_is_separate_from_read_contract_and_provider_neutral() {
|
|
let source_result = knowledge_domain_lib::SourceId::new("snippets-extra");
|
|
let source_id = match source_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let path_result = crate::GitMutationPath::new("snippets/example.md");
|
|
let path = match path_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let identity_result = crate::GitCommitIdentity::new("Knowledge Publisher", "knowledge@example.invalid");
|
|
let identity = match identity_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let write_result = crate::GitWriteRequest::new(
|
|
crate::GitRevision::new("base"),
|
|
vec![crate::GitFileMutation::Write { path, content: b"snippet".to_vec() }],
|
|
identity,
|
|
"Add snippet",
|
|
);
|
|
let write_request = match write_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let mut writer = FakeGitWriter;
|
|
let candidate_result = crate::GitRepositoryWriter::create_candidate(&mut writer, &source_id, &write_request);
|
|
assert_eq!(candidate_result, Ok(crate::GitRevision::new("candidate:base")));
|
|
let candidate = match candidate_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let branch_result = crate::GitBranch::new("refs/heads/proposals/snippet");
|
|
let branch = match branch_result {
|
|
Ok(value) => value,
|
|
Err(_) => return,
|
|
};
|
|
let push_request = crate::GitPushRequest::new(branch, crate::GitRemoteState::Absent, candidate.clone());
|
|
let push_result = crate::GitRepositoryWriter::push_candidate(&mut writer, &source_id, &push_request);
|
|
assert_eq!(push_result, Ok(crate::GitPushOutcome::Updated(candidate)));
|
|
return;
|
|
}
|