mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
feat(grasp-audit): add git HTTP protocol test for gzip-encoded requests
Add MVP implementation for testing git HTTP protocol compliance: - Add git module with GitClient wrapper for HTTP protocol testing - Add GitHttpProtocolTests with test_gzip_encoded_upload_pack test - Add integration test in ngit-grasp that uses TestRelay fixture The test validates that the server correctly handles gzip-encoded git-upload-pack requests, which was the source of a production bug. Uses actual git fetch operations to test the real production scenario.
This commit is contained in:
Generated
+1
@@ -787,6 +787,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"regex",
|
"regex",
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ chrono = "0.4"
|
|||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
# Compression (for gzip-encoded git requests)
|
||||||
|
flate2 = "1"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
//! Git HTTP protocol client for testing
|
||||||
|
//!
|
||||||
|
//! This module provides a minimal HTTP client specifically designed for testing
|
||||||
|
//! Git HTTP protocol compliance, including gzip-encoded request handling.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// Git HTTP protocol client for testing
|
||||||
|
///
|
||||||
|
/// This client provides methods for making Git HTTP protocol requests,
|
||||||
|
/// with support for gzip-encoded request bodies (which is how git clients
|
||||||
|
/// typically send data to servers).
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use grasp_audit::git::GitClient;
|
||||||
|
///
|
||||||
|
/// # async fn example() -> anyhow::Result<()> {
|
||||||
|
/// let client = GitClient::new("localhost:7000");
|
||||||
|
///
|
||||||
|
/// // Test gzip-encoded upload-pack request
|
||||||
|
/// let response = client.upload_pack_gzip("npub1...", "my-repo", b"want data").await?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub struct GitClient {
|
||||||
|
/// The relay domain (host:port)
|
||||||
|
relay_domain: String,
|
||||||
|
/// HTTP client for making requests
|
||||||
|
http_client: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitClient {
|
||||||
|
/// Create a new GitClient for the given relay domain
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `relay_domain` - The relay domain (e.g., "localhost:7000" or "127.0.0.1:8080")
|
||||||
|
pub fn new(relay_domain: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
relay_domain: relay_domain.to_string(),
|
||||||
|
http_client: reqwest::Client::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the info/refs endpoint for git-upload-pack service
|
||||||
|
///
|
||||||
|
/// This is the first request git makes when cloning/fetching.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `npub` - The bech32 public key of the repository owner
|
||||||
|
/// * `repo_id` - The repository identifier (d-tag value)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The raw response body as bytes
|
||||||
|
pub async fn get_info_refs(&self, npub: &str, repo_id: &str) -> Result<Vec<u8>> {
|
||||||
|
let url = format!(
|
||||||
|
"http://{}/{}/{}.git/info/refs?service=git-upload-pack",
|
||||||
|
self.relay_domain, npub, repo_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.get(&url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.context("Failed to send info/refs request")?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"info/refs request failed with status: {}",
|
||||||
|
response.status()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map(|b| b.to_vec())
|
||||||
|
.context("Failed to read info/refs response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a gzip-encoded git-upload-pack request
|
||||||
|
///
|
||||||
|
/// This tests the server's ability to handle gzip-encoded request bodies,
|
||||||
|
/// which is how git clients typically send data. This was the source of
|
||||||
|
/// a production bug where the server failed to decompress gzip requests.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `npub` - The bech32 public key of the repository owner
|
||||||
|
/// * `repo_id` - The repository identifier (d-tag value)
|
||||||
|
/// * `body` - The raw git protocol data to send (will be gzip-compressed)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Vec<u8>)` - The response body
|
||||||
|
/// * `Err` - If the request failed
|
||||||
|
pub async fn upload_pack_gzip(
|
||||||
|
&self,
|
||||||
|
npub: &str,
|
||||||
|
repo_id: &str,
|
||||||
|
body: &[u8],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let url = format!(
|
||||||
|
"http://{}/{}/{}.git/git-upload-pack",
|
||||||
|
self.relay_domain, npub, repo_id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compress the body with gzip
|
||||||
|
let compressed = gzip_compress(body)?;
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.post(&url)
|
||||||
|
.header("Content-Type", "application/x-git-upload-pack-request")
|
||||||
|
.header("Content-Encoding", "gzip")
|
||||||
|
.header("Accept-Encoding", "gzip")
|
||||||
|
.body(compressed)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.context("Failed to send git-upload-pack request")?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let body = response
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| "<failed to read body>".to_string());
|
||||||
|
anyhow::bail!(
|
||||||
|
"git-upload-pack request failed with status {}: {}",
|
||||||
|
status,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map(|b| b.to_vec())
|
||||||
|
.context("Failed to read git-upload-pack response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a gzip-encoded git-receive-pack request
|
||||||
|
///
|
||||||
|
/// This tests the server's ability to handle gzip-encoded push requests.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `npub` - The bech32 public key of the repository owner
|
||||||
|
/// * `repo_id` - The repository identifier (d-tag value)
|
||||||
|
/// * `body` - The raw git protocol data to send (will be gzip-compressed)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Vec<u8>)` - The response body
|
||||||
|
/// * `Err` - If the request failed
|
||||||
|
pub async fn receive_pack_gzip(
|
||||||
|
&self,
|
||||||
|
npub: &str,
|
||||||
|
repo_id: &str,
|
||||||
|
body: &[u8],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let url = format!(
|
||||||
|
"http://{}/{}/{}.git/git-receive-pack",
|
||||||
|
self.relay_domain, npub, repo_id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compress the body with gzip
|
||||||
|
let compressed = gzip_compress(body)?;
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.post(&url)
|
||||||
|
.header("Content-Type", "application/x-git-receive-pack-request")
|
||||||
|
.header("Content-Encoding", "gzip")
|
||||||
|
.body(compressed)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.context("Failed to send git-receive-pack request")?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let body = response
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| "<failed to read body>".to_string());
|
||||||
|
anyhow::bail!(
|
||||||
|
"git-receive-pack request failed with status {}: {}",
|
||||||
|
status,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map(|b| b.to_vec())
|
||||||
|
.context("Failed to read git-receive-pack response body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress data using gzip
|
||||||
|
fn gzip_compress(data: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
use flate2::write::GzEncoder;
|
||||||
|
use flate2::Compression;
|
||||||
|
|
||||||
|
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
||||||
|
encoder
|
||||||
|
.write_all(data)
|
||||||
|
.context("Failed to write data to gzip encoder")?;
|
||||||
|
encoder
|
||||||
|
.finish()
|
||||||
|
.context("Failed to finish gzip compression")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gzip_compress() {
|
||||||
|
let data = b"Hello, World!";
|
||||||
|
let compressed = gzip_compress(data).unwrap();
|
||||||
|
|
||||||
|
// Compressed data should be different from original
|
||||||
|
assert_ne!(compressed.as_slice(), data);
|
||||||
|
|
||||||
|
// Compressed data should have gzip magic bytes
|
||||||
|
assert!(compressed.len() >= 2);
|
||||||
|
assert_eq!(compressed[0], 0x1f);
|
||||||
|
assert_eq!(compressed[1], 0x8b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_git_client_new() {
|
||||||
|
let client = GitClient::new("localhost:7000");
|
||||||
|
assert_eq!(client.relay_domain, "localhost:7000");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//! Git HTTP protocol testing utilities
|
||||||
|
//!
|
||||||
|
//! This module provides a `GitClient` wrapper for testing Git HTTP protocol
|
||||||
|
//! compliance, particularly for operations that require specific HTTP headers
|
||||||
|
//! or encoding (like gzip-compressed request bodies).
|
||||||
|
//!
|
||||||
|
//! ## Overview
|
||||||
|
//!
|
||||||
|
//! The Git HTTP protocol uses specific content types and encodings that differ
|
||||||
|
//! from standard HTTP. This module provides utilities to:
|
||||||
|
//!
|
||||||
|
//! - Make raw Git HTTP protocol requests
|
||||||
|
//! - Test gzip-encoded request handling
|
||||||
|
//! - Verify protocol compliance
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! use grasp_audit::git::GitClient;
|
||||||
|
//!
|
||||||
|
//! # async fn example() -> anyhow::Result<()> {
|
||||||
|
//! let client = GitClient::new("localhost:7000");
|
||||||
|
//!
|
||||||
|
//! // Fetch refs advertisement
|
||||||
|
//! let refs = client.get_info_refs("npub1...", "my-repo").await?;
|
||||||
|
//! println!("Refs: {:?}", refs);
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
mod client;
|
||||||
|
|
||||||
|
pub use client::GitClient;
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
pub mod audit;
|
pub mod audit;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod fixtures;
|
pub mod fixtures;
|
||||||
|
pub mod git;
|
||||||
pub mod isolation;
|
pub mod isolation;
|
||||||
pub mod result;
|
pub mod result;
|
||||||
pub mod specs;
|
pub mod specs;
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
//! GRASP-01 Git HTTP Protocol Tests
|
||||||
|
//!
|
||||||
|
//! Tests that verify Git HTTP protocol compliance, particularly around
|
||||||
|
//! request encoding and content negotiation.
|
||||||
|
//!
|
||||||
|
//! ## Test Coverage
|
||||||
|
//!
|
||||||
|
//! - Gzip-encoded request handling (production bug fix validation)
|
||||||
|
//! - Content-Type header handling
|
||||||
|
//! - Git protocol version negotiation
|
||||||
|
//!
|
||||||
|
//! ## Background
|
||||||
|
//!
|
||||||
|
//! Git clients typically send gzip-compressed request bodies when communicating
|
||||||
|
//! with HTTP backends. A production bug was discovered where the server failed
|
||||||
|
//! to properly decompress these requests, causing fetch/clone operations to fail.
|
||||||
|
//!
|
||||||
|
//! These tests validate that the server correctly handles gzip-encoded requests.
|
||||||
|
//!
|
||||||
|
//! ## Running Tests
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! # From ngit-grasp root
|
||||||
|
//! cargo test --test git_http_protocol
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use crate::{clone_repo, AuditClient, FixtureKind, TestContext, TestResult};
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Test suite for Git HTTP protocol compliance
|
||||||
|
pub struct GitHttpProtocolTests;
|
||||||
|
|
||||||
|
impl GitHttpProtocolTests {
|
||||||
|
/// Run all Git HTTP protocol tests
|
||||||
|
pub async fn run_all(client: &AuditClient, relay_domain: &str) -> crate::AuditResult {
|
||||||
|
let mut results = crate::AuditResult::new("GRASP-01 Git HTTP Protocol Tests");
|
||||||
|
|
||||||
|
results.add(Self::test_gzip_encoded_upload_pack(client, relay_domain).await);
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that gzip-encoded git-upload-pack requests are handled correctly
|
||||||
|
///
|
||||||
|
/// This test validates the fix for a production bug where the server failed
|
||||||
|
/// to decompress gzip-encoded request bodies, causing git fetch/clone to fail.
|
||||||
|
///
|
||||||
|
/// ## What This Tests
|
||||||
|
///
|
||||||
|
/// 1. Creates a repository with pushed data (so there's something to fetch)
|
||||||
|
/// 2. Clones the repository
|
||||||
|
/// 3. Performs a git fetch operation (which sends gzip-encoded requests)
|
||||||
|
/// 4. Verifies the fetch succeeds
|
||||||
|
///
|
||||||
|
/// ## Git Protocol Details
|
||||||
|
///
|
||||||
|
/// Git clients send `Content-Encoding: gzip` headers when the request body
|
||||||
|
/// is compressed. The server MUST decompress the body before processing.
|
||||||
|
///
|
||||||
|
/// When git performs a fetch, it:
|
||||||
|
/// 1. Requests info/refs to discover available refs
|
||||||
|
/// 2. Sends a POST to git-upload-pack with "want" lines (gzip-encoded)
|
||||||
|
/// 3. Receives pack data in response
|
||||||
|
///
|
||||||
|
/// ## Spec Reference
|
||||||
|
///
|
||||||
|
/// While not explicitly in GRASP-01, this is required for HTTP backend
|
||||||
|
/// compatibility with standard git clients.
|
||||||
|
pub async fn test_gzip_encoded_upload_pack(
|
||||||
|
client: &AuditClient,
|
||||||
|
relay_domain: &str,
|
||||||
|
) -> TestResult {
|
||||||
|
let test_name = "test_gzip_encoded_upload_pack";
|
||||||
|
let spec_ref = "GRASP-01:git-http:implicit";
|
||||||
|
let requirement = "Server MUST handle gzip-encoded git-upload-pack requests";
|
||||||
|
|
||||||
|
let ctx = TestContext::new(client);
|
||||||
|
|
||||||
|
// Get a repository with pushed data (OwnerStateDataPushed ensures git data exists)
|
||||||
|
let state = match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
return TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail(format!("Failed to create fixture: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract repo info from the state event
|
||||||
|
let repo_id = match state
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.kind() == TagKind::d())
|
||||||
|
.and_then(|t| t.content())
|
||||||
|
{
|
||||||
|
Some(id) => id.to_string(),
|
||||||
|
None => {
|
||||||
|
return TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail("State event missing d tag");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the repo fixture to get the owner's npub
|
||||||
|
let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
return TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail(format!("Failed to get repo fixture: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let npub = match repo.pubkey.to_bech32() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => {
|
||||||
|
return TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail(format!("Failed to convert pubkey to npub: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clone the repository
|
||||||
|
let clone_path = match clone_repo(relay_domain, &npub, &repo_id) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
return TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail(format!("Failed to clone repo: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cleanup helper
|
||||||
|
let cleanup = || {
|
||||||
|
let _ = fs::remove_dir_all(&clone_path);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform a git fetch operation
|
||||||
|
// Git fetch sends gzip-encoded POST requests to git-upload-pack
|
||||||
|
// This is the actual production scenario we're testing
|
||||||
|
let fetch_output = Command::new("git")
|
||||||
|
.args(["fetch", "--all", "-v"])
|
||||||
|
.current_dir(&clone_path)
|
||||||
|
.env("GIT_TERMINAL_PROMPT", "0")
|
||||||
|
// Ensure gzip encoding is used (this is the default, but be explicit)
|
||||||
|
.env("GIT_HTTP_LOW_SPEED_LIMIT", "0")
|
||||||
|
.env("GIT_HTTP_LOW_SPEED_TIME", "999999")
|
||||||
|
.output();
|
||||||
|
|
||||||
|
match fetch_output {
|
||||||
|
Ok(output) => {
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
// Fetch succeeded - gzip handling works!
|
||||||
|
TestResult::new(test_name, spec_ref, requirement).pass()
|
||||||
|
} else {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|
||||||
|
// Check for specific gzip-related errors
|
||||||
|
if stderr.contains("gzip")
|
||||||
|
|| stderr.contains("inflate")
|
||||||
|
|| stderr.contains("decompress")
|
||||||
|
|| stderr.contains("Content-Encoding")
|
||||||
|
{
|
||||||
|
TestResult::new(test_name, spec_ref, requirement).fail(format!(
|
||||||
|
"Server failed to handle gzip-encoded request (production bug): {}",
|
||||||
|
stderr
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// Other fetch failure - might be unrelated to gzip
|
||||||
|
TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail(format!("Git fetch failed: {}", stderr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
cleanup();
|
||||||
|
TestResult::new(test_name, spec_ref, requirement)
|
||||||
|
.fail(format!("Failed to execute git fetch: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// Unit tests for helper functions can go here
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ pub mod cors;
|
|||||||
pub mod event_acceptance_policy;
|
pub mod event_acceptance_policy;
|
||||||
pub mod git_clone;
|
pub mod git_clone;
|
||||||
pub mod git_filter;
|
pub mod git_filter;
|
||||||
|
pub mod git_http_protocol;
|
||||||
pub mod nip01_smoke;
|
pub mod nip01_smoke;
|
||||||
pub mod nip11_document;
|
pub mod nip11_document;
|
||||||
pub mod push_authorization;
|
pub mod push_authorization;
|
||||||
@@ -27,6 +28,7 @@ pub use cors::CorsTests;
|
|||||||
pub use event_acceptance_policy::EventAcceptancePolicyTests;
|
pub use event_acceptance_policy::EventAcceptancePolicyTests;
|
||||||
pub use git_clone::GitCloneTests;
|
pub use git_clone::GitCloneTests;
|
||||||
pub use git_filter::GitFilterTests;
|
pub use git_filter::GitFilterTests;
|
||||||
|
pub use git_http_protocol::GitHttpProtocolTests;
|
||||||
pub use nip01_smoke::Nip01SmokeTests;
|
pub use nip01_smoke::Nip01SmokeTests;
|
||||||
pub use nip11_document::Nip11DocumentTests;
|
pub use nip11_document::Nip11DocumentTests;
|
||||||
pub use push_authorization::PushAuthorizationTests;
|
pub use push_authorization::PushAuthorizationTests;
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
//! Git HTTP Protocol Integration Tests
|
||||||
|
//!
|
||||||
|
//! Tests that verify Git HTTP protocol compliance, particularly around
|
||||||
|
//! request encoding (gzip) and content negotiation.
|
||||||
|
//!
|
||||||
|
//! # Test Strategy
|
||||||
|
//!
|
||||||
|
//! - Each test runs in complete isolation with its own fresh relay instance
|
||||||
|
//! - Uses macro to eliminate boilerplate while maintaining test isolation
|
||||||
|
//! - Calls individual test methods from grasp-audit for minimal duplication
|
||||||
|
//! - Automatic cleanup via TestRelay fixture (removes container and temp dirs)
|
||||||
|
//!
|
||||||
|
//! # Background
|
||||||
|
//!
|
||||||
|
//! These tests validate the fix for a production bug where the server failed
|
||||||
|
//! to properly decompress gzip-encoded request bodies, causing git fetch/clone
|
||||||
|
//! operations to fail.
|
||||||
|
//!
|
||||||
|
//! # Running Tests
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! # Run all git HTTP protocol tests
|
||||||
|
//! cargo test --test git_http_protocol
|
||||||
|
//!
|
||||||
|
//! # Run specific test
|
||||||
|
//! cargo test --test git_http_protocol test_gzip_encoded_upload_pack
|
||||||
|
//!
|
||||||
|
//! # With output
|
||||||
|
//! cargo test --test git_http_protocol -- --nocapture
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::TestRelay;
|
||||||
|
use grasp_audit::specs::grasp01::GitHttpProtocolTests;
|
||||||
|
use grasp_audit::*;
|
||||||
|
|
||||||
|
/// Macro to generate isolated integration tests with relay domain
|
||||||
|
///
|
||||||
|
/// Each test runs with its own fresh relay instance to ensure complete isolation.
|
||||||
|
/// This eliminates issues with leftover repositories and ensures clean state.
|
||||||
|
macro_rules! isolated_test {
|
||||||
|
($test_name:ident) => {
|
||||||
|
#[tokio::test]
|
||||||
|
async fn $test_name() {
|
||||||
|
let relay = TestRelay::start().await;
|
||||||
|
let config = AuditConfig::isolated();
|
||||||
|
let client = AuditClient::new(relay.url(), config)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create audit client");
|
||||||
|
|
||||||
|
let result = GitHttpProtocolTests::$test_name(&client, &relay.domain()).await;
|
||||||
|
|
||||||
|
relay.stop().await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.passed,
|
||||||
|
"{} failed: {}",
|
||||||
|
stringify!($test_name),
|
||||||
|
result.error.as_deref().unwrap_or("unknown error")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate isolated tests for all git HTTP protocol tests
|
||||||
|
isolated_test!(test_gzip_encoded_upload_pack);
|
||||||
Reference in New Issue
Block a user