Fix GitLab::getEvent() field/action parity with Gitea's shape#119
Fix GitLab::getEvent() field/action parity with Gitea's shape#119HarshMN2345 wants to merge 6 commits into
Conversation
Verified against real GitLab webhook payloads: push/MR events were missing repositoryId (breaking any DB lookup keyed on it), used non-standard key names (name/commitAuthor/commitMessage/commitUrl instead of repositoryName/headCommitAuthorName/headCommitMessage/ headCommitUrl), and passed merge_request action through raw (open/reopen/update/close) instead of normalizing to the GitHub/Gitea convention (opened/reopened/synchronize/closed) consumers already key off of. Also adds repositoryUrl, branchUrl, affectedFiles, branch create/delete detection, and cross-project (fork) detection for MRs, matching Gitea's return shape field-for-field.
Greptile SummaryThis PR fixes
Confidence Score: 5/5Safe to merge — the changes are well-scoped field renames and additions with matching test coverage for every new code path. The getEvent() return shapes align precisely with Gitea's adapter, the action-mapping constant is correct, the zero-SHA sentinel for branch create/delete is the standard GitLab convention, and searchRepositories() now matches the GitHub adapter's {items, total} structure. No logic paths were left untested by the new test methods. No files require special attention. Important Files Changed
Reviews (5): Last reviewed commit: "Fix searchRepositories() return shape to..." | Re-trigger Greptile |
GitLab sends x-gitlab-event as 'Push Hook'/'Merge Request Hook', not 'push'/'pull_request' -- every real webhook was silently falling through to the default no-op. Caught by Greptile review. Blocked on utopia-php/vcs#119 (the field/action-name parity fix for GitLab::getEvent()) merging and releasing before this integration is fully correct end-to-end -- verified against the patched library locally, but composer.json still points at the published 4.4.2 since a personal-fork dependency can't ship in this PR.
…l default - Added dedicated tests for branchCreated/branchDeleted sentinel paths. - Documented the external=false fallback as an intentional safe default. - Reverted a pullRequestNumber strval() cast: Gitea/GitHub both return the raw int-or-empty-string from getEvent() too, so casting only GitLab would trade one inconsistency for another. Leaving all three adapters consistent with each other for now; a real fix needs to touch all three, which is a separate, broader change.
…spaces GET /users/:id/projects only ever returns *public* projects, even for the authenticated user's own account -- confirmed live against a real GitLab account with private repos, which came back empty. Falls back to GET /projects?membership=true instead (every project, public and private, the token's user is a member of), filtered client-side to the requested owner's namespace since that endpoint isn't scoped to one owner. The group-namespace path is untouched, since it was already correctly scoped by GitLab itself. Note: the existing testSearchRepositories/testSearchRepositoriesWithSearch tests only cover a public repo, which is exactly why this didn't get caught -- worth adding a private-repo case as a follow-up.
…th2 tokens Every API call used the PRIVATE-TOKEN header (and ?private_token= for the presigned archive URL), which GitLab only honors for Personal Access Tokens. Our entire integration is OAuth2-based (authorization code flow), which GitLab requires via Authorization: Bearer <token> (or ?access_token= for URL-embedded tokens) -- confirmed live: a freshly-issued OAuth2 access token was rejected with 401 'Bad credentials' on every single request, immediately after being minted, which ruled out expiry/staleness and pointed at the auth mechanism itself.
…otal contract GitLab's version returned a bare indexed array; GitHub and Gitea both return ['items' => ..., 'total' => ...] (confirmed by reading both). Consumers destructure that shape directly (e.g. Appwrite's Installations/Repositories/XList.php), so this crashed with a TypeError the moment a real request reached it. Total count comes from GitLab's X-Total response header for the successfully-scoped group/personal-namespace fetch; falls back to the filtered count when client-side namespace filtering is in effect, since X-Total in that case reflects every namespace the token can see, not just the requested owner.
Summary
GitLab's
getEvent()(added in #118, feat/appwrite-provider-parity) returns push/merge_request event data in a shape that doesn't match Gitea's or GitHub's -- consumers relying on the common field contract (e.g. Appwrite's VCS integration) would silently drop every event.Verified against real GitLab webhook payload structure:
repositoryIdentirely -- any DB lookup keyed on it finds nothing.name/commitAuthor/commitMessage/commitUrlinstead ofrepositoryName/headCommitAuthorName/headCommitMessage/headCommitUrl(matching Gitea's/GitHub's convention).actionpassed through raw GitLab values (open/reopen/update/close) instead of the GitHub/Gitea convention (opened/reopened/synchronize/closed) consumers already branch on. Also maps GitLab's distinctmergeaction toclosed, since a merged MR is done either way.repositoryUrl,branchUrl,affectedFiles, branch create/delete detection, and cross-project (fork) detection for external MRs -- all present in Gitea's return shape but missing from GitLab's.While integrating against a real GitLab OAuth app end-to-end (not just mocked HTTP), three further real bugs surfaced -- all fixed here too:
PRIVATE-TOKEN: <token>(and?private_token=for presigned URLs), which is only valid for GitLab Personal Access Tokens. Our integration uses OAuth2 access tokens, which GitLab requires asAuthorization: Bearer <token>. Every API call was failing with 401 "Bad credentials" regardless of token freshness.searchRepositories()dropped private repos in personal namespaces. The personal-namespace fallback calledGET /users/{username}/projects, which GitLab only returns public projects for, even for the authenticated user's own account. Switched toGET /projects?membership=true(every project -- public and private -- the token's user is a member of) with client-side filtering bynamespace.path.searchRepositories()return shape didn't match GitHub/Gitea'sitems/totalcontract. It returned a bare indexed array instead of['items' => [...], 'total' => N], breaking any consumer destructuring the common shape.totalis now taken from theX-Totalresponse header (or the filtered count, when the personal-namespace client-side filter is in effect, since the header reflects all namespaces in that case).Test plan
testGetEventPush/testGetEventPullRequestto assert the corrected shapetestGetEventPullRequestActionMappingcovers all 5 native action valuestestGetEventPullRequestDetectsExternalcovers fork/cross-project MR detectiontestGetEventPushDetectsBranchCreated/testGetEventPushDetectsBranchDeletedtestSearchRepositories/testSearchRepositoriesWithSearchto assert against the newitems/totalshapecomposer format/composer check(PHPStan level 8) clean