1341 lines
52 KiB
Python
1341 lines
52 KiB
Python
from __future__ import annotations
|
|
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from src.api.v1.platform.events import ChannelEventRecurrenceResponse
|
|
from src.services import channel_event_dossier_service
|
|
from src.services.channel_event_dossier_service import (
|
|
RecurrenceWorkItemNotFoundError,
|
|
build_dossier_coverage,
|
|
build_dossier_event,
|
|
build_dossier_recurrence,
|
|
build_recurrence_work_item_dry_run,
|
|
build_recurrence_work_item_handoff,
|
|
build_recurrence_work_item_preview,
|
|
build_source_correlation_apply,
|
|
build_source_correlation_review_decision,
|
|
fetch_channel_event_dossier,
|
|
fetch_channel_event_dossier_coverage,
|
|
fetch_channel_event_dossier_recurrence,
|
|
)
|
|
|
|
|
|
def test_build_dossier_event_summarizes_source_envelope() -> None:
|
|
event = build_dossier_event(
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "h" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue redacted",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_url": "https://sentry.example.invalid/issues/issue-1",
|
|
"content_sha256": "a" * 64,
|
|
"content_length": 42,
|
|
"source_refs": {
|
|
"event_ids": ["issue-1"],
|
|
"sentry_issue_ids": ["issue-1", "sentry:received:issue-1"],
|
|
"fingerprints": ["sentry-issue-1"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "Sentry Issue",
|
|
"severity": "error",
|
|
"namespace": "sentry",
|
|
"target_resource": "frontend",
|
|
"fingerprint": "sentry-issue-1",
|
|
},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:46:00",
|
|
}
|
|
)
|
|
|
|
assert event["provider"] == "sentry"
|
|
assert event["stage"] == "received"
|
|
assert event["alertname"] == "Sentry Issue"
|
|
assert event["severity"] == "error"
|
|
assert event["source_ref_count"] == 4
|
|
assert event["has_redacted_content"] is True
|
|
assert event["content_sha256"] == "a" * 64
|
|
|
|
|
|
def test_build_dossier_coverage_summarizes_recent_sources() -> None:
|
|
coverage = build_dossier_coverage(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "h" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue redacted",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_refs": {
|
|
"sentry_issue_ids": ["issue-1"],
|
|
"alert_ids": ["sentry:received:issue-1"],
|
|
"fingerprints": ["fingerprint-1"],
|
|
},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:46:00",
|
|
},
|
|
{
|
|
"event_id": "event-2",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "signoz:received:alert-1",
|
|
"content_hash": "i" * 64,
|
|
"content_preview": "SignOz alert",
|
|
"content_redacted": None,
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "signoz",
|
|
"stage": "received",
|
|
"source_refs": {
|
|
"signoz_alerts": ["alert-1"],
|
|
"alert_ids": ["signoz:received:alert-1"],
|
|
},
|
|
},
|
|
"is_duplicate": True,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:45:00",
|
|
},
|
|
{
|
|
"event_id": "event-3",
|
|
"project_id": "awoooi",
|
|
"channel_type": "telegram",
|
|
"provider_event_id": "telegram:callback:1",
|
|
"content_hash": None,
|
|
"content_preview": "Callback",
|
|
"content_redacted": None,
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:44:00",
|
|
},
|
|
],
|
|
project_id="awoooi",
|
|
limit=100,
|
|
)
|
|
|
|
assert coverage["project_id"] == "awoooi"
|
|
assert coverage["summary"]["source_count"] == 3
|
|
assert coverage["summary"]["source_envelope_total"] == 2
|
|
assert coverage["summary"]["missing_source_envelope_total"] == 1
|
|
assert coverage["summary"]["with_source_refs_total"] == 2
|
|
assert coverage["summary"]["missing_source_refs_total"] == 1
|
|
assert coverage["summary"]["duplicate_total"] == 1
|
|
assert coverage["summary"]["redacted_total"] == 1
|
|
assert coverage["summary"]["sentry_ref_total"] == 1
|
|
assert coverage["summary"]["signoz_ref_total"] == 1
|
|
assert coverage["summary"]["alert_ref_total"] == 2
|
|
assert coverage["providers"][0]["provider"] == "sentry"
|
|
|
|
|
|
def test_build_dossier_recurrence_groups_events_and_run_state() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-2",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "alertmanager:received:2",
|
|
"content_hash": "b" * 64,
|
|
"content_preview": "Host disk pressure",
|
|
"content_redacted": "Host disk pressure",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "alertmanager",
|
|
"source_refs": {
|
|
"alert_ids": ["alert-2"],
|
|
"incident_ids": ["INC-20260513-ABCD"],
|
|
"fingerprints": ["fp-host-disk"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "HostDiskUsageHigh",
|
|
"severity": "warning",
|
|
"namespace": "node",
|
|
"target_resource": "host-110",
|
|
"fingerprint": "fp-host-disk",
|
|
},
|
|
},
|
|
"is_duplicate": True,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:47:00",
|
|
"run_id": UUID("11111111-1111-4111-8111-111111111111"),
|
|
"run_state": "waiting_approval",
|
|
"run_agent_id": "openclaw",
|
|
},
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "alertmanager:received:1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Host disk pressure",
|
|
"content_redacted": "Host disk pressure",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "alertmanager",
|
|
"source_refs": {
|
|
"alert_ids": ["alert-1"],
|
|
"incident_ids": ["INC-20260513-ABCD"],
|
|
"fingerprints": ["fp-host-disk"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "HostDiskUsageHigh",
|
|
"severity": "warning",
|
|
"namespace": "node",
|
|
"target_resource": "host-110",
|
|
"fingerprint": "fp-host-disk",
|
|
},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:46:00",
|
|
"run_id": UUID("22222222-2222-4222-8222-222222222222"),
|
|
"run_state": "completed",
|
|
"run_agent_id": "openclaw",
|
|
},
|
|
{
|
|
"event_id": "event-3",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "c" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"source_refs": {"sentry_issue_ids": ["issue-1"]},
|
|
"log_correlation": {"alertname": "Sentry Issue"},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-13T13:45:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
},
|
|
],
|
|
project_id="awoooi",
|
|
limit=100,
|
|
repair_summaries_by_incident={
|
|
"INC-20260513-ABCD": {
|
|
"schema_version": "awooop_recurrence_repair_summary_v1",
|
|
"incident_id": "INC-20260513-ABCD",
|
|
"latest_auto_repair_id": "repair-1",
|
|
"latest_playbook_id": "playbook-1",
|
|
"latest_playbook_name": "Restart workload",
|
|
"latest_success": False,
|
|
"latest_error_message_preview": "verifier failed",
|
|
"latest_triggered_by": "auto_repair",
|
|
"latest_risk_level": "low",
|
|
"latest_execution_time_ms": 1200,
|
|
"latest_auto_repair_at": "2026-05-13T13:48:00",
|
|
"latest_verification_result": "failed",
|
|
"latest_verification_at": "2026-05-13T13:49:00",
|
|
"auto_repair_total": 1,
|
|
"success_total": 0,
|
|
"failed_total": 1,
|
|
},
|
|
},
|
|
)
|
|
|
|
assert recurrence["summary"]["source_event_total"] == 3
|
|
assert recurrence["summary"]["recurrence_group_total"] == 2
|
|
assert recurrence["summary"]["recurrent_group_total"] == 1
|
|
assert recurrence["summary"]["duplicate_event_total"] == 1
|
|
assert recurrence["summary"]["linked_run_total"] == 2
|
|
assert recurrence["summary"]["unlinked_event_total"] == 1
|
|
assert recurrence["summary"]["auto_repair_linked_total"] == 1
|
|
assert recurrence["summary"]["open_work_item_group_total"] == 2
|
|
assert recurrence["summary"]["verified_repair_group_total"] == 0
|
|
assert recurrence["summary"]["automation_gap_group_total"] == 0
|
|
assert recurrence["summary"]["failed_repair_group_total"] == 1
|
|
assert recurrence["summary"]["source_correlation_review_group_total"] == 1
|
|
assert recurrence["summary"]["source_correlation_decision_recorded_group_total"] == 0
|
|
|
|
host_group = recurrence["items"][0]
|
|
assert host_group["recurrence_key"] == "fingerprint:fp-host-disk"
|
|
assert host_group["occurrence_total"] == 2
|
|
assert host_group["duplicate_total"] == 1
|
|
assert host_group["linked_run_total"] == 2
|
|
assert host_group["latest_run_state"] == "waiting_approval"
|
|
assert host_group["latest_incident_id"] == "INC-20260513-ABCD"
|
|
assert host_group["incident_ids"] == ["INC-20260513-ABCD"]
|
|
assert host_group["latest_stage"] == "received"
|
|
assert host_group["stage_counts"] == {"received": 2}
|
|
assert host_group["run_state_counts"] == {"waiting_approval": 1, "completed": 1}
|
|
assert host_group["alert_ref_total"] == 2
|
|
assert host_group["repair_summary"]["status"] == "auto_repair_failed"
|
|
assert host_group["repair_summary"]["latest_auto_repair_id"] == "repair-1"
|
|
assert host_group["work_item"] == {
|
|
"schema_version": "awooop_recurrence_work_item_link_v1",
|
|
"work_item_id": "verification:INC-20260513-ABCD:repair-1",
|
|
"incident_id": "INC-20260513-ABCD",
|
|
"matched_incident_id": None,
|
|
"auto_repair_id": "repair-1",
|
|
"status": "open",
|
|
"kind": "verification",
|
|
"next_step": "triage_failed_repair",
|
|
"reason": "auto_repair_failed",
|
|
"needs_human": True,
|
|
}
|
|
|
|
source_group = recurrence["items"][1]
|
|
assert source_group["provider"] == "sentry"
|
|
assert source_group["latest_stage"] == "received"
|
|
assert source_group["stage_counts"] == {"received": 1}
|
|
assert source_group["repair_summary"]["status"] == "source_correlation_review"
|
|
assert source_group["work_item"] == {
|
|
"schema_version": "awooop_recurrence_work_item_link_v1",
|
|
"work_item_id": "source-evidence:sentry:received:issue-1",
|
|
"incident_id": None,
|
|
"matched_incident_id": None,
|
|
"auto_repair_id": None,
|
|
"status": "open",
|
|
"kind": "source_correlation_review",
|
|
"next_step": "review_provider_source_match",
|
|
"reason": "provider_native_evidence_unlinked",
|
|
"needs_human": True,
|
|
}
|
|
|
|
|
|
def test_build_recurrence_work_item_preview_allows_source_correlation_review() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "signoz:upstream_canary:canary-1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "SignOz upstream canary",
|
|
"content_redacted": "SignOz upstream canary",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "signoz",
|
|
"stage": "upstream_canary",
|
|
"source_refs": {
|
|
"signoz_alerts": ["alert-1"],
|
|
"alert_ids": ["signoz:upstream_canary:canary-1"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "Source Provider Upstream Canary",
|
|
"severity": "info",
|
|
"namespace": "observability",
|
|
"target_resource": "signoz",
|
|
"fingerprint": "fp-signoz-canary",
|
|
},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-20T13:01:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
item = recurrence["items"][0]
|
|
work_item_id = "source-evidence:signoz:upstream_canary:canary-1"
|
|
assert recurrence["summary"]["source_correlation_review_group_total"] == 1
|
|
assert recurrence["summary"]["source_correlation_decision_recorded_group_total"] == 0
|
|
assert recurrence["summary"]["open_work_item_group_total"] == 1
|
|
assert item["latest_stage"] == "upstream_canary"
|
|
assert item["repair_summary"]["status"] == "source_correlation_review"
|
|
assert item["work_item"]["work_item_id"] == work_item_id
|
|
assert item["work_item"]["kind"] == "source_correlation_review"
|
|
assert item["work_item"]["next_step"] == "review_provider_source_match"
|
|
|
|
preview = build_recurrence_work_item_preview(
|
|
recurrence,
|
|
work_item_id=work_item_id,
|
|
)
|
|
|
|
assert preview["mode"] == "observe"
|
|
assert preview["allowed"] is True
|
|
assert preview["plan"]["target_action"] == "review_provider_source_match"
|
|
assert preview["plan"]["target"]["latest_stage"] == "upstream_canary"
|
|
|
|
dry_run = build_recurrence_work_item_dry_run(
|
|
recurrence,
|
|
work_item_id=work_item_id,
|
|
)
|
|
|
|
assert dry_run["verification_result_preview"] == "observe_only"
|
|
assert dry_run["ticket_preview"]["would_create"] is False
|
|
assert "Source evidence review" in dry_run["ticket_preview"]["title"]
|
|
assert dry_run["current_state_summary"]["latest_stage"] == "upstream_canary"
|
|
assert dry_run["current_state_summary"]["latest_provider_event_id"] == (
|
|
"signoz:upstream_canary:canary-1"
|
|
)
|
|
|
|
|
|
def test_source_correlation_review_decision_records_accepted_audit_contract() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_refs": {
|
|
"sentry_issue_ids": ["issue-1"],
|
|
"alert_ids": ["sentry:received:issue-1"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "Sentry Issue",
|
|
"severity": "error",
|
|
"namespace": "awoooi-prod",
|
|
"target_resource": "web",
|
|
"fingerprint": "fp-sentry-issue-1",
|
|
},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-20T13:10:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
decision = build_source_correlation_review_decision(
|
|
recurrence,
|
|
work_item_id="source-evidence:sentry:received:issue-1",
|
|
decision="accepted",
|
|
target_incident_id="INC-20260520-ABC123",
|
|
reviewer_id="operator_console",
|
|
operator_note="matches current frontend incident",
|
|
)
|
|
|
|
assert decision["schema_version"] == "awooop_source_correlation_review_decision_v1"
|
|
assert decision["allowed"] is True
|
|
assert decision["executed"] is True
|
|
assert decision["decision"] == "accepted"
|
|
assert decision["review_status"] == "accepted"
|
|
assert decision["target_incident_id"] == "INC-20260520-ABC123"
|
|
assert decision["writes_incident_state"] is False
|
|
assert decision["writes_source_event"] is False
|
|
assert decision["writes_auto_repair_result"] is False
|
|
assert decision["writes_ticket"] is False
|
|
assert decision["creates_external_ticket"] is False
|
|
assert decision["plan"]["step"] == "record_source_correlation_review_decision"
|
|
assert decision["plan"]["writes"] == ["alert_operation_log", "timeline_events"]
|
|
assert decision["next_step"] == "verify_source_match_in_status_chain"
|
|
|
|
|
|
def test_build_dossier_recurrence_closes_source_review_after_decision() -> None:
|
|
decision = {
|
|
"schema_version": "awooop_source_correlation_review_decision_v1",
|
|
"review_id": "review-1",
|
|
"work_item_id": "source-evidence:sentry:received:issue-1",
|
|
"decision": "accepted",
|
|
"review_status": "accepted",
|
|
"target_incident_id": "INC-20260520-ABC123",
|
|
"reviewer_id": "operator_console",
|
|
"latest_provider_event_id": "sentry:received:issue-1",
|
|
"recorded_at": "2026-05-20T13:11:00",
|
|
}
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_refs": {"sentry_issue_ids": ["issue-1"]},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-20T13:10:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
source_review_decisions_by_work_item={
|
|
"source-evidence:sentry:received:issue-1": decision,
|
|
},
|
|
)
|
|
|
|
item = recurrence["items"][0]
|
|
assert recurrence["summary"]["open_work_item_group_total"] == 0
|
|
assert recurrence["summary"]["source_correlation_review_group_total"] == 0
|
|
assert recurrence["summary"]["source_correlation_decision_recorded_group_total"] == 1
|
|
assert item["source_correlation_review"] == decision
|
|
assert item["repair_summary"]["status"] == "source_correlation_accepted"
|
|
assert item["work_item"]["status"] == "closed"
|
|
assert item["work_item"]["matched_incident_id"] == "INC-20260520-ABC123"
|
|
assert item["work_item"]["next_step"] == "verify_source_match_in_status_chain"
|
|
|
|
|
|
def test_source_correlation_apply_requires_accepted_review_and_plans_source_link() -> None:
|
|
accepted_decision = {
|
|
"schema_version": "awooop_source_correlation_review_decision_v1",
|
|
"review_id": "review-1",
|
|
"work_item_id": "source-evidence:sentry:received:issue-1",
|
|
"decision": "accepted",
|
|
"review_status": "accepted",
|
|
"target_incident_id": "INC-20260520-ABC123",
|
|
"reviewer_id": "operator_console",
|
|
"latest_provider_event_id": "sentry:received:issue-1",
|
|
"recorded_at": "2026-05-20T13:11:00",
|
|
}
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_refs": {
|
|
"sentry_issue_ids": ["issue-1"],
|
|
"alert_ids": ["sentry:received:issue-1"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "Sentry Issue",
|
|
"severity": "error",
|
|
"namespace": "awoooi-prod",
|
|
"target_resource": "web",
|
|
"fingerprint": "fp-sentry-issue-1",
|
|
},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-20T13:10:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
source_review_decisions_by_work_item={
|
|
"source-evidence:sentry:received:issue-1": accepted_decision,
|
|
},
|
|
)
|
|
|
|
apply_payload = build_source_correlation_apply(
|
|
recurrence,
|
|
work_item_id="source-evidence:sentry:received:issue-1",
|
|
)
|
|
|
|
assert apply_payload["schema_version"] == "awooop_source_correlation_apply_v1"
|
|
assert apply_payload["allowed"] is True
|
|
assert apply_payload["apply_status"] == "ready_to_apply"
|
|
assert apply_payload["target_incident_id"] == "INC-20260520-ABC123"
|
|
assert apply_payload["writes_source_event"] is True
|
|
assert apply_payload["writes_incident_state"] is False
|
|
assert apply_payload["writes_auto_repair_result"] is False
|
|
assert apply_payload["writes_ticket"] is False
|
|
assert apply_payload["source_event_stage"] == "source_correlation_linked"
|
|
assert apply_payload["source_event_provider_event_id"] == (
|
|
"sentry:source_correlation_linked:issue-1"
|
|
)
|
|
assert apply_payload["plan"]["writes"] == [
|
|
"awooop_conversation_event",
|
|
"timeline_events",
|
|
"alert_operation_log",
|
|
]
|
|
assert apply_payload["next_step"] == "verify_source_link_in_status_chain"
|
|
|
|
|
|
def test_source_correlation_apply_blocks_without_accepted_review() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_refs": {"sentry_issue_ids": ["issue-1"]},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-20T13:10:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
apply_payload = build_source_correlation_apply(
|
|
recurrence,
|
|
work_item_id="source-evidence:sentry:received:issue-1",
|
|
)
|
|
|
|
assert apply_payload["allowed"] is False
|
|
assert apply_payload["executed"] is False
|
|
assert apply_payload["writes_source_event"] is False
|
|
assert apply_payload["apply_status"] == "blocked"
|
|
assert apply_payload["plan"]["writes"] == []
|
|
assert apply_payload["next_step"] == "fix_preflight_checks"
|
|
|
|
|
|
def test_build_dossier_recurrence_surfaces_source_apply_history() -> None:
|
|
apply_record = {
|
|
"schema_version": "awooop_source_correlation_apply_v1",
|
|
"apply_id": "apply-1",
|
|
"work_item_id": "source-evidence:sentry:received:issue-1",
|
|
"apply_status": "applied",
|
|
"target_incident_id": "INC-20260520-ABC123",
|
|
"review_id": "review-1",
|
|
"source_event_id": "source-event-1",
|
|
"source_event_provider_event_id": (
|
|
"sentry:source_correlation_linked:issue-1"
|
|
),
|
|
"recorded_at": "2026-05-20T13:12:00",
|
|
}
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "sentry:received:issue-1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Sentry issue",
|
|
"content_redacted": "Sentry issue",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "sentry",
|
|
"stage": "received",
|
|
"source_refs": {"sentry_issue_ids": ["issue-1"]},
|
|
},
|
|
"is_duplicate": False,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-20T13:10:00",
|
|
"run_id": None,
|
|
"run_state": None,
|
|
"run_agent_id": None,
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
source_applies_by_work_item={
|
|
"source-evidence:sentry:received:issue-1": apply_record,
|
|
},
|
|
)
|
|
|
|
item = recurrence["items"][0]
|
|
assert recurrence["summary"]["source_correlation_applied_group_total"] == 1
|
|
assert item["source_correlation_apply"] == apply_record
|
|
assert item["work_item"]["matched_incident_id"] == "INC-20260520-ABC123"
|
|
assert item["work_item"]["next_step"] == "verify_source_link_in_status_chain"
|
|
|
|
|
|
def test_build_dossier_recurrence_opens_work_item_for_completed_run_without_repair() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "alertmanager:received:1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Docker container unhealthy",
|
|
"content_redacted": "Docker container unhealthy",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "alertmanager",
|
|
"source_refs": {
|
|
"alert_ids": ["alert-1"],
|
|
"incident_ids": ["INC-20260517-F25B4A"],
|
|
"fingerprints": ["fp-container-unhealthy"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "DockerContainerUnhealthy",
|
|
"severity": "warning",
|
|
"namespace": "momo",
|
|
"target_resource": "bitan-pharmacy-bitan-1",
|
|
"fingerprint": "fp-container-unhealthy",
|
|
},
|
|
},
|
|
"is_duplicate": True,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-17T23:47:00",
|
|
"run_id": UUID("33333333-3333-4333-8333-333333333333"),
|
|
"run_state": "completed",
|
|
"run_agent_id": "openclaw",
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
item = recurrence["items"][0]
|
|
assert recurrence["summary"]["open_work_item_group_total"] == 1
|
|
assert recurrence["summary"]["automation_gap_group_total"] == 1
|
|
assert item["repair_summary"]["status"] == "run_completed_no_repair"
|
|
assert item["work_item"] == {
|
|
"schema_version": "awooop_recurrence_work_item_link_v1",
|
|
"work_item_id": "incident:INC-20260517-F25B4A",
|
|
"incident_id": "INC-20260517-F25B4A",
|
|
"matched_incident_id": None,
|
|
"auto_repair_id": None,
|
|
"status": "open",
|
|
"kind": "automation_gap",
|
|
"next_step": "create_repair_ticket",
|
|
"reason": "completed_run_without_auto_repair",
|
|
"needs_human": True,
|
|
}
|
|
|
|
|
|
def test_build_recurrence_work_item_preview_selects_ticket_mode() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "alertmanager:received:1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Docker container unhealthy",
|
|
"content_redacted": "Docker container unhealthy",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "alertmanager",
|
|
"source_refs": {
|
|
"alert_ids": ["alert-1"],
|
|
"incident_ids": ["INC-20260517-F25B4A"],
|
|
"fingerprints": ["fp-container-unhealthy"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "DockerContainerUnhealthy",
|
|
"severity": "warning",
|
|
"namespace": "momo",
|
|
"target_resource": "bitan-pharmacy-bitan-1",
|
|
"fingerprint": "fp-container-unhealthy",
|
|
},
|
|
},
|
|
"is_duplicate": True,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-17T23:47:00",
|
|
"run_id": UUID("33333333-3333-4333-8333-333333333333"),
|
|
"run_state": "completed",
|
|
"run_agent_id": "openclaw",
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
preview = build_recurrence_work_item_preview(
|
|
recurrence,
|
|
work_item_id="incident:INC-20260517-F25B4A",
|
|
)
|
|
|
|
assert preview["schema_version"] == "awooop_recurrence_work_item_preview_v1"
|
|
assert preview["source"] == "channel_event_dossier.recurrence"
|
|
assert preview["mode"] == "ticket"
|
|
assert preview["allowed"] is True
|
|
assert preview["writes_incident_state"] is False
|
|
assert preview["writes_auto_repair_result"] is False
|
|
assert preview["writes_ticket"] is False
|
|
assert preview["plan"]["step"] == "prepare_repair_ticket_preview"
|
|
assert preview["plan"]["target"]["alertname"] == "DockerContainerUnhealthy"
|
|
|
|
|
|
def test_build_recurrence_work_item_dry_run_returns_ticket_preview_without_writes() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "alertmanager:received:1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Docker container unhealthy",
|
|
"content_redacted": "Docker container unhealthy",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "alertmanager",
|
|
"source_refs": {
|
|
"alert_ids": ["alert-1"],
|
|
"incident_ids": ["INC-20260517-F25B4A"],
|
|
"fingerprints": ["fp-container-unhealthy"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "DockerContainerUnhealthy",
|
|
"severity": "warning",
|
|
"namespace": "momo",
|
|
"target_resource": "bitan-pharmacy-bitan-1",
|
|
"fingerprint": "fp-container-unhealthy",
|
|
},
|
|
},
|
|
"is_duplicate": True,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-17T23:47:00",
|
|
"run_id": UUID("33333333-3333-4333-8333-333333333333"),
|
|
"run_state": "completed",
|
|
"run_agent_id": "openclaw",
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
dry_run = build_recurrence_work_item_dry_run(
|
|
recurrence,
|
|
work_item_id="incident:INC-20260517-F25B4A",
|
|
)
|
|
|
|
assert dry_run["schema_version"] == "awooop_recurrence_work_item_dry_run_v1"
|
|
assert dry_run["mode"] == "ticket"
|
|
assert dry_run["allowed"] is True
|
|
assert dry_run["executed"] is True
|
|
assert dry_run["writes_incident_state"] is False
|
|
assert dry_run["writes_auto_repair_result"] is False
|
|
assert dry_run["writes_ticket"] is False
|
|
assert dry_run["verification_result_preview"] == "ticket_preview_ready"
|
|
assert dry_run["ticket_preview"]["would_create"] is False
|
|
assert "DockerContainerUnhealthy" in dry_run["ticket_preview"]["title"]
|
|
assert dry_run["current_state_summary"]["repair_status"] == "run_completed_no_repair"
|
|
assert dry_run["read_model_route"]["required_scope"] == "read"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_source_correlation_dry_run_history_records_without_incident(
|
|
monkeypatch,
|
|
) -> None:
|
|
appended: list[dict[str, object]] = []
|
|
|
|
class FakeRecord:
|
|
id = "alert-op-1"
|
|
|
|
class FakeRepo:
|
|
async def append(self, event_type: str, **kwargs): # noqa: ANN001
|
|
appended.append({"event_type": event_type, **kwargs})
|
|
return FakeRecord()
|
|
|
|
from src.repositories import alert_operation_log_repository
|
|
|
|
monkeypatch.setattr(
|
|
alert_operation_log_repository,
|
|
"get_alert_operation_log_repository",
|
|
lambda: FakeRepo(),
|
|
)
|
|
|
|
history = (
|
|
await channel_event_dossier_service._record_recurrence_work_item_dry_run_history(
|
|
{
|
|
"source": "channel_event_dossier.recurrence",
|
|
"project_id": "awoooi",
|
|
"work_item_id": (
|
|
"source-evidence:sentry:upstream_canary:"
|
|
"awoooi-canary-codex-t115-production"
|
|
),
|
|
"incident_id": None,
|
|
"auto_repair_id": None,
|
|
"mode": "observe",
|
|
"requested_mode": "auto",
|
|
"allowed": True,
|
|
"executed": True,
|
|
"safety_level": "read_only",
|
|
"writes_incident_state": False,
|
|
"writes_auto_repair_result": False,
|
|
"writes_ticket": False,
|
|
"verification_result_preview": "observe_only",
|
|
"current_state_summary": {
|
|
"work_item_kind": "source_correlation_review",
|
|
"work_item_next_step": "review_provider_source_match",
|
|
"repair_status": "source_correlation_review",
|
|
"latest_stage": "upstream_canary",
|
|
"latest_provider_event_id": (
|
|
"sentry:upstream_canary:"
|
|
"awoooi-canary-codex-t115-production"
|
|
),
|
|
"latest_run_id": UUID("11111111-1111-4111-8111-111111111111"),
|
|
},
|
|
"ticket_preview": {"would_create": False},
|
|
"read_model_route": {
|
|
"agent_id": "awooop_recurrence_coordinator",
|
|
"tool_name": "channel_event_dossier.recurrence",
|
|
"required_scope": "read",
|
|
},
|
|
"checks": [],
|
|
"next_step": "review_provider_source_match",
|
|
}
|
|
)
|
|
)
|
|
|
|
assert history == {
|
|
"recorded": True,
|
|
"alert_operation_id": "alert-op-1",
|
|
"timeline_event_id": None,
|
|
"timeline_reason": "source_review_not_incident_scoped",
|
|
}
|
|
assert appended[0]["event_type"] == "PRE_FLIGHT_PASSED"
|
|
assert appended[0]["incident_id"] is None
|
|
assert appended[0]["actor"] == "awooop_recurrence_work_item_service"
|
|
assert appended[0]["context"]["work_item_id"].startswith("source-evidence:")
|
|
assert (
|
|
appended[0]["context"]["current_state_summary"]["latest_run_id"]
|
|
== "11111111-1111-4111-8111-111111111111"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_source_correlation_apply_history_appends_source_event_and_audit(
|
|
monkeypatch,
|
|
) -> None:
|
|
source_calls: list[dict[str, object]] = []
|
|
timeline_calls: list[dict[str, object]] = []
|
|
alert_calls: list[dict[str, object]] = []
|
|
|
|
async def fake_record_external_alert_event(**kwargs): # noqa: ANN003
|
|
source_calls.append(kwargs)
|
|
return UUID("99999999-9999-4999-8999-999999999999")
|
|
|
|
class FakeTimeline:
|
|
async def add_event(self, **kwargs): # noqa: ANN001
|
|
timeline_calls.append(kwargs)
|
|
return {"id": "timeline-1"}
|
|
|
|
class FakeRecord:
|
|
id = "alert-op-1"
|
|
|
|
class FakeRepo:
|
|
async def append(self, event_type: str, **kwargs): # noqa: ANN001
|
|
alert_calls.append({"event_type": event_type, **kwargs})
|
|
return FakeRecord()
|
|
|
|
from src.repositories import alert_operation_log_repository
|
|
from src.services import approval_db, channel_hub
|
|
|
|
monkeypatch.setattr(
|
|
channel_hub,
|
|
"record_external_alert_event",
|
|
fake_record_external_alert_event,
|
|
)
|
|
monkeypatch.setattr(approval_db, "get_timeline_service", lambda: FakeTimeline())
|
|
monkeypatch.setattr(
|
|
alert_operation_log_repository,
|
|
"get_alert_operation_log_repository",
|
|
lambda: FakeRepo(),
|
|
)
|
|
|
|
history = await channel_event_dossier_service._record_source_correlation_apply_history(
|
|
{
|
|
"schema_version": "awooop_source_correlation_apply_v1",
|
|
"source": "channel_event_dossier.recurrence",
|
|
"project_id": "awoooi",
|
|
"work_item_id": "source-evidence:sentry:received:issue-1",
|
|
"review_id": "review-1",
|
|
"target_incident_id": "INC-20260520-ABC123",
|
|
"reviewer_id": "operator_console",
|
|
"operator_note": "matches incident",
|
|
"allowed": True,
|
|
"apply_status": "ready_to_apply",
|
|
"safety_level": "source_review_append_only_apply",
|
|
"writes_incident_state": False,
|
|
"writes_source_event": True,
|
|
"writes_auto_repair_result": False,
|
|
"writes_ticket": False,
|
|
"creates_external_ticket": False,
|
|
"latest_provider_event_id": "sentry:received:issue-1",
|
|
"source_event_provider_event_id": (
|
|
"sentry:source_correlation_linked:issue-1"
|
|
),
|
|
"source_event_stage": "source_correlation_linked",
|
|
"raw_event_id": "issue-1",
|
|
"provider": "sentry",
|
|
"alertname": "Sentry Issue",
|
|
"severity": "error",
|
|
"namespace": "awoooi-prod",
|
|
"target_resource": "web",
|
|
"fingerprint": "fp-sentry-issue-1",
|
|
"checks": [],
|
|
"current_state_summary": {},
|
|
"plan": {"writes": ["awooop_conversation_event"]},
|
|
"read_model_route": {},
|
|
"next_step": "verify_source_link_in_status_chain",
|
|
}
|
|
)
|
|
|
|
assert history == {
|
|
"recorded": True,
|
|
"source_event_id": "99999999-9999-4999-8999-999999999999",
|
|
"alert_operation_id": "alert-op-1",
|
|
"timeline_event_id": "timeline-1",
|
|
}
|
|
assert source_calls[0]["stage"] == "source_correlation_linked"
|
|
assert source_calls[0]["incident_id"] == "INC-20260520-ABC123"
|
|
assert source_calls[0]["event_id"] == "issue-1"
|
|
assert timeline_calls[0]["incident_id"] == "INC-20260520-ABC123"
|
|
assert alert_calls[0]["actor"] == "awooop_source_correlation_apply_service"
|
|
assert alert_calls[0]["context"]["schema_version"] == (
|
|
"awooop_source_correlation_apply_v1"
|
|
)
|
|
assert alert_calls[0]["context"]["apply_status"] == "applied"
|
|
|
|
|
|
def test_build_recurrence_work_item_handoff_records_ticket_proposal_contract() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[
|
|
{
|
|
"event_id": "event-1",
|
|
"project_id": "awoooi",
|
|
"channel_type": "internal",
|
|
"provider_event_id": "alertmanager:received:1",
|
|
"content_hash": "a" * 64,
|
|
"content_preview": "Docker container unhealthy",
|
|
"content_redacted": "Docker container unhealthy",
|
|
"redaction_version": "audit_sink_v1",
|
|
"source_envelope": {
|
|
"provider": "alertmanager",
|
|
"source_refs": {
|
|
"alert_ids": ["alert-1"],
|
|
"incident_ids": ["INC-20260517-F25B4A"],
|
|
"fingerprints": ["fp-container-unhealthy"],
|
|
},
|
|
"log_correlation": {
|
|
"alertname": "DockerContainerUnhealthy",
|
|
"severity": "warning",
|
|
"namespace": "momo",
|
|
"target_resource": "bitan-pharmacy-bitan-1",
|
|
"fingerprint": "fp-container-unhealthy",
|
|
},
|
|
},
|
|
"is_duplicate": True,
|
|
"provider_ts": None,
|
|
"received_at": "2026-05-17T23:47:00",
|
|
"run_id": UUID("33333333-3333-4333-8333-333333333333"),
|
|
"run_state": "completed",
|
|
"run_agent_id": "openclaw",
|
|
}
|
|
],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
handoff = build_recurrence_work_item_handoff(
|
|
recurrence,
|
|
work_item_id="incident:INC-20260517-F25B4A",
|
|
)
|
|
|
|
assert handoff["schema_version"] == "awooop_recurrence_work_item_handoff_v1"
|
|
assert handoff["mode"] == "ticket"
|
|
assert handoff["handoff_kind"] == "ticket_proposal"
|
|
assert handoff["handoff_status"] == "ready_to_record"
|
|
assert handoff["handoff_owner"] == "operator"
|
|
assert handoff["safety_level"] == "handoff_record_only"
|
|
assert handoff["allowed"] is True
|
|
assert handoff["creates_external_ticket"] is False
|
|
assert handoff["writes_incident_state"] is False
|
|
assert handoff["writes_auto_repair_result"] is False
|
|
assert handoff["writes_ticket"] is False
|
|
assert handoff["ticket_preview"]["would_create"] is False
|
|
assert handoff["next_step"] == "operator_review_ticket_preview"
|
|
assert handoff["plan"]["step"] == "record_recurrence_work_item_handoff"
|
|
assert handoff["plan"]["writes"] == ["timeline_events", "alert_operation_log"]
|
|
assert handoff["read_model_route"]["required_scope"] == "record_history"
|
|
|
|
|
|
def test_build_recurrence_work_item_preview_raises_for_missing_item() -> None:
|
|
recurrence = build_dossier_recurrence(
|
|
[],
|
|
project_id="awoooi",
|
|
limit=20,
|
|
)
|
|
|
|
with pytest.raises(RecurrenceWorkItemNotFoundError):
|
|
build_recurrence_work_item_preview(
|
|
recurrence,
|
|
work_item_id="incident:INC-20260517-MISSING",
|
|
)
|
|
|
|
|
|
def test_recurrence_response_model_preserves_repair_work_item_fields() -> None:
|
|
response = ChannelEventRecurrenceResponse.model_validate(
|
|
{
|
|
"project_id": "awoooi",
|
|
"limit": 20,
|
|
"summary": {
|
|
"source_event_total": 1,
|
|
"recurrence_group_total": 1,
|
|
"recurrent_group_total": 1,
|
|
"duplicate_event_total": 0,
|
|
"linked_run_total": 1,
|
|
"unlinked_event_total": 0,
|
|
"auto_repair_linked_total": 1,
|
|
"verified_repair_group_total": 1,
|
|
"open_work_item_group_total": 0,
|
|
"manual_gate_group_total": 0,
|
|
"automation_gap_group_total": 0,
|
|
"failed_repair_group_total": 0,
|
|
"source_correlation_review_group_total": 0,
|
|
"latest_received_at": "2026-05-13T13:47:00",
|
|
},
|
|
"items": [
|
|
{
|
|
"recurrence_key": "fingerprint:fp-host-disk",
|
|
"provider": "alertmanager",
|
|
"alertname": "HostDiskUsageHigh",
|
|
"severity": "warning",
|
|
"namespace": "node",
|
|
"target_resource": "host-110",
|
|
"fingerprint": "fp-host-disk",
|
|
"latest_stage": "incident_linked",
|
|
"latest_event_id": "11111111-1111-4111-8111-111111111111",
|
|
"latest_provider_event_id": "alertmanager:received:1",
|
|
"latest_content_preview": "Host disk pressure",
|
|
"latest_run_id": "22222222-2222-4222-8222-222222222222",
|
|
"latest_run_state": "completed",
|
|
"latest_agent_id": "openclaw",
|
|
"latest_incident_id": "INC-20260513-ABCD",
|
|
"incident_ids": ["INC-20260513-ABCD"],
|
|
"repair_summary": {
|
|
"status": "auto_repair_verified",
|
|
"latest_auto_repair_id": "repair-1",
|
|
},
|
|
"work_item": {
|
|
"work_item_id": "verification:INC-20260513-ABCD:repair-1",
|
|
"status": "closed",
|
|
},
|
|
"occurrence_total": 1,
|
|
"duplicate_total": 0,
|
|
"linked_run_total": 1,
|
|
"source_ref_total": 3,
|
|
"missing_source_refs_total": 0,
|
|
"sentry_ref_total": 0,
|
|
"signoz_ref_total": 0,
|
|
"alert_ref_total": 1,
|
|
"stage_counts": {"incident_linked": 1},
|
|
"run_state_counts": {"completed": 1},
|
|
"first_received_at": "2026-05-13T13:47:00",
|
|
"latest_received_at": "2026-05-13T13:47:00",
|
|
}
|
|
],
|
|
}
|
|
)
|
|
|
|
payload = response.model_dump()
|
|
assert payload["summary"]["auto_repair_linked_total"] == 1
|
|
assert payload["summary"]["source_correlation_review_group_total"] == 0
|
|
assert payload["items"][0]["latest_incident_id"] == "INC-20260513-ABCD"
|
|
assert payload["items"][0]["latest_stage"] == "incident_linked"
|
|
assert payload["items"][0]["stage_counts"] == {"incident_linked": 1}
|
|
assert payload["items"][0]["repair_summary"]["status"] == "auto_repair_verified"
|
|
assert payload["items"][0]["work_item"]["status"] == "closed"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_channel_event_dossier_requires_source() -> None:
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await fetch_channel_event_dossier(
|
|
project_id="awoooi",
|
|
run_id=None,
|
|
provider_event_id=None,
|
|
limit=20,
|
|
)
|
|
|
|
assert exc_info.value.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_channel_event_dossier_uses_typed_run_filter(monkeypatch) -> None:
|
|
captured: dict[str, object] = {}
|
|
|
|
class FakeMappings:
|
|
def all(self) -> list[dict[str, object]]:
|
|
return []
|
|
|
|
class FakeResult:
|
|
def mappings(self) -> FakeMappings:
|
|
return FakeMappings()
|
|
|
|
class FakeDb:
|
|
async def execute(self, statement, params): # noqa: ANN001
|
|
captured["sql"] = str(statement)
|
|
captured["params"] = params
|
|
return FakeResult()
|
|
|
|
class FakeContext:
|
|
async def __aenter__(self) -> FakeDb:
|
|
return FakeDb()
|
|
|
|
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
|
return None
|
|
|
|
monkeypatch.setattr(
|
|
channel_event_dossier_service,
|
|
"get_db_context",
|
|
lambda _project_id: FakeContext(),
|
|
)
|
|
|
|
run_id = UUID("0a4c365f-609e-5441-bc29-4c7ebc3603b6")
|
|
result = await fetch_channel_event_dossier(
|
|
project_id="awoooi",
|
|
run_id=run_id,
|
|
provider_event_id=None,
|
|
limit=20,
|
|
)
|
|
|
|
assert result["total"] == 0
|
|
assert "run_id = CAST(:run_id AS uuid)" in str(captured["sql"])
|
|
assert ":run_id IS NULL" not in str(captured["sql"])
|
|
assert captured["params"] == {
|
|
"project_id": "awoooi",
|
|
"run_id": str(run_id),
|
|
"limit": 20,
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_channel_event_dossier_coverage_uses_typed_provider_filter(
|
|
monkeypatch,
|
|
) -> None:
|
|
captured: list[dict[str, object]] = []
|
|
|
|
class FakeMappings:
|
|
def all(self) -> list[dict[str, object]]:
|
|
return []
|
|
|
|
class FakeResult:
|
|
def mappings(self) -> FakeMappings:
|
|
return FakeMappings()
|
|
|
|
class FakeDb:
|
|
async def execute(self, statement, params): # noqa: ANN001
|
|
captured.append({"sql": str(statement), "params": params})
|
|
return FakeResult()
|
|
|
|
class FakeContext:
|
|
async def __aenter__(self) -> FakeDb:
|
|
return FakeDb()
|
|
|
|
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
|
return None
|
|
|
|
monkeypatch.setattr(
|
|
channel_event_dossier_service,
|
|
"get_db_context",
|
|
lambda _project_id: FakeContext(),
|
|
)
|
|
|
|
result = await fetch_channel_event_dossier_coverage(
|
|
project_id=None,
|
|
provider="sentry",
|
|
limit=500,
|
|
)
|
|
|
|
assert result["project_id"] == "awoooi"
|
|
assert result["limit"] == 200
|
|
coverage_query = captured[0]
|
|
assert "source_envelope->>'provider'" in str(coverage_query["sql"])
|
|
assert coverage_query["params"] == {
|
|
"project_id": "awoooi",
|
|
"provider": "sentry",
|
|
"limit": 200,
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_channel_event_dossier_recurrence_uses_joined_typed_filter(
|
|
monkeypatch,
|
|
) -> None:
|
|
captured: list[dict[str, object]] = []
|
|
|
|
class FakeMappings:
|
|
def all(self) -> list[dict[str, object]]:
|
|
return []
|
|
|
|
class FakeResult:
|
|
def mappings(self) -> FakeMappings:
|
|
return FakeMappings()
|
|
|
|
class FakeDb:
|
|
async def execute(self, statement, params): # noqa: ANN001
|
|
captured.append({"sql": str(statement), "params": params})
|
|
return FakeResult()
|
|
|
|
class FakeContext:
|
|
async def __aenter__(self) -> FakeDb:
|
|
return FakeDb()
|
|
|
|
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
|
return None
|
|
|
|
monkeypatch.setattr(
|
|
channel_event_dossier_service,
|
|
"get_db_context",
|
|
lambda _project_id: FakeContext(),
|
|
)
|
|
|
|
result = await fetch_channel_event_dossier_recurrence(
|
|
project_id="awoooi",
|
|
provider="alertmanager",
|
|
limit=500,
|
|
)
|
|
|
|
assert result["project_id"] == "awoooi"
|
|
assert result["limit"] == 300
|
|
recurrence_query = captured[0]
|
|
assert "LEFT JOIN awooop_run_state r" in str(recurrence_query["sql"])
|
|
assert "e.source_envelope->>'provider'" in str(recurrence_query["sql"])
|
|
assert ":provider IS NULL" not in str(recurrence_query["sql"])
|
|
assert recurrence_query["params"] == {
|
|
"project_id": "awoooi",
|
|
"provider": "alertmanager",
|
|
"limit": 300,
|
|
}
|