From 0f7d22750f15d533ad6aa63adf35321f48e73210 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Tue, 15 Sep 2026 15:55:59 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20solution/backend:=20Teams=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=ED=95=84=EC=88=98=20=ED=95=84=EB=93=9C=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 웹훅 요청의 contentUrl 및 스키마 선언 누락을 공식 형식에 맞춤. HTTP 202와 채널 게시 성공을 구분하도록 검증 한계 기록. 검증: Teams 요청 형식 회귀 테스트 1건 통과. 워크플로 실제 수신은 별도 확인 필요. --- docs/TEAMS_WEBHOOK.md | 11 ++++++++++ .../backend/services/search_console_alerts.py | 4 +++- .../tests/test_search_console_alerts.py | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 docs/TEAMS_WEBHOOK.md create mode 100644 solution/backend/tests/test_search_console_alerts.py diff --git a/docs/TEAMS_WEBHOOK.md b/docs/TEAMS_WEBHOOK.md new file mode 100644 index 0000000..8d106e0 --- /dev/null +++ b/docs/TEAMS_WEBHOOK.md @@ -0,0 +1,11 @@ +# Teams 웹훅 확인 (2026-09-15) + +- Adaptive Card 요청의 `contentUrl: null` 및 `$schema`를 공식 예제에 맞춰 보완했다. +- HTTP 202는 워크플로의 요청 접수다. Teams 채널 게시 성공을 뜻하지 않는다. +- 실제 전송 2건은 202였지만 사용자가 확인한 워크플로 실행은 실패였다. + 상세 오류를 확인하지 못했으므로 누락 필드를 실제 실패 원인으로 단정하지 않는다. +- 운영 자동 알림 활성화 전, 채널 수신 또는 워크플로의 최종 게시 단계 성공을 확인해야 한다. +- 웹훅은 `.env`에만 보관하고 커밋하지 않는다. + +검증: 백엔드에서 `APP_ENV=test PYTHONPATH=. .venv/bin/pytest tests/test_search_console_alerts.py --confcutdir=tests`. +공식 형식: https://learn.microsoft.com/en-us/connectors/teams/#adaptivecarditemschema diff --git a/solution/backend/services/search_console_alerts.py b/solution/backend/services/search_console_alerts.py index 5325b7b..13ad6bd 100644 --- a/solution/backend/services/search_console_alerts.py +++ b/solution/backend/services/search_console_alerts.py @@ -26,7 +26,9 @@ async def send_alert(webhook_url: str, page_url: str, reason: str) -> bool: return False payload = {"type": "message", "attachments": [{ "contentType": "application/vnd.microsoft.card.adaptive", - "content": {"type": "AdaptiveCard", "version": "1.2", "body": [ + "contentUrl": None, + "content": {"$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", "version": "1.2", "body": [ {"type": "TextBlock", "text": "Google 색인 확인 필요", "weight": "Bolder"}, {"type": "TextBlock", "text": page_url, "wrap": True}, {"type": "TextBlock", "text": reason, "wrap": True}, diff --git a/solution/backend/tests/test_search_console_alerts.py b/solution/backend/tests/test_search_console_alerts.py new file mode 100644 index 0000000..8040f0e --- /dev/null +++ b/solution/backend/tests/test_search_console_alerts.py @@ -0,0 +1,21 @@ +import httpx +from services import search_console_alerts as alerts + + +async def test_teams_payload_has_required_attachment_fields(monkeypatch): + requests = [] + real_client = httpx.AsyncClient + + def receive(request): + import json + requests.append(json.loads(request.content)) + return httpx.Response(202) + + monkeypatch.setattr(alerts.httpx, "AsyncClient", lambda **kw: real_client( + transport=httpx.MockTransport(receive), **kw, + )) + assert await alerts.send_alert("https://example.test/webhook", "https://example.test/s/a", "test") + card = requests[0]["attachments"][0] + assert card["contentType"] == "application/vnd.microsoft.card.adaptive" + assert "contentUrl" in card and card["contentUrl"] is None + assert card["content"]["$schema"] == "http://adaptivecards.io/schemas/adaptive-card.json"