php-admin/tests/Feature/Modal/PlatformUserModalTest.php

603 lines
21 KiB
PHP
Raw Normal View History

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\Livewire;
/**
* Tests for PlatformUser modal component patterns.
*
* These tests verify the PlatformUser admin panel behaviour using test doubles:
* tab navigation, tier management, verification toggling, delete confirmation
* flows, package/entitlement modals, and GDPR data export.
*/
// =============================================================================
// Test Double Components
// =============================================================================
/**
* PlatformUser-style component for admin user management.
*/
class PlatformUserModalDouble extends Component
{
// User data
public int $userId = 0;
public string $userName = '';
public string $userEmail = '';
// Editable fields
public string $editingTier = 'free';
public bool $editingVerified = false;
// Action state
public string $actionMessage = '';
public string $actionType = '';
// Tab navigation
public string $activeTab = 'overview';
// Delete confirmation flow
public bool $showDeleteConfirm = false;
public bool $immediateDelete = false;
public string $deleteReason = '';
// Package provisioning modal
public bool $showPackageModal = false;
public ?int $selectedWorkspaceId = null;
public string $selectedPackageCode = '';
// Entitlement provisioning modal
public bool $showEntitlementModal = false;
public ?int $entitlementWorkspaceId = null;
public string $entitlementFeatureCode = '';
public string $entitlementType = 'enable';
public ?int $entitlementLimit = null;
public string $entitlementDuration = 'permanent';
public ?string $entitlementExpiresAt = null;
// Export tracking
public bool $dataExported = false;
public function mount(int $id = 1): void
{
$this->userId = $id;
$this->userName = 'Test User';
$this->userEmail = 'test@example.com';
$this->editingTier = 'free';
$this->editingVerified = false;
}
public function setTab(string $tab): void
{
if (in_array($tab, ['overview', 'workspaces', 'entitlements', 'data', 'danger'])) {
$this->activeTab = $tab;
}
}
public function saveTier(): void
{
$this->actionMessage = "Tier updated to {$this->editingTier}.";
$this->actionType = 'success';
}
public function saveVerification(): void
{
$this->actionMessage = $this->editingVerified
? 'Email marked as verified.'
: 'Email verification removed.';
$this->actionType = 'success';
}
public function resendVerification(): void
{
if ($this->editingVerified) {
$this->actionMessage = 'User email is already verified.';
$this->actionType = 'warning';
return;
}
$this->actionMessage = 'Verification email sent.';
$this->actionType = 'success';
}
public function confirmDelete(bool $immediate = false): void
{
$this->immediateDelete = $immediate;
$this->showDeleteConfirm = true;
$this->deleteReason = '';
}
public function cancelDelete(): void
{
$this->showDeleteConfirm = false;
$this->immediateDelete = false;
$this->deleteReason = '';
}
public function scheduleDelete(): void
{
if ($this->immediateDelete) {
$this->actionMessage = 'User has been permanently deleted.';
$this->actionType = 'success';
} else {
$this->actionMessage = 'Account deletion scheduled. Will be deleted in 7 days unless cancelled.';
$this->actionType = 'warning';
}
$this->showDeleteConfirm = false;
}
public function anonymizeUser(): void
{
$this->userName = 'Anonymized User';
$this->userEmail = "anon_{$this->userId}@anonymized.local";
$this->editingTier = 'free';
$this->editingVerified = false;
$this->actionMessage = 'User data has been anonymized.';
$this->actionType = 'success';
}
public function openPackageModal(int $workspaceId): void
{
$this->selectedWorkspaceId = $workspaceId;
$this->selectedPackageCode = '';
$this->showPackageModal = true;
}
public function closePackageModal(): void
{
$this->showPackageModal = false;
$this->selectedWorkspaceId = null;
$this->selectedPackageCode = '';
}
public function provisionPackage(): void
{
if (! $this->selectedWorkspaceId || ! $this->selectedPackageCode) {
$this->actionMessage = 'Please select a workspace and package.';
$this->actionType = 'warning';
return;
}
$this->actionMessage = "Package provisioned to workspace.";
$this->actionType = 'success';
$this->closePackageModal();
}
public function openEntitlementModal(int $workspaceId): void
{
$this->entitlementWorkspaceId = $workspaceId;
$this->entitlementFeatureCode = '';
$this->entitlementType = 'enable';
$this->entitlementLimit = null;
$this->entitlementDuration = 'permanent';
$this->entitlementExpiresAt = null;
$this->showEntitlementModal = true;
}
public function closeEntitlementModal(): void
{
$this->showEntitlementModal = false;
$this->entitlementWorkspaceId = null;
$this->entitlementFeatureCode = '';
}
public function provisionEntitlement(): void
{
if (! $this->entitlementWorkspaceId || ! $this->entitlementFeatureCode) {
$this->actionMessage = 'Please select a workspace and feature.';
$this->actionType = 'warning';
return;
}
$this->actionMessage = "Entitlement added to workspace.";
$this->actionType = 'success';
$this->closeEntitlementModal();
}
public function exportUserData(): void
{
$this->dataExported = true;
}
#[Computed]
public function collectUserData(): array
{
return [
'export_info' => [
'exported_at' => now()->toIso8601String(),
'exported_by' => 'Platform Administrator',
],
'account' => [
'id' => $this->userId,
'name' => $this->userName,
'email' => $this->userEmail,
'tier' => $this->editingTier,
],
];
}
public function render(): string
{
return <<<'HTML'
<div>
<h1>User: {{ $userName }}</h1>
<span>Tab: {{ $activeTab }}</span>
<span>Tier: {{ $editingTier }}</span>
@if($showDeleteConfirm)
<div class="delete-confirm">Delete Confirm</div>
@endif
@if($showPackageModal)
<div class="package-modal">Package Modal</div>
@endif
@if($showEntitlementModal)
<div class="entitlement-modal">Entitlement Modal</div>
@endif
@if($actionMessage)
<div class="action-message {{ $actionType }}">{{ $actionMessage }}</div>
@endif
</div>
HTML;
}
}
// =============================================================================
// Tab Navigation Tests
// =============================================================================
describe('PlatformUser tab navigation', function () {
it('defaults to overview tab', function () {
Livewire::test(PlatformUserModalDouble::class)
->assertSet('activeTab', 'overview');
});
it('switches to valid tabs', function () {
$validTabs = ['overview', 'workspaces', 'entitlements', 'data', 'danger'];
foreach ($validTabs as $tab) {
Livewire::test(PlatformUserModalDouble::class)
->call('setTab', $tab)
->assertSet('activeTab', $tab);
}
});
it('ignores invalid tab names', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('setTab', 'nonexistent')
->assertSet('activeTab', 'overview');
});
it('renders current tab label', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('setTab', 'workspaces')
->assertSee('Tab: workspaces');
});
});
// =============================================================================
// Tier Management Tests
// =============================================================================
describe('PlatformUser tier management', function () {
it('loads user tier on mount', function () {
Livewire::test(PlatformUserModalDouble::class)
->assertSet('editingTier', 'free');
});
it('can change tier to apollo', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingTier', 'apollo')
->call('saveTier')
->assertSet('actionMessage', 'Tier updated to apollo.')
->assertSet('actionType', 'success');
});
it('can change tier to hades', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingTier', 'hades')
->call('saveTier')
->assertSet('actionMessage', 'Tier updated to hades.')
->assertSet('actionType', 'success');
});
it('renders tier value', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingTier', 'apollo')
->assertSee('Tier: apollo');
});
});
// =============================================================================
// Verification Management Tests
// =============================================================================
describe('PlatformUser verification management', function () {
it('can mark email as verified', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingVerified', true)
->call('saveVerification')
->assertSet('actionMessage', 'Email marked as verified.')
->assertSet('actionType', 'success');
});
it('can remove email verification', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingVerified', false)
->call('saveVerification')
->assertSet('actionMessage', 'Email verification removed.')
->assertSet('actionType', 'success');
});
it('warns when resending to already verified user', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingVerified', true)
->call('resendVerification')
->assertSet('actionMessage', 'User email is already verified.')
->assertSet('actionType', 'warning');
});
it('sends verification email for unverified user', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingVerified', false)
->call('resendVerification')
->assertSet('actionMessage', 'Verification email sent.')
->assertSet('actionType', 'success');
});
});
// =============================================================================
// Delete Confirmation Flow Tests
// =============================================================================
describe('PlatformUser delete confirmation flow', function () {
it('opens delete confirmation with scheduled mode', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('confirmDelete', false)
->assertSet('showDeleteConfirm', true)
->assertSet('immediateDelete', false)
->assertSet('deleteReason', '')
->assertSee('Delete Confirm');
});
it('opens delete confirmation with immediate mode', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('confirmDelete', true)
->assertSet('showDeleteConfirm', true)
->assertSet('immediateDelete', true);
});
it('cancels delete confirmation', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('confirmDelete', true)
->set('deleteReason', 'GDPR request')
->call('cancelDelete')
->assertSet('showDeleteConfirm', false)
->assertSet('immediateDelete', false)
->assertSet('deleteReason', '')
->assertDontSee('Delete Confirm');
});
it('schedules deletion (non-immediate)', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('confirmDelete', false)
->set('deleteReason', 'User request')
->call('scheduleDelete')
->assertSet('showDeleteConfirm', false)
->assertSet('actionMessage', 'Account deletion scheduled. Will be deleted in 7 days unless cancelled.')
->assertSet('actionType', 'warning');
});
it('executes immediate deletion', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('confirmDelete', true)
->call('scheduleDelete')
->assertSet('showDeleteConfirm', false)
->assertSet('actionMessage', 'User has been permanently deleted.')
->assertSet('actionType', 'success');
});
});
// =============================================================================
// Anonymization Tests
// =============================================================================
describe('PlatformUser anonymization', function () {
it('anonymizes user data', function () {
Livewire::test(PlatformUserModalDouble::class, ['id' => 42])
->call('anonymizeUser')
->assertSet('userName', 'Anonymized User')
->assertSet('userEmail', 'anon_42@anonymized.local')
->assertSet('editingTier', 'free')
->assertSet('editingVerified', false)
->assertSet('actionType', 'success');
});
it('displays anonymized name after anonymization', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('anonymizeUser')
->assertSee('Anonymized User');
});
});
// =============================================================================
// Package Modal Tests
// =============================================================================
describe('PlatformUser package modal', function () {
it('opens package modal for a workspace', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openPackageModal', 5)
->assertSet('showPackageModal', true)
->assertSet('selectedWorkspaceId', 5)
->assertSet('selectedPackageCode', '')
->assertSee('Package Modal');
});
it('closes package modal and resets state', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openPackageModal', 5)
->set('selectedPackageCode', 'pro-plan')
->call('closePackageModal')
->assertSet('showPackageModal', false)
->assertSet('selectedWorkspaceId', null)
->assertSet('selectedPackageCode', '')
->assertDontSee('Package Modal');
});
it('warns when provisioning without workspace or package', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openPackageModal', 5)
->call('provisionPackage')
->assertSet('actionMessage', 'Please select a workspace and package.')
->assertSet('actionType', 'warning');
});
it('provisions package and closes modal on success', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openPackageModal', 5)
->set('selectedPackageCode', 'pro-plan')
->call('provisionPackage')
->assertSet('showPackageModal', false)
->assertSet('actionType', 'success');
});
});
// =============================================================================
// Entitlement Modal Tests
// =============================================================================
describe('PlatformUser entitlement modal', function () {
it('opens entitlement modal with defaults', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openEntitlementModal', 7)
->assertSet('showEntitlementModal', true)
->assertSet('entitlementWorkspaceId', 7)
->assertSet('entitlementFeatureCode', '')
->assertSet('entitlementType', 'enable')
->assertSet('entitlementLimit', null)
->assertSet('entitlementDuration', 'permanent')
->assertSet('entitlementExpiresAt', null)
->assertSee('Entitlement Modal');
});
it('closes entitlement modal and resets state', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openEntitlementModal', 7)
->set('entitlementFeatureCode', 'core.srv.bio')
->call('closeEntitlementModal')
->assertSet('showEntitlementModal', false)
->assertSet('entitlementWorkspaceId', null)
->assertSet('entitlementFeatureCode', '')
->assertDontSee('Entitlement Modal');
});
it('warns when provisioning without workspace or feature', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openEntitlementModal', 7)
->call('provisionEntitlement')
->assertSet('actionMessage', 'Please select a workspace and feature.')
->assertSet('actionType', 'warning');
});
it('provisions entitlement and closes modal on success', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openEntitlementModal', 7)
->set('entitlementFeatureCode', 'core.srv.bio')
->call('provisionEntitlement')
->assertSet('showEntitlementModal', false)
->assertSet('actionType', 'success');
});
it('supports different entitlement types', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openEntitlementModal', 7)
->set('entitlementType', 'add_limit')
->set('entitlementLimit', 100)
->assertSet('entitlementType', 'add_limit')
->assertSet('entitlementLimit', 100);
});
it('supports duration-based entitlements', function () {
Livewire::test(PlatformUserModalDouble::class)
->call('openEntitlementModal', 7)
->set('entitlementDuration', 'duration')
->set('entitlementExpiresAt', '2026-12-31')
->assertSet('entitlementDuration', 'duration')
->assertSet('entitlementExpiresAt', '2026-12-31');
});
});
// =============================================================================
// Data Export Tests
// =============================================================================
describe('PlatformUser data export', function () {
it('collects user data for export', function () {
Livewire::test(PlatformUserModalDouble::class, ['id' => 42])
->assertSet(fn ($component) => $component->collectUserData['account']['id'] === 42
&& $component->collectUserData['account']['name'] === 'Test User'
&& $component->collectUserData['account']['email'] === 'test@example.com');
});
it('triggers data export', function () {
Livewire::test(PlatformUserModalDouble::class)
->assertSet('dataExported', false)
->call('exportUserData')
->assertSet('dataExported', true);
});
});
// =============================================================================
// Action Message Tests
// =============================================================================
describe('PlatformUser action messages', function () {
it('clears action message between operations', function () {
$component = Livewire::test(PlatformUserModalDouble::class)
->set('editingTier', 'apollo')
->call('saveTier')
->assertSet('actionType', 'success');
// Next operation replaces the message
$component->set('editingVerified', true)
->call('resendVerification')
->assertSet('actionType', 'warning')
->assertSet('actionMessage', 'User email is already verified.');
});
it('renders action message in view', function () {
Livewire::test(PlatformUserModalDouble::class)
->set('editingTier', 'hades')
->call('saveTier')
->assertSee('Tier updated to hades.');
});
});