*/ public static array $queue = []; /** @var string[] */ public static array $requestedUrls = []; public static function reset(): void { self::$queue = []; self::$requestedUrls = []; } } function curl_init(string $url = '') { CurlStub::$requestedUrls[] = $url; $handle = new \stdClass(); $handle->response = array_shift(CurlStub::$queue) ?? ['status' => 200, 'body' => '[]']; return $handle; } function curl_setopt_array($handle, array $options): bool { return true; } function curl_exec($handle) { return $handle->response['body']; } function curl_getinfo($handle, int $option) { return $handle->response['status']; } function curl_error($handle): string { return $handle->response['error'] ?? ''; } function curl_close($handle): void { } } namespace Monitor\Api\Tests\Sync { use Monitor\Api\Sync\CodebergApiException; use Monitor\Api\Sync\CurlCodebergClient; use Monitor\Api\Sync\CurlStub; use PHPUnit\Framework\TestCase; use RuntimeException; final class CurlCodebergClientTest extends TestCase { protected function setUp(): void { CurlStub::reset(); } public function testListIssuesParsesSinglePage(): void { CurlStub::$queue = [ ['status' => 200, 'body' => json_encode([ ['number' => 1, 'title' => 'A', 'body' => 'desc', 'labels' => [['name' => 'bug', 'color' => 'ff0000']]], ])], ]; $client = new CurlCodebergClient('https://codeberg.org/api/v1'); $issues = $client->listIssues('fds2610', 'monitor', 'open'); self::assertCount(1, $issues); self::assertSame(1, $issues[0]['number']); // Der Client reicht die Label-Farbe roh durch; das "#" setzt CodebergSyncService. self::assertSame('ff0000', $issues[0]['labels'][0]['color']); self::assertStringContainsString('state=open', CurlStub::$requestedUrls[0]); } public function testListIssuesAndPullRequestsReadAuthorLogin(): void { CurlStub::$queue = [ ['status' => 200, 'body' => json_encode([ ['number' => 1, 'title' => 'A', 'body' => null, 'user' => ['login' => 'jemand'], 'labels' => []], ['number' => 2, 'title' => 'B', 'body' => null, 'labels' => []], ])], ['status' => 200, 'body' => json_encode([ ['number' => 3, 'title' => 'PR', 'body' => null, 'user' => ['login' => 'fds2610-bot']], ])], ]; $client = new CurlCodebergClient('https://codeberg.org/api/v1'); $issues = $client->listIssues('fds2610', 'monitor', 'open'); $pulls = $client->listOpenPullRequests('fds2610', 'monitor'); self::assertSame('jemand', $issues[0]['author']); self::assertNull($issues[1]['author']); self::assertSame('fds2610-bot', $pulls[0]['author']); } public function testListIssuesStopsWhenPageEmpty(): void { CurlStub::$queue = [ ['status' => 200, 'body' => '[]'], ]; $client = new CurlCodebergClient('https://codeberg.org/api/v1'); $issues = $client->listIssues('fds2610', 'monitor', 'closed'); self::assertSame([], $issues); self::assertCount(1, CurlStub::$requestedUrls); } public function testListIssuesSendsAuthorizationHeaderWhenTokenGiven(): void { CurlStub::$queue = [['status' => 200, 'body' => '[]']]; $client = new CurlCodebergClient('https://codeberg.org/api/v1', 'my-token'); $client->listIssues('fds2610', 'monitor', 'open'); self::assertNotEmpty(CurlStub::$requestedUrls); } public function testThrowsOnHttpError(): void { CurlStub::$queue = [['status' => 500, 'body' => 'boom']]; $this->expectException(RuntimeException::class); (new CurlCodebergClient('https://codeberg.org/api/v1'))->listIssues('fds2610', 'monitor', 'open'); } public function testRepoPermissionsReadsPushFlag(): void { CurlStub::$queue = [ ['status' => 200, 'body' => json_encode(['permissions' => ['admin' => false, 'push' => true, 'pull' => true]])], ]; $client = new CurlCodebergClient('https://codeberg.org/api/v1'); $permissions = $client->repoPermissions('fds2610', 'monitor'); self::assertTrue($permissions['push']); self::assertStringContainsString('/repos/fds2610/monitor', CurlStub::$requestedUrls[0]); } public function testRepoPermissionsDefaultsToFalseWithoutPermissionsField(): void { CurlStub::$queue = [['status' => 200, 'body' => json_encode(['id' => 1])]]; $permissions = (new CurlCodebergClient('https://codeberg.org/api/v1'))->repoPermissions('fds2610', 'monitor'); self::assertFalse($permissions['push']); } public function testRepoPermissionsThrowsCodebergApiExceptionWithStatus(): void { CurlStub::$queue = [['status' => 404, 'body' => 'not found']]; try { (new CurlCodebergClient('https://codeberg.org/api/v1'))->repoPermissions('fds2610', 'unbekannt'); self::fail('Erwartete CodebergApiException wurde nicht geworfen.'); } catch (CodebergApiException $e) { self::assertSame(404, $e->status); } } public function testListWebhooksParsesActiveUrlAndContentType(): void { CurlStub::$queue = [ ['status' => 200, 'body' => json_encode([ ['active' => true, 'config' => ['url' => 'https://example.org/api/v1/webhooks/codeberg', 'content_type' => 'json']], ['active' => false, 'config' => ['url' => 'https://example.org/other', 'content_type' => 'form']], ])], ]; $client = new CurlCodebergClient('https://codeberg.org/api/v1'); $hooks = $client->listWebhooks('fds2610', 'monitor'); self::assertCount(2, $hooks); self::assertTrue($hooks[0]['active']); self::assertSame('https://example.org/api/v1/webhooks/codeberg', $hooks[0]['url']); self::assertSame('json', $hooks[0]['content_type']); self::assertFalse($hooks[1]['active']); self::assertStringContainsString('/hooks', CurlStub::$requestedUrls[0]); } public function testListWebhooksThrowsCodebergApiExceptionOn403(): void { CurlStub::$queue = [['status' => 403, 'body' => 'forbidden']]; try { (new CurlCodebergClient('https://codeberg.org/api/v1'))->listWebhooks('fds2610', 'monitor'); self::fail('Erwartete CodebergApiException wurde nicht geworfen.'); } catch (CodebergApiException $e) { self::assertSame(403, $e->status); } } public function testListOpenPullRequestsReturnsNumberTitleBodyAndAuthor(): void { CurlStub::$queue = [ ['status' => 200, 'body' => json_encode([ ['number' => 1, 'title' => 'PR A', 'body' => 'Text', 'author' => null], ['number' => 2, 'title' => 'PR B'], ])], ]; $pulls = (new CurlCodebergClient('https://codeberg.org/api/v1'))->listOpenPullRequests('fds2610', 'monitor'); self::assertSame( [ ['number' => 1, 'title' => 'PR A', 'body' => 'Text', 'author' => null], ['number' => 2, 'title' => 'PR B', 'body' => null, 'author' => null], ], $pulls ); self::assertStringContainsString('state=open', CurlStub::$requestedUrls[0]); self::assertStringContainsString('type=pulls', CurlStub::$requestedUrls[0]); } public function testListOpenPullRequestsFollowsPages(): void { $fullPage = array_fill(0, 50, ['number' => 1, 'title' => 'PR']); CurlStub::$queue = [ ['status' => 200, 'body' => json_encode($fullPage)], ['status' => 200, 'body' => json_encode([['number' => 99, 'title' => 'Letzte PR']])], ]; $pulls = (new CurlCodebergClient('https://codeberg.org/api/v1'))->listOpenPullRequests('fds2610', 'monitor'); self::assertCount(51, $pulls); self::assertCount(2, CurlStub::$requestedUrls); } } }