From 4f907fe1ee018bfa78f6a35bfea0fa7e0d3dbefb Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Mar 2026 10:54:49 +0530 Subject: [PATCH 1/2] feat: implement getOwnerName for Gitea using repositoryId - Add optional repositoryId parameter to abstract getOwnerName method - Update GitHub adapter to accept (but ignore) repositoryId parameter - Implement Gitea getOwnerName using /repositories/{id} endpoint - Add comprehensive tests for success and failure cases - Handle 404 errors with RepositoryNotFound exception - Resolves Gitea OAuth multi-organization access issue --- src/VCS/Adapter.php | 9 +++++++-- src/VCS/Adapter/Git/GitHub.php | 7 +++++-- src/VCS/Adapter/Git/Gitea.php | 30 ++++++++++++++++++++++++++++-- tests/VCS/Adapter/GiteaTest.php | 32 ++++++++++++++++++++++++++++---- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/VCS/Adapter.php b/src/VCS/Adapter.php index 27478a95..6986178d 100644 --- a/src/VCS/Adapter.php +++ b/src/VCS/Adapter.php @@ -80,9 +80,14 @@ abstract public function getUser(string $username): array; /** * Get owner name of the installation * - * @return string + * For GitHub: Uses installationId to identify the GitHub App installation + * For Gitea: Requires repositoryId since OAuth tokens can access multiple organizations + * + * @param string $installationId Installation ID (GitHub) or empty string (Gitea) + * @param int|null $repositoryId Repository ID (required for Gitea, ignored by GitHub) + * @return string Owner login/username */ - abstract public function getOwnerName(string $installationId): string; + abstract public function getOwnerName(string $installationId, ?int $repositoryId = null): string; /** * Search repositories for GitHub App diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index b57290d1..958cac09 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -618,10 +618,13 @@ public function getUser(string $username): array /** * Get owner name of the GitHub installation * - * @return string + * @param string $installationId GitHub App installation ID + * @param int|null $repositoryId Not used by GitHub (parameter exists for Gitea compatibility) + * @return string Owner login/username */ - public function getOwnerName(string $installationId): string + public function getOwnerName(string $installationId, ?int $repositoryId = null): string { + // GitHub doesn't use $repositoryId - only installationId $url = '/app/installations/' . $installationId; $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->jwtToken"]); diff --git a/src/VCS/Adapter/Git/Gitea.php b/src/VCS/Adapter/Git/Gitea.php index c7c3fd10..ffca8c9a 100644 --- a/src/VCS/Adapter/Git/Gitea.php +++ b/src/VCS/Adapter/Git/Gitea.php @@ -531,9 +531,35 @@ public function getUser(string $username): array throw new Exception("Not implemented yet"); } - public function getOwnerName(string $installationId): string + public function getOwnerName(string $installationId, ?int $repositoryId = null): string { - throw new Exception("getOwnerName() is not applicable for Gitea"); + if ($repositoryId === null) { + throw new Exception("repositoryId is required for Gitea"); + } + + $url = "/repositories/{$repositoryId}"; + + $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]); + + $responseHeaders = $response['headers'] ?? []; + $responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0; + + if ($responseHeadersStatusCode === 404) { + throw new RepositoryNotFound("Repository not found"); + } + + if ($responseHeadersStatusCode >= 400) { + throw new Exception("Failed to get repository: HTTP {$responseHeadersStatusCode}"); + } + + $responseBody = $response['body'] ?? []; + $owner = $responseBody['owner'] ?? []; + + if (!array_key_exists('login', $owner)) { + throw new Exception("Owner login missing in response"); + } + + return $owner['login'] ?? ''; } public function getPullRequest(string $owner, string $repositoryName, int $pullRequestNumber): array diff --git a/tests/VCS/Adapter/GiteaTest.php b/tests/VCS/Adapter/GiteaTest.php index 4405bab3..25aba592 100644 --- a/tests/VCS/Adapter/GiteaTest.php +++ b/tests/VCS/Adapter/GiteaTest.php @@ -721,19 +721,43 @@ public function testDeleteNonExistingRepositoryFails(): void } public function testGetOwnerName(): void + { + $repositoryName = 'test-get-owner-name-' . \uniqid(); + $created = $this->vcsAdapter->createRepository(self::$owner, $repositoryName, false); + + $this->assertIsArray($created); + $this->assertArrayHasKey('id', $created); + $this->assertIsScalar($created['id']); + $repositoryId = (int) $created['id']; + + $ownerName = $this->vcsAdapter->getOwnerName('', $repositoryId); + + $this->assertSame(self::$owner, $ownerName); + + $this->vcsAdapter->deleteRepository(self::$owner, $repositoryName); + } + + public function testGetOwnerNameWithoutRepositoryId(): void { $this->expectException(\Exception::class); - $this->expectExceptionMessage('not applicable for Gitea'); + $this->expectExceptionMessage('repositoryId is required for Gitea'); $this->vcsAdapter->getOwnerName(''); } - public function testGetOwnerNameWithRandomInput(): void + public function testGetOwnerNameWithInvalidRepositoryId(): void + { + $this->expectException(\Utopia\VCS\Exception\RepositoryNotFound::class); + + $this->vcsAdapter->getOwnerName('', 999999999); + } + + public function testGetOwnerNameWithNullRepositoryId(): void { $this->expectException(\Exception::class); - $this->expectExceptionMessage('not applicable for Gitea'); + $this->expectExceptionMessage('repositoryId is required for Gitea'); - $this->vcsAdapter->getOwnerName('random-gibberish-' . \uniqid()); + $this->vcsAdapter->getOwnerName('', null); } public function testGetPullRequestFromBranch(): void From 73b95f703d86c17fd66bc63e8de5f3e18aa76453 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Mar 2026 11:16:08 +0530 Subject: [PATCH 2/2] updated with suggestions --- src/VCS/Adapter/Git/Gitea.php | 8 ++++---- tests/VCS/Adapter/GiteaTest.php | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/VCS/Adapter/Git/Gitea.php b/src/VCS/Adapter/Git/Gitea.php index ffca8c9a..aedf6c7e 100644 --- a/src/VCS/Adapter/Git/Gitea.php +++ b/src/VCS/Adapter/Git/Gitea.php @@ -533,7 +533,7 @@ public function getUser(string $username): array public function getOwnerName(string $installationId, ?int $repositoryId = null): string { - if ($repositoryId === null) { + if ($repositoryId === null || $repositoryId <= 0) { throw new Exception("repositoryId is required for Gitea"); } @@ -555,11 +555,11 @@ public function getOwnerName(string $installationId, ?int $repositoryId = null): $responseBody = $response['body'] ?? []; $owner = $responseBody['owner'] ?? []; - if (!array_key_exists('login', $owner)) { - throw new Exception("Owner login missing in response"); + if (empty($owner['login'])) { + throw new Exception("Owner login missing or empty in response"); } - return $owner['login'] ?? ''; + return $owner['login']; } public function getPullRequest(string $owner, string $repositoryName, int $pullRequestNumber): array diff --git a/tests/VCS/Adapter/GiteaTest.php b/tests/VCS/Adapter/GiteaTest.php index 25aba592..bed2ff4c 100644 --- a/tests/VCS/Adapter/GiteaTest.php +++ b/tests/VCS/Adapter/GiteaTest.php @@ -725,16 +725,26 @@ public function testGetOwnerName(): void $repositoryName = 'test-get-owner-name-' . \uniqid(); $created = $this->vcsAdapter->createRepository(self::$owner, $repositoryName, false); - $this->assertIsArray($created); - $this->assertArrayHasKey('id', $created); - $this->assertIsScalar($created['id']); - $repositoryId = (int) $created['id']; + try { + $this->assertIsArray($created); + $this->assertArrayHasKey('id', $created); + $this->assertIsScalar($created['id']); + $repositoryId = (int) $created['id']; - $ownerName = $this->vcsAdapter->getOwnerName('', $repositoryId); + $ownerName = $this->vcsAdapter->getOwnerName('', $repositoryId); - $this->assertSame(self::$owner, $ownerName); + $this->assertSame(self::$owner, $ownerName); + } finally { + $this->vcsAdapter->deleteRepository(self::$owner, $repositoryName); + } + } - $this->vcsAdapter->deleteRepository(self::$owner, $repositoryName); + public function testGetOwnerNameWithZeroRepositoryId(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('repositoryId is required for Gitea'); + + $this->vcsAdapter->getOwnerName('', 0); } public function testGetOwnerNameWithoutRepositoryId(): void