Files
KanbanGit/tests/Feature/NavigationTest.php
Twyco 4adda6e53b
CI/CD / test_and_check_app (push) Canceled after 0s
CI/CD / Publish immutable image (push) Canceled after 0s
CI/CD / test_and_check_app (pull_request) Canceled after 0s
CI/CD / Publish immutable image (pull_request) Canceled after 0s
feat(KAN-19): add responsive permission-aware navigation
2026-09-11 14:16:43 +02:00

108 lines
4.6 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Project;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Process\Process;
use Tests\TestCase;
class NavigationTest extends TestCase
{
use RefreshDatabase;
#[DataProvider('projectRoles')]
public function test_navigation_uses_effective_project_permissions(string $role, bool $teamAccess, bool $canManage, bool $canCreateTickets): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$assignee = $user;
if ($teamAccess) {
$assignee = Team::factory()->create();
$assignee->users()->attach($user);
}
$this->syncProjectPermissions($project, $assignee, [$role]);
$this->actingAs($user)->get(route('projects.board.index', $project))
->assertOk()->assertInertia(fn (Assert $page) => $page
->where('navigation.project', [
'id' => $project->id,
'name' => $project->name,
'key' => $project->key,
'canManage' => $canManage,
'canCreateTickets' => $canCreateTickets,
]));
}
/** @return array<string, array{string, bool, bool, bool}> */
public static function projectRoles(): array
{
return [
'reader' => ['read', false, false, false],
'writer' => ['write', false, false, true],
'project admin' => ['admin', false, true, true],
'team reader' => ['read', true, false, false],
'team writer' => ['write', true, false, true],
'team admin' => ['admin', true, true, true],
];
}
public function test_global_admin_has_project_actions_without_direct_membership(): void
{
$user = User::factory()->admin()->create();
$project = Project::factory()->create();
$this->actingAs($user)->get(route('projects.show', $project))
->assertOk()->assertInertia(fn (Assert $page) => $page
->where('navigation.project.canManage', true)
->where('navigation.project.canCreateTickets', true)
->where('auth.permissions.globalAdmin', true));
}
public function test_every_project_admin_navigation_destination_is_authorized(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$this->syncProjectPermissions($project, $user, ['admin']);
foreach (['projects.show', 'projects.board.index', 'projects.backlog.index', 'projects.settings.general', 'projects.settings.board', 'projects.settings.metadata', 'projects.settings.repositories', 'projects.settings.notifications', 'projects.access'] as $route) {
$this->actingAs($user)->get(route($route, $project))
->assertOk()->assertInertia(fn (Assert $page) => $page
->where('navigation.project.id', $project->id)
->where('navigation.project.canManage', true)
->where('auth.permissions.globalAdmin', false));
}
}
public function test_project_context_is_cleared_when_leaving_a_project(): void
{
$user = User::factory()->admin()->create();
$project = Project::factory()->create();
$this->actingAs($user)->get(route('projects.show', $project))
->assertInertia(fn (Assert $page) => $page->where('navigation.project.id', $project->id));
$this->get(route('dashboard'))->assertOk()
->assertInertia(fn (Assert $page) => $page->where('navigation.project', null));
$this->get(route('projects.create'))->assertOk()
->assertInertia(fn (Assert $page) => $page->where('navigation.project', null));
}
public function test_unrelated_projects_remain_inaccessible(): void
{
$user = User::factory()->create();
$project = Project::factory()->create(['name' => 'Private project']);
$this->actingAs($user)->get(route('projects.show', $project))
->assertForbidden()->assertDontSee('Private project');
}
public function test_navigation_rendering_and_url_matching(): void
{
$process = new Process(['node', 'tests/Fixtures/navigation.mjs'], base_path());
$process->setTimeout(60);
$process->run();
$this->assertTrue($process->isSuccessful(), $process->getOutput().$process->getErrorOutput());
$this->assertStringContainsString('Navigation behavior checks passed.', $process->getOutput());
}
}