Skip to content

test: unify adapter tests into a shared Base and normalize adapter behavior - #124

Merged
HarshMN2345 merged 38 commits into
mainfrom
test/unify-adapter-base-tests
Jul 28, 2026
Merged

test: unify adapter tests into a shared Base and normalize adapter behavior#124
HarshMN2345 merged 38 commits into
mainfrom
test/unify-adapter-base-tests

Conversation

@HarshMN2345

@HarshMN2345 HarshMN2345 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Adapter changes

  • GitHub::updateCommitStatus() silently ignored API errors; it now throws with the HTTP status as the exception code, like every other adapter.
  • GitHub::searchRepositories() inferred failure from a missing response key, so a rate limit or auth error was indistinguishable from "no results". It now checks the status code: a 422 (the search API rejecting an owner that does not exist) returns the empty items/total shape, anything else >= 400 throws with the status.
  • Gitea::getCommitStatuses() returned raw Gitea objects (status); it now returns the same state/description/target_url/context shape GitLab already returned. The contract is documented on Git::getCommitStatuses().
  • GitLab::getOwnerName() treated a 0 repository id as a real id and looked up project 0. A non-positive id now means "no repository" and falls back to the authenticated user, matching Gitea.

Tests

  • Base now owns the shared cases: repositories, files, trees, contents, branches, tags, commits, commit status, comments, pull requests, clone commands, getUser, getOwnerName, searchRepositories.
  • Adapter classes keep only what is genuinely provider specific: webhook payloads and signatures, check runs (GitHub), presigned URL shapes, GitLab path-sentinel handling, Gogs' unsupported endpoints.
  • getEventPush, getEventPullRequest and validateWebhookEvent are abstract in Base, following the existing convention for provider-specific tests, instead of skipped placeholders.
  • Webhook tests read the header names off the adapter (getEventHeaderName()/getSignatureHeaderName()) instead of repeating them as test-class properties, so the advertised header is now covered too.
  • Adapter classes name the account to look up via $existingUser (utopia for Gitea/Forgejo/Gogs, root for GitLab). Feat/normalize adapter behavior #108 renamed the Gitea/Forgejo/Gogs admin to root in docker-compose.yml to make a shared getUser test pass — that change is dropped.

Net: 2823 lines deleted, 1442 added, and every adapter runs more active tests than before (GitHub 55 -> 68, GitLab 70 -> 84, Gitea/Forgejo 76 -> 78, Gogs 61 -> 62).

Two deltas worth calling out:

  • testCreateRepositoryWithInvalidName is now skipped for Gitea/Forgejo/Gogs. The old version wrapped $this->fail() in catch (\Exception), which swallowed the failure — it passed regardless of behaviour, and Gitea does accept names with spaces. GitLab keeps a real expectException version.
  • GitHubTest skips getUser with a precise reason: GitHub::getUser() returns the raw response envelope rather than the shared user shape, and does not throw for a missing user. Left alone here since callers may depend on it.
  • GitHubTest::testListRepositoryLanguages waits 60s and then reports skipped. GitHub computes language stats out of band with no guaranteed turnaround and reports none until it finishes, so a repository without stats says nothing about the adapter. Base stays strict for the self-hosted providers, where the value is synchronous.

CI

The gitlab job was failing on transport errors (502s, connection resets) with every suite run: GitLab reports healthy and then flaps while it finishes starting, so the sleep 15 in the workflow let tests start against an API that was not serving yet. Measured locally — /-/health, /-/readiness and /api/v4 all flip to 200 at the same moment, so a deeper probe buys nothing; the problem is the flapping after the first healthy report.

  • both workflows now wait for the provider to report healthy on three consecutive polls instead of sleeping a fixed 15s, and short-circuit for the github profile, which has no container
  • GitLabTest retries the one-time group creation while the API is still coming up

Adapter-level retries on 5xx would be the durable fix, but #103 is already adding those, so nothing here touches the adapters' error handling.

Verification

gitea, forgejo, gogs and gitlab suites were run locally against the docker stack — all green (79/79, 79/79, 79/79, 84/84). composer lint and composer check (PHPStan level 8) pass. All five adapter jobs, the linter and CodeQL are green in CI.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR consolidates adapter integration tests and normalizes several provider behaviors.

  • Moves shared repository, commit, status, pull-request, comment, webhook, and clone-command tests into the common test base.
  • Adds consistent HTTP error handling to GitHub repository creation, repository search, and commit-status updates.
  • Normalizes Gitea and Forgejo commit-status responses to the shared adapter contract.
  • Treats non-positive GitLab repository IDs as absent and falls back to the authenticated user.
  • Replaces fixed CI startup delays with Compose health waiting and strengthens GitLab readiness checks.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/VCS/Adapter/Git/GitHub.php Adds HTTP-status validation to repository creation, repository searches, and commit-status updates.
src/VCS/Adapter/Git/Gitea.php Normalizes commit statuses to the documented provider-independent response shape and surfaces repository-creation errors.
src/VCS/Adapter/Git/GitLab.php Makes non-positive repository IDs use the authenticated-user owner lookup path.
src/VCS/Adapter/Git/Gogs.php Adds repository-creation error handling and an explicit unsupported pull-request-files implementation.
src/VCS/Adapter/Git.php Documents the common commit-status response contract.
tests/VCS/Base.php Expands the shared adapter test contract and centralizes common setup, polling, and behavior assertions.
docker-compose.yml Strengthens GitLab health checks by requiring three consecutive successful probes.
.github/workflows/tests.yml Replaces the fixed startup sleep with bounded Docker Compose readiness waiting.
.github/workflows/tests-external.yml Applies the same bounded Compose readiness waiting to externally triggered adapter tests.

Reviews (19): Last reviewed commit: "Wait for GitHub to index a commit before..." | Re-trigger Greptile

jaysomani and others added 20 commits July 27, 2026 18:30
… status verification, and GitLab getOwnerName fallback
GitHub::searchRepositories() inferred failure from a missing response key,
so a rate limit or auth error looked the same as "no results". Check the
status code instead: throw with it on failures, otherwise return the
empty items/total shape the other adapters use.

Also aligns GitLab::getOwnerName() with Gitea: a non-positive repository
id means "no repository", not a lookup of project 0.
The branch was cut before listTags, presigned URLs and the GitLab event
parity work landed, so replaying it dropped those tests. Restores them and
folds the still-duplicated cases into Base:

- listTags, the empty-repository branch listing and getUser are now shared
- adapter classes name the account to look up via $existingUser instead of
  renaming the Gitea/Forgejo/Gogs admin to root in docker-compose
- webhook tests read the header names off the adapter instead of repeating
  them per test class
- getEventPush, getEventPullRequest and validateWebhookEvent are abstract
  in Base, since payloads and signatures are provider specific
@HarshMN2345
HarshMN2345 force-pushed the test/unify-adapter-base-tests branch from 5943f49 to 207c7cd Compare July 27, 2026 13:03
An audit of the diff against main turned up assertions that were dropped
rather than moved:

- commit status now has to come back under the context it was written with,
  with the description intact, which is what covers the Gitea normalization
  this branch adds
- delete failures have to carry the HTTP status as the exception code
- getRepository has to throw RepositoryNotFound, not any Exception
- createRepository's own response has to report the requested visibility and
  a parseable pushed_at, not just contain the key
- getCommit has to report a commit author
- a commit clone command has to name the commit it was asked for
- GitHub keeps its git blob SHA check, and Gitea its search-matches-name and
  delete-then-read cases, which no shared test covers

Also drops four GitHub overrides that were byte-identical to Base (one of
them leaked its clone directory), a GitLab getEvent test that duplicated
Base's, and uses the eventual-consistency helper in GitHub's commit status
test.
- repositories are asserted to belong to the owner they were created under,
  via an ownerPath()/ownerOf() pair that handles GitLab's "id:path" owners
  and GitLab's namespace shape
- getOwnerName is asserted exactly instead of by substring, which lets the
  Gitea-specific copy of the test go away
- searchRepositories items are asserted to carry id, name, private and a
  usable pushed_at for every adapter, replacing GitLab's private copy
- clone commands are asserted to set up sparse checkout, and commit clones
  to be shallow
- pull request state has to read 'open' or 'opened', not merely be non-empty
- the recursive tree has to be at least as large as the files put in it
- webhook header names are pinned per provider, which is what covers the
  Forgejo and Gogs overrides
- Gitea keeps its avatar-host check, GitLab a commit-less listTags case
- Gogs::getPullRequestFiles now reports the pull request API as unsupported
  like its siblings instead of inheriting Gitea's endpoint

Drops GiteaTest's comment workflow, which repeated the three shared comment
tests over four repository lifecycles.
Only GitLab checked the status code, so Gitea, Gogs and GitHub returned
whatever the API replied with — an error body looked like a created
repository. The Gitea suite's testCreateRepositoryWithInvalidName hid this:
it wrapped $this->fail() in catch (\Exception), which swallowed the failure
it was supposed to report, so the test passed either way.

All four now throw with the HTTP status as the exception code, and the
Gitea-family test asserts that instead of being skipped.
Comment thread .github/workflows/tests-external.yml Outdated
docker compose --profile ${{ matrix.adapter }} up -d
sleep 15

- name: Wait for the provider to settle

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lets check with Cloud team if this can be done nicer - if they have some examples of similar logic elsewhere

Waiting for container to be healthly, different container for each matrix

The previous fix polled docker inspect from the workflow to work around
GitLab flipping to "healthy" on a single successful check and then still
returning 502s for a while, since a container's healthcheck only needs one
success to transition out of "starting" - retries only govern how many
consecutive failures are needed to go back to unhealthy afterwards.

Move that requirement into the healthcheck itself instead: GitLab's test
now demands 3 consecutive successful curls before Docker reports healthy,
so the healthcheck can be trusted. Both workflows drop the custom polling
step for docker compose's own `up -d --wait`, which blocks until every
started service is healthy.
The last CI failure had GitLab go fully unreachable partway through a run
that had already been passing tests, not just slow to start - a different
failure mode than the boot-time flakiness the wait step handles, and one
we have no data on. Log gitlab-ctl status after the test step, whether or
not it passed, so the next occurrence shows which internal process (gitaly,
puma, postgresql, etc.) was down at that point instead of just an HTTP
error on our end.

Diagnostic only: does not change the existing wait-for-healthy step.
gitlab-bootstrap already depends on gitlab: condition: service_healthy, and
tests depends on gitlab-bootstrap completing - so Compose was already
refusing to start the tests container before GitLab reported healthy. The
only weak link was the healthcheck itself: one successful curl only proves
nginx answered, not that the API behind it can serve requests, which is what
let 502s through despite the dependency being wired correctly.

Requiring 3 consecutive successful checks closes that gap without any
custom polling. Both workflows now just call `up -d --wait`, which is
redundant with the existing dependency chain but fails loudly with a clear
timeout instead of a silent hang if something is ever wrong.

Verified locally against a freshly wiped gitlab-data volume: healthy at
2m16s, 84/84 tests immediately after.
Same run that had a clean, error-free boot and dependency chain (no 502s,
no connection failures) still saw both webhook tests time out waiting for
delivery at 30s. GitLab queues hook deliveries through Sidekiq, which can
still be warming up in the moments right after the instance becomes
reachable - a different bottleneck than the one the healthcheck fix
addresses. Widening to 60s.
Widening the timeout only cut the earlier failures in half (2 -> 1), and
by the time it still failed GitLab had been up for minutes and processed
plenty of other jobs already - a cold Sidekiq worker doesn't explain that.
No service was down per the existing diagnostic either.

Log the actual WebHookWorker entries from sidekiq's log and the catcher's
own log when a gitlab test fails, so the next occurrence shows whether
GitLab even attempted delivery, and whether it reached the catcher,
instead of leaving both as open questions.
Two separate steps each re-checking matrix.adapter == 'gitlab' was
redundant. One step now runs always(), and branches on the Run Tests
step's own outcome for the failure-only part instead of a second if:.
Comparing matrix.adapter to a literal string in the workflow breaks
silently if the adapter is ever renamed. Check whether a running gitlab
container actually exists instead, verified against both a real gitlab
run and a profile that doesn't start one.
It never actually explained a failure - it only confirmed nothing was
down, which we already suspected - and the flake it was added for hasn't
recurred since the timeout was widened. Speculative logging for a problem
that isn't currently happening is better added later, informed by whatever
that failure actually looks like, than guessed at now.
testListBranchesPagination created branches immediately after creating the
first file, without waiting for GitHub to index that commit first.
GitHub::createBranch resolves the source branch through getLatestCommit(),
which throws a 422 when the commit isn't queryable yet - exactly the
eventual-consistency gap the rest of the suite already guards against with
getLatestCommitEventually(), just missing from this one test.
@HarshMN2345
HarshMN2345 force-pushed the test/unify-adapter-base-tests branch from d379d43 to ce4df64 Compare July 28, 2026 09:56
@HarshMN2345
HarshMN2345 merged commit 054f652 into main Jul 28, 2026
8 checks passed
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.

3 participants