kingfisher/tests/int_gitea_clone_url_base.rs
Erich Blume 5d0b2d8355 feat(gitea): add --clone-url-base flag for clone URL rewriting
When scanning a self-hosted Gitea/Forgejo instance, the API may be
reachable at a different hostname than the git clone endpoint (e.g.,
internal API vs. public clone URL behind a reverse proxy). The
--clone-url-base flag rewrites the scheme, host, and port of clone
URLs returned by the API, preserving the path.

Example:
  kingfisher scan gitea \
    --api-url https://forge.internal.example.com/api/v1/ \
    --clone-url-base https://forge.internal.example.com/ \
    --user eblume

This avoids routing clone traffic through an external proxy when the
API and git endpoints share the same internal host but the instance's
ROOT_URL points to the public endpoint.

Includes unit tests for the URL rewriting function and an integration
test using wiremock to verify the full enumeration path.
2026-05-30 22:00:53 -07:00

84 lines
2.6 KiB
Rust

// tests/int_gitea_clone_url_base.rs
//
// Integration test: verify that --clone-url-base rewrites clone URLs
// returned by the Gitea API during repository enumeration.
//
// Uses wiremock to mock the Gitea API and assert_cmd to exercise the full
// CLI path: argument parsing → API enumeration → URL rewriting → output.
use assert_cmd::Command;
use predicates::str::contains;
use wiremock::{
matchers::{method, path, query_param},
Mock, MockServer, ResponseTemplate,
};
/// Run `kingfisher scan gitea --list-only` against a mock Gitea API with and
/// without --clone-url-base, verifying that clone URLs are rewritten.
#[tokio::test]
async fn clone_url_base_rewrites_listed_urls() {
let mock_server = MockServer::start().await;
let public_host = "https://forge.public.example.com";
let repo_json = serde_json::json!([{
"full_name": "eblume/kingfisher",
"clone_url": format!("{public_host}/eblume/kingfisher.git"),
"fork": false
}]);
// Page 1: return one repo.
Mock::given(method("GET"))
.and(path("/api/v1/users/eblume/repos"))
.and(query_param("page", "1"))
.respond_with(ResponseTemplate::new(200).set_body_json(&repo_json))
.mount(&mock_server)
.await;
// Page 2: return empty array to terminate pagination.
Mock::given(method("GET"))
.and(path("/api/v1/users/eblume/repos"))
.and(query_param("page", "2"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([])))
.mount(&mock_server)
.await;
let api_url = format!("{}/api/v1/", mock_server.uri());
// WITH --clone-url-base: URLs should be rewritten.
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
.args([
"scan",
"gitea",
"--api-url",
&api_url,
"--clone-url-base",
"https://forge.internal.example.com/",
"--user",
"eblume",
"--list-only",
"--no-update-check",
"--quiet",
])
.assert()
.success()
.stdout(contains("https://forge.internal.example.com/eblume/kingfisher.git"));
// WITHOUT --clone-url-base: URLs should be unchanged.
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
.args([
"scan",
"gitea",
"--api-url",
&api_url,
"--user",
"eblume",
"--list-only",
"--no-update-check",
"--quiet",
])
.assert()
.success()
.stdout(contains(&format!(
"{public_host}/eblume/kingfisher.git"
)));
}