# TASK-004: Implement the read-only Gitea API client Status: implemented and independently reviewed by Claude; one P2 fixed (409/422 typed failures) Owner: Codex Reviewer: Claude ## Writable paths - `app/src/main/java/com/ubisam/ubgitea/core/network/` - `app/src/main/java/com/ubisam/ubgitea/data/` - `app/src/main/java/com/ubisam/ubgitea/domain/` - `app/src/test/java/com/ubisam/ubgitea/core/network/` - `app/src/test/java/com/ubisam/ubgitea/data/` - `app/src/test/java/com/ubisam/ubgitea/domain/` - `app/build.gradle.kts` - `gradle/libs.versions.toml` - `docs/decisions/003-http-client.md` - `tasks/004-gitea-api-client.md` ## Outcome Connect the repository browsing domain to authenticated, paginated Gitea API calls while keeping DTOs and tokens out of domain and UI layers. ## Acceptance criteria - [x] Base URL is configurable with the approved production default. - [x] DTOs are separate from domain models and mapping is tested. - [x] Repository, branch, tag, tree, file, and commit endpoints support pagination where provided. - [x] Authentication is injected without logging or exposing tokens. - [x] HTTP, authorization, rate-limit, parsing, cancellation, and offline failures map to typed failures. - [x] Branch, ref, owner, repository, and path values are safely encoded as individual URL components. - [x] Tests use in-memory transport fixtures and a local MockWebServer and never contact production. ## Required checks ```text ./gradlew assembleDebug ./gradlew testDebugUnitTest ./gradlew lintDebug ``` ## Evidence - Implemented `GiteaRepositoryBrowserApi` for repositories, branches, tags, directory contents, Base64 files, and commits against Gitea 1.23-compatible response contracts. - Added immutable `TagSummary` and tag-list domain/use-case contract. - Focused adapter tests cover mapping, pagination, component encoding, authenticated headers, strict file decoding, malformed responses, 404, 429, and local HTTP transport behavior. - Full debug build, unit tests, lint, diff check, and sensitive-pattern scan are recorded in `docs/development-log.md`. ## Claude independent review closure Reviewed read-only at commit `8a47d30` (audited together with TASK-005 `bbbff77` and TASK-006 `16c46a5` per `handoff/claude-master-handoff.md`'s review-and-freeze instruction). Independently reran `assembleDebug testDebugUnitTest lintDebug --rerun-tasks` at `16c46a5`: BUILD SUCCESSFUL, 102 unit tests across 23 suites, 0 failures/errors, 0 lint errors; `git diff --check` clean; sensitive-pattern scan clean (one match was the test itself asserting `client_secret` is absent from the authorization URI). - P2 (fixed): `HttpTransport.kt:94-102` `requireSuccess()` mapped only 401/403/404/429/5xx to typed `ApiFailure`; HTTP 409 and 422 fell through to the generic `InvalidResponse`, losing the distinction the review checklist and the upcoming TASK-007 UDF error states (`conflict`/`unprocessable`) require. Fixed by adding `ApiFailure.Conflict` (409) and `ApiFailure.Unprocessable` (422) and extending `GiteaRepositoryBrowserApiTest`'s status-mapping test to cover both. Closure: full gates rerun and passing (above); TASK-007 can now type its conflict/unprocessable states directly from `core/network`. - URL encoding (verified, no finding): owner/name/path segments use `HttpUrl.Builder.addPathSegment` individually (`repoUrl`, `RepositoryPath.segments()`); `ref` and `path`-filter values use `addQueryParameter`. Confirmed by `GiteaRepositoryBrowserApiTest`'s encoding assertions on repository names containing spaces/`+`/`#` and refs containing `+`/`/`. - Pagination/page overflow (verified, no finding): `listTree`'s local pagination uses `Long` arithmetic before coercing to `Int`, so no overflow on large `page` values; `Int.MAX_VALUE` page numbers are rejected via `safeNextPage()` instead of wrapping. - No P0/P1 found. No other P2/P3 raised against this task's scope.