545 lines
15 KiB
Rust
545 lines
15 KiB
Rust
//! 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<T>(gb: &GitBare, f: impl FnOnce(RepositoryHeader) -> T) -> tonic::Request<T> {
|
|
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());
|
|
}
|