TenantAtlas/apps/platform/tests/Feature/Filament/Spec371BackupSetProductizationTest.php
ahmido 8713b35da5 feat(ui): implement core operator view surfaces productization for backup sets (#442)
Applied the decision-first global surface IA contract to BackupSet views. Includes decision summary header, usability status, and separation of technical metadata.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #442
2026-06-11 07:38:33 +00:00

228 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\BackupSetResource\Pages\ViewBackupSet;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Actions\Action;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function (): void {
bindFailHardGraphClient();
});
it('renders a healthy backup set as a decision-first restore point without Graph calls', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$backupSet = spec371HealthyBackupSet($tenant, [
'name' => 'Spec 371 Healthy Backup',
'created_by' => 'operator@example.test',
'completed_at' => now()->subHour(),
'metadata' => ['source' => 'spec371'],
]);
$response = $this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Restore-point decision')
->assertSee('Usable for restore review')
->assertSee('Captured item inventory is available for operator review.')
->assertSee('Review included items below before starting any separate restore workflow.')
->assertSee('Technical and lifecycle detail')
->assertDontSee('Item review state')
->assertDontSee('This backup set has captured items ready for operator review before any separate restore workflow starts.')
->assertDontSee('Usable for review')
->assertDontSee('No degradations detected across');
spec371AssertOrdered($response->getContent(), [
'Backup set #'.$backupSet->getKey(),
'Restore-point decision',
'Usable for restore review',
'Technical and lifecycle detail',
]);
});
it('shows degraded backup input as action-needed before restore review', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$backupSet = BackupSet::factory()
->for($tenant)
->degradedCompleted()
->create([
'name' => 'Spec 371 Degraded Backup',
]);
$response = $this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Action needed before restore review')
->assertSee('Current degradation')
->assertSee('1 degraded item')
->assertSee('Treat this backup as investigation input until degraded items are reviewed.')
->assertSee('Review degraded included items and source operation before continuing into restore.');
spec371AssertOrdered($response->getContent(), [
'Restore-point decision',
'Current degradation',
'Technical and lifecycle detail',
]);
});
it('explains an empty backup set blocker before technical context', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$backupSet = BackupSet::factory()
->for($tenant)
->create([
'name' => 'Spec 371 Empty Backup',
'item_count' => 0,
'metadata' => [],
]);
$response = $this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Blocked until items are captured')
->assertSee('Current blocker')
->assertSee('No backup items were captured.')
->assertSee('Create or refresh a backup set before starting restore review.')
->assertDontSee('Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.');
spec371AssertOrdered($response->getContent(), [
'Restore-point decision',
'Current blocker',
'Technical and lifecycle detail',
]);
});
it('keeps the backup sets list scan-first without repeated healthy zero-noise', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
spec371HealthyBackupSet($tenant, ['name' => 'Spec 371 Healthy Row']);
BackupSet::factory()
->for($tenant)
->degradedCompleted()
->create(['name' => 'Spec 371 Degraded Row']);
$this->actingAs($user)
->get(BackupSetResource::getUrl('index', tenant: $tenant))
->assertOk()
->assertSee('Restore-point decision')
->assertSee('Items captured')
->assertSee('Usable for restore review')
->assertSee('Action needed before restore review')
->assertSee('1 degraded item')
->assertDontSee('No degradations detected across');
});
it('shows a capability-aware empty state with one create backup CTA for authorized users', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$this->actingAs($user)
->get(BackupSetResource::getUrl('index', tenant: $tenant))
->assertOk()
->assertSee('No backup sets')
->assertSee('Create a backup set to capture policy versions and assignments for later restore review.')
->assertSee('Create backup set');
});
it('preserves destructive detail action confirmation and readonly blocking', function (): void {
[$owner, $tenant] = createUserWithTenant(role: 'owner');
$readonly = User::factory()->create();
createUserWithTenant(tenant: $tenant, user: $readonly, role: 'readonly');
$backupSet = spec371HealthyBackupSet($tenant, ['name' => 'Spec 371 Action Safety']);
$this->actingAs($owner);
setAdminPanelContext($tenant);
Livewire::actingAs($owner)
->test(ViewBackupSet::class, ['record' => (string) $backupSet->getKey()])
->assertActionExists('archive', fn (Action $action): bool => $action->isConfirmationRequired());
$this->actingAs($readonly);
setAdminPanelContext($tenant);
Livewire::actingAs($readonly)
->test(ViewBackupSet::class, ['record' => (string) $backupSet->getKey()])
->assertActionDisabled('archive')
->callAction('archive');
expect($backupSet->fresh()?->trashed())->toBeFalse();
});
it('denies wrong-environment backup set access as not found before content leaks', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$foreignTenant = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Spec 371 Foreign Environment',
]);
$foreignBackupSet = spec371HealthyBackupSet($foreignTenant, [
'name' => 'Spec 371 Foreign Backup',
]);
setAdminPanelContext($tenant);
$this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => (string) $foreignBackupSet->getKey()], tenant: $tenant))
->assertNotFound();
});
/**
* @param array<string, mixed> $attributes
*/
function spec371HealthyBackupSet(ManagedEnvironment $tenant, array $attributes = []): BackupSet
{
$backupSet = BackupSet::factory()
->for($tenant)
->create(array_merge([
'name' => 'Spec 371 Healthy Backup',
'item_count' => 1,
'completed_at' => now()->subMinutes(30),
'metadata' => [],
], $attributes));
BackupItem::factory()
->for($tenant)
->for($backupSet)
->create([
'payload' => ['id' => 'spec-371-policy'],
'metadata' => [],
'assignments' => [],
]);
return $backupSet;
}
/**
* @param list<string> $needles
*/
function spec371AssertOrdered(string $content, array $needles): void
{
$lastPosition = -1;
foreach ($needles as $needle) {
$position = strpos($content, $needle);
expect($position)->not->toBeFalse();
expect($position)->toBeGreaterThan($lastPosition);
$lastPosition = (int) $position;
}
}