//! Copyright (c) 2022-2026 GitDataAi All rights reserved. mod common; use gitks::bare::GitBare; use gitks::pb::repository_service_server::RepositoryService; use gitks::pb::*; fn header(gb: &GitBare) -> RepositoryHeader { let name = gb .bare_dir .file_name() .unwrap() .to_string_lossy() .into_owned(); RepositoryHeader { relative_path: name, ..Default::default() } } fn req(gb: &GitBare, f: impl FnOnce(RepositoryHeader) -> T) -> tonic::Request { tonic::Request::new(f(header(gb))) } #[tokio::test] async fn test_get_repository() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); let repo = svc .get_repository(req(&gb, |r| GetRepositoryRequest { repository: Some(r), })) .await .unwrap() .into_inner(); assert!(repo.bare); assert_eq!(repo.object_format, ObjectFormat::Sha1 as i32); assert_eq!(repo.default_branch, "main"); } #[tokio::test] async fn test_init_and_delete_repository() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); let hdr = RepositoryHeader { relative_path: "new-repo".into(), ..Default::default() }; svc.init_repository(tonic::Request::new(InitRepositoryRequest { repository: Some(hdr.clone()), bare: true, ..Default::default() })) .await .unwrap(); assert!( svc.repository_exists(tonic::Request::new(RepositoryExistsRequest { repository: Some(hdr.clone()) })) .await .unwrap() .into_inner() .exists ); svc.delete_repository(tonic::Request::new(DeleteRepositoryRequest { repository: Some(hdr.clone()), })) .await .unwrap(); assert!( !svc.repository_exists(tonic::Request::new(RepositoryExistsRequest { repository: Some(hdr) })) .await .unwrap() .into_inner() .exists ); } #[tokio::test] async fn test_get_object_format() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); let resp = svc .get_object_format(req(&gb, |r| RepositoryObjectFormatRequest { repository: Some(r), })) .await .unwrap() .into_inner(); assert_eq!(resp.object_format, ObjectFormat::Sha1 as i32); } #[tokio::test] async fn test_get_set_default_branch() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); let h = header(&gb); assert_eq!( svc.get_default_branch(tonic::Request::new(GetDefaultBranchRequest { repository: Some(h.clone()) })) .await .unwrap() .into_inner() .name, "main" ); svc.set_default_branch(tonic::Request::new(SetDefaultBranchRequest { repository: Some(h.clone()), name: "feature".into(), })) .await .unwrap(); assert_eq!( svc.get_default_branch(tonic::Request::new(GetDefaultBranchRequest { repository: Some(h) })) .await .unwrap() .into_inner() .name, "feature" ); } #[tokio::test] async fn test_get_set_repository_config() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); svc.set_repository_config(tonic::Request::new(SetRepositoryConfigRequest { repository: Some(header(&gb)), entries: vec![RepositoryConfigEntry { key: "test.key".into(), values: vec!["val1".into(), "val2".into()], }], })) .await .unwrap(); let entry = svc .get_repository_config(tonic::Request::new(GetRepositoryConfigRequest { repository: Some(header(&gb)), keys: vec!["test.key".into()], })) .await .unwrap() .into_inner() .entries .into_iter() .find(|e| e.key == "test.key") .unwrap(); assert_eq!(entry.values, vec!["val1", "val2"]); } #[tokio::test] async fn test_get_repository_statistics() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); let s = svc .get_repository_statistics(req(&gb, |r| RepositoryStatisticsRequest { repository: Some(r), })) .await .unwrap() .into_inner(); assert!(s.size_bytes > 0); assert!(s.loose_object_count > 0 || s.packed_object_count > 0); assert!(s.reference_count >= 2); } #[tokio::test] async fn test_check_repository_health() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); let h = svc .check_repository_health(tonic::Request::new(RepositoryHealthRequest { repository: Some(header(&gb)), connectivity_only: true, })) .await .unwrap() .into_inner(); assert!(h.ok && h.errors.is_empty()); } #[tokio::test] async fn test_garbage_collect() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); assert!( svc.garbage_collect(tonic::Request::new(GarbageCollectRequest { repository: Some(header(&gb)), ..Default::default() })) .await .unwrap() .into_inner() .ok ); } #[tokio::test] async fn test_repack() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); assert!( svc.repack(tonic::Request::new(RepackRequest { repository: Some(header(&gb)), ..Default::default() })) .await .unwrap() .into_inner() .ok ); } #[tokio::test] async fn test_write_commit_graph() { let (dir, gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); assert!( svc.write_commit_graph(tonic::Request::new(WriteCommitGraphRequest { repository: Some(header(&gb)), ..Default::default() })) .await .unwrap() .into_inner() .ok ); } #[tokio::test] async fn test_resolve_none_header() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); let result = svc .get_repository(tonic::Request::new(GetRepositoryRequest { repository: None, })) .await; assert!(result.is_err(), "should fail with None header"); let err = result.unwrap_err(); assert_eq!(err.code(), tonic::Code::InvalidArgument); } #[tokio::test] async fn test_resolve_empty_relative_path() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); let result = svc .get_repository(tonic::Request::new(GetRepositoryRequest { repository: Some(RepositoryHeader { relative_path: String::new(), ..Default::default() }), })) .await; assert!(result.is_err(), "should fail with empty relative_path"); } #[tokio::test] async fn test_resolve_nonexistent_repo() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); let result = svc .get_repository(tonic::Request::new(GetRepositoryRequest { repository: Some(RepositoryHeader { relative_path: "does-not-exist".into(), ..Default::default() }), })) .await; assert!(result.is_err(), "should fail for nonexistent repo"); } #[tokio::test] async fn test_init_empty_relative_path() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); let result = svc .init_repository(tonic::Request::new(InitRepositoryRequest { repository: Some(RepositoryHeader { relative_path: String::new(), ..Default::default() }), bare: true, ..Default::default() })) .await; assert!(result.is_err(), "should fail with empty relative_path"); } #[tokio::test] async fn test_delete_nonexistent_repo() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); // Deleting a non-existent path should succeed (fs::remove_dir_all on non-existent is ok) // or fail gracefully let result = svc .delete_repository(tonic::Request::new(DeleteRepositoryRequest { repository: Some(RepositoryHeader { relative_path: "ghost-repo".into(), ..Default::default() }), })) .await; // It either succeeds (dir doesn't exist, nothing to delete) or fails // Both are acceptable behaviors let _ = result; } #[tokio::test] async fn test_exists_nonexistent_repo() { let dir = tempfile::tempdir().unwrap(); let svc = common::setup_service(dir.path()); let result = svc .repository_exists(tonic::Request::new(RepositoryExistsRequest { repository: Some(RepositoryHeader { relative_path: "nonexistent".into(), ..Default::default() }), })) .await .unwrap() .into_inner(); assert!(!result.exists); } #[test] fn test_find_merge_base() { let (_dir, gb) = common::setup_bare_repo(); let main_oid = common::get_main_oid(&gb); let feature_oid = common::get_feature_oid(&gb); let resp = gb.find_merge_base(FindMergeBaseRequest { repository: Some(header(&gb)), revisions: vec![ main_oid.as_bytes().to_vec(), feature_oid.as_bytes().to_vec(), ], }).unwrap(); assert!(!resp.base_oid.is_empty()); } #[test] fn test_find_merge_base_empty() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.find_merge_base(FindMergeBaseRequest { repository: Some(header(&gb)), revisions: vec![], }).unwrap(); assert!(resp.base_oid.is_empty()); } #[test] fn test_find_merge_base_single() { let (_dir, gb) = common::setup_bare_repo(); let oid = common::get_main_oid(&gb); let resp = gb.find_merge_base(FindMergeBaseRequest { repository: Some(header(&gb)), revisions: vec![oid.as_bytes().to_vec()], }).unwrap(); assert!(!resp.base_oid.is_empty()); } #[test] fn test_commit_is_ancestor() { let (_dir, gb) = common::setup_bare_repo(); let feature_oid = common::get_feature_oid(&gb); let main_oid = common::get_main_oid(&gb); let resp = gb.commit_is_ancestor(CommitIsAncestorRequest { repository: Some(header(&gb)), ancestor_oid: feature_oid, descendant_oid: main_oid, }).unwrap(); assert!(resp.is_ancestor); } #[test] fn test_commit_is_ancestor_false() { let (_dir, gb) = common::setup_bare_repo(); let main_oid = common::get_main_oid(&gb); let feature_oid = common::get_feature_oid(&gb); let resp = gb.commit_is_ancestor(CommitIsAncestorRequest { repository: Some(header(&gb)), ancestor_oid: main_oid, descendant_oid: feature_oid, }).unwrap(); assert!(!resp.is_ancestor); } #[test] fn test_objects_size() { let (_dir, gb) = common::setup_bare_repo(); let oid = common::get_main_oid(&gb); let resp = gb.objects_size(ObjectsSizeRequest { repository: Some(header(&gb)), oids: vec![oid.clone(), "0000000000000000000000000000000000000000".into()], }).unwrap(); assert_eq!(resp.sizes.len(), 2); assert!(resp.sizes[0].found); assert!(resp.sizes[0].size > 0); } #[test] fn test_objects_size_empty() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.objects_size(ObjectsSizeRequest { repository: Some(header(&gb)), oids: vec![], }).unwrap(); assert!(resp.sizes.is_empty()); } #[test] fn test_repository_size() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.repository_size().unwrap(); assert!(resp.size_bytes > 0); } #[test] fn test_find_license_no_license() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.find_license().unwrap(); assert!(resp.license_spdx.is_empty()); } #[test] fn test_optimize_repository_heuristic() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.optimize_repository(OptimizeRepositoryRequest { repository: Some(header(&gb)), strategy: OptimizeStrategy::Heuristic as i32, }).unwrap(); assert!(resp.ok); } #[test] fn test_optimize_repository_incremental() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.optimize_repository(OptimizeRepositoryRequest { repository: Some(header(&gb)), strategy: OptimizeStrategy::Incremental as i32, }).unwrap(); assert!(resp.ok); } #[test] fn test_search_files_by_content() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.search_files_by_content(SearchFilesByContentRequest { repository: Some(header(&gb)), query: "Test".into(), revision: "main".into(), max_results: 10, case_sensitive: true, }).unwrap(); assert!(!resp.results.is_empty()); } #[test] fn test_search_files_by_content_no_match() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.search_files_by_content(SearchFilesByContentRequest { repository: Some(header(&gb)), query: "zzzznonexistentzzzz".into(), revision: "main".into(), max_results: 10, case_sensitive: true, }).unwrap(); assert!(resp.results.is_empty()); } #[test] fn test_search_files_by_content_empty_query() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.search_files_by_content(SearchFilesByContentRequest { repository: Some(header(&gb)), query: String::new(), revision: "main".into(), max_results: 10, case_sensitive: true, }); assert!(resp.is_err()); } #[test] fn test_search_files_by_name() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.search_files_by_name(SearchFilesByNameRequest { repository: Some(header(&gb)), query: "README".into(), revision: "main".into(), max_results: 10, recursive: true, }).unwrap(); assert!(!resp.results.is_empty()); assert!(resp.results.iter().any(|r| r.path.contains("README"))); } #[test] fn test_search_files_by_name_no_match() { let (_dir, gb) = common::setup_bare_repo(); let resp = gb.search_files_by_name(SearchFilesByNameRequest { repository: Some(header(&gb)), query: "zzzznonexistentzzzz".into(), revision: "main".into(), max_results: 10, recursive: true, }).unwrap(); assert!(resp.results.is_empty()); }