TenantAtlas/tests/Pest.php
ahmido 060a82a1ed feat/048-backup-restore-ui-graph-safety (#55)
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
2026-01-11 00:14:35 +00:00

98 lines
2.8 KiB
PHP

<?php
use App\Models\Tenant;
use App\Models\User;
use App\Services\Graph\GraphClientInterface;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\Support\FailHardGraphClient;
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind a different classes or traits.
|
*/
pest()->extend(Tests\TestCase::class)
->use(RefreshDatabase::class)
->in('Feature');
pest()->extend(Tests\TestCase::class)
->in('Unit');
beforeEach(function () {
putenv('INTUNE_TENANT_ID');
unset($_ENV['INTUNE_TENANT_ID'], $_SERVER['INTUNE_TENANT_ID']);
});
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
function fakeIdToken(string $tenantId): string
{
$header = rtrim(strtr(base64_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT'])), '+/', '-_'), '=');
$payload = rtrim(strtr(base64_encode(json_encode(['tid' => $tenantId])), '+/', '-_'), '=');
return $header.'.'.$payload.'.signature';
}
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
function something()
{
// ..
}
function bindFailHardGraphClient(): void
{
app()->instance(GraphClientInterface::class, new FailHardGraphClient);
}
/**
* @return array{0: User, 1: Tenant}
*/
function createUserWithTenant(?Tenant $tenant = null, ?User $user = null, string $role = 'owner'): array
{
$user ??= User::factory()->create();
$tenant ??= Tenant::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => $role],
]);
return [$user, $tenant];
}
/**
* @return array{tenant: string}
*/
function filamentTenantRouteParams(Tenant $tenant): array
{
return ['tenant' => (string) $tenant->external_id];
}