TenantAtlas/tests/Feature/BackupItemReaddTest.php
ahmido da1adbdeb5 Spec 119: Drift cutover to Baseline Compare (golden master) (#144)
Implements Spec 119 (Drift Golden Master Cutover):

- Baseline Compare is the only drift writer (`source = baseline.compare`).
- Drift findings now store diff-compatible `evidence_jsonb` (summary.kind, baseline/current policy_version_id refs, fidelity + provenance).
- Findings UI renders one-sided diffs for `missing_policy`/`unexpected_policy` when a single ref exists; otherwise shows explicit “diff unavailable”.
- Removes legacy drift generator runtime (jobs/services/UI) and related tests.
- Adds one-time migration to delete legacy drift findings (`finding_type=drift` where source is null or != baseline.compare).
- Scopes baseline capture & landing duplicate warnings to latest completed inventory sync.
- Canonicalizes compliance `scheduledActionsForRule` drift signal and keeps legacy snapshots comparable.

Tests:
- `vendor/bin/sail artisan test --compact` (full suite per tasks)
- Focused pack: BaselinePolicyVersionResolverTest, BaselineCompareDriftEvidenceContractTest, DriftFindingDiffUnavailableTest, LegacyDriftFindingsCleanupMigrationTest, ComplianceNoncomplianceActionsDriftTest

Notes:
- Livewire v4+ / Filament v5 compatible (no legacy APIs).
- No new external dependencies.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #144
2026-03-06 14:30:49 +00:00

136 lines
4.6 KiB
PHP

<?php
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\Policy;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Intune\BackupService;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->tenant = Tenant::create([
'tenant_id' => 'tenant-123',
'name' => 'Test Tenant',
]);
$this->tenant->makeCurrent();
$this->user = User::factory()->create();
$this->actingAs($this->user);
$this->policy = Policy::create([
'tenant_id' => $this->tenant->id,
'external_id' => 'policy-456',
'policy_type' => 'settingsCatalogPolicy',
'display_name' => 'Test Policy',
'platform' => 'windows',
]);
$this->backupSet = BackupSet::create([
'tenant_id' => $this->tenant->id,
'name' => 'Test Backup Set',
'status' => 'completed',
'created_by' => $this->user->email,
]);
});
it('excludes soft-deleted items when listing available policies to add', function () {
// Create a backup item
$backupItem = BackupItem::create([
'tenant_id' => $this->tenant->id,
'backup_set_id' => $this->backupSet->id,
'policy_id' => $this->policy->id,
'policy_identifier' => $this->policy->external_id,
'policy_type' => $this->policy->policy_type,
'platform' => $this->policy->platform,
'payload' => ['test' => 'data'],
'captured_at' => now(),
]);
// Get available policies (should be empty since policy is already in backup)
$existingPolicyIds = $this->backupSet->items()->pluck('policy_id')->filter()->all();
expect($existingPolicyIds)->toContain($this->policy->id);
// Soft-delete the backup item
$backupItem->delete();
// Verify it's soft-deleted
expect($this->backupSet->items()->count())->toBe(0);
expect($this->backupSet->items()->withTrashed()->count())->toBe(1);
// Get available policies again - soft-deleted items should NOT be in the list (UI can re-add them)
$existingPolicyIds = $this->backupSet->items()->pluck('policy_id')->filter()->all();
expect($existingPolicyIds)->not->toContain($this->policy->id)
->and($existingPolicyIds)->toHaveCount(0);
});
it('prevents re-adding soft-deleted policies via BackupService', function () {
// Create initial backup item
$backupItem = BackupItem::create([
'tenant_id' => $this->tenant->id,
'backup_set_id' => $this->backupSet->id,
'policy_id' => $this->policy->id,
'policy_identifier' => $this->policy->external_id,
'policy_type' => $this->policy->policy_type,
'platform' => $this->policy->platform,
'payload' => ['test' => 'data'],
'captured_at' => now(),
]);
// Soft-delete it
$backupItem->delete();
// Try to add the same policy again via BackupService
$service = app(BackupService::class);
$result = $service->addPoliciesToSet(
tenant: $this->tenant,
backupSet: $this->backupSet->refresh(),
policyIds: [$this->policy->id],
actorEmail: $this->user->email,
actorName: $this->user->name,
);
// Should restore the soft-deleted item, not create a new one
expect($this->backupSet->items()->count())->toBe(1)
->and($this->backupSet->items()->withTrashed()->count())->toBe(1)
->and($result->item_count)->toBe(1)
->and($backupItem->fresh()->deleted_at)->toBeNull(); // Item should be restored
});
it('allows adding different policy after one was soft-deleted', function () {
// Create initial backup item
$backupItem = BackupItem::create([
'tenant_id' => $this->tenant->id,
'backup_set_id' => $this->backupSet->id,
'policy_id' => $this->policy->id,
'policy_identifier' => $this->policy->external_id,
'policy_type' => $this->policy->policy_type,
'platform' => $this->policy->platform,
'payload' => ['test' => 'data'],
'captured_at' => now(),
]);
// Soft-delete it
$backupItem->delete();
// Create a different policy
$otherPolicy = Policy::create([
'tenant_id' => $this->tenant->id,
'external_id' => 'policy-789',
'policy_type' => 'settingsCatalogPolicy',
'display_name' => 'Other Policy',
'platform' => 'windows',
]);
// Check available policies - should include the new one but not the deleted one
$existingPolicyIds = $this->backupSet->items()->withTrashed()->pluck('policy_id')->filter()->all();
expect($existingPolicyIds)->toContain($this->policy->id)
->and($existingPolicyIds)->not->toContain($otherPolicy->id);
});