TenantAtlas/apps/platform/tests/Feature/Spec080WorkspaceManagedTenantAdminMigrationTest.php
Ahmed Darrazi 5443dba269
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 7m2s
refactor: consolidate internal tenant model naming
2026-05-14 13:09:36 +02:00

279 lines
11 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AuditLog;
use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Services\Auth\ManagedEnvironmentMembershipManager;
use App\Support\Audit\AuditActionId;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
beforeEach(function (): void {
Http::preventStrayRequests();
});
it('allows workspace members to open the canonical workspace-managed environments index', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentLinks::indexUrl($tenant))
->assertOk();
});
it('returns 404 for non-members on the workspace-managed tenants index', function (): void {
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get('/admin/tenants')
->assertNotFound();
});
it('allows workspace members to open the canonical workspace-managed environment view route', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentLinks::viewUrl($tenant))
->assertOk();
});
it('exposes a canonical provider connections link for a managed environment', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentLinks::viewUrl($tenant))
->assertOk();
expect(ManagedEnvironmentLinks::providerConnectionsUrl($tenant))
->toContain('/admin/provider-connections?managed_environment_id='.$tenant->external_id)
->not->toContain('/admin/tenants')
->not->toContain('/admin/t/');
});
it('returns 404 for non-members on the workspace-managed tenant view route', function (): void {
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/tenants/{$tenant->external_id}")
->assertNotFound();
});
it('exposes access-scope management under workspace scope', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentLinks::accessScopesUrl($tenant))
->assertOk();
});
it('keeps retired tenant panel operational routes unavailable even for entitled workspace members', function (): void {
$workspace = Workspace::factory()->create();
$tenant = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'external_id' => '11111111-1111-1111-1111-111111111111',
'managed_environment_id' => '11111111-1111-1111-1111-111111111111',
]);
[$entitledUser] = createMinimalUserWithTenant(tenant: $tenant, role: 'readonly');
$nonEntitledUser = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $nonEntitledUser->getKey(),
'role' => 'owner',
]);
$this->actingAs($entitledUser)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get("/admin/t/{$tenant->external_id}")
->assertNotFound();
$this->actingAs($entitledUser)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get("/admin/t/{$tenant->external_id}/diagnostics")
->assertNotFound();
$this->actingAs($nonEntitledUser)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get("/admin/t/{$tenant->external_id}")
->assertNotFound();
$this->actingAs($nonEntitledUser)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get("/admin/t/{$tenant->external_id}/diagnostics")
->assertNotFound();
});
it('keeps retired tenant panel route shapes unavailable and rejects duplicated /t prefixes', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/t/{$tenant->external_id}/diagnostics")
->assertNotFound();
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/t/t/{$tenant->external_id}/diagnostics")
->assertNotFound();
});
it('removes tenant-scoped management routes', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/t/{$tenant->external_id}/provider-connections")
->assertNotFound();
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/t/{$tenant->external_id}/required-permissions")
->assertNotFound();
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/t/{$tenant->external_id}/memberships")
->assertNotFound();
});
it('serves provider connection management under canonical admin routes only', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
]);
$this->followingRedirects()
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get('/admin/provider-connections?managed_environment_id='.$tenant->external_id)
->assertOk();
$this->followingRedirects()
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get('/admin/provider-connections/'.$connection->getKey().'/edit?managed_environment_id='.$tenant->external_id)
->assertOk();
});
it('returns 403 for workspace members missing mutation capability on canonical provider connection routes', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'readonly', workspaceRole: 'readonly');
$this->followingRedirects()
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get('/admin/provider-connections?managed_environment_id='.$tenant->external_id)
->assertOk();
$this->followingRedirects()
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get('/admin/provider-connections/create?managed_environment_id='.$tenant->external_id)
->assertForbidden();
});
it('writes managed-environment access scope audit entries for scope mutations', function (): void {
[$owner, $tenant] = createMinimalUserWithTenant(role: 'owner');
$member = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'user_id' => (int) $member->getKey(),
'role' => 'readonly',
]);
/** @var ManagedEnvironmentMembershipManager $manager */
$manager = app(ManagedEnvironmentMembershipManager::class);
$membership = $manager->addMember(
tenant: $tenant,
actor: $owner,
member: $member,
role: 'readonly',
source: 'manual',
);
$manager->removeMember(
tenant: $tenant,
actor: $owner,
membership: $membership,
);
$actions = AuditLog::query()
->where('managed_environment_id', (int) $tenant->getKey())
->whereIn('action', [
AuditActionId::ManagedEnvironmentAccessScopeGrant->value,
AuditActionId::ManagedEnvironmentAccessScopeRemove->value,
])
->pluck('action')
->all();
expect($actions)->toContain(AuditActionId::ManagedEnvironmentAccessScopeGrant->value)
->and($actions)->toContain(AuditActionId::ManagedEnvironmentAccessScopeRemove->value)
->and($actions)->not->toContain(AuditActionId::TenantMembershipRoleChange->value);
});
it('keeps the canonical managed-environment index available after panel split', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentLinks::indexUrl($tenant))
->assertOk()
->assertSee('Managed environments')
->assertDontSee('/admin/tenants', false)
->assertDontSee('/admin/t/', false);
});
it('does not expose tenant-management resources in tenant panel registration or navigation URLs', function (): void {
expect(Filament::getPanel('tenant'))->toBeNull();
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentLinks::viewUrl($tenant))
->assertOk()
->assertDontSee("/admin/t/{$tenant->external_id}/provider-connections", false)
->assertDontSee("/admin/t/{$tenant->external_id}/tenants", false);
});
it('disables global search on the retired ManagedEnvironmentResource product route owner', function (): void {
[$workspaceUser, $tenant] = createMinimalUserWithTenant(role: 'owner');
Filament::setCurrentPanel('admin');
Filament::setTenant(null, true);
$this->actingAs($workspaceUser);
expect(\App\Filament\Resources\ManagedEnvironmentResource::getGlobalSearchResults((string) $tenant->name))->toHaveCount(0);
$nonMember = User::factory()->create();
Filament::setCurrentPanel('admin');
Filament::setTenant(null, true);
$this->actingAs($nonMember);
expect(\App\Filament\Resources\ManagedEnvironmentResource::getGlobalSearchResults((string) $tenant->name))->toHaveCount(0);
});