Feature 048: Backup/Restore UI Graph-Safety (Phase 1) Dieses PR entfernt Microsoft Graph Calls aus UI-Renderpfaden (Filament/Livewire mount/render/options/typeahead/labels) in den kritischen Backup/Restore Screens und fügt Fail-Hard Guard Tests hinzu, die regressionssicher verhindern, dass UI-Rendering wieder Graph aufruft. ⸻ Motivation Backup/Restore UI wurde teilweise “fragil”, weil UI-Komponenten (z.B. Group Typeahead/Option Labels) Graph/Entra direkt beim Rendern triggern konnten. Das führt zu: • langsamen/unstabilen Seiten (429/Timeout/Permissions) • schwer reproduzierbaren UI-Fehlern im MSP-Scale • unnötiger Kopplung von “Page render” an Graph-Verfügbarkeit Ziel: UI muss DB-only rendern; Graph darf nur in Jobs/Run-Execution stattfinden. ⸻ Scope / Changes 1) Restore Wizard: Entfernt Graph-Typeahead & Label-Resolution • Group Mapping ist jetzt DB-only: • manuelle GUID Eingabe / Skip • GUID Validation • Helper Text, wo die Object ID zu finden ist • Keine Graph calls mehr in options() / getOptionLabelUsing() / typeahead beim Rendern. 2) Fail-Hard Guard Tests (Graph-Safety) • Neue Test-Infrastruktur: FailHardGraphClient (GraphClientInterface darf nicht aufgerufen werden) • Guard Tests als Pest Feature Tests (HTTP GET): • Backup Sets Index rendert mit fail-hard Graph client • Restore Wizard Route rendert mit fail-hard Graph client • Assertions: • 200 OK • plus stable UI marker string • Masking/Fallback Format ist deterministisch: Unresolved (…<last8>) 3) Spec/Plan/Tasks/Checklist • Spec 048 aktualisiert, Tasks abgehakt • requirements.md Checklist Gate: PASS ⸻ Out of Scope / Non-Goals • Kein Umbau der “Execution”-Actions zu Jobs (Capture snapshot, Restore rerun, Dry-Run execution etc.) → eigener Folge-Spec (Phase 2). • Keine Entra Group Name Resolution (separates Group-Inventory/Cache Feature). • Keine neuen Tabellen/Migrations in Phase 1. ⸻ How to verify (manual) Mit absichtlich kaputtem Tenant/Auth (Graph failt): 1. Öffne Backups & Restore → Backup Sets ✅ muss laden (UI render DB-only) 2. Öffne Restore Runs → Create Restore Run (Wizard) ✅ muss laden, kein Group-Typeahead mehr 3. Starte eine Restore Operation ❌ darf fehlschlagen (Graph kaputt) – wichtig ist: Render bleibt stabil, Run zeigt Fehler sauber pro Item. ⸻ Tests / Validation Executed: • ./vendor/bin/pint --dirty ✅ • ./vendor/bin/sail artisan test tests/Feature/Filament/BackupSetGraphSafetyTest.php tests/Feature/Filament/RestoreWizardGraphSafetyTest.php ✅ • (optional) Combined targeted suite ✅ ⸻ Notes • This PR intentionally focuses on UI Graph-Safety only. • Any future reintroduction of Graph search/typeahead in UI must go through contracts first and be executed asynchronously, never in UI render paths. Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #55
66 lines
1.9 KiB
PHP
66 lines
1.9 KiB
PHP
<?php
|
|
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Models\BackupItem;
|
|
use App\Models\BackupSet;
|
|
use App\Models\Tenant;
|
|
|
|
function makeAssignment(string $odataType, string $groupId, ?string $displayName = null): array
|
|
{
|
|
$target = [
|
|
'@odata.type' => $odataType,
|
|
'groupId' => $groupId,
|
|
];
|
|
|
|
if (is_string($displayName) && $displayName !== '') {
|
|
$target['group_display_name'] = $displayName;
|
|
}
|
|
|
|
return ['target' => $target];
|
|
}
|
|
|
|
test('restore wizard create page renders without touching graph', function () {
|
|
$tenant = Tenant::factory()->create();
|
|
[$user] = createUserWithTenant($tenant);
|
|
|
|
bindFailHardGraphClient();
|
|
|
|
$this->actingAs($user)
|
|
->get(RestoreRunResource::getUrl('create', tenant: $tenant))
|
|
->assertOk()
|
|
->assertSee('Create restore run')
|
|
->assertSee('Select Backup Set');
|
|
});
|
|
|
|
test('restore wizard group mapping renders DB-only with manual GUID UX', function () {
|
|
$tenant = Tenant::factory()->create();
|
|
[$user] = createUserWithTenant($tenant);
|
|
|
|
$backupSet = BackupSet::factory()->create([
|
|
'tenant_id' => $tenant->getKey(),
|
|
'name' => 'group-mapping-backup-set',
|
|
]);
|
|
|
|
$groupId = '11111111-2222-3333-4444-555555555555';
|
|
$expectedMasked = '…'.substr($groupId, -8);
|
|
|
|
BackupItem::factory()->create([
|
|
'tenant_id' => $tenant->getKey(),
|
|
'backup_set_id' => $backupSet->getKey(),
|
|
'assignments' => [
|
|
makeAssignment('#microsoft.graph.groupAssignmentTarget', $groupId, 'Example Group'),
|
|
],
|
|
]);
|
|
|
|
bindFailHardGraphClient();
|
|
|
|
$url = RestoreRunResource::getUrl('create', tenant: $tenant).'?backup_set_id='.$backupSet->getKey();
|
|
|
|
$this->actingAs($user)
|
|
->get($url)
|
|
->assertOk()
|
|
->assertSee($expectedMasked)
|
|
->assertSee('Paste the target Entra ID group Object ID')
|
|
->assertSee('Use SKIP to omit the assignment.');
|
|
});
|