Plan — BJ-8: BitbucketService (GitHub parity surface)#
Summary#
Create BitbucketService and BitbucketModule under src/bitbucket/, mirroring the public surface of GitHubService against the Bitbucket Cloud v2 API. Wire PLATFORM_AUTH_PROVIDER via factory and ship unit tests covering each public method.
Files#
| Path | Action | Purpose |
|---|---|---|
src/bitbucket/bitbucket.service.ts | create | All 18 public methods + private request/sleep helpers, base https://api.bitbucket.org/2.0. |
src/bitbucket/bitbucket.module.ts | create | NestJS module exporting BitbucketService + PLATFORM_AUTH_PROVIDER token, factory injects BitbucketOAuthAuthProvider (from #542). |
src/bitbucket/bitbucket.service.spec.ts | create | Unit tests with fetch spy + mocked PlatformAuthProvider (mirrors github.service.spec.ts). |
src/bitbucket/index.ts | create | Barrel re-export of module + service (mirrors src/github/index.ts). |
Steps#
- Implement
BitbucketServiceprivaterequest<T>()helper:Bearertoken fromauthProvider.getToken(), JSON Accept, 429 retry honouringRetry-After, 401 → callgetToken()once more then retry; throw typed Error otherwise. - Implement comments —
postComment(POST /repositories/{ws}/{repo}/pullrequests/{id}/commentswith{content:{raw}}),editComment(PUT …/comments/{cid}),findCommentByPrefix(paginatedGETovervalues[].content.raw). - Implement PR ops —
createPR(POST /repositories/{ws}/{repo}/pullrequests, return{number: id}),mergePR(POST …/{id}/mergewith squash strategy),updatePRBranch(no native API → log warn + no-op),closePR(POST …/{id}/decline, swallow 404/already-closed),enableAutoMerge(throwNotImplementedException). - Implement labels via PR title-prefix fallback —
addLabel/removeLabelfetch the PR, mutatetitleto add/strip[label]prefix,PUTit back. - Implement file ops —
getFileContent(GET /repositories/{ws}/{repo}/src/{ref}/{path}, return{content, sha:commit}, 404→null),getPRFiles(GET …/{id}/diffstat, mapnew.path),createOrUpdateFile(POST /repositories/{ws}/{repo}/srcmultipart with branch + message). - Implement branches —
createBranch(POST /repositories/{ws}/{repo}/refs/brancheswith{name, target:{hash}}, on conflict delete+recreate),getDefaultBranchSha(GET /repositories/{ws}/{repo}→mainbranch.namethenGET …/refs/branches/{name}→target.hash),deleteBranch(DELETE …/refs/branches/{name}, swallow 404). - Implement status —
getCheckStatus(GET /repositories/{ws}/{repo}/commit/{ref}/statuses→ mapstateSUCCESSFUL→passing, INPROGRESS→pending, return{passing,noChecks}),getReviewStatus(GET …/pullrequests/{id}→participants[].approvedany-true). - Treat
ownerargument as Bitbucket workspace slug across all paths (single positional rename only — keep the GitHub signature for substitutability). - Wire
bitbucket.module.ts:imports:[ConfigModule], factory provider forPLATFORM_AUTH_PROVIDERreturningnew BitbucketOAuthAuthProvider(config)(file from #542 — if missing at implementation time, raise as blocker). - Write
bitbucket.service.spec.ts: onedescribeper method group, spy onglobalThis.fetch, mockPlatformAuthProviderreturning a static token, assert URL + method + body + auth header; cover 429-retry, 401-refresh, label title-prefix round-trip,enableAutoMergethrows.
Verification#
npm run lintandnpm run testpass with zero warnings (per CLAUDE.md zero-warnings policy).- New spec covers every public method; coverage report shows
src/bitbucket/bitbucket.service.ts≥ existinggithub.service.tsline coverage. - Module compiles in isolation:
Test.createTestingModule({ imports: [BitbucketModule] })resolves bothBitbucketServiceandPLATFORM_AUTH_PROVIDER.
Risks#
- #542 not yet merged —
BitbucketOAuthAuthProviderdoes not exist onrc/atlassian-integrationtoday. Mitigation: depend on the import path declared in #542 (../atlassian/bitbucket-oauth-auth.provider); if absent at impl time, surface via BLOCKED. - Bitbucket API quirks (multipart
srcupload,stateenum naming) may force per-method shape divergence from GitHub — accept and document inline rather than force exact symmetry.
Open Questions#
- None blocking — assumptions noted in issue (
enableAutoMergethrows, label title-prefix fallback, retry-once on 401) are accepted.