From aa96a4136aa82f3abbd281cd31e688f4f2b45ef0 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 2 Jun 2026 13:38:13 +0530 Subject: [PATCH 1/3] feat: add getCheckRunByName to look up check run ID by commit ref and name --- src/VCS/Adapter.php | 9 +++++++++ src/VCS/Adapter/Git/GitHub.php | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/VCS/Adapter.php b/src/VCS/Adapter.php index 1d4ca02b..52da47c0 100644 --- a/src/VCS/Adapter.php +++ b/src/VCS/Adapter.php @@ -266,6 +266,15 @@ public function createCheckRun( throw new \Exception('createCheckRun() is not implemented for ' . $this->getName()); } + /** + * Finds the most recent check run on a commit by name. + * Returns the check run ID, or 0 if none found. + */ + public function getCheckRunByName(string $owner, string $repositoryName, string $ref, string $checkName): int + { + throw new \Exception('getCheckRunByName() is not implemented for ' . $this->getName()); + } + /** * Gets a check run by ID. * diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index 1af30388..b57aeee8 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -955,6 +955,29 @@ public function createCheckRun( return $response['body'] ?? []; } + /** + * Finds the most recent check run on a commit by name. + * Returns the check run ID, or 0 if none found. + */ + public function getCheckRunByName(string $owner, string $repositoryName, string $ref, string $checkName): int + { + $url = "/repos/$owner/$repositoryName/commits/$ref/check-runs"; + + $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ + 'check_name' => $checkName, + 'per_page' => 1, + ]); + + $responseHeadersStatusCode = $response['headers']['status-code'] ?? 0; + if ($responseHeadersStatusCode >= 400) { + return 0; + } + + $runs = $response['body']['check_runs'] ?? []; + + return (int) ($runs[0]['id'] ?? 0); + } + /** * Gets a check run by ID. * From 3bbbbef8862451148a83c0c7492c0051c0bc983b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 2 Jun 2026 13:40:27 +0530 Subject: [PATCH 2/3] test: add tests for getCheckRunByName --- src/VCS/Adapter/Git/GitHub.php | 1 + tests/VCS/Adapter/GitHubTest.php | 118 +++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index b57aeee8..784cccec 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -965,6 +965,7 @@ public function getCheckRunByName(string $owner, string $repositoryName, string $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ 'check_name' => $checkName, + 'filter' => 'latest', 'per_page' => 1, ]); diff --git a/tests/VCS/Adapter/GitHubTest.php b/tests/VCS/Adapter/GitHubTest.php index f1c4b2fc..1471b81b 100644 --- a/tests/VCS/Adapter/GitHubTest.php +++ b/tests/VCS/Adapter/GitHubTest.php @@ -932,6 +932,124 @@ public function testUpdateCheckRunWithMissingConclusion(): void } } + public function testGetCheckRunByName(): void + { + $repositoryName = 'test-get-check-run-by-name-' . \uniqid(); + $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); + + try { + $this->vcsAdapter->createFile(static::$owner, $repositoryName, 'README.md', '# Test'); + $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); + $commitHash = $commit['commitHash']; + + $checkRun = $this->vcsAdapter->createCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + headSha: $commitHash, + name: 'ci/build', + status: 'in_progress', + ); + + $foundId = $this->vcsAdapter->getCheckRunByName( + static::$owner, + $repositoryName, + $commitHash, + 'ci/build' + ); + + $this->assertEquals($checkRun['id'], $foundId); + } finally { + $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); + } + } + + public function testGetCheckRunByNameNotFound(): void + { + $repositoryName = 'test-get-check-run-by-name-not-found-' . \uniqid(); + $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); + + try { + $this->vcsAdapter->createFile(static::$owner, $repositoryName, 'README.md', '# Test'); + $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); + $commitHash = $commit['commitHash']; + + $foundId = $this->vcsAdapter->getCheckRunByName( + static::$owner, + $repositoryName, + $commitHash, + 'ci/non-existing' + ); + + $this->assertEquals(0, $foundId); + } finally { + $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); + } + } + + public function testGetCheckRunByNameReturnsZeroOnNoCheckRuns(): void + { + $repositoryName = 'test-get-check-run-by-name-empty-' . \uniqid(); + $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); + + try { + $this->vcsAdapter->createFile(static::$owner, $repositoryName, 'README.md', '# Test'); + $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); + $commitHash = $commit['commitHash']; + + $foundId = $this->vcsAdapter->getCheckRunByName( + static::$owner, + $repositoryName, + $commitHash, + 'ci/build' + ); + + $this->assertEquals(0, $foundId); + } finally { + $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); + } + } + + public function testGetCheckRunByNameReturnsMostRecent(): void + { + $repositoryName = 'test-get-check-run-by-name-recent-' . \uniqid(); + $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); + + try { + $this->vcsAdapter->createFile(static::$owner, $repositoryName, 'README.md', '# Test'); + $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); + $commitHash = $commit['commitHash']; + + $first = $this->vcsAdapter->createCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + headSha: $commitHash, + name: 'ci/build', + status: 'in_progress', + ); + + $second = $this->vcsAdapter->createCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + headSha: $commitHash, + name: 'ci/build', + status: 'in_progress', + ); + + $this->assertGreaterThan($first['id'], $second['id']); + + $foundId = $this->vcsAdapter->getCheckRunByName( + static::$owner, + $repositoryName, + $commitHash, + 'ci/build' + ); + + $this->assertEquals($second['id'], $foundId); + } finally { + $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); + } + } + public function testGenerateCloneCommand(): void { $repositoryName = 'test-clone-command-' . \uniqid(); From 37f11ca1dc94ee99c829a4f9a76d9676421e8848 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 2 Jun 2026 13:41:42 +0530 Subject: [PATCH 3/3] =?UTF-8?q?test:=20rewrite=20getCheckRunByName=20tests?= =?UTF-8?q?=20=E2=80=94=20remove=20duplicate,=20add=20invalid-repo=20and?= =?UTF-8?q?=20end-to-end=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/VCS/Adapter/GitHubTest.php | 91 ++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/tests/VCS/Adapter/GitHubTest.php b/tests/VCS/Adapter/GitHubTest.php index 1471b81b..2ae28c3b 100644 --- a/tests/VCS/Adapter/GitHubTest.php +++ b/tests/VCS/Adapter/GitHubTest.php @@ -963,9 +963,11 @@ public function testGetCheckRunByName(): void } } - public function testGetCheckRunByNameNotFound(): void + public function testGetCheckRunByNameNoMatchReturnsZero(): void { - $repositoryName = 'test-get-check-run-by-name-not-found-' . \uniqid(); + // Verifies the check_name filter is actually applied: + // a run with a different name must not be returned. + $repositoryName = 'test-get-check-run-by-name-nomatch-' . \uniqid(); $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); try { @@ -973,11 +975,19 @@ public function testGetCheckRunByNameNotFound(): void $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); $commitHash = $commit['commitHash']; + $this->vcsAdapter->createCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + headSha: $commitHash, + name: 'ci/build', + status: 'in_progress', + ); + $foundId = $this->vcsAdapter->getCheckRunByName( static::$owner, $repositoryName, $commitHash, - 'ci/non-existing' + 'ci/lint' // different name ); $this->assertEquals(0, $foundId); @@ -986,9 +996,25 @@ public function testGetCheckRunByNameNotFound(): void } } - public function testGetCheckRunByNameReturnsZeroOnNoCheckRuns(): void + public function testGetCheckRunByNameInvalidRepositoryReturnsZero(): void { - $repositoryName = 'test-get-check-run-by-name-empty-' . \uniqid(); + // Non-existent repo must return 0, not throw — callers rely on this + // for graceful fallback to the legacy commit status API. + $foundId = $this->vcsAdapter->getCheckRunByName( + static::$owner, + 'non-existing-repository-' . \uniqid(), + str_repeat('a', 40), + 'ci/build' + ); + + $this->assertEquals(0, $foundId); + } + + public function testGetCheckRunByNameReturnsMostRecent(): void + { + // When a commit has multiple runs with the same name (e.g. retries), + // the most recently created one must be returned. + $repositoryName = 'test-get-check-run-by-name-recent-' . \uniqid(); $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); try { @@ -996,6 +1022,24 @@ public function testGetCheckRunByNameReturnsZeroOnNoCheckRuns(): void $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); $commitHash = $commit['commitHash']; + $first = $this->vcsAdapter->createCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + headSha: $commitHash, + name: 'ci/build', + status: 'in_progress', + ); + + $second = $this->vcsAdapter->createCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + headSha: $commitHash, + name: 'ci/build', + status: 'in_progress', + ); + + $this->assertGreaterThan($first['id'], $second['id']); + $foundId = $this->vcsAdapter->getCheckRunByName( static::$owner, $repositoryName, @@ -1003,15 +1047,17 @@ public function testGetCheckRunByNameReturnsZeroOnNoCheckRuns(): void 'ci/build' ); - $this->assertEquals(0, $foundId); + $this->assertEquals($second['id'], $foundId); } finally { $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); } } - public function testGetCheckRunByNameReturnsMostRecent(): void + public function testGetCheckRunByNameThenUpdate(): void { - $repositoryName = 'test-get-check-run-by-name-recent-' . \uniqid(); + // End-to-end: create as in_progress, look up by name, update to completed. + // This is the exact workflow the method was designed for — no stored ID needed. + $repositoryName = 'test-get-check-run-by-name-update-' . \uniqid(); $this->vcsAdapter->createRepository(static::$owner, $repositoryName, false); try { @@ -1019,15 +1065,7 @@ public function testGetCheckRunByNameReturnsMostRecent(): void $commit = $this->vcsAdapter->getLatestCommit(static::$owner, $repositoryName, static::$defaultBranch); $commitHash = $commit['commitHash']; - $first = $this->vcsAdapter->createCheckRun( - owner: static::$owner, - repositoryName: $repositoryName, - headSha: $commitHash, - name: 'ci/build', - status: 'in_progress', - ); - - $second = $this->vcsAdapter->createCheckRun( + $this->vcsAdapter->createCheckRun( owner: static::$owner, repositoryName: $repositoryName, headSha: $commitHash, @@ -1035,16 +1073,27 @@ public function testGetCheckRunByNameReturnsMostRecent(): void status: 'in_progress', ); - $this->assertGreaterThan($first['id'], $second['id']); - - $foundId = $this->vcsAdapter->getCheckRunByName( + $checkRunId = $this->vcsAdapter->getCheckRunByName( static::$owner, $repositoryName, $commitHash, 'ci/build' ); - $this->assertEquals($second['id'], $foundId); + $this->assertGreaterThan(0, $checkRunId); + + $updated = $this->vcsAdapter->updateCheckRun( + owner: static::$owner, + repositoryName: $repositoryName, + checkRunId: $checkRunId, + conclusion: 'success', + title: 'Build succeeded.', + summary: 'All steps passed.', + ); + + $this->assertEquals($checkRunId, $updated['id']); + $this->assertEquals('completed', $updated['status']); + $this->assertEquals('success', $updated['conclusion']); } finally { $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); }