upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/common/git_server.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-08 00:50:54 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-08 00:50:54 +0000
commitf75e1c59aacf5ce668fd327e4e3d827511661c2a (patch)
tree867926c7503e7c587e86c67896a9e7347600447b /tests/common/git_server.rs
parent3f14f998d64b5fa15bdddd7570b4f72874eb9f29 (diff)
chore: cargo fmt
Diffstat (limited to 'tests/common/git_server.rs')
-rw-r--r--tests/common/git_server.rs98
1 files changed, 61 insertions, 37 deletions
diff --git a/tests/common/git_server.rs b/tests/common/git_server.rs
index adf66b5..d0d727e 100644
--- a/tests/common/git_server.rs
+++ b/tests/common/git_server.rs
@@ -301,7 +301,10 @@ fn find_free_port() -> u16 {
301 use std::net::TcpListener; 301 use std::net::TcpListener;
302 302
303 let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port"); 303 let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port");
304 let port = listener.local_addr().expect("Failed to get local addr").port(); 304 let port = listener
305 .local_addr()
306 .expect("Failed to get local addr")
307 .port();
305 drop(listener); 308 drop(listener);
306 port 309 port
307} 310}
@@ -320,7 +323,10 @@ async fn wait_for_server_ready(port: u16) {
320 } 323 }
321 Err(_) => { 324 Err(_) => {
322 if attempt == max_attempts - 1 { 325 if attempt == max_attempts - 1 {
323 panic!("SimpleGitServer failed to start after {} attempts", max_attempts); 326 panic!(
327 "SimpleGitServer failed to start after {} attempts",
328 max_attempts
329 );
324 } 330 }
325 tokio::time::sleep(delay).await; 331 tokio::time::sleep(delay).await;
326 } 332 }
@@ -366,10 +372,13 @@ mod tests {
366 .await 372 .await
367 .expect("Failed to fetch info/refs"); 373 .expect("Failed to fetch info/refs");
368 374
369 assert!(response.status().is_success(), "info/refs should be accessible"); 375 assert!(
376 response.status().is_success(),
377 "info/refs should be accessible"
378 );
370 379
371 let body = response.text().await.expect("Failed to read response body"); 380 let body = response.text().await.expect("Failed to read response body");
372 381
373 // Should contain at least one ref (HEAD or refs/heads/main) 382 // Should contain at least one ref (HEAD or refs/heads/main)
374 assert!( 383 assert!(
375 body.contains("refs/heads/main") || body.contains("HEAD"), 384 body.contains("refs/heads/main") || body.contains("HEAD"),
@@ -404,7 +413,7 @@ mod tests {
404 ); 413 );
405 414
406 let stdout = String::from_utf8_lossy(&output.stdout); 415 let stdout = String::from_utf8_lossy(&output.stdout);
407 416
408 // Should list the main branch with the correct commit 417 // Should list the main branch with the correct commit
409 assert!( 418 assert!(
410 stdout.contains(&commit_hash), 419 stdout.contains(&commit_hash),
@@ -433,7 +442,7 @@ mod tests {
433 442
434 // Create a destination repo to fetch into 443 // Create a destination repo to fetch into
435 let dest_dir = tempfile::tempdir().expect("Failed to create dest dir"); 444 let dest_dir = tempfile::tempdir().expect("Failed to create dest dir");
436 445
437 // Initialize empty repo (using tokio::process::Command) 446 // Initialize empty repo (using tokio::process::Command)
438 let output = tokio::process::Command::new("git") 447 let output = tokio::process::Command::new("git")
439 .args(["init"]) 448 .args(["init"])
@@ -487,14 +496,23 @@ mod tests {
487 #[test] 496 #[test]
488 fn test_is_safe_path_blocks_traversal() { 497 fn test_is_safe_path_blocks_traversal() {
489 let repo_path = Path::new("/tmp/repo"); 498 let repo_path = Path::new("/tmp/repo");
490 499
491 // Safe paths 500 // Safe paths
492 assert!(is_safe_path(Path::new("/tmp/repo/info/refs"), repo_path)); 501 assert!(is_safe_path(Path::new("/tmp/repo/info/refs"), repo_path));
493 assert!(is_safe_path(Path::new("/tmp/repo/objects/pack/file.pack"), repo_path)); 502 assert!(is_safe_path(
494 503 Path::new("/tmp/repo/objects/pack/file.pack"),
504 repo_path
505 ));
506
495 // Unsafe paths (path traversal) 507 // Unsafe paths (path traversal)
496 assert!(!is_safe_path(Path::new("/tmp/repo/../etc/passwd"), repo_path)); 508 assert!(!is_safe_path(
497 assert!(!is_safe_path(Path::new("/tmp/repo/../../etc/passwd"), repo_path)); 509 Path::new("/tmp/repo/../etc/passwd"),
510 repo_path
511 ));
512 assert!(!is_safe_path(
513 Path::new("/tmp/repo/../../etc/passwd"),
514 repo_path
515 ));
498 } 516 }
499} 517}
500 518
@@ -563,17 +581,19 @@ impl SmartGitServer {
563 } 581 }
564 582
565 // 3. Create and bind listener (eliminates port race condition) 583 // 3. Create and bind listener (eliminates port race condition)
566 let std_listener = std::net::TcpListener::bind("127.0.0.1:0") 584 let std_listener =
567 .expect("Failed to bind to random port"); 585 std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port");
568 let port = std_listener.local_addr() 586 let port = std_listener
587 .local_addr()
569 .expect("Failed to get local addr") 588 .expect("Failed to get local addr")
570 .port(); 589 .port();
571 590
572 // Convert to tokio listener (keeps port bound) 591 // Convert to tokio listener (keeps port bound)
573 std_listener.set_nonblocking(true) 592 std_listener
593 .set_nonblocking(true)
574 .expect("Failed to set non-blocking"); 594 .expect("Failed to set non-blocking");
575 let listener = TcpListener::from_std(std_listener) 595 let listener =
576 .expect("Failed to convert to tokio listener"); 596 TcpListener::from_std(std_listener).expect("Failed to convert to tokio listener");
577 597
578 // 4. Create shutdown channel 598 // 4. Create shutdown channel
579 let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); 599 let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>();
@@ -690,15 +710,13 @@ async fn handle_smart_request(
690 // Route: GET /info/refs?service=git-upload-pack 710 // Route: GET /info/refs?service=git-upload-pack
691 if method == hyper::Method::GET && path.ends_with("/info/refs") { 711 if method == hyper::Method::GET && path.ends_with("/info/refs") {
692 // Parse service from query string 712 // Parse service from query string
693 let service = query 713 let service = query.split('&').find_map(|param| {
694 .split('&') 714 let mut parts = param.splitn(2, '=');
695 .find_map(|param| { 715 match (parts.next(), parts.next()) {
696 let mut parts = param.splitn(2, '='); 716 (Some("service"), Some(svc)) => Some(svc),
697 match (parts.next(), parts.next()) { 717 _ => None,
698 (Some("service"), Some(svc)) => Some(svc), 718 }
699 _ => None, 719 });
700 }
701 });
702 720
703 match service { 721 match service {
704 Some("git-upload-pack") => { 722 Some("git-upload-pack") => {
@@ -714,7 +732,9 @@ async fn handle_smart_request(
714 _ => { 732 _ => {
715 return Ok(Response::builder() 733 return Ok(Response::builder()
716 .status(StatusCode::BAD_REQUEST) 734 .status(StatusCode::BAD_REQUEST)
717 .body(Full::new(Bytes::from("Missing or invalid service parameter"))) 735 .body(Full::new(Bytes::from(
736 "Missing or invalid service parameter",
737 )))
718 .unwrap()); 738 .unwrap());
719 } 739 }
720 } 740 }
@@ -740,8 +760,8 @@ async fn handle_info_refs_upload_pack(
740 git_protocol_version: Option<&str>, 760 git_protocol_version: Option<&str>,
741) -> Result<Response<Full<Bytes>>, hyper::Error> { 761) -> Result<Response<Full<Bytes>>, hyper::Error> {
742 use std::process::Stdio; 762 use std::process::Stdio;
743 use tokio::process::Command as TokioCommand;
744 use tokio::io::AsyncReadExt; 763 use tokio::io::AsyncReadExt;
764 use tokio::process::Command as TokioCommand;
745 765
746 // Spawn git upload-pack --advertise-refs 766 // Spawn git upload-pack --advertise-refs
747 let mut cmd = TokioCommand::new("git"); 767 let mut cmd = TokioCommand::new("git");
@@ -763,8 +783,7 @@ async fn handle_info_refs_upload_pack(
763 .stdout(Stdio::piped()) 783 .stdout(Stdio::piped())
764 .stderr(Stdio::piped()); 784 .stderr(Stdio::piped());
765 785
766 let mut child = match cmd.spawn() 786 let mut child = match cmd.spawn() {
767 {
768 Ok(child) => child, 787 Ok(child) => child,
769 Err(e) => { 788 Err(e) => {
770 eprintln!("Failed to spawn git upload-pack: {}", e); 789 eprintln!("Failed to spawn git upload-pack: {}", e);
@@ -800,7 +819,7 @@ async fn handle_info_refs_upload_pack(
800 let len = service_line.len() + 4; 819 let len = service_line.len() + 4;
801 response_body.extend_from_slice(format!("{:04x}", len).as_bytes()); 820 response_body.extend_from_slice(format!("{:04x}", len).as_bytes());
802 response_body.extend_from_slice(service_line.as_bytes()); 821 response_body.extend_from_slice(service_line.as_bytes());
803 822
804 // Flush packet 823 // Flush packet
805 response_body.extend_from_slice(b"0000"); 824 response_body.extend_from_slice(b"0000");
806 825
@@ -809,7 +828,10 @@ async fn handle_info_refs_upload_pack(
809 828
810 Ok(Response::builder() 829 Ok(Response::builder()
811 .status(StatusCode::OK) 830 .status(StatusCode::OK)
812 .header("Content-Type", "application/x-git-upload-pack-advertisement") 831 .header(
832 "Content-Type",
833 "application/x-git-upload-pack-advertisement",
834 )
813 .header("Cache-Control", "no-cache") 835 .header("Cache-Control", "no-cache")
814 .body(Full::new(Bytes::from(response_body))) 836 .body(Full::new(Bytes::from(response_body)))
815 .unwrap()) 837 .unwrap())
@@ -850,8 +872,7 @@ async fn handle_upload_pack(
850 .stdout(Stdio::piped()) 872 .stdout(Stdio::piped())
851 .stderr(Stdio::piped()); 873 .stderr(Stdio::piped());
852 874
853 let mut child = match cmd.spawn() 875 let mut child = match cmd.spawn() {
854 {
855 Ok(child) => child, 876 Ok(child) => child,
856 Err(e) => { 877 Err(e) => {
857 eprintln!("Failed to spawn git upload-pack: {}", e); 878 eprintln!("Failed to spawn git upload-pack: {}", e);
@@ -957,7 +978,10 @@ mod smart_git_server_tests {
957 content_type 978 content_type
958 ); 979 );
959 980
960 let body = response.bytes().await.expect("Failed to read response body"); 981 let body = response
982 .bytes()
983 .await
984 .expect("Failed to read response body");
961 985
962 // Should start with service advertisement pkt-line 986 // Should start with service advertisement pkt-line
963 let body_str = String::from_utf8_lossy(&body); 987 let body_str = String::from_utf8_lossy(&body);
@@ -1077,7 +1101,7 @@ mod smart_git_server_tests {
1077 #[tokio::test] 1101 #[tokio::test]
1078 async fn test_smart_git_server_shallow_fetch() { 1102 async fn test_smart_git_server_shallow_fetch() {
1079 // This is the KEY test - shallow fetch requires smart HTTP protocol 1103 // This is the KEY test - shallow fetch requires smart HTTP protocol
1080 1104
1081 // Create a source repo with a commit 1105 // Create a source repo with a commit
1082 let source_dir = tempfile::tempdir().expect("Failed to create source dir"); 1106 let source_dir = tempfile::tempdir().expect("Failed to create source dir");
1083 let commit_hash = create_test_repo_with_commit(source_dir.path(), CommitVariant::StateTest) 1107 let commit_hash = create_test_repo_with_commit(source_dir.path(), CommitVariant::StateTest)