From 62e8fff274abc4dc61b302bea2570df4135fd9ac Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 16:43:57 +0530 Subject: [PATCH 01/10] fix(bitbucket): name a project when the workspace won't pick one A Bitbucket repository belongs to a project. Bitbucket names one itself where the workspace has a default, which is why creating a repository has worked, and refuses the repository where it doesn't -- leaving the console reporting that the repository could not be created and nothing a user can do about it. The workspace is asked for a project and the create repeated with it, only where Bitbucket said the project was what it objected to. A name already taken, or a credential lacking the scope to create anything, is reported as it was. --- src/VCS/Adapter/Git/Bitbucket.php | 33 +++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/VCS/Adapter/Git/Bitbucket.php b/src/VCS/Adapter/Git/Bitbucket.php index 95f2eff8..3b0f5db1 100644 --- a/src/VCS/Adapter/Git/Bitbucket.php +++ b/src/VCS/Adapter/Git/Bitbucket.php @@ -259,17 +259,38 @@ private function normalizeRepository(array $repository): array public function createRepository(string $owner, string $repositoryName, bool $private): array { $url = "/repositories/{$owner}/{$repositoryName}"; - - $response = $this->call(self::METHOD_POST, $url, ['Authorization' => $this->authorizationHeader()], [ + $payload = [ 'scm' => 'git', 'name' => $repositoryName, 'is_private' => $private, - ]); + ]; + + $response = $this->call(self::METHOD_POST, $url, ['Authorization' => $this->authorizationHeader()], $payload); + + $statusCode = $response['headers']['status-code'] ?? 0; + $error = $response['body']['error']['message'] ?? ''; + + // A repository belongs to a project. Bitbucket names one itself where + // the workspace has a default, and refuses the repository where it + // doesn't, so the workspace is asked for a project and the create + // repeated with it. + if ($statusCode >= 400 && \stripos($error, 'project') !== false) { + $projects = $this->call( + self::METHOD_GET, + "/workspaces/{$owner}/projects?pagelen=1", + ['Authorization' => $this->authorizationHeader()] + ); + $key = (string) ($projects['body']['values'][0]['key'] ?? ''); + + if ($key !== '') { + $payload['project'] = ['key' => $key]; + $response = $this->call(self::METHOD_POST, $url, ['Authorization' => $this->authorizationHeader()], $payload); + $statusCode = $response['headers']['status-code'] ?? 0; + $error = $response['body']['error']['message'] ?? ''; + } + } - $responseHeaders = $response['headers'] ?? []; - $statusCode = $responseHeaders['status-code'] ?? 0; if ($statusCode >= 400) { - $error = $response['body']['error']['message'] ?? ''; throw new Exception( "Creating repository {$repositoryName} failed with status code {$statusCode}" . ($error !== '' ? ": {$error}" : ''), $statusCode From 0fd9b98168e27f9a7d1b1dd46262e25d8564468d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 16:49:27 +0530 Subject: [PATCH 02/10] feat(bitbucket): take the project a repository belongs to Bitbucket groups repositories under a project and asks for one when a repository is created, which GitHub and GitLab have no equivalent of. The caller can now name it, and the adapters that don't group repositories ignore what they're given. --- src/VCS/Adapter.php | 3 ++- src/VCS/Adapter/Git/Bitbucket.php | 14 ++++++++------ src/VCS/Adapter/Git/GitHub.php | 2 +- src/VCS/Adapter/Git/GitLab.php | 2 +- src/VCS/Adapter/Git/Gitea.php | 2 +- src/VCS/Adapter/Git/Gogs.php | 2 +- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/VCS/Adapter.php b/src/VCS/Adapter.php index 8411ee8e..1cc2877d 100644 --- a/src/VCS/Adapter.php +++ b/src/VCS/Adapter.php @@ -131,9 +131,10 @@ abstract public function getRepository(string $owner, string $repositoryName): a /** * Create new repository * + * @param string $project Project the repository belongs to, where the provider groups them * @return array Details of new repository */ - abstract public function createRepository(string $owner, string $repositoryName, bool $private): array; + abstract public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array; /** * Delete repository diff --git a/src/VCS/Adapter/Git/Bitbucket.php b/src/VCS/Adapter/Git/Bitbucket.php index 3b0f5db1..87879246 100644 --- a/src/VCS/Adapter/Git/Bitbucket.php +++ b/src/VCS/Adapter/Git/Bitbucket.php @@ -256,7 +256,7 @@ private function normalizeRepository(array $repository): array return $repository; } - public function createRepository(string $owner, string $repositoryName, bool $private): array + public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array { $url = "/repositories/{$owner}/{$repositoryName}"; $payload = [ @@ -265,16 +265,18 @@ public function createRepository(string $owner, string $repositoryName, bool $pr 'is_private' => $private, ]; + if ($project !== '') { + $payload['project'] = ['key' => $project]; + } + $response = $this->call(self::METHOD_POST, $url, ['Authorization' => $this->authorizationHeader()], $payload); $statusCode = $response['headers']['status-code'] ?? 0; $error = $response['body']['error']['message'] ?? ''; - // A repository belongs to a project. Bitbucket names one itself where - // the workspace has a default, and refuses the repository where it - // doesn't, so the workspace is asked for a project and the create - // repeated with it. - if ($statusCode >= 400 && \stripos($error, 'project') !== false) { + // A repository belongs to a project. Where the caller named none and + // the workspace has no default to fall back on, one is chosen for it. + if ($project === '' && $statusCode >= 400 && \stripos($error, 'project') !== false) { $projects = $this->call( self::METHOD_GET, "/workspaces/{$owner}/projects?pagelen=1", diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index 826b92ee..d8421ebc 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -92,7 +92,7 @@ public function initializeVariables(string $installationId, string $privateKey, * * @return array Details of new repository */ - public function createRepository(string $owner, string $repositoryName, bool $private): array + public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array { $url = "/orgs/{$owner}/repos"; diff --git a/src/VCS/Adapter/Git/GitLab.php b/src/VCS/Adapter/Git/GitLab.php index 6d25871b..153e37a0 100644 --- a/src/VCS/Adapter/Git/GitLab.php +++ b/src/VCS/Adapter/Git/GitLab.php @@ -134,7 +134,7 @@ private function getNamespaceId(string $owner): string return $owner; } - public function createRepository(string $owner, string $repositoryName, bool $private): array + public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array { $namespaceId = (int) $this->getNamespaceId($owner); diff --git a/src/VCS/Adapter/Git/Gitea.php b/src/VCS/Adapter/Git/Gitea.php index d5d7ab18..c11e12bf 100644 --- a/src/VCS/Adapter/Git/Gitea.php +++ b/src/VCS/Adapter/Git/Gitea.php @@ -91,7 +91,7 @@ protected function generateAccessToken(string $privateKey, string $appId): void * * @return array Details of new repository */ - public function createRepository(string $owner, string $repositoryName, bool $private): array + public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array { $url = "/orgs/{$owner}/repos"; diff --git a/src/VCS/Adapter/Git/Gogs.php b/src/VCS/Adapter/Git/Gogs.php index e84de099..9a6b0875 100644 --- a/src/VCS/Adapter/Git/Gogs.php +++ b/src/VCS/Adapter/Git/Gogs.php @@ -44,7 +44,7 @@ public function getBranchUrl(string $owner, string $repositoryName, string $bran * * @return array Details of new repository */ - public function createRepository(string $owner, string $repositoryName, bool $private): array + public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array { $url = "/org/{$owner}/repos"; From 0cfd6528cad9a1b6e0caa61e5e779f2238863f43 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:03:20 +0530 Subject: [PATCH 03/10] fix(bitbucket): read the project lookup the way the rest of the file does --- src/VCS/Adapter/Git/Bitbucket.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VCS/Adapter/Git/Bitbucket.php b/src/VCS/Adapter/Git/Bitbucket.php index 87879246..ec32cae8 100644 --- a/src/VCS/Adapter/Git/Bitbucket.php +++ b/src/VCS/Adapter/Git/Bitbucket.php @@ -282,7 +282,8 @@ public function createRepository(string $owner, string $repositoryName, bool $pr "/workspaces/{$owner}/projects?pagelen=1", ['Authorization' => $this->authorizationHeader()] ); - $key = (string) ($projects['body']['values'][0]['key'] ?? ''); + $projectsBody = $projects['body'] ?? []; + $key = \is_array($projectsBody) ? (string) ($projectsBody['values'][0]['key'] ?? '') : ''; if ($key !== '') { $payload['project'] = ['key' => $key]; From ce1c8343c6859ad85b033a0d56827fc2b9bee9ad Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:08:50 +0530 Subject: [PATCH 04/10] refactor(bitbucket): let the workspace say it needs a project Choosing a project for the caller put the repository somewhere nobody asked for, guessed from whichever the workspace listed first, and read the provider's wording to decide when to guess. A workspace that wants a project says so, and the caller now has a way to answer. --- src/VCS/Adapter/Git/Bitbucket.php | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/VCS/Adapter/Git/Bitbucket.php b/src/VCS/Adapter/Git/Bitbucket.php index ec32cae8..04c6d560 100644 --- a/src/VCS/Adapter/Git/Bitbucket.php +++ b/src/VCS/Adapter/Git/Bitbucket.php @@ -271,29 +271,10 @@ public function createRepository(string $owner, string $repositoryName, bool $pr $response = $this->call(self::METHOD_POST, $url, ['Authorization' => $this->authorizationHeader()], $payload); - $statusCode = $response['headers']['status-code'] ?? 0; - $error = $response['body']['error']['message'] ?? ''; - - // A repository belongs to a project. Where the caller named none and - // the workspace has no default to fall back on, one is chosen for it. - if ($project === '' && $statusCode >= 400 && \stripos($error, 'project') !== false) { - $projects = $this->call( - self::METHOD_GET, - "/workspaces/{$owner}/projects?pagelen=1", - ['Authorization' => $this->authorizationHeader()] - ); - $projectsBody = $projects['body'] ?? []; - $key = \is_array($projectsBody) ? (string) ($projectsBody['values'][0]['key'] ?? '') : ''; - - if ($key !== '') { - $payload['project'] = ['key' => $key]; - $response = $this->call(self::METHOD_POST, $url, ['Authorization' => $this->authorizationHeader()], $payload); - $statusCode = $response['headers']['status-code'] ?? 0; - $error = $response['body']['error']['message'] ?? ''; - } - } - + $responseHeaders = $response['headers'] ?? []; + $statusCode = $responseHeaders['status-code'] ?? 0; if ($statusCode >= 400) { + $error = $response['body']['error']['message'] ?? ''; throw new Exception( "Creating repository {$repositoryName} failed with status code {$statusCode}" . ($error !== '' ? ": {$error}" : ''), $statusCode From fd7f0d92b8d550fe37c5f8fac4b11e4d2068a0c2 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:12:55 +0530 Subject: [PATCH 05/10] test(bitbucket): cover the project a repository is created in One test names the project the workspace already chose and reads it back off the repository. The other names one the workspace doesn't hold and expects the refusal, so a name that never left the adapter would fail it. --- tests/VCS/Adapter/BitbucketTest.php | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/VCS/Adapter/BitbucketTest.php b/tests/VCS/Adapter/BitbucketTest.php index 3bb9cffb..1c52f1f9 100644 --- a/tests/VCS/Adapter/BitbucketTest.php +++ b/tests/VCS/Adapter/BitbucketTest.php @@ -6,6 +6,7 @@ use Utopia\Cache\Cache; use Utopia\System\System; use Utopia\Tests\Base; +use Exception; use Utopia\VCS\Adapter\Git\Bitbucket; class BitbucketTest extends Base @@ -175,6 +176,52 @@ private function eventActor(): array * Bitbucket only names the author in a raw "Name " string; a commit * linked to an account is named by the account instead. */ + /** + * Bitbucket groups repositories under a project, which the other providers + * have no equivalent of. + */ + public function testCreateRepositoryInANamedProject(): void + { + $repositoryName = 'test-create-repository-project-' . \uniqid(); + + // The workspace names a project where the caller doesn't, which is the + // one to name back at it -- reading it here keeps the test off a key + // only one workspace has. + $default = $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); + + try { + $key = (string) ($default['project']['key'] ?? ''); + $this->assertNotEmpty($key, 'Bitbucket reported no project for the new repository'); + + $named = 'test-create-repository-project-named-' . \uniqid(); + + try { + $result = $this->vcsAdapter->createRepository(static::$owner, $named, false, $key); + $this->assertSame($key, (string) ($result['project']['key'] ?? '')); + } finally { + $this->discardRepositories($named); + } + } finally { + $this->discardRepositories($repositoryName); + } + } + + /** + * A project the workspace doesn't hold is refused, so the name reaches + * Bitbucket rather than being dropped on the way. + */ + public function testCreateRepositoryInAnUnknownProjectFails(): void + { + $this->expectException(Exception::class); + + $this->vcsAdapter->createRepository( + static::$owner, + 'test-create-repository-unknown-project-' . \uniqid(), + false, + 'NOSUCHPROJECTKEY' + ); + } + public function testGetEventPushWithLinkedAuthor(): void { $payload = json_decode($this->pushPayload(static::$defaultBranch), true); From ee6922f15275adc6117fbe2bdca4d49de10db6f9 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:20:42 +0530 Subject: [PATCH 06/10] test(bitbucket): expect an unknown project to be refused --- tests/VCS/Adapter/BitbucketTest.php | 34 ----------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/VCS/Adapter/BitbucketTest.php b/tests/VCS/Adapter/BitbucketTest.php index 1c52f1f9..4c697563 100644 --- a/tests/VCS/Adapter/BitbucketTest.php +++ b/tests/VCS/Adapter/BitbucketTest.php @@ -176,40 +176,6 @@ private function eventActor(): array * Bitbucket only names the author in a raw "Name " string; a commit * linked to an account is named by the account instead. */ - /** - * Bitbucket groups repositories under a project, which the other providers - * have no equivalent of. - */ - public function testCreateRepositoryInANamedProject(): void - { - $repositoryName = 'test-create-repository-project-' . \uniqid(); - - // The workspace names a project where the caller doesn't, which is the - // one to name back at it -- reading it here keeps the test off a key - // only one workspace has. - $default = $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); - - try { - $key = (string) ($default['project']['key'] ?? ''); - $this->assertNotEmpty($key, 'Bitbucket reported no project for the new repository'); - - $named = 'test-create-repository-project-named-' . \uniqid(); - - try { - $result = $this->vcsAdapter->createRepository(static::$owner, $named, false, $key); - $this->assertSame($key, (string) ($result['project']['key'] ?? '')); - } finally { - $this->discardRepositories($named); - } - } finally { - $this->discardRepositories($repositoryName); - } - } - - /** - * A project the workspace doesn't hold is refused, so the name reaches - * Bitbucket rather than being dropped on the way. - */ public function testCreateRepositoryInAnUnknownProjectFails(): void { $this->expectException(Exception::class); From 74963301bbf9328beecd5a26a106f6d789e2c5f7 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:36:05 +0530 Subject: [PATCH 07/10] refactor(bitbucket): keep the project off the shared contract --- src/VCS/Adapter.php | 3 +-- src/VCS/Adapter/Git/Bitbucket.php | 6 ++++++ src/VCS/Adapter/Git/GitHub.php | 2 +- src/VCS/Adapter/Git/GitLab.php | 2 +- src/VCS/Adapter/Git/Gitea.php | 2 +- src/VCS/Adapter/Git/Gogs.php | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/VCS/Adapter.php b/src/VCS/Adapter.php index 1cc2877d..8411ee8e 100644 --- a/src/VCS/Adapter.php +++ b/src/VCS/Adapter.php @@ -131,10 +131,9 @@ abstract public function getRepository(string $owner, string $repositoryName): a /** * Create new repository * - * @param string $project Project the repository belongs to, where the provider groups them * @return array Details of new repository */ - abstract public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array; + abstract public function createRepository(string $owner, string $repositoryName, bool $private): array; /** * Delete repository diff --git a/src/VCS/Adapter/Git/Bitbucket.php b/src/VCS/Adapter/Git/Bitbucket.php index 04c6d560..938a741e 100644 --- a/src/VCS/Adapter/Git/Bitbucket.php +++ b/src/VCS/Adapter/Git/Bitbucket.php @@ -256,6 +256,12 @@ private function normalizeRepository(array $repository): array return $repository; } + /** + * Bitbucket alone groups repositories under a project, so the argument + * stays optional and off the shared three-argument contract. + * + * @return array Details of new repository + */ public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array { $url = "/repositories/{$owner}/{$repositoryName}"; diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index d8421ebc..826b92ee 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -92,7 +92,7 @@ public function initializeVariables(string $installationId, string $privateKey, * * @return array Details of new repository */ - public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array + public function createRepository(string $owner, string $repositoryName, bool $private): array { $url = "/orgs/{$owner}/repos"; diff --git a/src/VCS/Adapter/Git/GitLab.php b/src/VCS/Adapter/Git/GitLab.php index 153e37a0..6d25871b 100644 --- a/src/VCS/Adapter/Git/GitLab.php +++ b/src/VCS/Adapter/Git/GitLab.php @@ -134,7 +134,7 @@ private function getNamespaceId(string $owner): string return $owner; } - public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array + public function createRepository(string $owner, string $repositoryName, bool $private): array { $namespaceId = (int) $this->getNamespaceId($owner); diff --git a/src/VCS/Adapter/Git/Gitea.php b/src/VCS/Adapter/Git/Gitea.php index c11e12bf..d5d7ab18 100644 --- a/src/VCS/Adapter/Git/Gitea.php +++ b/src/VCS/Adapter/Git/Gitea.php @@ -91,7 +91,7 @@ protected function generateAccessToken(string $privateKey, string $appId): void * * @return array Details of new repository */ - public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array + public function createRepository(string $owner, string $repositoryName, bool $private): array { $url = "/orgs/{$owner}/repos"; diff --git a/src/VCS/Adapter/Git/Gogs.php b/src/VCS/Adapter/Git/Gogs.php index 9a6b0875..e84de099 100644 --- a/src/VCS/Adapter/Git/Gogs.php +++ b/src/VCS/Adapter/Git/Gogs.php @@ -44,7 +44,7 @@ public function getBranchUrl(string $owner, string $repositoryName, string $bran * * @return array Details of new repository */ - public function createRepository(string $owner, string $repositoryName, bool $private, string $project = ''): array + public function createRepository(string $owner, string $repositoryName, bool $private): array { $url = "/org/{$owner}/repos"; From 16b6af23642501176a833cbf55407777d79445b6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:36:05 +0530 Subject: [PATCH 08/10] test(bitbucket): cover creating with, without and against an unknown project --- tests/VCS/Adapter/BitbucketTest.php | 70 +++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/tests/VCS/Adapter/BitbucketTest.php b/tests/VCS/Adapter/BitbucketTest.php index 4c697563..97106deb 100644 --- a/tests/VCS/Adapter/BitbucketTest.php +++ b/tests/VCS/Adapter/BitbucketTest.php @@ -46,6 +46,12 @@ class BitbucketTest extends Base // Bitbucket Cloud can't reach a local test catcher protected static bool $supportsWebhookDelivery = false; + private static string $projectKey = ''; + + // Projects are Bitbucket's alone, so they are reached off the adapter + // itself rather than through the shared contract + private Bitbucket $bitbucket; + protected function signWebhookPayload(string $payload, string $secret): string { return 'sha256=' . hash_hmac('sha256', $payload, $secret); @@ -76,6 +82,7 @@ protected function setupAdapter(): void } $this->vcsAdapter = $adapter; + $this->bitbucket = $adapter; } /** @@ -173,14 +180,71 @@ private function eventActor(): array } /** - * Bitbucket only names the author in a raw "Name " string; a commit - * linked to an account is named by the account instead. + * @param array $repository + */ + private function projectKeyOf(array $repository): string + { + $this->assertArrayHasKey('project', $repository); + $this->assertIsArray($repository['project']); + $this->assertArrayHasKey('key', $repository['project']); + $this->assertIsString($repository['project']['key']); + + return $repository['project']['key']; + } + + /** + * Nothing configures a project key, so it is read back off a repository the + * workspace filed under its own default. */ + private function defaultProjectKey(): string + { + if (self::$projectKey === '') { + $repositoryName = 'test-default-project-' . \uniqid(); + $repository = $this->bitbucket->createRepository(static::$owner, $repositoryName, false); + + try { + self::$projectKey = $this->projectKeyOf($repository); + } finally { + $this->discardRepositories($repositoryName); + } + } + + return self::$projectKey; + } + + public function testCreateRepositoryWithoutProject(): void + { + $repositoryName = 'test-create-repository-no-project-' . \uniqid(); + $repository = $this->bitbucket->createRepository(static::$owner, $repositoryName, false); + + try { + $this->assertSame($repositoryName, $repository['name']); + self::$projectKey = $this->projectKeyOf($repository); + } finally { + $this->discardRepositories($repositoryName); + } + } + + public function testCreateRepositoryInProject(): void + { + $projectKey = $this->defaultProjectKey(); + + $repositoryName = 'test-create-repository-project-' . \uniqid(); + $repository = $this->bitbucket->createRepository(static::$owner, $repositoryName, false, $projectKey); + + try { + $this->assertSame($repositoryName, $repository['name']); + $this->assertSame($projectKey, $this->projectKeyOf($repository)); + } finally { + $this->discardRepositories($repositoryName); + } + } + public function testCreateRepositoryInAnUnknownProjectFails(): void { $this->expectException(Exception::class); - $this->vcsAdapter->createRepository( + $this->bitbucket->createRepository( static::$owner, 'test-create-repository-unknown-project-' . \uniqid(), false, From f397ec76b3eab57cb8d72f1f4bf1423222dd0923 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:43:22 +0530 Subject: [PATCH 09/10] test(bitbucket): walk both project scenarios in one test --- tests/VCS/Adapter/BitbucketTest.php | 49 +++++++---------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/tests/VCS/Adapter/BitbucketTest.php b/tests/VCS/Adapter/BitbucketTest.php index 97106deb..14d87430 100644 --- a/tests/VCS/Adapter/BitbucketTest.php +++ b/tests/VCS/Adapter/BitbucketTest.php @@ -46,8 +46,6 @@ class BitbucketTest extends Base // Bitbucket Cloud can't reach a local test catcher protected static bool $supportsWebhookDelivery = false; - private static string $projectKey = ''; - // Projects are Bitbucket's alone, so they are reached off the adapter // itself rather than through the shared contract private Bitbucket $bitbucket; @@ -192,51 +190,28 @@ private function projectKeyOf(array $repository): string return $repository['project']['key']; } - /** - * Nothing configures a project key, so it is read back off a repository the - * workspace filed under its own default. - */ - private function defaultProjectKey(): string + public function testCreateRepositoryProject(): void { - if (self::$projectKey === '') { - $repositoryName = 'test-default-project-' . \uniqid(); - $repository = $this->bitbucket->createRepository(static::$owner, $repositoryName, false); - - try { - self::$projectKey = $this->projectKeyOf($repository); - } finally { - $this->discardRepositories($repositoryName); - } - } - - return self::$projectKey; - } - - public function testCreateRepositoryWithoutProject(): void - { - $repositoryName = 'test-create-repository-no-project-' . \uniqid(); - $repository = $this->bitbucket->createRepository(static::$owner, $repositoryName, false); + // Naming no project files the repository under the workspace default, + // which is also the only place an existing key can be read from + $withoutProject = 'test-create-repository-no-project-' . \uniqid(); + $repository = $this->bitbucket->createRepository(static::$owner, $withoutProject, false); try { - $this->assertSame($repositoryName, $repository['name']); - self::$projectKey = $this->projectKeyOf($repository); + $this->assertSame($withoutProject, $repository['name']); + $projectKey = $this->projectKeyOf($repository); } finally { - $this->discardRepositories($repositoryName); + $this->discardRepositories($withoutProject); } - } - - public function testCreateRepositoryInProject(): void - { - $projectKey = $this->defaultProjectKey(); - $repositoryName = 'test-create-repository-project-' . \uniqid(); - $repository = $this->bitbucket->createRepository(static::$owner, $repositoryName, false, $projectKey); + $inProject = 'test-create-repository-project-' . \uniqid(); + $repository = $this->bitbucket->createRepository(static::$owner, $inProject, false, $projectKey); try { - $this->assertSame($repositoryName, $repository['name']); + $this->assertSame($inProject, $repository['name']); $this->assertSame($projectKey, $this->projectKeyOf($repository)); } finally { - $this->discardRepositories($repositoryName); + $this->discardRepositories($inProject); } } From 0c88b471c45eddf9520f5c500352fab95f5fa9f0 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 11 Aug 2026 17:45:41 +0530 Subject: [PATCH 10/10] test(bitbucket): read the project key where it is asserted --- tests/VCS/Adapter/BitbucketTest.php | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tests/VCS/Adapter/BitbucketTest.php b/tests/VCS/Adapter/BitbucketTest.php index 14d87430..b3233a02 100644 --- a/tests/VCS/Adapter/BitbucketTest.php +++ b/tests/VCS/Adapter/BitbucketTest.php @@ -177,19 +177,6 @@ private function eventActor(): array ]; } - /** - * @param array $repository - */ - private function projectKeyOf(array $repository): string - { - $this->assertArrayHasKey('project', $repository); - $this->assertIsArray($repository['project']); - $this->assertArrayHasKey('key', $repository['project']); - $this->assertIsString($repository['project']['key']); - - return $repository['project']['key']; - } - public function testCreateRepositoryProject(): void { // Naming no project files the repository under the workspace default, @@ -199,7 +186,11 @@ public function testCreateRepositoryProject(): void try { $this->assertSame($withoutProject, $repository['name']); - $projectKey = $this->projectKeyOf($repository); + $this->assertArrayHasKey('project', $repository); + $this->assertIsArray($repository['project']); + $this->assertArrayHasKey('key', $repository['project']); + $this->assertIsString($repository['project']['key']); + $projectKey = $repository['project']['key']; } finally { $this->discardRepositories($withoutProject); } @@ -209,7 +200,10 @@ public function testCreateRepositoryProject(): void try { $this->assertSame($inProject, $repository['name']); - $this->assertSame($projectKey, $this->projectKeyOf($repository)); + $this->assertArrayHasKey('project', $repository); + $this->assertIsArray($repository['project']); + $this->assertArrayHasKey('key', $repository['project']); + $this->assertSame($projectKey, $repository['project']['key']); } finally { $this->discardRepositories($inProject); }