Skip to content

Commit a833148

Browse files
committed
test: add unit tests
1 parent 11403d5 commit a833148

File tree

9 files changed

+1210
-8
lines changed

9 files changed

+1210
-8
lines changed

.github/workflows/unit-test.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Python Unit Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.12'
23+
24+
- name: Install dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
pip install pytest pytest-cov
28+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
29+
30+
- name: Test with pytest
31+
run: |
32+
pytest --cov=. tests/
33+
coverage xml
34+
35+
- name: Upload coverage reports to Codecov
36+
uses: codecov/codecov-action@v5
37+
with:
38+
token: ${{ secrets.CODECOV_TOKEN }}

endpoints/ __init__.py

Whitespace-only changes.

middlewares/discord_middleware.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,24 @@
77
logger = logging.getLogger(__name__)
88

99
class DiscordMiddleware:
10+
"""
11+
Middleware for handling Discord interaction webhooks.
12+
13+
This middleware verifies the authenticity of Discord requests using Ed25519 signatures,
14+
and provides appropriate responses for different Discord interaction types.
15+
"""
16+
1017
def __init__(self, signature_verification_key=None):
18+
"""
19+
Initialize the Discord middleware with a signature verification key.
20+
21+
Args:
22+
signature_verification_key (str): The hexadecimal public key for verifying
23+
Discord's request signatures.
24+
25+
Raises:
26+
ValueError: If signature_verification_key is None.
27+
"""
1128
if signature_verification_key is None:
1229
logger.error("Signature verification key is required")
1330
raise ValueError("signature_verification_key is required")
@@ -16,6 +33,19 @@ def __init__(self, signature_verification_key=None):
1633
logger.info("DiscordMiddleware initialized with verification key")
1734

1835
def invoke(self, r: Request) -> Response:
36+
"""
37+
Process an incoming request from Discord.
38+
39+
This method verifies the request signature, then determines the type of
40+
Discord interaction and returns the appropriate response.
41+
42+
Args:
43+
r (Request): The incoming request to process.
44+
45+
Returns:
46+
Response: A response to send back to Discord, or None if the request
47+
doesn't match any expected interaction type.
48+
"""
1949
logger.debug("Request received with body: %s", r.data)
2050

2151
if not self.verify_request(r):
@@ -24,34 +54,62 @@ def invoke(self, r: Request) -> Response:
2454

2555
logger.info("Request signature verified")
2656

27-
if r.method == 'POST' and self.is_webhook_event(r):
28-
logger.info("Ping received, sending ping response")
29-
return Response(json.dumps({"type": 1}), content_type="application/json")
30-
31-
elif r.method == 'POST' and self.is_ping(r):
57+
if r.method == 'POST' and self.is_ping(r):
3258
logger.info("Ping received, sending ping response")
3359
return Response(status=204)
60+
elif r.method == 'POST' and self.is_webhook_event(r):
61+
logger.info("Webhook event received, sending acknowledgment")
62+
return Response(json.dumps({"type": 1}), content_type="application/json")
3463

35-
logger.info("No ping response required")
64+
logger.info("No specific handler for this request")
3665
return None
3766

3867
def is_webhook_event(self, request: Request) -> bool:
68+
"""
69+
Check if the request is a Discord webhook event (type 1).
70+
71+
Args:
72+
request (Request): The request to check.
73+
74+
Returns:
75+
bool: True if the request is a webhook event, False otherwise.
76+
"""
3977
try:
4078
logger.debug("Checking if request is a webhook event")
4179
return request.json.get('type') == 1
4280
except (TypeError, ValueError) as e:
43-
logger.error("Failed to parse request JSON for request check: %s", e)
81+
logger.error("Failed to parse request JSON for webhook event check: %s", e)
4482
return False
4583

4684
def is_ping(self, request: Request) -> bool:
85+
"""
86+
Check if the request is a Discord ping (type 0).
87+
88+
Discord sends this when registering a new webhook to verify it's working.
89+
90+
Args:
91+
request (Request): The request to check.
92+
93+
Returns:
94+
bool: True if the request is a ping, False otherwise.
95+
"""
4796
try:
4897
logger.debug("Checking if request is a ping")
4998
return request.json.get('type') == 0
5099
except (TypeError, ValueError) as e:
51-
logger.error("Failed to parse request JSON for request check: %s", e)
100+
logger.error("Failed to parse request JSON for ping check: %s", e)
52101
return False
53102

54103
def verify_request(self, request: Request) -> bool:
104+
"""
105+
Verify the authenticity of a Discord request using Ed25519 signatures.
106+
107+
Args:
108+
request (Request): The request to verify.
109+
110+
Returns:
111+
bool: True if the request signature is valid, False otherwise.
112+
"""
55113
try:
56114
logger.debug("Verifying request with headers: %s", request.headers)
57115
signature = request.headers['X-Signature-Ed25519']

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
testpaths = tests
3+
pythonpath = .

0 commit comments

Comments
 (0)