diff --git a/docker-compose.yml b/docker-compose.yml index 77a3aa8e..b7bc513f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,17 +7,23 @@ services: - ./tests:/usr/local/src/tests - ./phpunit.xml:/usr/local/src/phpunit.xml - gitea-data:/data:ro + - forgejo-data:/forgejo-data:ro environment: - TESTS_GITHUB_PRIVATE_KEY - TESTS_GITHUB_APP_IDENTIFIER - TESTS_GITHUB_INSTALLATION_ID - TESTS_GITEA_URL=http://gitea:3000 - - TESTS_GITEA_REQUEST_CATCHER_URL=http://request-catcher:5000 + - TESTS_GITEA_REQUEST_CATCHER_URL=http://request-catcher:5000 + - TESTS_FORGEJO_URL=http://forgejo:3000 depends_on: gitea: condition: service_healthy gitea-bootstrap: condition: service_completed_successfully + forgejo: + condition: service_healthy + forgejo-bootstrap: + condition: service_completed_successfully request-catcher: condition: service_started @@ -58,12 +64,57 @@ services: command: > -c " su git -c \"gitea admin user create --username $$GITEA_ADMIN_USERNAME --password $$GITEA_ADMIN_PASSWORD --email $$GITEA_ADMIN_EMAIL --admin --must-change-password=false\" || true && - TOKEN=$$(su git -c \"gitea admin user generate-access-token --username $$GITEA_ADMIN_USERNAME --token-name $$GITEA_ADMIN_USERNAME-token --scopes all --raw\") && - echo $$TOKEN > /data/gitea/token.txt + if [ ! -f /data/gitea/token.txt ]; then + TOKEN=$$(su git -c \"gitea admin user generate-access-token --username $$GITEA_ADMIN_USERNAME --token-name $$GITEA_ADMIN_USERNAME-token --scopes all --raw\") && + echo $$TOKEN > /data/gitea/token.txt; + fi " request-catcher: image: appwrite/requestcatcher:1.1.0 ports: - "5000:5000" + + forgejo: + image: codeberg.org/forgejo/forgejo:9 + environment: + - USER_UID=1000 + - USER_GID=1000 + - FORGEJO__database__DB_TYPE=sqlite3 + - FORGEJO__security__INSTALL_LOCK=true + - FORGEJO__webhook__ALLOWED_HOST_LIST=* + - FORGEJO__webhook__SKIP_TLS_VERIFY=true + - FORGEJO__webhook__DELIVER_TIMEOUT=10 + - FORGEJO__server__LOCAL_ROOT_URL=http://forgejo:3000/ + volumes: + - forgejo-data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 10s + + forgejo-bootstrap: + image: codeberg.org/forgejo/forgejo:9 + volumes: + - forgejo-data:/data + depends_on: + forgejo: + condition: service_healthy + entrypoint: /bin/sh + environment: + - FORGEJO_ADMIN_USERNAME=${FORGEJO_ADMIN_USERNAME:-utopia} + - FORGEJO_ADMIN_PASSWORD=${FORGEJO_ADMIN_PASSWORD:-password} + - FORGEJO_ADMIN_EMAIL=${FORGEJO_ADMIN_EMAIL:-utopia@example.com} + command: > + -c " + su git -c \"forgejo admin user create --username $$FORGEJO_ADMIN_USERNAME --password $$FORGEJO_ADMIN_PASSWORD --email $$FORGEJO_ADMIN_EMAIL --admin --must-change-password=false\" || true && + if [ ! -f /data/gitea/token.txt ]; then + TOKEN=$$(su git -c \"forgejo admin user generate-access-token --username $$FORGEJO_ADMIN_USERNAME --token-name $$FORGEJO_ADMIN_USERNAME-token --scopes all --raw\") && + echo $$TOKEN > /data/gitea/token.txt; + fi + " + volumes: - gitea-data: \ No newline at end of file + gitea-data: + forgejo-data: \ No newline at end of file diff --git a/src/VCS/Adapter/Git/Forgejo.php b/src/VCS/Adapter/Git/Forgejo.php new file mode 100644 index 00000000..c7f09fee --- /dev/null +++ b/src/VCS/Adapter/Git/Forgejo.php @@ -0,0 +1,23 @@ + "token $this->accessToken"], [ - 'type' => 'gitea', + 'type' => $this->getHookType(), 'active' => true, 'events' => $events, 'config' => [ diff --git a/tests/VCS/Adapter/ForgejoTest.php b/tests/VCS/Adapter/ForgejoTest.php new file mode 100644 index 00000000..ad1ec502 --- /dev/null +++ b/tests/VCS/Adapter/ForgejoTest.php @@ -0,0 +1,62 @@ +setupForgejo(); + } + + $adapter = new Forgejo(new Cache(new None())); + $forgejoUrl = System::getEnv('TESTS_FORGEJO_URL', 'http://forgejo:3000'); + + $adapter->initializeVariables( + installationId: '', + privateKey: '', + appId: '', + accessToken: static::$accessToken, + refreshToken: '' + ); + $adapter->setEndpoint($forgejoUrl); + if (empty(static::$owner)) { + $orgName = 'test-org-' . \uniqid(); + static::$owner = $adapter->createOrganization($orgName); + } + + $this->vcsAdapter = $adapter; + } + + protected function setupForgejo(): void + { + $tokenFile = '/forgejo-data/gitea/token.txt'; + + if (file_exists($tokenFile)) { + $contents = file_get_contents($tokenFile); + if ($contents !== false) { + static::$accessToken = trim($contents); + } + } + } +} diff --git a/tests/VCS/Adapter/GitHubTest.php b/tests/VCS/Adapter/GitHubTest.php index cb2c54ba..ffab30c6 100644 --- a/tests/VCS/Adapter/GitHubTest.php +++ b/tests/VCS/Adapter/GitHubTest.php @@ -1,6 +1,6 @@ vcsAdapter = $adapter; } - private function setupGitea(): void + protected function setupGitea(): void { $tokenFile = '/data/gitea/token.txt'; @@ -747,7 +751,7 @@ public function testGetCommit(): void $this->assertSame($commitHash, $result['commitHash']); $this->assertSame('utopia', $result['commitAuthor']); $this->assertStringStartsWith($customMessage, $result['commitMessage']); - $this->assertStringContainsString('gravatar.com', $result['commitAuthorAvatar']); + $this->assertStringContainsString($this->avatarDomain, $result['commitAuthorAvatar']); $this->assertNotEmpty($result['commitUrl']); $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); @@ -775,7 +779,7 @@ public function testGetLatestCommit(): void $this->assertNotEmpty($commit1['commitHash']); $this->assertSame('utopia', $commit1['commitAuthor']); $this->assertStringStartsWith($firstMessage, $commit1['commitMessage']); - $this->assertStringContainsString('gravatar.com', $commit1['commitAuthorAvatar']); + $this->assertStringContainsString($this->avatarDomain, $commit1['commitAuthorAvatar']); $this->assertNotEmpty($commit1['commitUrl']); $commit1Hash = $commit1['commitHash']; @@ -1499,14 +1503,14 @@ public function testWebhookPushEvent(): void $webhookData = $this->getLastWebhookRequest(); $this->assertNotEmpty($webhookData, 'No webhook received'); $this->assertNotEmpty($webhookData['data'] ?? '', 'Webhook payload is empty'); - $this->assertSame('push', $webhookData['headers']['X-Gitea-Event'] ?? '', 'Expected push event'); + $this->assertSame('push', $webhookData['headers'][$this->webhookEventHeader] ?? '', 'Expected push event'); }, 15000, 500); $payload = $webhookData['data']; $headers = $webhookData['headers'] ?? []; - $signature = $headers['X-Gitea-Signature'] ?? ''; + $signature = $headers[$this->webhookSignatureHeader] ?? ''; - $this->assertNotEmpty($signature, 'Missing X-Gitea-Signature header'); + $this->assertNotEmpty($signature, 'Missing ' . $this->webhookSignatureHeader . ' header'); $this->assertTrue( $this->vcsAdapter->validateWebhookEvent($payload, $signature, $secret), 'Webhook signature validation failed' @@ -1538,7 +1542,7 @@ public function testWebhookPullRequestEvent(): void $this->vcsAdapter->createFile(static::$owner, $repositoryName, 'feature.txt', 'content', 'Add feature', 'feature-branch'); $catcherUrl = System::getEnv('TESTS_GITEA_REQUEST_CATCHER_URL', 'http://request-catcher:5000'); - $this->vcsAdapter->createWebhook(static::$owner, $repositoryName, $catcherUrl . '/webhook', $secret); + $this->vcsAdapter->createWebhook(static::$owner, $repositoryName, $catcherUrl . '/webhook', $secret, ['pull_request']); // Clear after setup so only PR event will arrive $this->deleteLastWebhookRequest(); @@ -1558,14 +1562,14 @@ public function testWebhookPullRequestEvent(): void $webhookData = $this->getLastWebhookRequest(); $this->assertNotEmpty($webhookData, 'No webhook received'); $this->assertNotEmpty($webhookData['data'] ?? '', 'Webhook payload is empty'); - $this->assertSame('pull_request', $webhookData['headers']['X-Gitea-Event'] ?? '', 'Expected pull_request event'); + $this->assertSame('pull_request', $webhookData['headers'][$this->webhookEventHeader] ?? '', 'Expected pull_request event'); }, 15000, 500); $payload = $webhookData['data']; $headers = $webhookData['headers'] ?? []; - $signature = $headers['X-Gitea-Signature'] ?? ''; + $signature = $headers[$this->webhookSignatureHeader] ?? ''; - $this->assertNotEmpty($signature, 'Missing X-Gitea-Signature header'); + $this->assertNotEmpty($signature, 'Missing ' . $this->webhookSignatureHeader . ' header'); $this->assertTrue( $this->vcsAdapter->validateWebhookEvent($payload, $signature, $secret), 'Webhook signature validation failed'