set('app.maintenance.driver', 'file'); config()->set('cache.default', 'array'); InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]); $this->team = Team::factory()->create(); $this->user = User::factory()->create(); $this->team->members()->attach($this->user->id, ['role' => 'owner']); session(['currentTeam' => $this->team]); $this->token = $this->user->createToken('test-token', ['*']); $this->bearerToken = $this->token->plainTextToken; }); describe('GET /api/v1/gitlab-apps', function () { test('returns 401 when not authenticated', function () { $this->getJson('/api/v1/gitlab-apps')->assertStatus(401); }); test('returns empty array when no gitlab apps exist', function () { $this->withHeaders([ 'Authorization' => 'Bearer '.$this->bearerToken, ])->getJson('/api/v1/gitlab-apps') ->assertSuccessful() ->assertJson([]); }); test('returns team gitlab apps without secrets for read tokens', function () { GitlabApp::create([ 'name' => 'Team GitLab', 'api_url' => 'https://gitlab.com/api/v4', 'html_url' => 'https://gitlab.com', 'custom_user' => 'git', 'custom_port' => 22, 'client_id' => 'client-id', 'client_secret' => 'secret-should-be-hidden', 'webhook_token' => 'webhook-should-be-hidden', 'team_id' => $this->team->id, 'is_system_wide' => false, 'is_public' => false, ]); $readToken = $this->user->createToken('read-token', ['read'])->plainTextToken; $response = $this->withHeaders([ 'Authorization' => 'Bearer '.$readToken, ])->getJson('/api/v1/gitlab-apps'); $response->assertSuccessful() ->assertJsonCount(1) ->assertJsonFragment(['name' => 'Team GitLab']); expect($response->json('0'))->not->toHaveKey('client_secret') ->and($response->json('0'))->not->toHaveKey('webhook_token'); }); }); describe('POST /api/v1/gitlab-apps', function () { test('creates a gitlab app with derived api url and generated webhook token', function () { $response = $this->withHeaders([ 'Authorization' => 'Bearer '.$this->bearerToken, ])->postJson('/api/v1/gitlab-apps', [ 'name' => 'Self-hosted GitLab', 'html_url' => 'https://gitlab.com/', 'group_name' => 'mygroup', ]); $response->assertCreated() ->assertJsonFragment([ 'name' => 'Self-hosted GitLab', 'html_url' => 'https://gitlab.com', 'api_url' => 'https://gitlab.com/api/v4', 'group_name' => 'mygroup', 'custom_user' => 'git', 'custom_port' => 22, ]); $app = GitlabApp::where('name', 'Self-hosted GitLab')->first(); expect($app)->not->toBeNull() ->and($app->team_id)->toBe($this->team->id) ->and($app->webhook_token)->not->toBeEmpty() ->and(strlen((string) $app->webhook_token))->toBe(32); }); test('creates a fully configured gitlab oauth source', function () { $response = $this->withHeaders([ 'Authorization' => 'Bearer '.$this->bearerToken, ])->postJson('/api/v1/gitlab-apps', [ 'name' => 'Configured GitLab', 'html_url' => 'https://gitlab.com', 'client_id' => 'oauth-app-id', 'client_secret' => 'oauth-app-secret', 'webhook_token' => 'custom-webhook-token', 'redirect_uri' => 'https://example.com/webhooks/source/gitlab/redirect', ]); $response->assertCreated() ->assertJsonFragment([ 'name' => 'Configured GitLab', 'client_id' => 'oauth-app-id', 'redirect_uri' => 'https://example.com/webhooks/source/gitlab/redirect', ]); $app = GitlabApp::where('name', 'Configured GitLab')->first(); $app->makeVisible(['client_secret', 'webhook_token']); expect($app->client_secret)->toBe('oauth-app-secret') ->and($app->webhook_token)->toBe('custom-webhook-token'); }); test('rejects members without create permission', function () { $member = User::factory()->create(); $this->team->members()->attach($member->id, ['role' => 'member']); session(['currentTeam' => $this->team]); $memberToken = $member->createToken('member-token', ['write'])->plainTextToken; $this->withHeaders([ 'Authorization' => 'Bearer '.$memberToken, ])->postJson('/api/v1/gitlab-apps', [ 'name' => 'Forbidden GitLab', 'html_url' => 'https://gitlab.com', ])->assertForbidden(); }); }); describe('PATCH /api/v1/gitlab-apps/{id}', function () { test('updates gitlab app credentials', function () { $app = GitlabApp::create([ 'name' => 'Existing', 'api_url' => 'https://gitlab.com/api/v4', 'html_url' => 'https://gitlab.com', 'custom_user' => 'git', 'custom_port' => 22, 'team_id' => $this->team->id, 'is_system_wide' => false, 'is_public' => false, ]); $this->withHeaders([ 'Authorization' => 'Bearer '.$this->bearerToken, ])->patchJson("/api/v1/gitlab-apps/{$app->id}", [ 'client_id' => 'new-client-id', 'group_name' => 'ops', ])->assertSuccessful() ->assertJsonPath('message', 'GitLab app updated successfully') ->assertJsonPath('data.client_id', 'new-client-id') ->assertJsonPath('data.group_name', 'ops'); }); }); describe('DELETE /api/v1/gitlab-apps/{id}', function () { test('deletes unused gitlab app', function () { $app = GitlabApp::create([ 'name' => 'Delete me', 'api_url' => 'https://gitlab.com/api/v4', 'html_url' => 'https://gitlab.com', 'custom_user' => 'git', 'custom_port' => 22, 'team_id' => $this->team->id, 'is_system_wide' => false, 'is_public' => false, ]); $this->withHeaders([ 'Authorization' => 'Bearer '.$this->bearerToken, ])->deleteJson("/api/v1/gitlab-apps/{$app->id}") ->assertSuccessful() ->assertJsonPath('message', 'GitLab app deleted successfully'); expect(GitlabApp::find($app->id))->toBeNull(); }); });