diff --git a/API_COVERAGE.md b/API_COVERAGE.md index ab8b6b6..59b879d 100644 --- a/API_COVERAGE.md +++ b/API_COVERAGE.md @@ -11,9 +11,10 @@ relationships, while Generated API modules and client configuration are deliberately private so the CLI and app cannot duplicate Gitea behavior. -Forgejo uses the common generated client for its documented Gitea-compatible -`/api/v1` surface. Provider discovery and provider-specific endpoints, such as -Forgejo's dedicated version API, are routed inside `gotcha_gitea::Client`. +Forgejo uses operations from its advertised Gitea 1.22-compatible `/api/v1` +surface. Gotcha must not assume that newer operations from its generated Gitea +1.25 client exist on Forgejo: provider-specific endpoints and any operation +outside the shared contract are routed inside `gotcha_gitea::Client`. | Domain | Queries | Other actions | Generated client | CLI queries | CLI writes | Capabilities | | --- | ---: | ---: | --- | ---: | ---: | --- | diff --git a/crates/gitea/src/repositories.rs b/crates/gitea/src/repositories.rs index d9f4c51..3969ce1 100644 --- a/crates/gitea/src/repositories.rs +++ b/crates/gitea/src/repositories.rs @@ -6,7 +6,7 @@ use std::{ use tokio::task::JoinSet; use crate::{ - Client, Error, Result, + Client, Error, Method, Result, domain::{DEFAULT_PAGE_SIZE, HistoryCommit, Page, RepositoryId}, models, validate_page, }; @@ -95,17 +95,17 @@ impl Client { .await .map_err(Error::generated) } else { - apis::repository_api::repo_get_contents_ext( - &configuration, - &repository.owner, - &repository.repository, - path, - None, - None, - ) - .await - .map(|contents| contents.dir_contents.unwrap_or_default()) - .map_err(Error::generated) + let endpoint = format!( + "repos/{}/{}/contents/{}", + apis::urlencode(&repository.owner), + apis::urlencode(&repository.repository), + apis::urlencode(path), + ); + self.execute(self.request(Method::GET, &endpoint)?) + .await? + .json() + .await + .map_err(Into::into) } } diff --git a/crates/gitea/src/repositories/tests.rs b/crates/gitea/src/repositories/tests.rs index 127f426..39f6f58 100644 --- a/crates/gitea/src/repositories/tests.rs +++ b/crates/gitea/src/repositories/tests.rs @@ -1,4 +1,9 @@ use super::*; +use std::{ + io::{Read, Write}, + net::TcpListener, + thread, +}; fn commit(sha: &str, parents: &[&str]) -> models::Commit { models::Commit { @@ -95,3 +100,41 @@ fn labels_first_commit_of_pull_branch() { .contains(&"feature".into()) ); } + +#[tokio::test] +async fn reads_nested_directory_from_compatible_contents_endpoint() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + let server = thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + let mut buffer = [0; 4096]; + let length = stream.read(&mut buffer).unwrap(); + let request = String::from_utf8_lossy(&buffer[..length]).into_owned(); + let body = r#"[{"name":"guide.md","path":"docs/guide.md","type":"file"}]"#; + write!( + stream, + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ) + .unwrap(); + request + }); + + let client = + Client::with_provider(&format!("http://{address}"), None, crate::Provider::Forgejo) + .unwrap(); + let repository = RepositoryId::new("forgejo", "forgejo").unwrap(); + let contents = client + .repository_contents(&repository, "docs/guide") + .await + .unwrap(); + + assert_eq!(contents.len(), 1); + assert_eq!(contents[0].path.as_deref(), Some("docs/guide.md")); + assert!( + server + .join() + .unwrap() + .starts_with("GET /api/v1/repos/forgejo/forgejo/contents/docs%2Fguide ") + ); +}