*/ 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 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 testCountOpenPullRequestsCountsSinglePage(): void { CurlStub::$queue = [ ['status' => 200, 'body' => json_encode([ ['number' => 1, 'title' => 'PR A'], ['number' => 2, 'title' => 'PR B'], ])], ]; $count = (new CurlCodebergClient('https://codeberg.org/api/v1'))->countOpenPullRequests('fds2610', 'monitor'); self::assertSame(2, $count); self::assertStringContainsString('state=open', CurlStub::$requestedUrls[0]); self::assertStringContainsString('type=pulls', CurlStub::$requestedUrls[0]); } public function testCountOpenPullRequestsSumsAcrossPages(): 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']])], ]; $count = (new CurlCodebergClient('https://codeberg.org/api/v1'))->countOpenPullRequests('fds2610', 'monitor'); self::assertSame(51, $count); self::assertCount(2, CurlStub::$requestedUrls); } } }