diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsAccessTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsAccessTest.php new file mode 100644 index 0000000..c11861c --- /dev/null +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsAccessTest.php @@ -0,0 +1,63 @@ +actingAs($user) + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") + ->assertOk(); +}); + +it('returns 404 for workspace members without tenant entitlement on the canonical route', function (): void { + $user = User::factory()->create(); + $workspace = Workspace::factory()->create(); + $tenant = Tenant::factory()->create([ + 'workspace_id' => (int) $workspace->getKey(), + ]); + + WorkspaceMembership::factory()->create([ + 'workspace_id' => (int) $workspace->getKey(), + 'user_id' => (int) $user->getKey(), + 'role' => 'owner', + ]); + + $this->actingAs($user) + ->withSession([ + WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), + ]) + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") + ->assertNotFound(); +}); + +it('returns 404 for users who are not workspace members', function (): void { + $user = User::factory()->create(); + $workspace = Workspace::factory()->create(); + $tenant = Tenant::factory()->create([ + 'workspace_id' => (int) $workspace->getKey(), + ]); + + $this->actingAs($user) + ->withSession([ + WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), + ]) + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") + ->assertNotFound(); +}); + +it('returns 404 when the route tenant is invalid instead of falling back to the current tenant context', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'readonly'); + + Tenant::query()->whereKey((int) $tenant->getKey())->update(['is_current' => true]); + + $this->actingAs($user) + ->get('/admin/tenants/invalid-tenant-id/required-permissions') + ->assertNotFound(); +}); diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsCopyActionsTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsCopyActionsTest.php index 175ed2e..842072c 100644 --- a/tests/Feature/RequiredPermissions/RequiredPermissionsCopyActionsTest.php +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsCopyActionsTest.php @@ -13,7 +13,7 @@ [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly'); $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") ->assertSuccessful() ->assertSee('Guidance') ->assertSee('Who can fix this?', false) diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsDbOnlyRenderTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsDbOnlyRenderTest.php index 7600534..3188f60 100644 --- a/tests/Feature/RequiredPermissions/RequiredPermissionsDbOnlyRenderTest.php +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsDbOnlyRenderTest.php @@ -1,13 +1,21 @@ actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") ->assertSuccessful(); }); + + Queue::assertNothingPushed(); }); diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsEmptyStateTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsEmptyStateTest.php new file mode 100644 index 0000000..d7b735a --- /dev/null +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsEmptyStateTest.php @@ -0,0 +1,14 @@ +actingAs($user) + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") + ->assertSuccessful() + ->assertSee('Keine Daten verfügbar') + ->assertSee('/admin/onboarding', false) + ->assertSee('Start verification'); +}); diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsFiltersTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsFiltersTest.php index b95eb6c..b3b6b6a 100644 --- a/tests/Feature/RequiredPermissions/RequiredPermissionsFiltersTest.php +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsFiltersTest.php @@ -51,7 +51,7 @@ ]); $missingResponse = $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") ->assertSuccessful() ->assertSee('All required permissions are present', false); @@ -61,7 +61,7 @@ ->assertDontSee('data-permission-key="Gamma.Manage.All"', false); $presentResponse = $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions?status=present") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions?status=present") ->assertSuccessful() ->assertSee('wire:model.live="status"', false); @@ -71,7 +71,7 @@ ->assertSee('data-permission-key="Gamma.Manage.All"', false); $delegatedResponse = $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions?status=present&type=delegated") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions?status=present&type=delegated") ->assertSuccessful(); $delegatedResponse @@ -85,7 +85,7 @@ ]); $featureResponse = $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions?{$featureQuery}") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions?{$featureQuery}") ->assertSuccessful(); $featureResponse @@ -94,7 +94,7 @@ ->assertDontSee('data-permission-key="Beta.Read.All"', false); $searchResponse = $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions?status=all&search=delegated") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions?status=all&search=delegated") ->assertSuccessful(); $searchResponse diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsLegacyRouteTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsLegacyRouteTest.php new file mode 100644 index 0000000..ed59e05 --- /dev/null +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsLegacyRouteTest.php @@ -0,0 +1,11 @@ +actingAs($user) + ->get("/admin/t/{$tenant->external_id}/required-permissions") + ->assertNotFound(); +}); diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsLinksTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsLinksTest.php new file mode 100644 index 0000000..2ef7921 --- /dev/null +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsLinksTest.php @@ -0,0 +1,25 @@ +actingAs($user) + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") + ->assertSuccessful() + ->assertSee('Re-run verification') + ->assertSee('/admin/onboarding', false) + ->assertDontSee('/admin/t/', false); +}); + +it('renders sections in summary-issues-passed-technical order and keeps technical details collapsed by default', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'readonly'); + + $this->actingAs($user) + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") + ->assertSuccessful() + ->assertSeeInOrder(['Summary', 'Issues', 'Passed', 'Technical details']) + ->assertSee('
assertDontSee('data-testid="technical-details" open', false); +}); diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsOverviewTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsOverviewTest.php index b837ffb..3ffba20 100644 --- a/tests/Feature/RequiredPermissions/RequiredPermissionsOverviewTest.php +++ b/tests/Feature/RequiredPermissions/RequiredPermissionsOverviewTest.php @@ -26,7 +26,7 @@ ]); $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions") + ->get("/admin/tenants/{$tenant->external_id}/required-permissions") ->assertSuccessful() ->assertSee('Blocked', false) ->assertSee('applyFeatureFilter', false) diff --git a/tests/Feature/RequiredPermissions/RequiredPermissionsRbacTest.php b/tests/Feature/RequiredPermissions/RequiredPermissionsRbacTest.php deleted file mode 100644 index 6d04504..0000000 --- a/tests/Feature/RequiredPermissions/RequiredPermissionsRbacTest.php +++ /dev/null @@ -1,33 +0,0 @@ -create([ - 'workspace_id' => (int) $tenant->workspace_id, - ]); - - $this->actingAs($user) - ->get("/admin/t/{$otherTenant->external_id}/required-permissions") - ->assertNotFound(); -}); - -it('returns 403 for members without tenant.view capability accessing required permissions', function (): void { - [$user, $tenant] = createUserWithTenant(role: 'readonly'); - - $this->mock(CapabilityResolver::class, function ($mock): void { - $mock->shouldReceive('isMember') - ->andReturn(true); - - $mock->shouldReceive('can') - ->andReturnUsing(fn ($user, $tenant, $capability): bool => $capability !== Capabilities::TENANT_VIEW); - }); - - $this->actingAs($user) - ->get("/admin/t/{$tenant->external_id}/required-permissions") - ->assertForbidden(); -}); diff --git a/tests/Unit/TenantRequiredPermissionsFreshnessTest.php b/tests/Unit/TenantRequiredPermissionsFreshnessTest.php new file mode 100644 index 0000000..250dbd3 --- /dev/null +++ b/tests/Unit/TenantRequiredPermissionsFreshnessTest.php @@ -0,0 +1,34 @@ +toBeNull() + ->and($freshness['is_stale'])->toBeTrue(); +}); + +it('marks freshness as stale when last refreshed is older than 30 days', function (): void { + $freshness = TenantRequiredPermissionsViewModelBuilder::deriveFreshness( + CarbonImmutable::parse('2026-01-08 11:59:59'), + CarbonImmutable::parse('2026-02-08 12:00:00'), + ); + + expect($freshness['is_stale'])->toBeTrue(); +}); + +it('marks freshness as not stale when last refreshed is exactly 30 days old', function (): void { + $freshness = TenantRequiredPermissionsViewModelBuilder::deriveFreshness( + CarbonImmutable::parse('2026-01-09 12:00:00'), + CarbonImmutable::parse('2026-02-08 12:00:00'), + ); + + expect($freshness['is_stale'])->toBeFalse(); +}); diff --git a/tests/Unit/TenantRequiredPermissionsOverallStatusTest.php b/tests/Unit/TenantRequiredPermissionsOverallStatusTest.php index 1807efc..9bf9291 100644 --- a/tests/Unit/TenantRequiredPermissionsOverallStatusTest.php +++ b/tests/Unit/TenantRequiredPermissionsOverallStatusTest.php @@ -98,3 +98,27 @@ expect(TenantRequiredPermissionsViewModelBuilder::deriveOverallStatus($rows)) ->toBe(VerificationReportOverall::Ready->value); }); + +it('maps overall to needs_attention when freshness is stale without explicit permission gaps', function (): void { + $rows = [ + [ + 'key' => 'A', + 'type' => 'application', + 'description' => null, + 'features' => ['backup'], + 'status' => 'granted', + 'details' => null, + ], + [ + 'key' => 'B', + 'type' => 'delegated', + 'description' => null, + 'features' => ['backup'], + 'status' => 'granted', + 'details' => null, + ], + ]; + + expect(TenantRequiredPermissionsViewModelBuilder::deriveOverallStatus($rows, true)) + ->toBe(VerificationReportOverall::NeedsAttention->value); +});