Skip to content

Commit 9adb5bd

Browse files
committed
create endpoint for webhook
1 parent 9a5585a commit 9adb5bd

File tree

6 files changed

+265
-7
lines changed

6 files changed

+265
-7
lines changed

requirements.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fastapi[all]
2+
pytest
3+
pip-tools

requirements.txt

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#
2+
# This file is autogenerated by pip-compile with python 3.10
3+
# To update, run:
4+
#
5+
# pip-compile requirements.in
6+
#
7+
anyio==3.6.2
8+
# via
9+
# httpcore
10+
# starlette
11+
# watchfiles
12+
attrs==22.1.0
13+
# via pytest
14+
build==0.9.0
15+
# via pip-tools
16+
certifi==2022.9.24
17+
# via
18+
# httpcore
19+
# httpx
20+
click==8.1.3
21+
# via
22+
# pip-tools
23+
# uvicorn
24+
dnspython==2.2.1
25+
# via email-validator
26+
email-validator==1.3.0
27+
# via fastapi
28+
exceptiongroup==1.0.4
29+
# via pytest
30+
fastapi[all]==0.87.0
31+
# via -r requirements.in
32+
h11==0.14.0
33+
# via
34+
# httpcore
35+
# uvicorn
36+
httpcore==0.16.1
37+
# via httpx
38+
httptools==0.5.0
39+
# via uvicorn
40+
httpx==0.23.1
41+
# via fastapi
42+
idna==3.4
43+
# via
44+
# anyio
45+
# email-validator
46+
# rfc3986
47+
iniconfig==1.1.1
48+
# via pytest
49+
itsdangerous==2.1.2
50+
# via fastapi
51+
jinja2==3.1.2
52+
# via fastapi
53+
markupsafe==2.1.1
54+
# via jinja2
55+
orjson==3.8.2
56+
# via fastapi
57+
packaging==21.3
58+
# via
59+
# build
60+
# pytest
61+
pep517==0.13.0
62+
# via build
63+
pip-tools==6.10.0
64+
# via -r requirements.in
65+
pluggy==1.0.0
66+
# via pytest
67+
pydantic==1.10.2
68+
# via fastapi
69+
pyparsing==3.0.9
70+
# via packaging
71+
pytest==7.2.0
72+
# via -r requirements.in
73+
python-dotenv==0.21.0
74+
# via uvicorn
75+
python-multipart==0.0.5
76+
# via fastapi
77+
pyyaml==6.0
78+
# via
79+
# fastapi
80+
# uvicorn
81+
rfc3986[idna2008]==1.5.0
82+
# via httpx
83+
six==1.16.0
84+
# via python-multipart
85+
sniffio==1.3.0
86+
# via
87+
# anyio
88+
# httpcore
89+
# httpx
90+
starlette==0.21.0
91+
# via fastapi
92+
tomli==2.0.1
93+
# via
94+
# build
95+
# pep517
96+
# pytest
97+
typing-extensions==4.4.0
98+
# via pydantic
99+
ujson==5.5.0
100+
# via fastapi
101+
uvicorn[standard]==0.20.0
102+
# via fastapi
103+
uvloop==0.17.0
104+
# via uvicorn
105+
watchfiles==0.18.1
106+
# via uvicorn
107+
websockets==10.4
108+
# via uvicorn
109+
wheel==0.38.4
110+
# via pip-tools
111+
112+
# The following packages are considered to be unsafe in a requirements file:
113+
# pip
114+
# setuptools

src/sentry/main.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
from typing import Any, Literal, Mapping
1+
from typing import Any, Mapping
2+
from fastapi.requests import Request
3+
from fastapi.responses import Response
24
from uuid import UUID
35

46
from fastapi import FastAPI
7+
from fastapi import status
8+
from pydantic.config import Extra
59
from pydantic.main import BaseModel
610

711
app = FastAPI()
@@ -11,16 +15,24 @@ class Installation(BaseModel):
1115
uuid: UUID
1216

1317

14-
class Request(BaseModel):
18+
class Webhook(BaseModel, extra=Extra.allow):
1519
action: str
1620
data: Mapping[str, Any]
17-
actor: Mapping[str, Any]
1821
installation: Installation
1922

2023

21-
SentryHookResource = Literal['installation', 'event_alert', 'issue', 'metric_alert', 'error', 'comment']
24+
@app.post("/api/sentry/webhook", status_code=status.HTTP_200_OK)
25+
async def sentry_webhook(webhook: Webhook, request: Request):
26+
if request.headers.get('sentry-hook-resource') == 'issue':
2227

28+
#todo send webhook.data.get('title') to the Basecamp
2329

24-
@app.post("/api/sentry/webhook")
25-
async def root(request: Request):
26-
return request
30+
return 'ok!'
31+
32+
elif request.headers.get('sentry-hook-resource') == 'event-alert':
33+
34+
return 'ok!'
35+
36+
else:
37+
38+
return Response(status_code=status.HTTP_400_BAD_REQUEST)

src/sentry/tests/conftest.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from sentry.main import app
4+
from sentry.tests.request_data import event, issue
5+
6+
7+
@pytest.fixture
8+
def client():
9+
return TestClient(app)
10+
11+
12+
@pytest.fixture
13+
def headers():
14+
return {
15+
'sentry-hook-resource': 'issue',
16+
}
17+
18+
19+
SENTRY_INSTALLATION_UUID = "7a485448-a9e2-4c85-8a3c-4f44175783c9"
20+
21+
22+
@pytest.fixture
23+
def installation():
24+
return {
25+
"app": {
26+
"uuid": "64bf2cf4-37ca-4365-8dd3-6b6e56d741b8",
27+
"slug": "app",
28+
},
29+
"organization": {
30+
"slug": "example",
31+
},
32+
"uuid": SENTRY_INSTALLATION_UUID,
33+
}
34+
35+
36+
@pytest.fixture
37+
def issue_resolved_webhook(installation):
38+
return {
39+
'action': 'resolved',
40+
'data': {'issue': issue},
41+
'installation': installation,
42+
}
43+
44+
45+
@pytest.fixture
46+
def alert_triggered_webhook(installation):
47+
return {
48+
"action": "triggered",
49+
"data": {"event": event},
50+
"installation": installation,
51+
}

src/sentry/tests/request_data.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
issue = {
2+
"id": "123",
3+
"shortId": "IPE-1",
4+
"title": "Error #1: This is a test error!",
5+
"culprit": "SentryCustomError(frontend/src/util)",
6+
"level": "error",
7+
"status": "unresolved",
8+
"statusDetails": {},
9+
"isPublic": False,
10+
"platform": "javascript",
11+
"project": {
12+
"id": "456",
13+
"name": "ipe",
14+
"slug": "ipe",
15+
"platform": "javascript-react",
16+
},
17+
"type": "error",
18+
"metadata": {
19+
"value": "This is a test error!",
20+
"type": "Another Error #1",
21+
"filename": "/frontend/src/util.ts",
22+
"function": "SentryCustomError",
23+
"display_title_with_tree_label": False,
24+
},
25+
"numComments": 0,
26+
"assignedTo": {
27+
"email": "person@example.com",
28+
"type": "user",
29+
"id": "789",
30+
"name": "Person",
31+
},
32+
"isBookmarked": False,
33+
"isSubscribed": False,
34+
"hasSeen": False,
35+
"isUnhandled": False,
36+
"count": "1",
37+
"userCount": 1,
38+
"firstSeen": "2022-04-04T18:17:18.320000Z",
39+
"lastSeen": "2022-04-04T18:17:18.320000Z",
40+
}
41+
42+
event = {
43+
"event_id": "def456",
44+
"platform": issue["platform"],
45+
"datetime": issue["firstSeen"],
46+
"culprit": issue["culprit"],
47+
"metadata": issue["metadata"],
48+
"title": issue["title"],
49+
"issue_id": issue["id"],
50+
"environment": "production",
51+
"web_url": f"https://sentry.io/organizations/lxyz/issues/{issue['id']}/events/def456/",
52+
"breadcrumbs": {},
53+
"contexts": {},
54+
"sdk": {},
55+
}

src/sentry/tests/test_webhook.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
def test_response_if_issue(client, issue_resolved_webhook, headers):
3+
response = client.post('/api/sentry/webhook', json=issue_resolved_webhook, headers=headers)
4+
5+
assert response.status_code == 200
6+
7+
8+
def test_response_if_alert(client, alert_triggered_webhook, headers):
9+
response = client.post('/api/sentry/webhook', json=alert_triggered_webhook, headers=headers)
10+
11+
assert response.status_code == 200
12+
13+
14+
def test_bad_response_if_bad_headers(client, issue_resolved_webhook):
15+
response = client.post('/api/sentry/webhook', json=issue_resolved_webhook, headers={'missing': 'header'})
16+
17+
assert response.status_code == 400
18+
19+
20+
def test_bad_response_if_bad_content(client, issue_resolved_webhook, headers):
21+
response = client.post('/api/sentry/webhook', json={'malformed': True}, headers=headers)
22+
23+
assert response.status_code == 422

0 commit comments

Comments
 (0)