fix(ci): tolerate empty source link canary response
All checks were successful
CD Pipeline / tests (push) Successful in 1m27s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 4m2s
CD Pipeline / post-deploy-checks (push) Successful in 1m45s

This commit is contained in:
Your Name
2026-06-04 20:31:43 +08:00
parent 6a3348795f
commit 29a67ec775
2 changed files with 122 additions and 6 deletions

View File

@@ -430,6 +430,53 @@ class AlertChainSmokeMetricTest(unittest.TestCase):
self.assertIn(["target_incident_id", "INC-20260505-25E744"], tags)
self.assertEqual(calls[0]["headers"]["X-AwoooP-Operator-Key"], "secret")
def test_source_link_canary_accepts_empty_2xx_for_downstream_readback(self):
def fake_post(url, payload, *, headers=None, timeout=None):
self.assertTrue(url.endswith("/api/v1/webhooks/sentry/error"))
self.assertEqual(payload["data"]["issue"]["title"], "AwoooPSourceLinkCanary")
return alert_chain_smoke_test.HttpGetResult(204, "")
original_post = alert_chain_smoke_test.http_post_json
try:
alert_chain_smoke_test.http_post_json = fake_post
result = alert_chain_smoke_test.send_source_link_canary(
"https://awoooi.example",
target_incident_id="INC-20260505-25E744",
operator_key="secret",
operator_id="gitea-e2e-health",
run_ref="run/123",
)
finally:
alert_chain_smoke_test.http_post_json = original_post
self.assertTrue(result.passed)
self.assertIn("source-correlation smoke must verify readback", result.message)
def test_source_link_canary_reports_http_error_before_json_parse(self):
def fake_post(url, payload, *, headers=None, timeout=None):
self.assertTrue(url.endswith("/api/v1/webhooks/sentry/error"))
return alert_chain_smoke_test.HttpGetResult(
502,
"<html><body>bad gateway</body></html>",
)
original_post = alert_chain_smoke_test.http_post_json
try:
alert_chain_smoke_test.http_post_json = fake_post
result = alert_chain_smoke_test.send_source_link_canary(
"https://awoooi.example",
target_incident_id="INC-20260505-25E744",
operator_key="secret",
operator_id="gitea-e2e-health",
run_ref="run/123",
)
finally:
alert_chain_smoke_test.http_post_json = original_post
self.assertFalse(result.passed)
self.assertIn("sentry HTTP 502", result.message)
self.assertIn("bad gateway", result.message)
if __name__ == "__main__":
unittest.main()

View File

@@ -137,6 +137,22 @@ def _http_error_message(error: Exception) -> str:
return str(error)
def _response_body_preview(text: str, limit: int = 240) -> str:
cleaned = " ".join((text or "").split())
if not cleaned:
return "<empty body>"
if len(cleaned) <= limit:
return cleaned
return f"{cleaned[:limit]}..."
def _response_json(resp: HttpGetResult) -> dict[str, Any] | None:
text = (resp.text or "").strip()
if not text:
return None
return resp.json()
def _api_health_probe_summary(attempt: int) -> str:
return (
f"attempts={attempt}/{API_HEALTH_ATTEMPTS}, "
@@ -782,18 +798,43 @@ def send_source_provider_upstream_canary(
},
timeout=TIMEOUT,
)
data = resp.json()
except (URLError, TimeoutError, OSError, json.JSONDecodeError) as e:
except (URLError, TimeoutError, OSError) as e:
return CheckResult(
"Source Provider Upstream Canary",
False,
f"{provider} upstream canary failed: {_http_error_message(e)}",
)
if resp.status_code >= 400:
try:
data = _response_json(resp)
except json.JSONDecodeError:
data = None
detail = (
data.get("detail")
if isinstance(data, dict)
else _response_body_preview(resp.text)
)
return CheckResult(
"Source Provider Upstream Canary",
False,
f"{provider} HTTP {resp.status_code}: {data.get('detail', resp.text) if isinstance(data, dict) else resp.text}",
f"{provider} HTTP {resp.status_code}: {detail}",
)
try:
data = _response_json(resp)
except json.JSONDecodeError:
return CheckResult(
"Source Provider Upstream Canary",
False,
(
f"{provider} upstream canary returned non-JSON "
f"HTTP {resp.status_code}: {_response_body_preview(resp.text)}"
),
)
if data is None:
return CheckResult(
"Source Provider Upstream Canary",
False,
f"{provider} upstream canary returned empty HTTP {resp.status_code}",
)
validation_error = _validate_upstream_canary_response(provider, data)
if validation_error:
@@ -850,18 +891,46 @@ def send_source_link_canary(
},
timeout=TIMEOUT,
)
data = resp.json()
except (URLError, TimeoutError, OSError, json.JSONDecodeError) as e:
except (URLError, TimeoutError, OSError) as e:
return CheckResult(
"Source Link Canary",
False,
f"sentry source-link canary failed: {_http_error_message(e)}",
)
if resp.status_code >= 400:
try:
data = _response_json(resp)
except json.JSONDecodeError:
data = None
detail = (
data.get("detail")
if isinstance(data, dict)
else _response_body_preview(resp.text)
)
return CheckResult(
"Source Link Canary",
False,
f"sentry HTTP {resp.status_code}: {data.get('detail', resp.text) if isinstance(data, dict) else resp.text}",
f"sentry HTTP {resp.status_code}: {detail}",
)
if not (resp.text or "").strip():
return CheckResult(
"Source Link Canary",
True,
(
"accepted sentry source-link canary post with empty "
f"HTTP {resp.status_code}; source-correlation smoke must verify readback"
),
)
try:
data = resp.json()
except json.JSONDecodeError:
return CheckResult(
"Source Link Canary",
False,
(
"sentry source-link canary returned non-JSON "
f"HTTP {resp.status_code}: {_response_body_preview(resp.text)}"
),
)
validation_error = _validate_upstream_canary_response("sentry", data)
if validation_error: