0.0.3-alpha.2
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/knowledge-git-api/unit_tests/lib.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct FakeGitError;
|
||||
@@ -22,6 +22,20 @@ impl crate::GitRepository for FakeGitRepository {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -43,3 +57,140 @@ fn generic_repository_contract_does_not_require_a_forge() {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user