Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
eea715f
inital testing
jaysomani May 22, 2026
c538349
test: migrate shared tests to Base class with dynamic pattern
jaysomani May 25, 2026
bce2b15
updated with suggestions
jaysomani May 25, 2026
06e0027
suggestion 2 update
jaysomani May 25, 2026
dd802a1
updated with cleanup suggestion
jaysomani May 25, 2026
b8439d8
fixed the testCreateRepositoryWithInvalidName issue
jaysomani May 25, 2026
908c47a
use exception
jaysomani May 25, 2026
1917b74
test: migrate additional shared tests to Base class
jaysomani May 29, 2026
c491a97
fix: normalize searchRepositories return structure and updateCommitSt…
jaysomani Jun 1, 2026
a89bd2e
test: move normalized tests to Base class after adapter fixes
jaysomani Jun 1, 2026
b818f89
retrieved some changes
jaysomani Jun 3, 2026
de8ccaa
fix: restore lost test coverage for private repo, public repo, commit…
jaysomani Jun 4, 2026
a5da570
fix: restore lost test coverage identified by agent review
jaysomani Jun 5, 2026
c5c8f0a
fix: use assertEventually for getLatestCommit calls to handle GitHub …
jaysomani Jul 1, 2026
1d5b3ce
fix: increase assertEventually timeout for listRepositoryLanguages an…
jaysomani Jul 1, 2026
cd0191d
Report HTTP status on GitHub search and commit status errors
HarshMN2345 Jul 27, 2026
2494f7b
Reconcile shared Base tests with main
HarshMN2345 Jul 27, 2026
368b25a
Treat GitHub search 422 as no results and merge duplicate GitLab toke…
HarshMN2345 Jul 27, 2026
99da654
Loosen shared owner-name assertion and wait longer for GitLab hooks
HarshMN2345 Jul 27, 2026
207c7cd
Keep GitLab listNamespaces tests after the rebase
HarshMN2345 Jul 27, 2026
a1fb9fc
Wait longer for GitHub to compute repository languages
HarshMN2345 Jul 27, 2026
c3fcda4
Report GitHub language stats that never arrive as inconclusive
HarshMN2345 Jul 27, 2026
451b453
Retry GitLab org creation while the API is still coming up
HarshMN2345 Jul 27, 2026
57aa7d1
Wait for the provider to stay healthy before running tests
HarshMN2345 Jul 27, 2026
94bd774
Restore assertions that were weakened when tests moved into Base
HarshMN2345 Jul 28, 2026
90b9756
Close the remaining gaps the coverage audit found
HarshMN2345 Jul 28, 2026
fb714a4
Let tree and content reads converge after the writes that produced them
HarshMN2345 Jul 28, 2026
af07a12
Report repository creation failures instead of returning the error body
HarshMN2345 Jul 28, 2026
0cb040d
Wait for the provider to stay healthy before running tests
HarshMN2345 Jul 28, 2026
a483f08
Revert "Wait for the provider to stay healthy before running tests"
HarshMN2345 Jul 28, 2026
1e9f36b
Log GitLab's internal service status after each test run
HarshMN2345 Jul 28, 2026
8a47ada
Let docker compose's own health-gated dependency chain wait for GitLab
HarshMN2345 Jul 28, 2026
ef5f7be
Widen the GitLab webhook delivery window
HarshMN2345 Jul 28, 2026
62aa7df
Log WebHookWorker activity and request-catcher on a webhook test failure
HarshMN2345 Jul 28, 2026
591802a
Merge the two GitLab diagnostic steps into one
HarshMN2345 Jul 28, 2026
5590e47
Detect GitLab by container state instead of matrix.adapter
HarshMN2345 Jul 28, 2026
a77817b
Drop the GitLab diagnostic step
HarshMN2345 Jul 28, 2026
ce4df64
Wait for GitHub to index a commit before branching from it
HarshMN2345 Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/tests-external.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ jobs:
TESTS_GITHUB_APP_IDENTIFIER: ${{ secrets.TESTS_GITHUB_APP_IDENTIFIER }}
TESTS_GITHUB_INSTALLATION_ID: ${{ secrets.TESTS_GITHUB_INSTALLATION_ID }}
run: |
docker compose --profile ${{ matrix.adapter }} up -d
sleep 15
docker compose --profile ${{ matrix.adapter }} up -d --wait --wait-timeout 900
- name: Doctor
run: |
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ jobs:
TESTS_GITHUB_APP_IDENTIFIER: ${{ secrets.TESTS_GITHUB_APP_IDENTIFIER }}
TESTS_GITHUB_INSTALLATION_ID: ${{ secrets.TESTS_GITHUB_INSTALLATION_ID }}
run: |
docker compose --profile ${{ matrix.adapter }} up -d
sleep 15
docker compose --profile ${{ matrix.adapter }} up -d --wait --wait-timeout 900
- name: Doctor
run: |
Expand Down
9 changes: 6 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,13 @@ services:
ports:
- "3003:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/-/health"]
# A single successful check only means nginx answered, not that the API
# underneath it can serve requests, so require 3 in a row before
# depends_on: condition: service_healthy (gitlab-bootstrap below) proceeds.
test: ["CMD-SHELL", "for i in 1 2 3; do curl -f http://localhost/-/health || exit 1; sleep 10; done"]
interval: 30s
timeout: 10s
retries: 20
timeout: 45s
retries: 5
start_period: 300s
profiles:
- gitlab
Expand Down
3 changes: 3 additions & 0 deletions src/VCS/Adapter/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ abstract public function createTag(string $owner, string $repositoryName, string
/**
* Get commit statuses
*
* Every adapter reports each status as
* ['state' => string, 'description' => string, 'target_url' => string, 'context' => string].
*
* @param string $owner Owner of the repository
* @param string $repositoryName Name of the repository
* @param string $commitHash SHA of the commit
Expand Down
48 changes: 36 additions & 12 deletions src/VCS/Adapter/Git/GitHub.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public function createRepository(string $owner, string $repositoryName, bool $pr
'private' => $private,
]);

$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Creating repository {$repositoryName} failed with status code {$statusCode}", $statusCode);
}

return $response['body'] ?? [];
}
/**
Expand Down Expand Up @@ -254,12 +260,21 @@ public function searchRepositories(string $owner, int $page, int $per_page, stri
'per_page' => $per_page,
'sort' => 'updated'
]);
$responseBody = $response['body'] ?? [];
$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;

if (!array_key_exists('items', $responseBody)) {
throw new Exception("Repositories list missing in the response.");
// The search API rejects a query naming an owner that does not exist,
// which is a missing owner rather than a failure worth reporting.
if ($statusCode === 422) {
return ['items' => [], 'total' => 0];
}

if ($statusCode >= 400) {
throw new Exception("Failed to search repositories: HTTP {$statusCode}", $statusCode);
}

$responseBody = $response['body'] ?? [];

return [
'items' => $responseBody['items'] ?? [],
'total' => $responseBody['total_count'] ?? 0,
Expand All @@ -277,12 +292,14 @@ public function searchRepositories(string $owner, int $page, int $per_page, stri
'per_page' => $per_page,
]);

$responseBody = $response['body'] ?? [];

if (!array_key_exists('repositories', $responseBody)) {
throw new Exception("Repositories list missing in the response.");
$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Failed to list installation repositories: HTTP {$statusCode}", $statusCode);
}

$responseBody = $response['body'] ?? [];

return [
'items' => $responseBody['repositories'] ?? [],
'total' => $responseBody['total_count'] ?? 0,
Expand All @@ -297,12 +314,14 @@ public function searchRepositories(string $owner, int $page, int $per_page, stri
'per_page' => 100, // Maximum allowed by GitHub API
]);

$responseBody = $response['body'] ?? [];

if (!array_key_exists('repositories', $responseBody)) {
throw new Exception("Repositories list missing in the response.");
$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Failed to list installation repositories: HTTP {$statusCode}", $statusCode);
}

$responseBody = $response['body'] ?? [];

// Filter repositories to only include those that match the search query.
$filteredRepositories = array_filter($responseBody['repositories'] ?? [], fn ($repo) => stripos($repo['name'] ?? '', $search) !== false);

Expand Down Expand Up @@ -984,7 +1003,12 @@ public function updateCommitStatus(string $repositoryName, string $commitHash, s
'context' => $context,
];

$this->call(self::METHOD_POST, $url, ['Authorization' => "Bearer $this->accessToken"], $body);
$response = $this->call(self::METHOD_POST, $url, ['Authorization' => "Bearer $this->accessToken"], $body);
$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Failed to update commit status: HTTP {$statusCode}", $statusCode);
}
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/VCS/Adapter/Git/GitLab.php
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,8 @@ public function getUser(string $username): array

public function getOwnerName(string $installationId, ?int $repositoryId = null): string
{
if ($repositoryId !== null) {
// A missing or non-positive id means "no repository", same as on Gitea
if ($repositoryId !== null && $repositoryId > 0) {
$url = "/projects/{$repositoryId}";
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => 'Bearer ' . $this->accessToken]);
$responseHeaders = $response['headers'] ?? [];
Expand Down
23 changes: 22 additions & 1 deletion src/VCS/Adapter/Git/Gitea.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ public function createRepository(string $owner, string $repositoryName, bool $pr
'private' => $private,
]);

$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Creating repository {$repositoryName} failed with status code {$statusCode}", $statusCode);
}

$result = $response['body'] ?? [];
if (is_array($result)) {
// Gitea's API does not expose `pushed_at`; surface `updated_at` under that key
Expand Down Expand Up @@ -1289,6 +1295,21 @@ public function getCommitStatuses(string $owner, string $repositoryName, string
throw new Exception("Failed to get commit statuses: HTTP {$responseHeadersStatusCode}", $responseHeadersStatusCode);
}

return $response['body'] ?? [];
$responseBody = $response['body'] ?? [];
if (!is_array($responseBody)) {
return [];
}

$statuses = [];
foreach ($responseBody as $status) {
$statuses[] = [
'state' => $status['status'] ?? '',
'description' => $status['description'] ?? '',
'target_url' => $status['target_url'] ?? '',
'context' => $status['context'] ?? '',
];
}

return $statuses;
}
}
16 changes: 16 additions & 0 deletions src/VCS/Adapter/Git/Gogs.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public function createRepository(string $owner, string $repositoryName, bool $pr
'readme' => 'Default',
]);

$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Creating repository {$repositoryName} failed with status code {$statusCode}", $statusCode);
}

$result = $response['body'] ?? [];
if (is_array($result)) {
// Gogs' API does not expose `pushed_at`; surface `updated_at` under that key
Expand Down Expand Up @@ -491,6 +497,16 @@ public function getPullRequestFromBranch(string $owner, string $repositoryName,
throw new Exception("Pull request API is not supported by Gogs");
}

/**
* Get the files of a pull request
*
* @return array<mixed>
*/
public function getPullRequestFiles(string $owner, string $repositoryName, int $pullRequestNumber): array
{
throw new Exception("Pull request API is not supported by Gogs");
}

/**
* Update commit status
*
Expand Down
14 changes: 4 additions & 10 deletions tests/VCS/Adapter/ForgejoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,19 @@
use Utopia\Cache\Adapter\None;
use Utopia\Cache\Cache;
use Utopia\System\System;
use Utopia\VCS\Adapter\Git;
use Utopia\VCS\Adapter\Git\Forgejo;

class ForgejoTest extends GiteaTest
{
protected static string $accessToken = '';

protected static string $owner = '';
protected static string $avatarDomain = '/avatars/';
protected static string $eventHeader = 'x-forgejo-event';
protected static string $signatureHeader = 'x-forgejo-signature';

protected string $webhookEventHeader = 'X-Forgejo-Event';
protected string $webhookSignatureHeader = 'X-Forgejo-Signature';
protected string $avatarDomain = 'http://localhost:3000/avatars/';

protected function createVCSAdapter(): Git
{
return new Forgejo(new Cache(new None()));
}

public function setUp(): void
protected function setupAdapter(): void
{
if (empty(static::$accessToken)) {
$this->setupForgejo();
Expand Down
Loading
Loading