set('git.providers.gitea.url', $this->instance); config()->set('git.providers.gitea.token', 'a-token'); } /** * @param array $repo * @param array> $issues * @param array> $commits */ protected function fakeGitea(array $repo = [], array $issues = [], array $commits = []): void { Http::fake([ "{$this->instance}/api/v1/repos/*/languages" => Http::response(['PHP' => 90000, 'Blade' => 12000]), "{$this->instance}/api/v1/repos/*/topics" => Http::response(['topics' => ['laravel', 'comunidad']]), "{$this->instance}/api/v1/repos/*/issues*" => Http::response($issues), "{$this->instance}/api/v1/repos/*/commits*" => Http::response($commits ?: [ ['author' => ['login' => 'ada'], 'commit' => ['author' => ['email' => 'ada@x.test']]], ['author' => ['login' => 'grace'], 'commit' => ['author' => ['email' => 'grace@x.test']]], ['author' => ['login' => 'ada'], 'commit' => ['author' => ['email' => 'ada@x.test']]], ]), "{$this->instance}/api/v1/repos/*" => Http::response([ 'full_name' => 'colectivo/web', 'html_url' => "{$this->instance}/colectivo/web", 'description' => 'La web del colectivo', 'website' => 'https://colectivo.test', 'default_branch' => 'main', 'language' => 'PHP', // Gitea's counter names differ from GitHub's. 'stars_count' => 42, 'forks_count' => 3, 'watchers_count' => 7, 'open_issues_count' => 5, 'archived' => false, 'fork' => false, 'updated_at' => '2026-08-01T10:00:00Z', 'created_at' => '2024-01-01T00:00:00Z', ...$repo, ]), ]); } #[Test] public function it_reads_giteas_counter_names(): void { $this->fakeGitea(); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web', 'stars' => 0, ]); app(GitManager::class)->sync($repository); $repository->refresh(); $this->assertSame(42, $repository->stars); $this->assertSame(3, $repository->forks); $this->assertSame(7, $repository->watchers); $this->assertSame(5, $repository->open_issues_count); $this->assertSame('PHP', $repository->primary_language); $this->assertSame(['laravel', 'comunidad'], $repository->topics); $this->assertSame('https://colectivo.test', $repository->homepage); $this->assertNull($repository->sync_error); } #[Test] public function it_authenticates_with_giteas_token_scheme(): void { $this->fakeGitea(); app(GitManager::class)->sync( Repository::factory()->create(['provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web']) ); // Gitea expects "token ", not "Bearer ". Http::assertSent(fn ($request) => $request->hasHeader('Authorization', 'token a-token')); } #[Test] public function it_asks_gitea_to_exclude_pull_requests(): void { $this->fakeGitea(); app(GitManager::class)->sync( Repository::factory()->create(['provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web']) ); // Unlike GitHub, Gitea can filter PRs server-side. Http::assertSent(fn ($request) => ! str_contains($request->url(), '/issues') || str_contains($request->url(), 'type=issues')); } #[Test] public function it_mirrors_only_newcomer_friendly_issues(): void { $this->fakeGitea(issues: [ [ 'number' => 1, 'title' => 'Buena para empezar', 'html_url' => "{$this->instance}/x/1", 'labels' => [['name' => 'good first issue']], 'comments' => 2, 'user' => ['login' => 'ada'], 'assignees' => [], 'created_at' => '2026-07-01T00:00:00Z', 'updated_at' => '2026-07-02T00:00:00Z', ], [ 'number' => 2, 'title' => 'Se busca ayuda', 'html_url' => "{$this->instance}/x/2", 'labels' => [['name' => 'Se busca ayuda']], 'comments' => 0, 'user' => ['login' => 'ada'], // Gitea reports assignees as a list. 'assignees' => [['login' => 'grace']], 'created_at' => '2026-07-01T00:00:00Z', 'updated_at' => '2026-07-02T00:00:00Z', ], [ 'number' => 3, 'title' => 'Refactor interno', 'html_url' => "{$this->instance}/x/3", 'labels' => [['name' => 'chore']], 'comments' => 0, 'user' => ['login' => 'ada'], 'assignees' => [], 'created_at' => '2026-07-01T00:00:00Z', 'updated_at' => '2026-07-02T00:00:00Z', ], ]); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web', ]); app(GitManager::class)->sync($repository); $this->assertSame(2, $repository->issues()->count()); $this->assertSame(1, $repository->goodFirstIssues()->count()); // The Spanish label spelling from config/git.php matches too. $claimed = $repository->issues()->where('number', 2)->first(); $this->assertTrue($claimed->is_help_wanted); $this->assertSame('grace', $claimed->assignee); $this->assertFalse($claimed->isUnclaimed()); } #[Test] public function the_contributor_count_is_derived_from_distinct_commit_authors(): void { $this->fakeGitea(); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web', ]); app(GitManager::class)->sync($repository); // Gitea has no contributors endpoint: three commits by two people. $this->assertSame(2, $repository->fresh()->contributors_count); } #[Test] public function commit_authors_without_an_account_still_count_once(): void { $this->fakeGitea(commits: [ ['commit' => ['author' => ['email' => 'sin-cuenta@x.test']]], ['commit' => ['author' => ['email' => 'Sin-Cuenta@x.test']]], ['author' => ['login' => 'ada'], 'commit' => ['author' => ['email' => 'ada@x.test']]], ]); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web', ]); app(GitManager::class)->sync($repository); $this->assertSame(2, $repository->fresh()->contributors_count); } #[Test] public function a_private_repository_without_a_token_reports_the_real_cause(): void { Http::fake(["{$this->instance}/api/v1/repos/*" => Http::response([], 403)]); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'colectivo/privado', ]); try { app(GitManager::class)->sync($repository); $this->fail('Expected a GitException.'); } catch (GitException $e) { $this->assertSame(403, $e->status); $this->assertStringContainsString('GITEA_TOKEN', $e->getMessage()); } } #[Test] public function an_instance_that_is_down_is_treated_as_transient(): void { Http::fake(["{$this->instance}/api/v1/repos/*" => Http::response([], 502)]); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'colectivo/web', ]); try { app(GitManager::class)->sync($repository); $this->fail('Expected a GitException.'); } catch (GitException $e) { // Self-hosted instances restart; the job should retry, not give up. $this->assertTrue($e->isTransient); } } #[Test] public function without_an_instance_url_gitea_is_not_offered_as_a_provider(): void { config()->set('git.providers.gitea.url', null); // A driver with nowhere to point is worse than no driver: it would // only produce failures later. $this->assertFalse(GitProvider::Gitea->isSupported()); $repository = Repository::factory()->create(['provider' => GitProvider::Gitea]); $this->expectException(GitException::class); $this->expectExceptionMessage('GITEA_URL'); app(GitManager::class)->sync($repository); } #[Test] public function a_bare_name_is_qualified_with_the_configured_org(): void { config()->set('git.providers.gitea.default_owner', 'colectivo'); $this->fakeGitea(); $repository = Repository::factory()->create([ 'provider' => GitProvider::Gitea, 'full_name' => 'web', ]); app(GitManager::class)->sync($repository); $this->assertSame('colectivo/web', $repository->fresh()->full_name); Http::assertSent(fn ($r) => str_contains($r->url(), '/repos/colectivo/web')); } #[Test] public function gitea_is_the_default_provider(): void { $this->assertSame(GitProvider::Gitea, GitProvider::default()); $this->assertSame('gitea', Repository::factory()->create()->provider->value); } }