create(); $otherTenant = Tenant::factory()->create(); EntraGroupSyncRun::query()->create([ 'tenant_id' => $tenant->getKey(), 'selection_key' => 'groups-v1:all', 'slot_key' => 'slot-a', 'status' => EntraGroupSyncRun::STATUS_SUCCEEDED, ]); EntraGroupSyncRun::query()->create([ 'tenant_id' => $otherTenant->getKey(), 'selection_key' => 'groups-v1:all', 'slot_key' => 'slot-b', 'status' => EntraGroupSyncRun::STATUS_SUCCEEDED, ]); $user = User::factory()->create(); $user->tenants()->syncWithoutDetaching([ $tenant->getKey() => ['role' => 'owner'], $otherTenant->getKey() => ['role' => 'owner'], ]); $this->actingAs($user) ->get(EntraGroupSyncRunResource::getUrl('index', tenant: $tenant)) ->assertOk() ->assertSee('slot-a') ->assertDontSee('slot-b'); }); test('entra group sync run view is forbidden cross-tenant (403)', function () { $tenantA = Tenant::factory()->create(); $tenantB = Tenant::factory()->create(); $runB = EntraGroupSyncRun::query()->create([ 'tenant_id' => $tenantB->getKey(), 'selection_key' => 'groups-v1:all', 'slot_key' => null, 'status' => EntraGroupSyncRun::STATUS_SUCCEEDED, ]); $user = User::factory()->create(); $user->tenants()->syncWithoutDetaching([ $tenantA->getKey() => ['role' => 'owner'], $tenantB->getKey() => ['role' => 'owner'], ]); $this->actingAs($user) ->get(EntraGroupSyncRunResource::getUrl('view', ['record' => $runB], tenant: $tenantA)) ->assertForbidden(); }); test('sync groups action enqueues job and writes database notification', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ListEntraGroupSyncRuns::class) ->callAction('sync_groups'); Queue::assertPushed(EntraGroupSyncJob::class); $run = EntraGroupSyncRun::query()->where('tenant_id', $tenant->getKey())->latest('id')->first(); expect($run)->not->toBeNull(); $this->assertDatabaseHas('notifications', [ 'notifiable_id' => $user->getKey(), 'notifiable_type' => $user->getMorphClass(), 'type' => RunStatusChangedNotification::class, ]); $notification = $user->notifications()->latest('id')->first(); expect($notification)->not->toBeNull(); expect($notification->data['actions'][0]['url'] ?? null) ->toBe(EntraGroupSyncRunResource::getUrl('view', ['record' => $run->getKey()], tenant: $tenant)); }); test('sync groups action is disabled for readonly users with standard tooltip', function () { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'readonly'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::actingAs($user) ->test(ListEntraGroupSyncRuns::class) ->assertActionVisible('sync_groups') ->assertActionDisabled('sync_groups') ->assertActionExists('sync_groups', fn ($action): bool => $action->getTooltip() === UiTooltips::insufficientPermission()); Queue::assertNothingPushed(); });