// 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 { 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 { 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 { 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 { 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; }