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
47 lines
1.9 KiB
PHP
47 lines
1.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Support\Verification\VerificationReportFingerprint;
|
|
|
|
it('computes the same fingerprint regardless of check ordering', function (): void {
|
|
$checksA = [
|
|
['key' => 'b', 'status' => 'fail', 'blocking' => false, 'reason_code' => 'missing_configuration', 'severity' => 'high'],
|
|
['key' => 'a', 'status' => 'pass', 'blocking' => false, 'reason_code' => 'ok', 'severity' => 'info'],
|
|
];
|
|
|
|
$checksB = [
|
|
['key' => 'a', 'status' => 'pass', 'blocking' => false, 'reason_code' => 'ok', 'severity' => 'info'],
|
|
['key' => 'b', 'status' => 'fail', 'blocking' => false, 'reason_code' => 'missing_configuration', 'severity' => 'high'],
|
|
];
|
|
|
|
expect(VerificationReportFingerprint::forChecks($checksA))
|
|
->toBe(VerificationReportFingerprint::forChecks($checksB));
|
|
});
|
|
|
|
it('treats missing severity as empty string for fingerprint determinism', function (): void {
|
|
$withMissingSeverity = [
|
|
['key' => 'a', 'status' => 'fail', 'blocking' => true, 'reason_code' => 'permission_denied'],
|
|
];
|
|
|
|
$withEmptySeverity = [
|
|
['key' => 'a', 'status' => 'fail', 'blocking' => true, 'reason_code' => 'permission_denied', 'severity' => ''],
|
|
];
|
|
|
|
expect(VerificationReportFingerprint::forChecks($withMissingSeverity))
|
|
->toBe(VerificationReportFingerprint::forChecks($withEmptySeverity));
|
|
});
|
|
|
|
it('treats severity-only changes as different fingerprints (missing != info)', function (): void {
|
|
$missingSeverity = [
|
|
['key' => 'a', 'status' => 'fail', 'blocking' => false, 'reason_code' => 'unknown_error'],
|
|
];
|
|
|
|
$infoSeverity = [
|
|
['key' => 'a', 'status' => 'fail', 'blocking' => false, 'reason_code' => 'unknown_error', 'severity' => 'info'],
|
|
];
|
|
|
|
expect(VerificationReportFingerprint::forChecks($missingSeverity))
|
|
->not->toBe(VerificationReportFingerprint::forChecks($infoSeverity));
|
|
});
|