Skip to content

feat: paginate RepositoryService.getTeams using PaginatedRequest#36

Open
Hayena wants to merge 2 commits into
mainfrom
feat/issue-19-paginate-repository-get-teams
Open

feat: paginate RepositoryService.getTeams using PaginatedRequest#36
Hayena wants to merge 2 commits into
mainfrom
feat/issue-19-paginate-repository-get-teams

Conversation

@Hayena

@Hayena Hayena commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #19

RepositoryService.getTeams() previously used a plain GET and deserialised only the first page, silently truncating repositories with many teams. This PR replaces it with a PaginatedRequest-based implementation, consistent with all other paginated list endpoints in the library.

Changes

  • GitHubRepositoryClient.getTeams() replaced with a PaginatedRequest-based implementation
  • Behaviour and method signature unchanged
  • Existing tests still pass
  • New test added verifying multi-page Link header pagination

@Hayena Hayena left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kotlin Code Review

🟡 Improvement (missed idioms / non-idiomatic patterns)

Two minor improvements noted below as inline comments — see specific lines for details.

🟢 Looks Good

The core refactor is clean and correct. Delegating to PaginatedRequest via the companion invoke(uri) is the right pattern, consistent with how getAllContributors uses the full constructor. Error behaviour is preserved (non-200 → GitHubApiException), and the pagination tests are well-structured and meaningful.

every { httpClient.invoke(matchUri("/repos/${repo.owner}/${repo.name}/teams?page=2&per_page=100")) }
.returns(Response(Status.OK).body("[$secondTeam]"))

val result = client.repositories.getTeams(repo)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multi-page test duplicates what a Defaults.repositoryTeam() helper could centralise. Defaults already provides contributor() for its equivalent test, and a repositoryTeam() factory (analogous to Defaults.team() for GitHubTeam) would:

  1. Cut the 60-line inline JSON + full constructor assertion down to a couple of named calls.
  2. Keep fixture data in one place — if a field is added to GitHubRepositoryTeam the helper breaks the build instead of silently passing with stale JSON.

GetTeamResponse is internal, so raw JSON for the mock response body is unavoidable, but the expected GitHubRepositoryTeam values in the assertion can be expressed through a helper:

// Defaults.kt
fun repositoryTeam(
    organization: String = OWNER,
    id: Int = 1,
    name: String = "Justice League",
    slug: String = "justice-league",
    // …sensible defaults for other fields
) = GitHubRepositoryTeam(organization = organization, id = id, name = name, slug = slug, …)

Then the assertion becomes:

result.getOrThrow() shouldBe listOf(
    Defaults.repositoryTeam(id = 1, name = "Justice League", …),
    Defaults.repositoryTeam(id = 2, name = "Avengers", …),
)

result.getOrThrow() shouldBe emptyList()
}

"follow Link rel=next headers across multiple pages" {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite for getTeams covers empty list, multi-page, and error — but is missing a single non-empty page with no Link header. The getAllContributors suite has this as its first case: "return Found with contributors when a single page is returned".

Without it, the scenario where defaultPageResult produces hasNext = false from the very first request (because no Link: rel="next" header is present) is not exercised as a standalone path. The multi-page test happens to cover page-2 termination, but a dedicated case like:

"return Found with teams when a single page is returned" {
    every { httpClient.invoke(matchUri(".../teams?page=1&per_page=100")) }
        .returns(Response(Status.OK).body("[$firstTeam]"))  // no Link header

    val result = client.repositories.getTeams(repo)

    result.shouldBeInstanceOf<ApiResult.Found<List<GitHubRepositoryTeam>>>()
    result.getOrThrow() shouldHaveSize 1
}

would make the intent explicit and guard against regressions in the hasNext logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant