Implemented sync capture backup operation semantics as requested. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #433
148 lines
5.6 KiB
PHP
148 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\BackupSet;
|
|
use App\Models\BaselineProfile;
|
|
use App\Models\BaselineSnapshot;
|
|
use App\Models\OperationRun;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use App\Services\OperationRunService;
|
|
use App\Support\Inventory\InventoryCoverage;
|
|
use App\Support\OperationRunLinks;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('renders selected-family reconciled operations DB-only on list and detail surfaces in Spec362', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$profile = BaselineProfile::factory()->active()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
]);
|
|
$snapshot = BaselineSnapshot::factory()->complete()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
]);
|
|
$backupSet = BackupSet::factory()->for($tenant)->create();
|
|
|
|
$inventoryRun = OperationRun::factory()->forTenant($tenant)->create([
|
|
'type' => 'inventory.sync',
|
|
'status' => OperationRunStatus::Queued->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'context' => [
|
|
'policy_types' => ['deviceConfiguration'],
|
|
'inventory' => [
|
|
'coverage' => InventoryCoverage::buildPayload([
|
|
'deviceConfiguration' => 'succeeded',
|
|
], []),
|
|
],
|
|
],
|
|
]);
|
|
$baselineRun = OperationRun::factory()->forTenant($tenant)->create([
|
|
'type' => 'baseline.capture',
|
|
'status' => OperationRunStatus::Queued->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'context' => [
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
'baseline_snapshot_id' => (int) $snapshot->getKey(),
|
|
],
|
|
]);
|
|
$backupRun = OperationRun::factory()->forTenant($tenant)->create([
|
|
'type' => 'backup.schedule.execute',
|
|
'status' => OperationRunStatus::Queued->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'context' => [
|
|
'backup_set_id' => (int) $backupSet->getKey(),
|
|
],
|
|
]);
|
|
|
|
$service = app(OperationRunService::class);
|
|
|
|
$service->updateRunWithReconciliation(
|
|
run: $inventoryRun,
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
summaryCounts: ['total' => 1, 'succeeded' => 1, 'failed' => 0],
|
|
failures: [[
|
|
'code' => 'inventory.partial',
|
|
'reason_code' => 'run.adapter_out_of_sync',
|
|
'message' => 'Usable coverage exists, but not every type completed cleanly.',
|
|
]],
|
|
reasonCode: 'run.adapter_out_of_sync',
|
|
reasonMessage: 'Usable coverage exists, but not every type completed cleanly.',
|
|
source: 'adapter_reconciler',
|
|
evidence: ['adapter' => 'inventory_sync'],
|
|
adapter: 'inventory_sync',
|
|
decision: 'reconciled_partially_succeeded',
|
|
);
|
|
|
|
$service->updateRunWithReconciliation(
|
|
run: $baselineRun,
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: OperationRunOutcome::Succeeded->value,
|
|
summaryCounts: ['total' => 1, 'succeeded' => 1, 'failed' => 0],
|
|
failures: [],
|
|
reasonCode: 'run.adapter_out_of_sync',
|
|
reasonMessage: 'A matching baseline snapshot was already available.',
|
|
source: 'adapter_reconciler',
|
|
evidence: ['adapter' => 'baseline_capture'],
|
|
adapter: 'baseline_capture',
|
|
decision: 'reconciled_succeeded',
|
|
related: [
|
|
'type' => 'baseline_snapshot',
|
|
'id' => (int) $snapshot->getKey(),
|
|
'lifecycle_state' => 'complete',
|
|
],
|
|
);
|
|
|
|
$service->updateRunWithReconciliation(
|
|
run: $backupRun,
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: OperationRunOutcome::Succeeded->value,
|
|
summaryCounts: ['total' => 1, 'succeeded' => 1, 'failed' => 0],
|
|
failures: [],
|
|
reasonCode: 'run.adapter_out_of_sync',
|
|
reasonMessage: 'A matching backup set was already available.',
|
|
source: 'adapter_reconciler',
|
|
evidence: ['adapter' => 'backup_schedule_execution'],
|
|
adapter: 'backup_schedule_execution',
|
|
decision: 'reconciled_succeeded',
|
|
related: [
|
|
'type' => 'backup_set',
|
|
'id' => (int) $backupSet->getKey(),
|
|
'status' => 'completed',
|
|
],
|
|
);
|
|
|
|
$this->mock(GraphClientInterface::class, function ($mock): void {
|
|
$mock->shouldReceive('listPolicies')->never();
|
|
$mock->shouldReceive('getPolicy')->never();
|
|
$mock->shouldReceive('getOrganization')->never();
|
|
$mock->shouldReceive('applyPolicy')->never();
|
|
$mock->shouldReceive('getServicePrincipalPermissions')->never();
|
|
$mock->shouldReceive('request')->never();
|
|
});
|
|
|
|
Filament::setTenant(null, true);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(route('admin.operations.index', ['workspace' => $tenant->workspace]))
|
|
->assertSuccessful()
|
|
->assertSee('Inventory sync')
|
|
->assertSee('Baseline capture')
|
|
->assertSee('Backup schedule run');
|
|
|
|
foreach ([$inventoryRun, $baselineRun, $backupRun] as $run) {
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(OperationRunLinks::tenantlessView($run->fresh()))
|
|
->assertSuccessful();
|
|
}
|
|
});
|