Skip to content

test(vcs): assert what a provider refuses instead of skipping - #220

Open
HarshMN2345 wants to merge 1 commit into
mainfrom
test/vcs-assert-refusals
Open

test(vcs): assert what a provider refuses instead of skipping#220
HarshMN2345 wants to merge 1 commit into
mainfrom
test/vcs-assert-refusals

Conversation

@HarshMN2345

@HarshMN2345 HarshMN2345 commented Sep 7, 2026

Copy link
Copy Markdown
Member

A capability the shared suite declares unsupported dropped the test that needed it. $supportsTags = false meant nobody ever checked what the adapter does when asked for a tag. This makes a capability narrow a test instead of removing it: where the adapter documents a refusal the test asserts it, and where the provider answers differently the test asserts that answer.

The e2e base goes from 45 skips to 27. The unit tier gains two shared tests and loses three that were written out per adapter.

Refusals are asserted, not skipped

assertRefused() requires the message the Git defaults already use, X() is not supported by <name>, so a 404 from a repository that never existed cannot pass for a refusal. Nine capabilities assert their refusal: repository languages, commit statuses and their lookup, pull request creation and lookup, tags, check runs, namespaces, and installation repositories.

GitHub's three stubs said Not implemented; Gogs's seven said Pull request API is not supported by Gogs. Both now name the method, which is what makes the assertion meaningful.

Skips that were hiding an answer

test was now
testListBranchesEmptyRepository skipped on Gogs asserts [master] — the adapter asks for an initial commit
testListTagsCommitlessRepository skipped on Gogs asserts [], which holds either way
testGetCommitAuthorAvatar skipped on GitLab asserts ''
assertCommitAuthorLinks asserted nothing when a flag was false asserts '', so a provider that starts reporting one flips the flag
testGetOwnerNameWith{out,Zero,Null}RepositoryId three tests, skipped on GitHub and Bitbucket one test, runs everywhere
testGenerateCloneCommandWithTag skipped on GitHub runs everywhere; the command is built, never run, so the tag need not exist
testGetUserWithInvalidUsername skipped on Bitbucket runs everywhere
testGetRepositoryAfterDeleteFails skipped on GitLab runs everywhere
languages on Bitbucket assertIsArray on a method typed : array asserts []

Adapter changes

Sharing a test meant running it against a provider for the first time, which is where these came from.

  • Gitea reports synchronized for a pushed head. Every consumer acts on synchronize — Appwrite's four VCS event handlers each test for it — so a pull request update on Gitea, Forgejo and Gogs was read as an action nobody handles. The adapter normalizes it; every other action passes through as sent.
  • GitLab raised a bare Exception for a repository that does not exist, which is why the e2e class had to declare $repositoryNotFoundException = \Exception::class. getRepositoryName() and getOwnerName() now raise RepositoryNotFound on a 404, and the flag is gone.
  • GitLab answered the old path of a deleted project through the redirect it issues for a moved-aside project, so getRepository() reported a repository that had been deleted. It no longer follows that redirect, which also drops $deletesRepositoriesSynchronously.
  • GitHub::getUser() sent no credential, the only call in the adapter that did not, and returned the whole response rather than the body — a 404 came back looking like a user. GitHub's user lookup was switched off in the suite because of it.
  • getInstallationRepository() was stubbed in three adapters with the wording each chose. Only GitHub and Origin model installations, so the refusal moved to Git beside the other five.

Tests hoisted into the unit tier

Two tests were provider specific only because the payload going in was.

  • Pull request action mapping, previously written out in both Unit\GitLabTest and Unit\BitbucketTest, is now a declared $pullRequestActions map of native to shared name. Base asserts every provider covers the three actions consumers act on and that nothing falls outside the vocabulary. Bitbucket names the action in the event rather than the payload, which one hook expresses.
  • The event describes the head commit, not the first one listed, previously a GitLab test. pushPayload() takes the commits to list before the head, and Base asserts the head's hash, message, author and url.

Unit\GitLabTest now holds no test of its own. Unit\BitbucketTest keeps the two that are genuinely Bitbucket: batched pushes and a linked commit author. Unit\OriginTest is outside this contract and untouched.

Also

testGetRepositoryContent asserts the exact byte size rather than "greater than zero", testGetRepositoryPresignedUrlWithInvalidFormat asserts the message rather than any exception, and testGetRepositoryWithNonExistingOwner and testGetRepositoryNameWithInvalidId expect RepositoryNotFound. Thirteen cleanup blocks moved onto discardRepositories(), which retries and reports a repository it could not remove instead of leaving it to contaminate later runs. testGetDeletedRepositoryFails looked up a repository that was never deleted; it is now testGetNonExistingRepositoryFails, beside the one that does delete.

The adapter guide's file tree still drew tests/VCS/Adapter/ and tests/VCS/Base.php, which the tier split replaced. It now describes both tiers, their hooks, and what belongs in an adapter's own class.

Verification

bin/monorepo check vcs and bin/monorepo validate pass.

The unit tier runs on a bare host and is green: 94 tests, 445 assertions, the one skip being Origin's live suite.

The e2e tier was run locally against the self-hosted providers, before and after, on the same containers:

Gitea, Forgejo and Gogs main this branch
tests 279 273
assertions 787 816
skipped 54 41

Both green. Six fewer test methods is the three getOwnerNameWith*RepositoryId tests collapsing into one across three classes; thirteen fewer skips and twenty-nine more assertions is the point of the change.

GitHub, GitLab and Bitbucket are not covered by that run. CI's e2e job cannot cover them either right now: tests/credentials.php fails its GitHub App preflight on this branch with appStatus: 401, errorCategory: issuer, which exits before PHPUnit starts and takes the whole tier with it. That preflight is untouched here, and the three commits that added it (c4869998, a4d80b70, c78ce23e) are the in-flight diagnosis of the same problem, so it needs the CI secret sorted out rather than a change to this branch.

🤖 Generated with Claude Code

A declared capability dropped the test that needed it. Where the adapter
documents a refusal, the shared test now asserts it, and where the provider
answers differently the test asserts that answer.

Four behaviours the sharing surfaced:

- Gitea says 'synchronized' for a pushed head. Consumers only act on
  'synchronize', so a pull request update was read as an unknown action.
- GitLab raised a bare Exception for a repository that does not exist, and
  answered the old path of a deleted one through a redirect.
- GitHub asked for a user with no credential, the only call in the adapter
  that did.
- getInstallationRepository() was stubbed three times over; the refusal
  belongs on Git, next to the other five.

The pull request action mapping and the head commit test move into the unit
tier, leaving no test of its own in GitLab's class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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