//! Copyright (c) 2022-2026 GitDataAi All rights reserved. mod common; use gitks::pb::commit_service_server::CommitService; use gitks::pb::diff_service_server::DiffService; use gitks::pb::*; 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(), })), }), head: Some(ObjectSelector { selector: Some(object_selector::Selector::Revision(ObjectName { revision: "main".into(), })), }), options: None, pagination: None, })) .await .unwrap() .into_inner(); assert!(!result.files.is_empty()); let paths: Vec<&str> = result.files.iter().map(|f| f.new_path.as_str()).collect(); assert!( paths.iter().any(|p| p.contains("src.txt")), "should include src.txt, got: {:?}", paths ); let stats = result.stats.unwrap(); assert!(stats.additions > 0 || stats.changed_files > 0); } #[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(), })), }), head: Some(ObjectSelector { selector: Some(object_selector::Selector::Revision(ObjectName { revision: "main".into(), })), }), options: Some(DiffOptions { include_patch: true, context_lines: 3, ..Default::default() }), pagination: None, })) .await .unwrap() .into_inner(); assert!(!result.files.is_empty()); for file in &result.files { if !file.binary { assert!( !file.patch.is_empty(), "non-binary file should have patch: {}", file.new_path ); } } } #[tokio::test] async fn test_get_diff_with_rename_detection() { let (dir, _gb) = common::setup_bare_repo(); let svc = common::setup_service(dir.path()); svc.create_commit(tonic::Request::new(CreateCommitRequest { repository: Some(hdr()), branch: "main".into(), message: "rename file".into(), author: None, committer: None, actions: vec![ CreateCommitAction { action: create_commit_action::Action::CreateCommitActionCreate as i32, file_path: "renamed.txt".into(), previous_path: String::new(), content: b"source\n".to_vec(), encoding: String::new(), executable: false, last_commit_oid: None, }, CreateCommitAction { action: create_commit_action::Action::CreateCommitActionDelete as i32, file_path: "src.txt".into(), previous_path: String::new(), content: vec![], encoding: String::new(), executable: false, last_commit_oid: None, }, ], start_revision: Some(ObjectSelector { selector: Some(object_selector::Selector::Revision(ObjectName { revision: "main".into(), })), }), force: false, trailers: vec![], })) .await .unwrap(); 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(), })), }), head: Some(ObjectSelector { selector: Some(object_selector::Selector::Revision(ObjectName { revision: "main".into(), })), }), options: Some(DiffOptions { rename_detection: true, ..Default::default() }), pagination: None, })) .await .unwrap() .into_inner(); let has_rename = result .files .iter() .any(|f| f.change_type == diff_file::ChangeType::DiffFileChangeTypeRenamed as i32); assert!( has_rename, "should detect rename, files: {:?}", result.files.len() ); } #[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(), })), }), path: String::new(), since: None, until: None, first_parent: false, all: false, reverse: true, max_parents: 0, min_parents: 0, pagination: Some(Pagination { page_size: 1, page_token: String::new(), }), })) .await .unwrap() .into_inner(); let root_oid = commits.commits[0].oid.as_ref().unwrap().hex.clone(); 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, })), }), options: None, pagination: None, })) .await .unwrap() .into_inner(); assert!(!result.files.is_empty(), "root commit should have files"); } #[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(), })), }), head: Some(ObjectSelector { selector: Some(object_selector::Selector::Revision(ObjectName { revision: "main".into(), })), }), options: None, })) .await .unwrap() .into_inner(); assert!(stats.additions > 0 || stats.changed_files > 0); } #[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(), })), }), head: Some(ObjectSelector { selector: Some(object_selector::Selector::Revision(ObjectName { revision: "main".into(), })), }), options: None, })) .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.as_ref().unwrap().data).to_string()) .collect(); assert!(combined.contains("diff --git") || combined.contains("@@")); } #[test] fn test_find_changed_paths() { 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.find_changed_paths(FindChangedPathsRequest { repository: Some(hdr()), base: feature_oid, head: main_oid, paths: vec![], }).unwrap(); assert!(!resp.paths.is_empty()); } #[test] fn test_find_changed_paths_same_ref() { let (_dir, gb) = common::setup_bare_repo(); let oid = common::get_main_oid(&gb); let resp = gb.find_changed_paths(FindChangedPathsRequest { repository: Some(hdr()), base: oid.clone(), head: oid, paths: vec![], }).unwrap(); assert!(resp.paths.is_empty()); } #[test] fn test_raw_diff() { let (_dir, gb) = common::setup_bare_repo(); let feature_oid = common::get_feature_oid(&gb); let main_oid = common::get_main_oid(&gb); let chunks = gb.raw_diff(RawDiffRequest { repository: Some(hdr()), base: feature_oid, head: main_oid, options: None, }).unwrap(); assert!(!chunks.is_empty()); let combined: Vec = chunks.iter().flat_map(|c| c.data.clone()).collect(); let text = String::from_utf8_lossy(&combined); assert!(text.contains("diff")); } #[test] fn test_raw_patch() { let (_dir, gb) = common::setup_bare_repo(); let feature_oid = common::get_feature_oid(&gb); let main_oid = common::get_main_oid(&gb); let chunks = gb.raw_patch(RawPatchRequest { repository: Some(hdr()), base: feature_oid, head: main_oid, }).unwrap(); assert!(!chunks.is_empty()); let combined: Vec = chunks.iter().flat_map(|c| c.data.clone()).collect(); let text = String::from_utf8_lossy(&combined); assert!(text.contains("From")); } #[test] fn test_get_raw_changes() { 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.get_raw_changes(GetRawChangesRequest { repository: Some(hdr()), base: feature_oid, head: main_oid, }).unwrap(); assert!(!resp.changes.is_empty()); }