create(); $tenantB = Tenant::factory()->create(); OperationRun::factory()->create([ 'tenant_id' => $tenantA->getKey(), 'type' => 'policy.sync', 'status' => 'queued', 'outcome' => 'pending', ]); OperationRun::factory()->create([ 'tenant_id' => $tenantB->getKey(), 'type' => 'inventory.sync', 'status' => 'queued', 'outcome' => 'pending', ]); [$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner'); $tenantB->forceFill(['workspace_id' => (int) $tenantA->workspace_id])->save(); $user->tenants()->syncWithoutDetaching([ $tenantB->getKey() => ['role' => 'owner'], ]); $this->actingAs($user) ->get(OperationRunResource::getUrl('index', tenant: $tenantA)) ->assertOk() ->assertSee('Policy sync') ->assertDontSee('Inventory sync'); }); test('operation run view is not accessible cross-tenant', function () { $tenantA = Tenant::factory()->create(); $tenantB = Tenant::factory()->create(); $runB = OperationRun::factory()->create([ 'tenant_id' => $tenantB->getKey(), 'type' => 'inventory.sync', 'status' => 'queued', 'outcome' => 'pending', ]); [$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner'); $tenantB->forceFill(['workspace_id' => (int) $tenantA->workspace_id])->save(); $user->tenants()->syncWithoutDetaching([ $tenantB->getKey() => ['role' => 'owner'], ]); $this->actingAs($user) ->get(OperationRunResource::getUrl('view', ['record' => $runB], tenant: $tenantA)) ->assertNotFound(); }); test('readonly users can view operation runs for their tenant', function () { $tenant = Tenant::factory()->create(); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'drift.generate', 'status' => 'queued', 'outcome' => 'pending', ]); [$user, $tenant] = createUserWithTenant($tenant, role: 'readonly'); $this->actingAs($user) ->get(OperationRunResource::getUrl('index', tenant: $tenant)) ->assertOk() ->assertSee('Drift generation'); $this->actingAs($user) ->get(OperationRunResource::getUrl('view', ['record' => $run], tenant: $tenant)) ->assertOk() ->assertSee('Drift generation'); });