Add project repository connection settings

This commit is contained in:
2026-03-26 09:11:37 +01:00
parent 5f52d050ee
commit fee14d93a7
21 changed files with 1597 additions and 5 deletions
+53
View File
@@ -7,6 +7,7 @@ use App\Models\Project;
use Database\Seeders\BoardColumnSeeder;
use Database\Seeders\ProjectSeeder;
use Database\Seeders\ProjectTicketMetadataSeeder;
use Database\Seeders\RepositoryConnectionSeeder;
use Database\Seeders\TicketSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@@ -141,4 +142,56 @@ class ProjectTest extends TestCase
$coreProject->tickets->pluck('ticket_key')->all(),
);
}
/**
* Ensure projects expose linked repository connections in a stable order.
*/
public function test_projects_expose_repository_connections_in_name_order(): void
{
$project = Project::factory()->create();
$project->repositoryConnections()->create([
'base_url' => 'https://git.example.test',
'owner_name' => 'core-team',
'provider' => 'gitea',
'provider_repository_id' => '502',
'repository_full_name' => 'core-team/platform',
'repository_name' => 'platform',
'repository_url' => 'https://git.example.test/core-team/platform',
]);
$project->repositoryConnections()->create([
'base_url' => 'https://git.example.test',
'owner_name' => 'core-team',
'provider' => 'gitea',
'provider_repository_id' => '501',
'repository_full_name' => 'core-team/kanbangit',
'repository_name' => 'kanbangit',
'repository_url' => 'https://git.example.test/core-team/kanbangit',
]);
$this->assertSame(
['core-team/kanbangit', 'core-team/platform'],
$project->fresh()->repositoryConnections->pluck('repository_full_name')->all(),
);
}
/**
* Ensure repository connection seeding adds starter repositories to seeded projects.
*/
public function test_repository_connection_seeder_adds_repositories_to_seeded_projects(): void
{
$this->seed([
ProjectSeeder::class,
RepositoryConnectionSeeder::class,
]);
$coreProject = Project::query()->where('key', 'CORE')->firstOrFail();
$this->assertDatabaseHas('repository_connections', [
'project_id' => $coreProject->id,
'provider' => 'gitea',
'provider_repository_id' => '501',
'repository_full_name' => 'core-team/kanbangit',
]);
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Models;
use App\Actions\Git\GitProviderKey;
use App\Models\RepositoryConnection;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class RepositoryConnectionTest extends TestCase
{
use RefreshDatabase;
/**
* Ensure the repository connection factory creates a project-scoped Gitea link.
*/
public function test_factory_creates_a_project_repository_connection(): void
{
$repositoryConnection = RepositoryConnection::factory()->create();
$this->assertSame(GitProviderKey::Gitea, $repositoryConnection->fresh()->provider);
$this->assertNotNull($repositoryConnection->project_id);
$this->assertNotSame('', $repositoryConnection->repository_full_name);
}
/**
* Ensure repository connections belong to a project.
*/
public function test_repository_connections_belong_to_a_project(): void
{
$repositoryConnection = RepositoryConnection::factory()->create();
$this->assertTrue($repositoryConnection->project()->exists());
$this->assertTrue($repositoryConnection->project->is($repositoryConnection->fresh()->project));
}
}
@@ -0,0 +1,218 @@
<?php
namespace Tests\Feature\Projects;
use App\Actions\Git\GitProviderKey;
use App\Actions\ProjectPermissions\ProjectPermissionService;
use App\Models\Project;
use App\Models\RepositoryConnection;
use App\Models\User;
use App\Models\UserGitConnection;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class ProjectRepositorySettingsControllerTest extends TestCase
{
use RefreshDatabase;
/**
* Ensure users with edit permission can view repository settings.
*/
public function test_users_with_edit_permission_can_view_repository_settings(): void
{
$user = User::factory()->create();
$project = Project::factory()->create([
'key' => 'CORE',
]);
RepositoryConnection::factory()->for($project)->create([
'base_url' => 'https://git.example.test',
'owner_name' => 'core-team',
'provider' => GitProviderKey::Gitea,
'provider_repository_id' => '501',
'repository_full_name' => 'core-team/kanbangit',
'repository_name' => 'kanbangit',
'repository_url' => 'https://git.example.test/core-team/kanbangit',
]);
app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['edit']);
$this->actingAs($user)
->get(route('projects.settings.repositories', $project))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('projects/settings/Repositories')
->where('project.id', $project->id)
->where('repositoryConnections.0.repository_full_name', 'core-team/kanbangit')
->where('repositoryConnections.0.has_personal_access', false)
->where('availableRepositorySources', []),
);
}
/**
* Ensure users without edit permission cannot manage repository settings.
*/
public function test_users_without_edit_permission_cannot_view_repository_settings(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$this->actingAs($user)
->get(route('projects.settings.repositories', $project))
->assertForbidden();
}
/**
* Ensure authorized users can link a repository through their own Git connection.
*/
public function test_users_with_edit_permission_can_link_project_repositories(): void
{
Http::preventStrayRequests();
Http::fake([
'https://git.example.test/api/v1/user/repos*' => Http::response([
[
'id' => 501,
'name' => 'kanbangit',
'full_name' => 'core-team/kanbangit',
'html_url' => 'https://git.example.test/core-team/kanbangit',
'owner' => [
'login' => 'core-team',
],
'private' => true,
],
]),
]);
$user = User::factory()->create();
$project = Project::factory()->create();
$userGitConnection = UserGitConnection::factory()->for($user)->create([
'base_url' => 'https://git.example.test',
'provider' => GitProviderKey::Gitea,
]);
app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['edit']);
$this->actingAs($user)
->post(route('projects.settings.repositories.store', $project), [
'provider_repository_id' => '501',
'user_git_connection_id' => $userGitConnection->id,
])
->assertRedirect(route('projects.settings.repositories', $project))
->assertSessionHas('status', 'core-team/kanbangit was linked to '.$project->name.'.');
$this->assertDatabaseHas('repository_connections', [
'project_id' => $project->id,
'provider' => GitProviderKey::Gitea->value,
'base_url' => 'https://git.example.test',
'provider_repository_id' => '501',
'owner_name' => 'core-team',
'repository_name' => 'kanbangit',
'repository_full_name' => 'core-team/kanbangit',
]);
}
/**
* Ensure users cannot link repositories through another user's Git connection.
*/
public function test_users_cannot_link_repositories_through_foreign_git_connections(): void
{
Http::preventStrayRequests();
Http::fake();
$user = User::factory()->create();
$otherUser = User::factory()->create();
$project = Project::factory()->create();
$foreignGitConnection = UserGitConnection::factory()->for($otherUser)->create();
app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['edit']);
$this->actingAs($user)
->post(route('projects.settings.repositories.store', $project), [
'provider_repository_id' => '501',
'user_git_connection_id' => $foreignGitConnection->id,
])
->assertForbidden();
}
/**
* Ensure authorized users can relink an existing project repository.
*/
public function test_users_with_edit_permission_can_update_repository_connections(): void
{
Http::preventStrayRequests();
Http::fake([
'https://git.internal.test/api/v1/user/repos*' => Http::response([
[
'id' => 902,
'name' => 'platform',
'full_name' => 'infra/platform',
'html_url' => 'https://git.internal.test/infra/platform',
'owner' => [
'login' => 'infra',
],
'private' => false,
],
]),
]);
$user = User::factory()->create();
$project = Project::factory()->create();
$repositoryConnection = RepositoryConnection::factory()->for($project)->create([
'base_url' => 'https://git.example.test',
'owner_name' => 'core-team',
'provider' => GitProviderKey::Gitea,
'provider_repository_id' => '501',
'repository_full_name' => 'core-team/kanbangit',
'repository_name' => 'kanbangit',
'repository_url' => 'https://git.example.test/core-team/kanbangit',
]);
$userGitConnection = UserGitConnection::factory()->for($user)->create([
'base_url' => 'https://git.internal.test',
'provider' => GitProviderKey::Gitea,
]);
app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['edit']);
$this->actingAs($user)
->patch(route('projects.settings.repositories.update', [$project, $repositoryConnection]), [
'provider_repository_id' => '902',
'user_git_connection_id' => $userGitConnection->id,
])
->assertRedirect(route('projects.settings.repositories', $project))
->assertSessionHas('status', 'infra/platform connection was updated.');
$this->assertDatabaseHas('repository_connections', [
'id' => $repositoryConnection->id,
'base_url' => 'https://git.internal.test',
'provider_repository_id' => '902',
'owner_name' => 'infra',
'repository_name' => 'platform',
'repository_full_name' => 'infra/platform',
]);
}
/**
* Ensure authorized users can remove project repository links.
*/
public function test_users_with_edit_permission_can_delete_repository_connections(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$repositoryConnection = RepositoryConnection::factory()->for($project)->create([
'repository_full_name' => 'core-team/kanbangit',
]);
app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['edit']);
$this->actingAs($user)
->delete(route('projects.settings.repositories.destroy', [$project, $repositoryConnection]))
->assertRedirect(route('projects.settings.repositories', $project))
->assertSessionHas('status', 'core-team/kanbangit was removed from the project.');
$this->assertDatabaseMissing('repository_connections', [
'id' => $repositoryConnection->id,
]);
}
}