## Summary - cut over `EntraGroupResource` to an environment-bound admin Directory Groups surface in the workspace-first runtime - adopt workspace-scoped admin list/detail URLs and add the bounded `Directory > Groups` navigation entry in the admin panel - keep workspace-home navigation clean while preserving existing scoped list, detail, and global-search behavior - update focused feature coverage and add a browser smoke for the rendered sidebar drilldown path - include the Spec 303 package under `specs/303-admin-directory-groups-cutover/` ## Testing - updated focused Pest coverage for admin navigation segregation, Entra group admin scoping, Entra group global search scoping, and directory group browsing - added browser smoke coverage in `apps/platform/tests/Browser/Spec303AdminDirectoryGroupsCutoverSmokeTest.php` ## Filament / Runtime Notes - remains compliant with Filament v5 on Livewire v4 - no provider registration changes; provider registration location remains `apps/platform/bootstrap/providers.php` - `EntraGroupResource` remains eligible for global search because it has a View page - no destructive actions were added or changed; confirmation and authorization behavior is unchanged - no asset registration changes; existing `cd apps/platform && php artisan filament:assets` deploy posture is unchanged Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #358
187 lines
6.3 KiB
PHP
187 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\EntraGroupResource;
|
|
use App\Filament\Resources\EntraGroupResource\Pages\ListEntraGroups;
|
|
use App\Models\EntraGroup;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('returns not found for the retired direct admin group list when no canonical tenant context exists', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$this->actingAs($user);
|
|
Filament::setTenant(null, true);
|
|
|
|
expect(EntraGroupResource::getUrl(panel: 'admin'))
|
|
->not->toContain('/entra-groups');
|
|
|
|
$this->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
|
|
])->get('/admin/entra-groups')
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('scopes the admin group list to the canonical tenant context', function (): void {
|
|
$tenantA = ManagedEnvironment::factory()->create();
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
|
|
|
|
$tenantB = ManagedEnvironment::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
]);
|
|
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
|
|
|
|
$groupA = EntraGroup::factory()->for($tenantA)->create([
|
|
'display_name' => 'Remembered tenant group',
|
|
]);
|
|
|
|
EntraGroup::factory()->for($tenantB)->create([
|
|
'display_name' => 'Other tenant group',
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
Filament::setTenant(null, true);
|
|
|
|
$url = EntraGroupResource::getUrl(panel: 'admin', tenant: $tenantA);
|
|
|
|
expect($url)
|
|
->toContain('/admin/workspaces/')
|
|
->toContain('/environments/')
|
|
->toContain('/entra-groups')
|
|
->not->toContain('/admin/t/')
|
|
->not->toBe('/admin/entra-groups');
|
|
|
|
$this->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id,
|
|
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
|
|
(string) $tenantA->workspace_id => (int) $tenantA->getKey(),
|
|
],
|
|
])->get($url)
|
|
->assertOk()
|
|
->assertSee((string) $groupA->display_name)
|
|
->assertDontSee('Other tenant group');
|
|
});
|
|
|
|
it('returns not found for admin direct group detail outside the canonical tenant scope', function (): void {
|
|
$tenantA = ManagedEnvironment::factory()->create();
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
|
|
|
|
$tenantB = ManagedEnvironment::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
]);
|
|
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
|
|
|
|
$groupA = EntraGroup::factory()->for($tenantA)->create();
|
|
$groupB = EntraGroup::factory()->for($tenantB)->create();
|
|
|
|
$this->actingAs($user);
|
|
Filament::setTenant(null, true);
|
|
|
|
$session = [
|
|
WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id,
|
|
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
|
|
(string) $tenantA->workspace_id => (int) $tenantA->getKey(),
|
|
],
|
|
];
|
|
|
|
$groupAUrl = EntraGroupResource::getUrl('view', ['record' => $groupA], panel: 'admin', tenant: $tenantA);
|
|
$groupBUrl = EntraGroupResource::getUrl('view', ['record' => $groupB], panel: 'admin', tenant: $tenantA);
|
|
|
|
expect($groupAUrl)
|
|
->toContain('/admin/workspaces/')
|
|
->toContain('/environments/')
|
|
->not->toContain('/admin/t/');
|
|
|
|
$this->withSession($session)
|
|
->get($groupAUrl)
|
|
->assertOk();
|
|
|
|
$this->withSession($session)
|
|
->get($groupBUrl)
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('returns not found when the remembered admin tenant belongs to another workspace', function (): void {
|
|
$tenantA = ManagedEnvironment::factory()->create();
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
|
|
|
|
$tenantB = ManagedEnvironment::factory()->create();
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
|
|
|
|
EntraGroup::factory()->for($tenantB)->create([
|
|
'display_name' => 'Cross workspace remembered group',
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
Filament::setTenant(null, true);
|
|
|
|
$mismatchedWorkspaceUrl = EntraGroupResource::getUrl(
|
|
parameters: ['workspace' => $tenantA->workspace],
|
|
panel: 'admin',
|
|
tenant: $tenantB,
|
|
);
|
|
|
|
expect($mismatchedWorkspaceUrl)
|
|
->toContain('/entra-groups')
|
|
->not->toContain('/admin/t/');
|
|
|
|
$this->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id,
|
|
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
|
|
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
|
],
|
|
])->get($mismatchedWorkspaceUrl)
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('keeps persisted admin group search inside the remembered canonical tenant after tenant changes', function (): void {
|
|
$tenantA = ManagedEnvironment::factory()->create();
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
|
|
|
|
$tenantB = ManagedEnvironment::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
]);
|
|
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
|
|
|
|
$groupA = EntraGroup::factory()->for($tenantA)->create([
|
|
'display_name' => 'Shared Search Group',
|
|
]);
|
|
|
|
$groupB = EntraGroup::factory()->for($tenantB)->create([
|
|
'display_name' => 'Shared Search Group',
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
Filament::setCurrentPanel('admin');
|
|
Filament::setTenant(null, true);
|
|
Filament::bootCurrentPanel();
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenantA->workspace_id);
|
|
session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [
|
|
(string) $tenantA->workspace_id => (int) $tenantA->getKey(),
|
|
]);
|
|
|
|
Livewire::actingAs($user)->test(ListEntraGroups::class)
|
|
->searchTable('Shared Search')
|
|
->assertCanSeeTableRecords([$groupA])
|
|
->assertCanNotSeeTableRecords([$groupB]);
|
|
|
|
session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [
|
|
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
|
]);
|
|
|
|
Livewire::actingAs($user)->test(ListEntraGroups::class)
|
|
->assertSet('tableSearch', 'Shared Search')
|
|
->assertCanSeeTableRecords([$groupB])
|
|
->assertCanNotSeeTableRecords([$groupA]);
|
|
});
|