create([ 'workspace_id' => $workspaceId, 'is_enabled' => true, ]); $rule = AlertRule::factory()->create([ 'workspace_id' => $workspaceId, 'event_type' => 'baseline_compare_failed', 'minimum_severity' => 'low', 'is_enabled' => true, 'cooldown_seconds' => $cooldownSeconds, ]); $rule->destinations()->attach($destination->getKey(), [ 'workspace_id' => $workspaceId, ]); return [$rule, $destination]; } /** * @return array> */ function invokeBaselineCompareFailedEvents(int $workspaceId, CarbonImmutable $windowStart): array { $job = new EvaluateAlertsJob($workspaceId); $reflection = new ReflectionMethod($job, 'baselineCompareFailedEvents'); /** @var array> $events */ $events = $reflection->invoke($job, $workspaceId, $windowStart); return $events; } it('produces baseline compare failed events for failed and partially succeeded baseline compare runs', function (): void { $now = CarbonImmutable::parse('2026-02-28T12:00:00Z'); CarbonImmutable::setTestNow($now); [$user, $tenant] = createUserWithTenant(role: 'owner'); $workspaceId = (int) session()->get(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY); $windowStart = $now->subHour(); $failedRun = OperationRun::factory()->create([ 'workspace_id' => $workspaceId, 'tenant_id' => (int) $tenant->getKey(), 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Failed->value, 'completed_at' => $now->subMinutes(10), 'failure_summary' => [ ['message' => 'The baseline compare failed.'], ], ]); $partialRun = OperationRun::factory()->create([ 'workspace_id' => $workspaceId, 'tenant_id' => (int) $tenant->getKey(), 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::PartiallySucceeded->value, 'completed_at' => $now->subMinutes(5), 'failure_summary' => [ ['message' => 'The baseline compare partially succeeded.'], ], ]); OperationRun::factory()->create([ 'workspace_id' => $workspaceId, 'tenant_id' => (int) $tenant->getKey(), 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'completed_at' => $now->subMinutes(5), ]); OperationRun::factory()->create([ 'workspace_id' => $workspaceId, 'tenant_id' => (int) $tenant->getKey(), 'type' => 'drift_generate_findings', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Failed->value, 'completed_at' => $now->subMinutes(5), ]); $events = invokeBaselineCompareFailedEvents($workspaceId, $windowStart); expect($events)->toHaveCount(2); $eventsByRunId = collect($events)->keyBy(static fn (array $event): int => (int) $event['metadata']['operation_run_id']); expect($eventsByRunId[$failedRun->getKey()]) ->toMatchArray([ 'event_type' => 'baseline_compare_failed', 'tenant_id' => (int) $tenant->getKey(), 'severity' => 'high', 'fingerprint_key' => 'operation_run:'.$failedRun->getKey(), 'metadata' => [ 'operation_run_id' => (int) $failedRun->getKey(), ], ]); expect($eventsByRunId[$partialRun->getKey()]) ->toMatchArray([ 'event_type' => 'baseline_compare_failed', 'tenant_id' => (int) $tenant->getKey(), 'severity' => 'high', 'fingerprint_key' => 'operation_run:'.$partialRun->getKey(), 'metadata' => [ 'operation_run_id' => (int) $partialRun->getKey(), ], ]); }); it('keeps baseline compare failed events compatible with dispatcher cooldown dedupe', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $workspaceId = (int) session()->get(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY); [$rule, $destination] = createBaselineCompareFailedRuleWithDestination($workspaceId, cooldownSeconds: 3600); $run = OperationRun::factory()->create([ 'workspace_id' => $workspaceId, 'tenant_id' => (int) $tenant->getKey(), 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Failed->value, 'completed_at' => now(), ]); $event = [ 'event_type' => 'baseline_compare_failed', 'tenant_id' => (int) $tenant->getKey(), 'severity' => 'high', 'fingerprint_key' => 'operation_run:'.$run->getKey(), 'title' => 'Baseline compare failed', 'body' => 'The baseline compare failed.', 'metadata' => [ 'operation_run_id' => (int) $run->getKey(), ], ]; $workspace = Workspace::query()->findOrFail($workspaceId); $dispatchService = app(AlertDispatchService::class); expect($dispatchService->dispatchEvent($workspace, $event))->toBe(1); expect($dispatchService->dispatchEvent($workspace, $event))->toBe(1); $deliveries = AlertDelivery::query() ->where('workspace_id', $workspaceId) ->where('alert_rule_id', (int) $rule->getKey()) ->where('alert_destination_id', (int) $destination->getKey()) ->where('event_type', 'baseline_compare_failed') ->orderBy('id') ->get(); expect($deliveries)->toHaveCount(2); expect($deliveries[0]->status)->toBe(AlertDelivery::STATUS_QUEUED); expect($deliveries[1]->status)->toBe(AlertDelivery::STATUS_SUPPRESSED); });