feat(server): add tracing spans and caching to archive and blame services

- Add tracing spans with repo labels for archive and blame operations
- Implement caching for archive list entries when using OID selectors
- Implement caching for blame operations when using OID selectors
- Add detailed
This commit is contained in:
zhenyi
2026-06-04 15:33:16 +08:00
parent 729604f13b
commit cc202d6d1f
41 changed files with 2400 additions and 1067 deletions
+123 -58
View File
@@ -1,13 +1,23 @@
mod common;
use gitks::pb::archive_service_server::ArchiveService;
use gitks::pb::pack_service_server::PackService;
use gitks::pb::*;
#[test]
fn test_get_archive_tar() {
let (_dir, gb) = common::setup_bare_repo();
let chunks = gb
.get_archive(ArchiveRequest {
repository: None,
fn hdr(name: &str) -> RepositoryHeader {
RepositoryHeader {
relative_path: name.into(),
..Default::default()
}
}
#[tokio::test]
async fn test_get_archive_tar() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let chunks = svc
.get_archive(tonic::Request::new(ArchiveRequest {
repository: Some(hdr("test-repo")),
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -17,20 +27,24 @@ fn test_get_archive_tar() {
format: archive_options::Format::ArchiveFormatTar as i32,
..Default::default()
}),
})
.expect("get_archive tar");
}))
.await
.unwrap()
.into_inner();
let chunks: Vec<_> = tokio_stream::StreamExt::collect(chunks).await;
assert!(!chunks.is_empty(), "should produce archive data");
let total_size: usize = chunks.iter().map(|c| c.data.len()).sum();
let total_size: usize = chunks.iter().map(|c| c.as_ref().unwrap().data.len()).sum();
assert!(total_size > 0, "archive should not be empty");
}
#[test]
fn test_get_archive_zip() {
let (_dir, gb) = common::setup_bare_repo();
let chunks = gb
.get_archive(ArchiveRequest {
repository: None,
#[tokio::test]
async fn test_get_archive_zip() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let chunks = svc
.get_archive(tonic::Request::new(ArchiveRequest {
repository: Some(hdr("test-repo")),
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -40,23 +54,27 @@ fn test_get_archive_zip() {
format: archive_options::Format::ArchiveFormatZip as i32,
..Default::default()
}),
})
.expect("get_archive zip");
}))
.await
.unwrap()
.into_inner();
let chunks: Vec<_> = tokio_stream::StreamExt::collect(chunks).await;
assert!(!chunks.is_empty());
let data = &chunks[0].data;
let data = &chunks[0].as_ref().unwrap().data;
assert!(
data.starts_with(b"PK"),
"zip archive should start with PK magic bytes"
);
}
#[test]
fn test_list_archive_entries() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_archive_entries(ListArchiveEntriesRequest {
repository: None,
#[tokio::test]
async fn test_list_archive_entries() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.list_archive_entries(tonic::Request::new(ListArchiveEntriesRequest {
repository: Some(hdr("test-repo")),
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -64,8 +82,10 @@ fn test_list_archive_entries() {
}),
pathspec: vec![],
pagination: None,
})
.expect("list_archive_entries");
}))
.await
.unwrap()
.into_inner();
assert!(!result.entries.is_empty(), "should list entries");
let paths: Vec<&str> = result.entries.iter().map(|e| e.path.as_str()).collect();
@@ -76,12 +96,13 @@ fn test_list_archive_entries() {
);
}
#[test]
fn test_get_archive_with_prefix() {
let (_dir, gb) = common::setup_bare_repo();
let chunks = gb
.get_archive(ArchiveRequest {
repository: None,
#[tokio::test]
async fn test_get_archive_with_prefix() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let chunks = svc
.get_archive(tonic::Request::new(ArchiveRequest {
repository: Some(hdr("test-repo")),
treeish: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -92,29 +113,68 @@ fn test_get_archive_with_prefix() {
prefix: "project/".into(),
..Default::default()
}),
})
.expect("get_archive with prefix");
}))
.await
.unwrap()
.into_inner();
let chunks: Vec<_> = tokio_stream::StreamExt::collect(chunks).await;
assert!(!chunks.is_empty());
}
#[test]
fn test_fsck_clean_repo() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.fsck(FsckRequest {
repository: None,
#[tokio::test]
async fn test_fsck_clean_repo() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.fsck(tonic::Request::new(FsckRequest {
repository: Some(hdr("test-repo")),
strict: false,
connectivity_only: false,
})
.expect("fsck");
}))
.await
.unwrap()
.into_inner();
assert!(result.ok);
assert!(result.errors.is_empty());
}
#[test]
fn test_list_packfiles() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_fsck_strict() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.fsck(tonic::Request::new(FsckRequest {
repository: Some(hdr("test-repo")),
strict: true,
connectivity_only: false,
}))
.await
.unwrap()
.into_inner();
assert!(result.ok, "strict fsck should pass on clean repo");
}
#[tokio::test]
async fn test_fsck_connectivity_only() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.fsck(tonic::Request::new(FsckRequest {
repository: Some(hdr("test-repo")),
strict: false,
connectivity_only: true,
}))
.await
.unwrap()
.into_inner();
assert!(result.ok, "connectivity-only fsck should pass");
}
#[tokio::test]
async fn test_list_packfiles() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
duct::cmd(
"git",
@@ -128,12 +188,14 @@ fn test_list_packfiles() {
.run()
.expect("git gc");
let result = gb
.list_packfiles(ListPackfilesRequest {
repository: None,
let result = svc
.list_packfiles(tonic::Request::new(ListPackfilesRequest {
repository: Some(hdr("test-repo")),
pagination: None,
})
.expect("list_packfiles");
}))
.await
.unwrap()
.into_inner();
assert!(
!result.packfiles.is_empty(),
@@ -145,16 +207,19 @@ fn test_list_packfiles() {
}
}
#[test]
fn test_advertise_refs() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.advertise_refs(AdvertiseRefsRequest {
repository: None,
#[tokio::test]
async fn test_advertise_refs() {
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.advertise_refs(tonic::Request::new(AdvertiseRefsRequest {
repository: Some(hdr("test-repo")),
protocol: None,
service: String::new(),
})
.expect("advertise_refs");
}))
.await
.unwrap()
.into_inner();
assert!(!result.references.is_empty(), "should have refs");
let ref_names: Vec<&str> = result.references.iter().map(|r| r.name.as_str()).collect();
+100 -47
View File
@@ -1,13 +1,22 @@
mod common;
use gitks::pb::blame_service_server::BlameService;
use gitks::pb::*;
#[test]
fn test_blame_basic() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.blame(BlameRequest {
repository: None,
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_blame_basic() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.blame(tonic::Request::new(BlameRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -17,8 +26,10 @@ fn test_blame_basic() {
range: None,
options: None,
pagination: None,
})
.expect("blame");
}))
.await
.unwrap()
.into_inner();
assert!(!result.hunks.is_empty(), "should have blame hunks");
for hunk in &result.hunks {
@@ -31,12 +42,13 @@ fn test_blame_basic() {
}
}
#[test]
fn test_blame_line_content() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.blame(BlameRequest {
repository: None,
#[tokio::test]
async fn test_blame_line_content() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.blame(tonic::Request::new(BlameRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -46,8 +58,10 @@ fn test_blame_line_content() {
range: None,
options: None,
pagination: None,
})
.expect("blame");
}))
.await
.unwrap()
.into_inner();
let all_lines: Vec<String> = result
.hunks
@@ -63,12 +77,13 @@ fn test_blame_line_content() {
);
}
#[test]
fn test_blame_with_range() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.blame(BlameRequest {
repository: None,
#[tokio::test]
async fn test_blame_with_range() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.blame(tonic::Request::new(BlameRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -78,18 +93,21 @@ fn test_blame_with_range() {
range: Some(LineRange { start: 1, end: 1 }),
options: None,
pagination: None,
})
.expect("blame with range");
}))
.await
.unwrap()
.into_inner();
assert!(!result.hunks.is_empty(), "should have hunks for range");
}
#[test]
fn test_blame_author_info() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.blame(BlameRequest {
repository: None,
#[tokio::test]
async fn test_blame_author_info() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.blame(tonic::Request::new(BlameRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -99,8 +117,10 @@ fn test_blame_author_info() {
range: None,
options: None,
pagination: None,
})
.expect("blame");
}))
.await
.unwrap()
.into_inner();
let hunk = &result.hunks[0];
let commit = hunk.commit.as_ref().unwrap();
@@ -112,21 +132,54 @@ fn test_blame_author_info() {
}
}
#[test]
fn test_blame_nonexistent_file() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb.blame(BlameRequest {
repository: None,
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "nonexistent.txt".into(),
range: None,
options: None,
pagination: None,
});
#[tokio::test]
async fn test_blame_nonexistent_file() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.blame(tonic::Request::new(BlameRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "nonexistent.txt".into(),
range: None,
options: None,
pagination: None,
}))
.await;
assert!(result.is_err(), "blame on nonexistent file should fail");
}
#[tokio::test]
async fn test_stream_blame() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let stream = svc
.stream_blame(tonic::Request::new(BlameRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "README.md".into(),
range: None,
options: None,
pagination: None,
}))
.await
.unwrap()
.into_inner();
let hunks: Vec<_> = tokio_stream::StreamExt::collect(stream).await;
assert!(!hunks.is_empty(), "stream should produce blame hunks");
for hunk in &hunks {
let hunk = hunk.as_ref().unwrap();
assert!(hunk.commit.is_some());
assert!(hunk.line_count > 0);
}
}
+197 -82
View File
@@ -1,51 +1,70 @@
mod common;
use gitks::pb::branch_service_server::BranchService;
use gitks::pb::*;
#[test]
fn test_list_branches() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_branches(ListBranchesRequest {
repository: None,
#[allow(unused_imports)]
use gitks::pb::{BranchUpstream, SetBranchUpstreamRequest, UpdateBranchTargetRequest};
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_list_branches() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.list_branches(tonic::Request::new(ListBranchesRequest {
repository: Some(hdr()),
pattern: String::new(),
merged_into_head: false,
not_merged_into_head: false,
pagination: None,
sort_direction: 0,
})
.expect("list_branches");
}))
.await
.unwrap()
.into_inner();
let names: Vec<String> = result.branches.iter().map(|b| b.name.clone()).collect();
assert!(names.contains(&"feature".to_string()));
assert!(names.contains(&"main".to_string()));
assert!(result.branches.len() >= 2);
}
#[test]
fn test_list_branches_merged_filter() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_list_branches_merged_filter() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let merged = gb
.list_branches(ListBranchesRequest {
repository: None,
let merged = svc
.list_branches(tonic::Request::new(ListBranchesRequest {
repository: Some(hdr()),
pattern: String::new(),
merged_into_head: true,
not_merged_into_head: false,
pagination: None,
sort_direction: 0,
})
.expect("list_branches merged");
}))
.await
.unwrap()
.into_inner();
let not_merged = gb
.list_branches(ListBranchesRequest {
repository: None,
let not_merged = svc
.list_branches(tonic::Request::new(ListBranchesRequest {
repository: Some(hdr()),
pattern: String::new(),
merged_into_head: false,
not_merged_into_head: true,
pagination: None,
sort_direction: 0,
})
.expect("list_branches not merged");
}))
.await
.unwrap()
.into_inner();
let merged_names: Vec<&str> = merged.branches.iter().map(|b| b.name.as_str()).collect();
let not_merged_names: Vec<&str> = not_merged
@@ -66,15 +85,18 @@ fn test_list_branches_merged_filter() {
);
}
#[test]
fn test_get_branch() {
let (_dir, gb) = common::setup_bare_repo();
let branch = gb
.get_branch(GetBranchRequest {
repository: None,
#[tokio::test]
async fn test_get_branch() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let branch = svc
.get_branch(tonic::Request::new(GetBranchRequest {
repository: Some(hdr()),
name: "feature".into(),
})
.expect("get_branch");
}))
.await
.unwrap()
.into_inner();
assert_eq!(branch.full_ref, "refs/heads/feature");
let oid = branch.target_oid.unwrap();
assert!(!oid.value.is_empty());
@@ -82,12 +104,13 @@ fn test_get_branch() {
assert_eq!(oid.hex.len(), 40);
}
#[test]
fn test_branch_pagination() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_branches(ListBranchesRequest {
repository: None,
#[tokio::test]
async fn test_branch_pagination() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.list_branches(tonic::Request::new(ListBranchesRequest {
repository: Some(hdr()),
pattern: String::new(),
merged_into_head: false,
not_merged_into_head: false,
@@ -96,15 +119,17 @@ fn test_branch_pagination() {
page_token: String::new(),
}),
sort_direction: 0,
})
.expect("list_branches page 1");
}))
.await
.unwrap()
.into_inner();
let page_info = result.page_info.unwrap();
assert_eq!(result.branches.len(), 1);
assert!(page_info.has_next_page);
let result2 = gb
.list_branches(ListBranchesRequest {
repository: None,
let result2 = svc
.list_branches(tonic::Request::new(ListBranchesRequest {
repository: Some(hdr()),
pattern: String::new(),
merged_into_head: false,
not_merged_into_head: false,
@@ -113,18 +138,21 @@ fn test_branch_pagination() {
page_token: page_info.next_page_token,
}),
sort_direction: 0,
})
.expect("list_branches page 2");
}))
.await
.unwrap()
.into_inner();
assert!(!result2.branches.is_empty());
assert_ne!(result.branches[0].name, result2.branches[0].name);
}
#[test]
fn test_create_and_delete_branch() {
let (_dir, gb) = common::setup_bare_repo();
let branch = gb
.create_branch(CreateBranchRequest {
repository: None,
#[tokio::test]
async fn test_create_and_delete_branch() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let branch = svc
.create_branch(tonic::Request::new(CreateBranchRequest {
repository: Some(hdr()),
name: "new-branch".into(),
start_point: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -132,29 +160,35 @@ fn test_create_and_delete_branch() {
})),
}),
force: false,
})
.expect("create_branch");
}))
.await
.unwrap()
.into_inner();
assert_eq!(branch.name, "new-branch");
gb.delete_branch(DeleteBranchRequest {
repository: None,
svc.delete_branch(tonic::Request::new(DeleteBranchRequest {
repository: Some(hdr()),
name: "new-branch".into(),
force: true,
})
.expect("delete_branch");
}))
.await
.unwrap();
let result = gb.get_branch(GetBranchRequest {
repository: None,
name: "new-branch".into(),
});
let result = svc
.get_branch(tonic::Request::new(GetBranchRequest {
repository: Some(hdr()),
name: "new-branch".into(),
}))
.await;
assert!(result.is_err(), "deleted branch should not exist");
}
#[test]
fn test_rename_branch() {
let (_dir, gb) = common::setup_bare_repo();
gb.create_branch(CreateBranchRequest {
repository: None,
#[tokio::test]
async fn test_rename_branch() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
svc.create_branch(tonic::Request::new(CreateBranchRequest {
repository: Some(hdr()),
name: "to-rename".into(),
start_point: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -162,35 +196,43 @@ fn test_rename_branch() {
})),
}),
force: false,
})
.expect("create branch for rename");
}))
.await
.unwrap();
let renamed = gb
.rename_branch(RenameBranchRequest {
repository: None,
let renamed = svc
.rename_branch(tonic::Request::new(RenameBranchRequest {
repository: Some(hdr()),
old_name: "to-rename".into(),
new_name: "renamed".into(),
})
.expect("rename_branch");
}))
.await
.unwrap()
.into_inner();
assert_eq!(renamed.name, "renamed");
let old = gb.get_branch(GetBranchRequest {
repository: None,
name: "to-rename".into(),
});
let old = svc
.get_branch(tonic::Request::new(GetBranchRequest {
repository: Some(hdr()),
name: "to-rename".into(),
}))
.await;
assert!(old.is_err(), "old branch name should not exist");
}
#[test]
fn test_compare_branch() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.compare_branch(CompareBranchRequest {
repository: None,
#[tokio::test]
async fn test_compare_branch() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.compare_branch(tonic::Request::new(CompareBranchRequest {
repository: Some(hdr()),
source_branch: "feature".into(),
target_branch: "main".into(),
})
.expect("compare_branch");
}))
.await
.unwrap()
.into_inner();
assert!(
result.ahead_by > 0 || result.behind_by > 0,
@@ -198,3 +240,76 @@ fn test_compare_branch() {
);
assert!(result.merge_base.is_some(), "should find merge base");
}
#[tokio::test]
async fn test_update_branch_target() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
// Get current main OID
let main_branch = svc
.get_branch(tonic::Request::new(GetBranchRequest {
repository: Some(hdr()),
name: "main".into(),
}))
.await
.unwrap()
.into_inner();
let main_oid = main_branch.target_oid.as_ref().unwrap().clone();
// Create a new branch pointing to main's HEAD
svc.create_branch(tonic::Request::new(CreateBranchRequest {
repository: Some(hdr()),
name: "movable".into(),
start_point: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~2".into(),
})),
}),
force: false,
}))
.await
.unwrap();
// Update target to main's OID
let updated = svc
.update_branch_target(tonic::Request::new(UpdateBranchTargetRequest {
repository: Some(hdr()),
name: "movable".into(),
expected_old_oid: None,
new_oid: Some(main_oid),
force: true,
}))
.await
.unwrap()
.into_inner();
assert_eq!(updated.name, "movable");
assert_eq!(
updated.target_oid.as_ref().unwrap().hex,
main_branch.target_oid.as_ref().unwrap().hex
);
}
#[tokio::test]
async fn test_set_branch_upstream() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.set_branch_upstream(tonic::Request::new(SetBranchUpstreamRequest {
repository: Some(hdr()),
name: "main".into(),
upstream: Some(BranchUpstream {
remote_name: "origin".into(),
remote_url: String::new(),
remote_branch_name: "main".into(),
local_branch_name: "main".into(),
}),
}))
.await;
// This may fail if no remote is configured, which is expected
// The important thing is that the code path is exercised
assert!(result.is_ok() || result.is_err());
}
+172 -120
View File
@@ -1,13 +1,23 @@
mod common;
use gitks::pb::commit_service_server::CommitService;
use gitks::pb::tree_service_server::TreeService;
use gitks::pb::*;
#[test]
fn test_get_commit_with_author() {
let (_dir, gb) = common::setup_bare_repo();
let commit = gb
.get_commit(GetCommitRequest {
repository: None,
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_get_commit_with_author() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let commit = svc
.get_commit(tonic::Request::new(GetCommitRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -15,8 +25,10 @@ fn test_get_commit_with_author() {
}),
include_stats: false,
include_raw: false,
})
.expect("get_commit");
}))
.await
.unwrap()
.into_inner();
assert!(commit.author.is_some(), "author must be populated");
let author = commit.author.as_ref().unwrap();
@@ -43,12 +55,13 @@ fn test_get_commit_with_author() {
);
}
#[test]
fn test_get_commit_subject_body() {
let (_dir, gb) = common::setup_bare_repo();
let commit = gb
.get_commit(GetCommitRequest {
repository: None,
#[tokio::test]
async fn test_get_commit_subject_body() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let commit = svc
.get_commit(tonic::Request::new(GetCommitRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~2".into(),
@@ -56,8 +69,10 @@ fn test_get_commit_subject_body() {
}),
include_stats: false,
include_raw: false,
})
.expect("get_commit");
}))
.await
.unwrap()
.into_inner();
assert_eq!(commit.subject, "second commit");
assert!(!commit.message.is_empty());
@@ -65,12 +80,13 @@ fn test_get_commit_subject_body() {
assert!(!commit.parent_oids.is_empty());
}
#[test]
fn test_get_commit_with_raw() {
let (_dir, gb) = common::setup_bare_repo();
let commit = gb
.get_commit(GetCommitRequest {
repository: None,
#[tokio::test]
async fn test_get_commit_with_raw() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let commit = svc
.get_commit(tonic::Request::new(GetCommitRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -78,8 +94,10 @@ fn test_get_commit_with_raw() {
}),
include_stats: false,
include_raw: true,
})
.expect("get_commit with raw");
}))
.await
.unwrap()
.into_inner();
assert!(
!commit.raw.is_empty(),
@@ -90,12 +108,13 @@ fn test_get_commit_with_raw() {
assert!(raw_str.contains("author"), "raw should contain author line");
}
#[test]
fn test_list_commits_with_pagination() {
let (_dir, gb) = common::setup_bare_repo();
let page1 = gb
.list_commits(ListCommitsRequest {
repository: None,
#[tokio::test]
async fn test_list_commits_with_pagination() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let page1 = svc
.list_commits(tonic::Request::new(ListCommitsRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -113,15 +132,17 @@ fn test_list_commits_with_pagination() {
page_size: 2,
page_token: String::new(),
}),
})
.expect("list_commits page 1");
}))
.await
.unwrap()
.into_inner();
assert_eq!(page1.commits.len(), 2);
let pi = page1.page_info.unwrap();
assert!(pi.has_next_page);
let page2 = gb
.list_commits(ListCommitsRequest {
repository: None,
let page2 = svc
.list_commits(tonic::Request::new(ListCommitsRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -139,18 +160,21 @@ fn test_list_commits_with_pagination() {
page_size: 2,
page_token: pi.next_page_token,
}),
})
.expect("list_commits page 2");
}))
.await
.unwrap()
.into_inner();
assert!(!page2.commits.is_empty());
assert_ne!(page1.commits[0].oid, page2.commits[0].oid);
}
#[test]
fn test_get_commit_ancestors_pagination() {
let (_dir, gb) = common::setup_bare_repo();
let page1 = gb
.get_commit_ancestors(GetCommitAncestorsRequest {
repository: None,
#[tokio::test]
async fn test_get_commit_ancestors_pagination() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let page1 = svc
.get_commit_ancestors(tonic::Request::new(GetCommitAncestorsRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -161,8 +185,10 @@ fn test_get_commit_ancestors_pagination() {
page_size: 2,
page_token: String::new(),
}),
})
.expect("ancestors page 1");
}))
.await
.unwrap()
.into_inner();
assert_eq!(page1.commits.len(), 2);
let pi = page1.page_info.unwrap();
@@ -172,9 +198,9 @@ fn test_get_commit_ancestors_pagination() {
"next_page_token must be set"
);
let page2 = gb
.get_commit_ancestors(GetCommitAncestorsRequest {
repository: None,
let page2 = svc
.get_commit_ancestors(tonic::Request::new(GetCommitAncestorsRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -185,8 +211,10 @@ fn test_get_commit_ancestors_pagination() {
page_size: 2,
page_token: pi.next_page_token,
}),
})
.expect("ancestors page 2");
}))
.await
.unwrap()
.into_inner();
assert!(!page2.commits.is_empty(), "page 2 should have commits");
assert_ne!(
@@ -195,12 +223,13 @@ fn test_get_commit_ancestors_pagination() {
);
}
#[test]
fn test_compare_commits() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.compare_commits(CompareCommitsRequest {
repository: None,
#[tokio::test]
async fn test_compare_commits() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.compare_commits(tonic::Request::new(CompareCommitsRequest {
repository: Some(hdr()),
base: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "feature".into(),
@@ -217,8 +246,10 @@ fn test_compare_commits() {
page_size: 100,
page_token: String::new(),
}),
})
.expect("compare_commits");
}))
.await
.unwrap()
.into_inner();
assert!(!result.commits.is_empty());
assert!(result.merge_base.is_some());
@@ -226,13 +257,14 @@ fn test_compare_commits() {
assert!(stats.additions > 0);
}
#[test]
fn test_create_commit_and_cherry_pick() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_create_commit_and_cherry_pick() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let created = gb
.create_commit(CreateCommitRequest {
repository: None,
let created = svc
.create_commit(tonic::Request::new(CreateCommitRequest {
repository: Some(hdr()),
branch: "feature".into(),
message: "cherry-pick source".into(),
author: Some(Signature {
@@ -265,8 +297,10 @@ fn test_create_commit_and_cherry_pick() {
}),
force: false,
trailers: vec![],
})
.expect("create_commit for cherry-pick source");
}))
.await
.unwrap()
.into_inner();
let source_oid = created
.commit
@@ -278,9 +312,9 @@ fn test_create_commit_and_cherry_pick() {
.hex
.clone();
let cp_result = gb
.cherry_pick_commit(CherryPickCommitRequest {
repository: None,
let cp_result = svc
.cherry_pick_commit(tonic::Request::new(CherryPickCommitRequest {
repository: Some(hdr()),
commit: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: source_oid.clone(),
@@ -296,15 +330,17 @@ fn test_create_commit_and_cherry_pick() {
}),
message: String::new(),
mainline: 0,
})
.expect("cherry_pick_commit");
}))
.await
.unwrap()
.into_inner();
let cp_commit = cp_result.commit.unwrap();
assert_eq!(cp_commit.subject, "cherry-pick source");
let blob = gb
.get_blob(GetBlobRequest {
repository: None,
let blob = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -313,14 +349,17 @@ fn test_create_commit_and_cherry_pick() {
path: "cp_file.txt".into(),
oid: None,
max_bytes: 0,
})
.expect("get_blob after cherry-pick");
}))
.await
.unwrap()
.into_inner();
assert_eq!(blob.data, b"cherry pick me");
}
#[test]
fn test_cherry_pick_root_commit() {
let (dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_cherry_pick_root_commit() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let work_dir = dir.path().join("work");
common::run(&work_dir, &["checkout", "--orphan", "root-source"]);
@@ -338,8 +377,8 @@ fn test_cherry_pick_root_commit() {
.stdout;
let root_oid = String::from_utf8(root_oid).unwrap().trim().to_string();
gb.cherry_pick_commit(CherryPickCommitRequest {
repository: None,
svc.cherry_pick_commit(tonic::Request::new(CherryPickCommitRequest {
repository: Some(hdr()),
commit: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: root_oid,
@@ -349,12 +388,13 @@ fn test_cherry_pick_root_commit() {
committer: None,
message: String::new(),
mainline: 0,
})
.expect("cherry_pick_commit root");
}))
.await
.unwrap();
let blob = gb
.get_blob(GetBlobRequest {
repository: None,
let blob = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "feature".into(),
@@ -363,18 +403,21 @@ fn test_cherry_pick_root_commit() {
path: "root_only.txt".into(),
oid: None,
max_bytes: 0,
})
.expect("get root file after cherry-pick");
}))
.await
.unwrap()
.into_inner();
assert_eq!(blob.data, b"from root\n");
}
#[test]
fn test_revert_commit() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_revert_commit() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let created = gb
.create_commit(CreateCommitRequest {
repository: None,
let created = svc
.create_commit(tonic::Request::new(CreateCommitRequest {
repository: Some(hdr()),
branch: "main".into(),
message: "to be reverted".into(),
author: None,
@@ -395,8 +438,10 @@ fn test_revert_commit() {
}),
force: false,
trailers: vec![],
})
.expect("create_commit");
}))
.await
.unwrap()
.into_inner();
let to_revert = created
.commit
@@ -408,9 +453,9 @@ fn test_revert_commit() {
.hex
.clone();
let revert_result = gb
.revert_commit(RevertCommitRequest {
repository: None,
let revert_result = svc
.revert_commit(tonic::Request::new(RevertCommitRequest {
repository: Some(hdr()),
commit: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: to_revert,
@@ -419,8 +464,10 @@ fn test_revert_commit() {
branch: "main".into(),
committer: None,
message: String::new(),
})
.expect("revert_commit");
}))
.await
.unwrap()
.into_inner();
let revert_commit = revert_result.commit.unwrap();
assert!(
@@ -429,29 +476,32 @@ fn test_revert_commit() {
revert_commit.subject
);
let blob_result = gb.get_blob(GetBlobRequest {
repository: None,
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "revert_me.txt".into(),
oid: None,
max_bytes: 0,
});
let blob_result = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "revert_me.txt".into(),
oid: None,
max_bytes: 0,
}))
.await;
assert!(
blob_result.is_err(),
"revert_me.txt should be deleted after revert"
);
}
#[test]
fn test_oid_binary_encoding() {
let (_dir, gb) = common::setup_bare_repo();
let commit = gb
.get_commit(GetCommitRequest {
repository: None,
#[tokio::test]
async fn test_oid_binary_encoding() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let commit = svc
.get_commit(tonic::Request::new(GetCommitRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -459,8 +509,10 @@ fn test_oid_binary_encoding() {
}),
include_stats: false,
include_raw: false,
})
.expect("get_commit");
}))
.await
.unwrap()
.into_inner();
let oid = commit.oid.unwrap();
assert_eq!(oid.value.len(), 20);
assert_eq!(oid.hex.len(), 40);
+10 -2
View File
@@ -1,4 +1,12 @@
use gitks::bare::GitBare;
use gitks::server::GitksService;
/// Create a GitksService with a temp directory as repo_prefix
pub fn setup_service(dir: &std::path::Path) -> GitksService {
GitksService {
repo_prefix: dir.to_path_buf(),
}
}
pub fn run_git(work_dir: &std::path::Path, args: &[&str]) -> duct::Expression {
duct::cmd("git", {
@@ -96,7 +104,7 @@ pub fn setup_bare_repo() -> (tempfile::TempDir, GitBare) {
.run()
.expect("set HEAD to main");
(dir, GitBare { bare_dir })
(dir, GitBare::new(bare_dir))
}
pub fn setup_bare_repo_with_conflict() -> (tempfile::TempDir, GitBare) {
@@ -163,5 +171,5 @@ pub fn setup_bare_repo_with_conflict() -> (tempfile::TempDir, GitBare) {
.run()
.expect("set HEAD to main");
(dir, GitBare { bare_dir })
(dir, GitBare::new(bare_dir))
}
+90 -58
View File
@@ -1,13 +1,23 @@
mod common;
use gitks::pb::commit_service_server::CommitService;
use gitks::pb::diff_service_server::DiffService;
use gitks::pb::*;
#[test]
fn test_get_diff() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.get_diff(GetDiffRequest {
repository: None,
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_get_diff() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.get_diff(tonic::Request::new(GetDiffRequest {
repository: Some(hdr()),
base: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~3".into(),
@@ -20,8 +30,10 @@ fn test_get_diff() {
}),
options: None,
pagination: None,
})
.expect("get_diff");
}))
.await
.unwrap()
.into_inner();
assert!(!result.files.is_empty());
let paths: Vec<&str> = result.files.iter().map(|f| f.new_path.as_str()).collect();
@@ -34,12 +46,13 @@ fn test_get_diff() {
assert!(stats.additions > 0 || stats.changed_files > 0);
}
#[test]
fn test_get_diff_with_patch() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.get_diff(GetDiffRequest {
repository: None,
#[tokio::test]
async fn test_get_diff_with_patch() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.get_diff(tonic::Request::new(GetDiffRequest {
repository: Some(hdr()),
base: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~1".into(),
@@ -56,8 +69,10 @@ fn test_get_diff_with_patch() {
..Default::default()
}),
pagination: None,
})
.expect("get_diff with patch");
}))
.await
.unwrap()
.into_inner();
assert!(!result.files.is_empty());
for file in &result.files {
@@ -71,12 +86,13 @@ fn test_get_diff_with_patch() {
}
}
#[test]
fn test_get_diff_with_rename_detection() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_get_diff_with_rename_detection() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
gb.create_commit(CreateCommitRequest {
repository: None,
svc.create_commit(tonic::Request::new(CreateCommitRequest {
repository: Some(hdr()),
branch: "main".into(),
message: "rename file".into(),
author: None,
@@ -108,12 +124,13 @@ fn test_get_diff_with_rename_detection() {
}),
force: false,
trailers: vec![],
})
.expect("create rename commit");
}))
.await
.unwrap();
let result = gb
.get_diff(GetDiffRequest {
repository: None,
let result = svc
.get_diff(tonic::Request::new(GetDiffRequest {
repository: Some(hdr()),
base: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~1".into(),
@@ -129,8 +146,10 @@ fn test_get_diff_with_rename_detection() {
..Default::default()
}),
pagination: None,
})
.expect("get_diff with rename detection");
}))
.await
.unwrap()
.into_inner();
let has_rename = result
.files
@@ -143,12 +162,13 @@ fn test_get_diff_with_rename_detection() {
);
}
#[test]
fn test_get_commit_diff_root() {
let (_dir, gb) = common::setup_bare_repo();
let commits = gb
.list_commits(ListCommitsRequest {
repository: None,
#[tokio::test]
async fn test_get_commit_diff_root() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let commits = svc
.list_commits(tonic::Request::new(ListCommitsRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -166,13 +186,15 @@ fn test_get_commit_diff_root() {
page_size: 1,
page_token: String::new(),
}),
})
.expect("list_commits for root");
}))
.await
.unwrap()
.into_inner();
let root_oid = commits.commits[0].oid.as_ref().unwrap().hex.clone();
let result = gb
.get_commit_diff(GetCommitDiffRequest {
repository: None,
let result = svc
.get_commit_diff(tonic::Request::new(GetCommitDiffRequest {
repository: Some(hdr()),
commit: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: root_oid,
@@ -180,18 +202,21 @@ fn test_get_commit_diff_root() {
}),
options: None,
pagination: None,
})
.expect("get_commit_diff on root");
}))
.await
.unwrap()
.into_inner();
assert!(!result.files.is_empty(), "root commit should have files");
}
#[test]
fn test_get_diff_stats() {
let (_dir, gb) = common::setup_bare_repo();
let stats = gb
.get_diff_stats(GetDiffStatsRequest {
repository: None,
#[tokio::test]
async fn test_get_diff_stats() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let stats = svc
.get_diff_stats(tonic::Request::new(GetDiffStatsRequest {
repository: Some(hdr()),
base: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~3".into(),
@@ -203,17 +228,20 @@ fn test_get_diff_stats() {
})),
}),
options: None,
})
.expect("get_diff_stats");
}))
.await
.unwrap()
.into_inner();
assert!(stats.additions > 0 || stats.changed_files > 0);
}
#[test]
fn test_get_patch() {
let (_dir, gb) = common::setup_bare_repo();
let patches = gb
.get_patch(GetPatchRequest {
repository: None,
#[tokio::test]
async fn test_get_patch() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let stream = svc
.get_patch(tonic::Request::new(GetPatchRequest {
repository: Some(hdr()),
base: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main~1".into(),
@@ -225,12 +253,16 @@ fn test_get_patch() {
})),
}),
options: None,
})
.expect("get_patch");
}))
.await
.unwrap()
.into_inner();
let patches: Vec<_> = tokio_stream::StreamExt::collect(stream).await;
assert!(!patches.is_empty());
let combined: String = patches
.iter()
.map(|p| String::from_utf8_lossy(&p.data).to_string())
.map(|p| String::from_utf8_lossy(&p.as_ref().unwrap().data).to_string())
.collect();
assert!(combined.contains("diff --git") || combined.contains("@@"));
}
+1 -6
View File
@@ -95,12 +95,7 @@ fn setup_bare_repo() -> (tempfile::TempDir, GitBare) {
&["push", "-f", "origin", "refs/tags/v0.1.0:refs/tags/v0.1.0"],
);
(
dir,
GitBare {
bare_dir: bare_dir.clone(),
},
)
(dir, GitBare::new(bare_dir.clone()))
}
#[test]
+128 -86
View File
@@ -1,13 +1,24 @@
mod common;
use gitks::pb::commit_service_server::CommitService;
use gitks::pb::merge_service_server::MergeService;
use gitks::pb::tree_service_server::TreeService;
use gitks::pb::*;
#[test]
fn test_check_merge_no_conflict() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.check_merge(CheckMergeRequest {
repository: None,
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_check_merge_no_conflict() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.check_merge(tonic::Request::new(CheckMergeRequest {
repository: Some(hdr()),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -19,8 +30,10 @@ fn test_check_merge_no_conflict() {
})),
}),
options: None,
})
.expect("check_merge");
}))
.await
.unwrap()
.into_inner();
assert!(
result.status == merge_result::Status::MergeResultStatusMerged as i32
@@ -31,12 +44,13 @@ fn test_check_merge_no_conflict() {
);
}
#[test]
fn test_check_merge_with_conflict() {
let (_dir, gb) = common::setup_bare_repo_with_conflict();
let result = gb
.check_merge(CheckMergeRequest {
repository: None,
#[tokio::test]
async fn test_check_merge_with_conflict() {
let (dir, _gb) = common::setup_bare_repo_with_conflict();
let svc = common::setup_service(dir.path());
let result = svc
.check_merge(tonic::Request::new(CheckMergeRequest {
repository: Some(hdr()),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "branch-a".into(),
@@ -48,8 +62,10 @@ fn test_check_merge_with_conflict() {
})),
}),
options: None,
})
.expect("check_merge with conflict");
}))
.await
.unwrap()
.into_inner();
assert_eq!(
result.status,
@@ -59,12 +75,13 @@ fn test_check_merge_with_conflict() {
assert!(!result.conflicts.is_empty(), "should list conflicted files");
}
#[test]
fn test_check_merge_already_up_to_date() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.check_merge(CheckMergeRequest {
repository: None,
#[tokio::test]
async fn test_check_merge_already_up_to_date() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.check_merge(tonic::Request::new(CheckMergeRequest {
repository: Some(hdr()),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -76,8 +93,10 @@ fn test_check_merge_already_up_to_date() {
})),
}),
options: None,
})
.expect("check_merge same ref");
}))
.await
.unwrap()
.into_inner();
assert_eq!(
result.status,
@@ -85,12 +104,13 @@ fn test_check_merge_already_up_to_date() {
);
}
#[test]
fn test_merge_fast_forward() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.merge(MergeRequest {
repository: None,
#[tokio::test]
async fn test_merge_fast_forward() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.merge(tonic::Request::new(MergeRequest {
repository: Some(hdr()),
target_branch: "feature".into(),
source: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -100,8 +120,10 @@ fn test_merge_fast_forward() {
committer: None,
message: String::new(),
options: None,
})
.expect("merge fast-forward");
}))
.await
.unwrap()
.into_inner();
assert!(
result.status == merge_result::Status::MergeResultStatusFastForward as i32
@@ -111,12 +133,13 @@ fn test_merge_fast_forward() {
);
}
#[test]
fn test_merge_with_conflict() {
let (_dir, gb) = common::setup_bare_repo_with_conflict();
let result = gb
.merge(MergeRequest {
repository: None,
#[tokio::test]
async fn test_merge_with_conflict() {
let (dir, _gb) = common::setup_bare_repo_with_conflict();
let svc = common::setup_service(dir.path());
let result = svc
.merge(tonic::Request::new(MergeRequest {
repository: Some(hdr()),
target_branch: "branch-a".into(),
source: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -126,8 +149,10 @@ fn test_merge_with_conflict() {
committer: None,
message: String::new(),
options: None,
})
.expect("merge with conflict");
}))
.await
.unwrap()
.into_inner();
assert_eq!(
result.status,
@@ -136,12 +161,13 @@ fn test_merge_with_conflict() {
);
}
#[test]
fn test_merge_fast_forward_only_aborts_non_fast_forward() {
let (_dir, gb) = common::setup_bare_repo_with_conflict();
let result = gb
.merge(MergeRequest {
repository: None,
#[tokio::test]
async fn test_merge_fast_forward_only_aborts_non_fast_forward() {
let (dir, _gb) = common::setup_bare_repo_with_conflict();
let svc = common::setup_service(dir.path());
let result = svc
.merge(tonic::Request::new(MergeRequest {
repository: Some(hdr()),
target_branch: "branch-a".into(),
source: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -154,8 +180,10 @@ fn test_merge_fast_forward_only_aborts_non_fast_forward() {
fast_forward: merge_options::FastForwardMode::MergeFastForwardModeOnly as i32,
..Default::default()
}),
})
.expect("merge fast-forward only");
}))
.await
.unwrap()
.into_inner();
assert_eq!(
result.status,
@@ -164,12 +192,13 @@ fn test_merge_fast_forward_only_aborts_non_fast_forward() {
assert!(result.commit.is_none());
}
#[test]
fn test_list_merge_conflicts() {
let (_dir, gb) = common::setup_bare_repo_with_conflict();
let result = gb
.list_merge_conflicts(ListMergeConflictsRequest {
repository: None,
#[tokio::test]
async fn test_list_merge_conflicts() {
let (dir, _gb) = common::setup_bare_repo_with_conflict();
let svc = common::setup_service(dir.path());
let result = svc
.list_merge_conflicts(tonic::Request::new(ListMergeConflictsRequest {
repository: Some(hdr()),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "branch-a".into(),
@@ -181,8 +210,10 @@ fn test_list_merge_conflicts() {
})),
}),
pagination: None,
})
.expect("list_merge_conflicts");
}))
.await
.unwrap()
.into_inner();
assert!(!result.conflicts.is_empty(), "should list conflicted files");
assert!(
@@ -191,13 +222,14 @@ fn test_list_merge_conflicts() {
);
}
#[test]
fn test_resolve_merge_conflicts() {
let (_dir, gb) = common::setup_bare_repo_with_conflict();
#[tokio::test]
async fn test_resolve_merge_conflicts() {
let (dir, _gb) = common::setup_bare_repo_with_conflict();
let svc = common::setup_service(dir.path());
let result = gb
.resolve_merge_conflicts(ResolveMergeConflictsRequest {
repository: None,
let result = svc
.resolve_merge_conflicts(tonic::Request::new(ResolveMergeConflictsRequest {
repository: Some(hdr()),
target_branch: "branch-a".into(),
source: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -210,8 +242,10 @@ fn test_resolve_merge_conflicts() {
}],
committer: None,
message: "resolved conflicts".into(),
})
.expect("resolve_merge_conflicts");
}))
.await
.unwrap()
.into_inner();
assert_eq!(
result.status,
@@ -219,9 +253,9 @@ fn test_resolve_merge_conflicts() {
);
assert!(result.commit.is_some());
let blob = gb
.get_blob(GetBlobRequest {
repository: None,
let blob = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "branch-a".into(),
@@ -230,17 +264,20 @@ fn test_resolve_merge_conflicts() {
path: "file.txt".into(),
oid: None,
max_bytes: 0,
})
.expect("get resolved blob");
}))
.await
.unwrap()
.into_inner();
assert_eq!(String::from_utf8_lossy(&blob.data), "resolved content\n");
}
#[test]
fn test_rebase() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_rebase() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
gb.create_commit(CreateCommitRequest {
repository: None,
svc.create_commit(tonic::Request::new(CreateCommitRequest {
repository: Some(hdr()),
branch: "feature".into(),
message: "feature work".into(),
author: None,
@@ -261,12 +298,13 @@ fn test_rebase() {
}),
force: false,
trailers: vec![],
})
.expect("create feature commit");
}))
.await
.unwrap();
let result = gb
.rebase(RebaseRequest {
repository: None,
let result = svc
.rebase(tonic::Request::new(RebaseRequest {
repository: Some(hdr()),
branch: "feature".into(),
upstream: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -274,8 +312,10 @@ fn test_rebase() {
})),
}),
committer: None,
})
.expect("rebase");
}))
.await
.unwrap()
.into_inner();
assert_eq!(
result.status,
@@ -283,9 +323,9 @@ fn test_rebase() {
);
assert!(result.head.is_some());
let blob = gb
.get_blob(GetBlobRequest {
repository: None,
let blob = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "feature".into(),
@@ -294,7 +334,9 @@ fn test_rebase() {
path: "feature.txt".into(),
oid: None,
max_bytes: 0,
})
.expect("get rebased feature file");
}))
.await
.unwrap()
.into_inner();
assert_eq!(String::from_utf8_lossy(&blob.data), "feature content");
}
+174 -81
View File
@@ -3,10 +3,7 @@ mod common;
use gitks::bare::GitBare;
use gitks::pb::repository_service_server::RepositoryService;
use gitks::pb::*;
use gitks::server::GitksService;
fn header(gb: &GitBare) -> RepositoryHeader {
let parent = gb.bare_dir.parent().unwrap().to_string_lossy().into_owned();
let name = gb
.bare_dir
.file_name()
@@ -14,7 +11,6 @@ fn header(gb: &GitBare) -> RepositoryHeader {
.to_string_lossy()
.into_owned();
RepositoryHeader {
storage_path: parent,
relative_path: name,
..Default::default()
}
@@ -26,8 +22,9 @@ fn req<T>(gb: &GitBare, f: impl FnOnce(RepositoryHeader) -> T) -> tonic::Request
#[tokio::test]
async fn test_get_repository() {
let (_dir, gb) = common::setup_bare_repo();
let repo = GitksService
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),
}))
@@ -42,13 +39,11 @@ async fn test_get_repository() {
#[tokio::test]
async fn test_init_and_delete_repository() {
let dir = tempfile::tempdir().unwrap();
let storage = dir.path().to_string_lossy().into_owned();
let svc = common::setup_service(dir.path());
let hdr = RepositoryHeader {
storage_path: storage.clone(),
relative_path: "new-repo".into(),
..Default::default()
};
let svc = GitksService;
svc.init_repository(tonic::Request::new(InitRepositoryRequest {
repository: Some(hdr.clone()),
bare: true,
@@ -83,8 +78,9 @@ async fn test_init_and_delete_repository() {
#[tokio::test]
async fn test_get_object_format() {
let (_dir, gb) = common::setup_bare_repo();
let resp = GitksService
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),
}))
@@ -96,53 +92,51 @@ async fn test_get_object_format() {
#[tokio::test]
async fn test_get_set_default_branch() {
let (_dir, gb) = common::setup_bare_repo();
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let h = header(&gb);
assert_eq!(
GitksService
.get_default_branch(tonic::Request::new(GetDefaultBranchRequest {
repository: Some(h.clone())
}))
.await
.unwrap()
.into_inner()
.name,
"main"
);
GitksService
.set_default_branch(tonic::Request::new(SetDefaultBranchRequest {
repository: Some(h.clone()),
name: "feature".into(),
svc.get_default_branch(tonic::Request::new(GetDefaultBranchRequest {
repository: Some(h.clone())
}))
.await
.unwrap();
.unwrap()
.into_inner()
.name,
"main"
);
svc.set_default_branch(tonic::Request::new(SetDefaultBranchRequest {
repository: Some(h.clone()),
name: "feature".into(),
}))
.await
.unwrap();
assert_eq!(
GitksService
.get_default_branch(tonic::Request::new(GetDefaultBranchRequest {
repository: Some(h)
}))
.await
.unwrap()
.into_inner()
.name,
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();
GitksService
.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 = GitksService
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()],
@@ -159,8 +153,9 @@ async fn test_get_set_repository_config() {
#[tokio::test]
async fn test_get_repository_statistics() {
let (_dir, gb) = common::setup_bare_repo();
let s = GitksService
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),
}))
@@ -174,8 +169,9 @@ async fn test_get_repository_statistics() {
#[tokio::test]
async fn test_check_repository_health() {
let (_dir, gb) = common::setup_bare_repo();
let h = GitksService
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,
@@ -188,48 +184,145 @@ async fn test_check_repository_health() {
#[tokio::test]
async fn test_garbage_collect() {
let (_dir, gb) = common::setup_bare_repo();
let (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
assert!(
GitksService
.garbage_collect(tonic::Request::new(GarbageCollectRequest {
repository: Some(header(&gb)),
..Default::default()
}))
.await
.unwrap()
.into_inner()
.ok
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 (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
assert!(
GitksService
.repack(tonic::Request::new(RepackRequest {
repository: Some(header(&gb)),
..Default::default()
}))
.await
.unwrap()
.into_inner()
.ok
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 (dir, gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
assert!(
GitksService
.write_commit_graph(tonic::Request::new(WriteCommitGraphRequest {
repository: Some(header(&gb)),
..Default::default()
}))
.await
.unwrap()
.into_inner()
.ok
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);
}
+91 -61
View File
@@ -1,44 +1,59 @@
mod common;
use gitks::pb::tag_service_server::TagService;
use gitks::pb::*;
#[test]
fn test_list_tags() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_tags(ListTagsRequest {
repository: None,
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_list_tags() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.list_tags(tonic::Request::new(ListTagsRequest {
repository: Some(hdr()),
pattern: String::new(),
pagination: None,
sort_direction: 0,
})
.expect("list_tags");
}))
.await
.unwrap()
.into_inner();
let names: Vec<String> = result.tags.iter().map(|t| t.name.clone()).collect();
assert!(names.contains(&"v0.1.0".to_string()));
}
#[test]
fn test_get_tag() {
let (_dir, gb) = common::setup_bare_repo();
let tag = gb
.get_tag(GetTagRequest {
repository: None,
#[tokio::test]
async fn test_get_tag() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let tag = svc
.get_tag(tonic::Request::new(GetTagRequest {
repository: Some(hdr()),
name: "v0.1.0".into(),
include_raw: false,
})
.expect("get_tag");
}))
.await
.unwrap()
.into_inner();
assert_eq!(tag.name, "v0.1.0");
assert!(tag.target_oid.is_some());
assert_eq!(tag.full_ref, "refs/tags/v0.1.0");
}
#[test]
fn test_create_and_delete_lightweight_tag() {
let (_dir, gb) = common::setup_bare_repo();
let tag = gb
.create_tag(CreateTagRequest {
repository: None,
#[tokio::test]
async fn test_create_and_delete_lightweight_tag() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let tag = svc
.create_tag(tonic::Request::new(CreateTagRequest {
repository: Some(hdr()),
name: "v0.2.0".into(),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -49,32 +64,38 @@ fn test_create_and_delete_lightweight_tag() {
tagger: None,
force: false,
annotated: false,
})
.expect("create_tag");
}))
.await
.unwrap()
.into_inner();
assert_eq!(tag.name, "v0.2.0");
assert!(!tag.annotated);
gb.delete_tag(DeleteTagRequest {
repository: None,
svc.delete_tag(tonic::Request::new(DeleteTagRequest {
repository: Some(hdr()),
name: "v0.2.0".into(),
})
.expect("delete_tag");
}))
.await
.unwrap();
let result = gb.get_tag(GetTagRequest {
repository: None,
name: "v0.2.0".into(),
include_raw: false,
});
let result = svc
.get_tag(tonic::Request::new(GetTagRequest {
repository: Some(hdr()),
name: "v0.2.0".into(),
include_raw: false,
}))
.await;
assert!(result.is_err(), "deleted tag should not exist");
}
#[test]
fn test_create_annotated_tag() {
let (_dir, gb) = common::setup_bare_repo();
let tag = gb
.create_tag(CreateTagRequest {
repository: None,
#[tokio::test]
async fn test_create_annotated_tag() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let tag = svc
.create_tag(tonic::Request::new(CreateTagRequest {
repository: Some(hdr()),
name: "v1.0.0".into(),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -85,8 +106,10 @@ fn test_create_annotated_tag() {
tagger: None,
force: false,
annotated: true,
})
.expect("create annotated tag");
}))
.await
.unwrap()
.into_inner();
assert_eq!(tag.name, "v1.0.0");
assert!(tag.annotated, "should be annotated");
@@ -97,12 +120,13 @@ fn test_create_annotated_tag() {
);
}
#[test]
fn test_list_tags_with_pattern() {
let (_dir, gb) = common::setup_bare_repo();
#[tokio::test]
async fn test_list_tags_with_pattern() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
gb.create_tag(CreateTagRequest {
repository: None,
svc.create_tag(tonic::Request::new(CreateTagRequest {
repository: Some(hdr()),
name: "release-1.0".into(),
target: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
@@ -113,17 +137,20 @@ fn test_list_tags_with_pattern() {
tagger: None,
force: false,
annotated: false,
})
.expect("create release tag");
}))
.await
.unwrap();
let result = gb
.list_tags(ListTagsRequest {
repository: None,
let result = svc
.list_tags(tonic::Request::new(ListTagsRequest {
repository: Some(hdr()),
pattern: "release".into(),
pagination: None,
sort_direction: 0,
})
.expect("list_tags with pattern");
}))
.await
.unwrap()
.into_inner();
assert!(
result.tags.iter().all(|t| t.name.contains("release")),
@@ -132,15 +159,18 @@ fn test_list_tags_with_pattern() {
assert!(!result.tags.is_empty());
}
#[test]
fn test_verify_tag() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.verify_tag(VerifyTagRequest {
repository: None,
#[tokio::test]
async fn test_verify_tag() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.verify_tag(tonic::Request::new(VerifyTagRequest {
repository: Some(hdr()),
name: "v0.1.0".into(),
})
.expect("verify_tag");
}))
.await
.unwrap()
.into_inner();
assert!(!result.verified, "unsigned tag should not be verified");
}
+119 -61
View File
@@ -1,13 +1,22 @@
mod common;
use gitks::pb::tree_service_server::TreeService;
use gitks::pb::*;
#[test]
fn test_list_tree_recursive() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_tree(ListTreeRequest {
repository: None,
fn hdr() -> RepositoryHeader {
RepositoryHeader {
relative_path: "test-repo".into(),
..Default::default()
}
}
#[tokio::test]
async fn test_list_tree_recursive() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.list_tree(tonic::Request::new(ListTreeRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -16,8 +25,10 @@ fn test_list_tree_recursive() {
path: String::new(),
recursive: true,
pagination: None,
})
.expect("list_tree recursive");
}))
.await
.unwrap()
.into_inner();
let paths: Vec<String> = result.entries.iter().map(|e| e.path.clone()).collect();
assert!(
@@ -27,33 +38,38 @@ fn test_list_tree_recursive() {
);
}
#[test]
fn test_get_tree_subpath() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.get_tree(GetTreeRequest {
repository: None,
#[tokio::test]
async fn test_get_tree_subpath() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.get_tree(tonic::Request::new(GetTreeRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "src".into(),
})
.expect("get_tree subpath");
}))
.await
.unwrap()
.into_inner();
assert!(result.oid.is_some());
let root_tree = gb
.get_tree(GetTreeRequest {
repository: None,
let root_tree = svc
.get_tree(tonic::Request::new(GetTreeRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: String::new(),
})
.expect("get_tree root");
}))
.await
.unwrap()
.into_inner();
assert_ne!(
result.oid.unwrap().hex,
root_tree.oid.unwrap().hex,
@@ -61,12 +77,13 @@ fn test_get_tree_subpath() {
);
}
#[test]
fn test_find_files() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.find_files(FindFilesRequest {
repository: None,
#[tokio::test]
async fn test_find_files() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.find_files(tonic::Request::new(FindFilesRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -75,19 +92,22 @@ fn test_find_files() {
pattern: "mod.rs".into(),
pathspec: vec![],
pagination: None,
})
.expect("find_files");
}))
.await
.unwrap()
.into_inner();
assert!(!result.files.is_empty());
assert!(result.files.iter().all(|f| f.path.contains("mod.rs")));
}
#[test]
fn test_get_blob() {
let (_dir, gb) = common::setup_bare_repo();
let blob = gb
.get_blob(GetBlobRequest {
repository: None,
#[tokio::test]
async fn test_get_blob() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let blob = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -96,8 +116,10 @@ fn test_get_blob() {
path: "README.md".into(),
oid: None,
max_bytes: 0,
})
.expect("get_blob");
}))
.await
.unwrap()
.into_inner();
let content = String::from_utf8_lossy(&blob.data);
assert!(content.contains("# Test"));
@@ -105,12 +127,13 @@ fn test_get_blob() {
assert!(!blob.binary);
}
#[test]
fn test_get_blob_with_truncation() {
let (_dir, gb) = common::setup_bare_repo();
let blob = gb
.get_blob(GetBlobRequest {
repository: None,
#[tokio::test]
async fn test_get_blob_with_truncation() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let blob = svc
.get_blob(tonic::Request::new(GetBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -119,8 +142,10 @@ fn test_get_blob_with_truncation() {
path: "README.md".into(),
oid: None,
max_bytes: 5,
})
.expect("get_blob truncated");
}))
.await
.unwrap()
.into_inner();
assert_eq!(blob.data.len(), 5);
assert!(blob.truncated);
@@ -130,32 +155,36 @@ fn test_get_blob_with_truncation() {
);
}
#[test]
fn test_get_file_metadata() {
let (_dir, gb) = common::setup_bare_repo();
let meta = gb
.get_file_metadata(GetFileMetadataRequest {
repository: None,
#[tokio::test]
async fn test_get_file_metadata() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let meta = svc
.get_file_metadata(tonic::Request::new(GetFileMetadataRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "README.md".into(),
})
.expect("get_file_metadata");
}))
.await
.unwrap()
.into_inner();
assert_eq!(meta.path, "README.md");
assert!(meta.oid.is_some());
assert_eq!(meta.r#type, ObjectType::Blob as i32);
}
#[test]
fn test_list_tree_with_pagination() {
let (_dir, gb) = common::setup_bare_repo();
let result = gb
.list_tree(ListTreeRequest {
repository: None,
#[tokio::test]
async fn test_list_tree_with_pagination() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let result = svc
.list_tree(tonic::Request::new(ListTreeRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
@@ -167,10 +196,39 @@ fn test_list_tree_with_pagination() {
page_size: 1,
page_token: String::new(),
}),
})
.expect("list_tree paginated");
}))
.await
.unwrap()
.into_inner();
assert_eq!(result.entries.len(), 1);
let pi = result.page_info.unwrap();
assert!(pi.has_next_page);
}
#[tokio::test]
async fn test_get_raw_blob() {
let (dir, _gb) = common::setup_bare_repo();
let svc = common::setup_service(dir.path());
let stream = svc
.get_raw_blob(tonic::Request::new(GetRawBlobRequest {
repository: Some(hdr()),
revision: Some(ObjectSelector {
selector: Some(object_selector::Selector::Revision(ObjectName {
revision: "main".into(),
})),
}),
path: "README.md".into(),
oid: None,
}))
.await
.unwrap()
.into_inner();
let chunks: Vec<_> = tokio_stream::StreamExt::collect(stream).await;
assert!(!chunks.is_empty(), "should have raw blob data");
let data = &chunks[0].as_ref().unwrap().data;
assert!(!data.is_empty(), "raw blob should not be empty");
let content = String::from_utf8_lossy(data);
assert!(content.contains("# Test"));
}