TenantAtlas/tests/Feature/Baselines/BaselineOperabilityAutoCloseTest.php
2026-03-01 03:23:39 +01:00

189 lines
6.1 KiB
PHP

<?php
use App\Jobs\CompareBaselineToTenantJob;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineSnapshotItem;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Models\User;
use App\Models\WorkspaceSetting;
use App\Services\Baselines\BaselineAutoCloseService;
use App\Services\Baselines\BaselineSnapshotIdentity;
use App\Services\Drift\DriftHasher;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
/**
* @return array{0: User, 1: Tenant, 2: BaselineProfile, 3: BaselineSnapshot}
*/
function createBaselineOperabilityFixture(): array
{
[$user, $tenant] = createUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => $tenant->workspace_id,
'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
]);
$snapshot = BaselineSnapshot::factory()->create([
'workspace_id' => $tenant->workspace_id,
'baseline_profile_id' => $profile->getKey(),
]);
$profile->update(['active_snapshot_id' => $snapshot->getKey()]);
return [$user, $tenant, $profile, $snapshot];
}
function runBaselineCompareForSnapshot(
User $user,
Tenant $tenant,
BaselineProfile $profile,
BaselineSnapshot $snapshot,
): OperationRun {
$operationRuns = app(OperationRunService::class);
$run = $operationRuns->ensureRunWithIdentity(
tenant: $tenant,
type: OperationRunType::BaselineCompare->value,
identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
context: [
'baseline_profile_id' => (int) $profile->getKey(),
'baseline_snapshot_id' => (int) $snapshot->getKey(),
'effective_scope' => ['policy_types' => ['deviceConfiguration']],
],
initiator: $user,
);
$job = new CompareBaselineToTenantJob($run);
$job->handle(
app(DriftHasher::class),
app(BaselineSnapshotIdentity::class),
app(AuditLogger::class),
$operationRuns,
);
return $run->fresh();
}
it('resolves stale baseline findings after a fully successful compare', function (): void {
[$user, $tenant, $profile, $firstSnapshot] = createBaselineOperabilityFixture();
BaselineSnapshotItem::factory()->create([
'baseline_snapshot_id' => $firstSnapshot->getKey(),
'subject_type' => 'policy',
'subject_external_id' => 'stale-policy',
'policy_type' => 'deviceConfiguration',
'baseline_hash' => hash('sha256', 'baseline-content'),
'meta_jsonb' => ['display_name' => 'Stale Policy'],
]);
$firstRun = runBaselineCompareForSnapshot($user, $tenant, $profile, $firstSnapshot);
$finding = Finding::query()
->where('tenant_id', (int) $tenant->getKey())
->where('source', 'baseline.compare')
->first();
expect($finding)->not->toBeNull();
expect($finding?->status)->toBe(Finding::STATUS_NEW);
$secondSnapshot = BaselineSnapshot::factory()->create([
'workspace_id' => $tenant->workspace_id,
'baseline_profile_id' => $profile->getKey(),
]);
$profile->update(['active_snapshot_id' => $secondSnapshot->getKey()]);
$firstRun->update(['completed_at' => now()->subMinute()]);
$secondRun = runBaselineCompareForSnapshot($user, $tenant, $profile, $secondSnapshot);
$finding->refresh();
expect($finding->status)->toBe(Finding::STATUS_RESOLVED);
expect($finding->resolved_reason)->toBe('no_longer_drifting');
expect($finding->resolved_at)->not->toBeNull();
expect($finding->current_operation_run_id)->toBe((int) $secondRun->getKey());
});
dataset('baseline auto close safety gates', [
'safe and enabled' => [
OperationRunOutcome::Succeeded->value,
['total' => 2, 'processed' => 2, 'failed' => 0],
null,
true,
],
'disabled by workspace setting' => [
OperationRunOutcome::Succeeded->value,
['total' => 2, 'processed' => 2, 'failed' => 0],
false,
false,
],
'partially succeeded outcome' => [
OperationRunOutcome::PartiallySucceeded->value,
['total' => 2, 'processed' => 2, 'failed' => 0],
null,
false,
],
'failed outcome' => [
OperationRunOutcome::Failed->value,
['total' => 2, 'processed' => 2, 'failed' => 0],
null,
false,
],
'incomplete processed count' => [
OperationRunOutcome::Succeeded->value,
['total' => 2, 'processed' => 1, 'failed' => 0],
null,
false,
],
'failed work recorded' => [
OperationRunOutcome::Succeeded->value,
['total' => 2, 'processed' => 2, 'failed' => 1],
null,
false,
],
'missing counters' => [
OperationRunOutcome::Succeeded->value,
['processed' => 2, 'failed' => 0],
null,
false,
],
]);
it('gates auto close on outcome completion counts and workspace setting', function (
string $outcome,
array $summaryCounts,
?bool $settingValue,
bool $expected,
): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
if ($settingValue !== null) {
WorkspaceSetting::query()->create([
'workspace_id' => (int) $tenant->workspace_id,
'domain' => 'baseline',
'key' => 'auto_close_enabled',
'value' => $settingValue,
'updated_by_user_id' => (int) $user->getKey(),
]);
}
$run = OperationRun::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'tenant_id' => (int) $tenant->getKey(),
'user_id' => (int) $user->getKey(),
'type' => OperationRunType::BaselineCompare->value,
'status' => OperationRunStatus::Completed->value,
'outcome' => $outcome,
'summary_counts' => $summaryCounts,
'completed_at' => now(),
]);
expect(app(BaselineAutoCloseService::class)->shouldAutoClose($tenant, $run))->toBe($expected);
})->with('baseline auto close safety gates');