Compare commits

..

1 commit

Author SHA1 Message Date
677c7a5d5f feat(gitea): add --clone-url-base flag for clone URL rewriting
Some checks failed
ClusterFuzzLite PR fuzzing / PR (address) (pull_request) Has been cancelled
CI Pull Request / Linux x64 (pull_request) Has been cancelled
CI Pull Request / Linux arm64 (pull_request) Has been cancelled
CI Pull Request / macOS arm64 (pull_request) Has been cancelled
CI Pull Request / Windows arm64 (pull_request) Has been cancelled
CI Pull Request / Windows x64 (pull_request) Has been cancelled
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-03-29 08:28:36 -07:00

View file

@ -2,19 +2,21 @@
//
// 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 anyhow::Result;
use kingfisher::gitea::{self, RepoSpecifiers, RepoType};
use url::Url;
use assert_cmd::Command;
use predicates::str::contains;
use wiremock::{
matchers::{method, path, query_param},
Mock, MockServer, ResponseTemplate,
};
/// Mock a Gitea API that returns repos with clone URLs on one host,
/// then verify that enumerate_repo_urls rewrites them to a different host.
/// 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_enumerated_urls() -> Result<()> {
async fn clone_url_base_rewrites_listed_urls() {
let mock_server = MockServer::start().await;
let public_host = "https://forge.public.example.com";
@ -40,42 +42,43 @@ async fn clone_url_base_rewrites_enumerated_urls() -> Result<()> {
.mount(&mock_server)
.await;
let api_url = Url::parse(&format!("{}/api/v1/", mock_server.uri()))?;
let clone_base = Url::parse("https://forge.internal.example.com/")?;
let api_url = format!("{}/api/v1/", mock_server.uri());
let specifiers = RepoSpecifiers {
user: vec!["eblume".into()],
organization: vec![],
all_organizations: false,
repo_filter: RepoType::All,
exclude_repos: vec![],
};
// 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"));
// Call WITH clone_url_base — URLs should be rewritten.
let urls = gitea::enumerate_repo_urls(
&specifiers,
api_url.clone(),
Some(&clone_base),
false,
None,
)
.await?;
assert_eq!(urls.len(), 1);
assert_eq!(
urls[0],
"https://forge.internal.example.com/eblume/kingfisher.git"
);
// Call WITHOUT clone_url_base — URLs should be unchanged.
let urls_no_rewrite =
gitea::enumerate_repo_urls(&specifiers, api_url, None, false, None).await?;
assert_eq!(urls_no_rewrite.len(), 1);
assert_eq!(
urls_no_rewrite[0],
format!("{public_host}/eblume/kingfisher.git")
);
Ok(())
// 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"
)));
}