Skip to content

Commit

Permalink
chore(test): implement unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hairmare committed Jul 4, 2024
1 parent b3ab4a7 commit 6fcfaa0
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 19 deletions.
31 changes: 30 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,40 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: 3.x
cache: pip
- name: ruff
run: |
pip install -r requirements-dev.txt
pip install '.[test]'
ruff format --check
ruff check . --output-format=github
pytest:
runs-on: ubuntu-latest
name: Test python ${{ matrix.python-version }}
strategy:
matrix:
python-version:
- '3.9'
- '3.10'
- '3.11'
- '3.12'
experimental: [false]
include:
- python-version: '3.x'
experimental: true
continue-on-error: ${{ matrix.experimental }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: pytest
run: |
pip install '.[test]'
pip install pytest-github-actions-annotate-failures
pytest --cov --cov-fail-under=100
shellcheck:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ build/
dist/
*.spec
.venv/
.coverage
31 changes: 31 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[project]
name = "signalilo-scrubbed"
description = "Signalilo with alert scrubber included"
version = "0.0.0"
authors = [
{ name = "Adfinis", email = "[email protected]" }
]

dependencies = [
"Flask==3.0.3",
"requests==2.32.3",
"pyinstaller==6.8.0",
"waitress==3.0.0",
]

[project.optional-dependencies]
test = [
"ruff==0.5.0",
"types-waitress==3.0.0.20240423",
"types-requests==2.32.0.20240622",
"pytest==8.2.2",
"pytest-cov==5.0.0",
"requests-mock==1.12.1",
]

[tool.flit.module]
name = "scrubbed"

[build-system]
build-backend = "flit_core.buildapi"
requires = ["flit_core >=3.2,<4"]
3 changes: 0 additions & 3 deletions requirements-dev.txt

This file was deleted.

4 changes: 0 additions & 4 deletions requirements.txt

This file was deleted.

Empty file added scrubbed/__init__.py
Empty file.
21 changes: 10 additions & 11 deletions scrubbed.py → scrubbed/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@
COMMON_ANNOTATIONS = os.environ.get("SCRUBBED_COMMON_ANNOTATIONS", "").split()

# Service configuration
HOST = os.environ.get("SCRUBBED_LISTEN_HOST", "0.0.0.0") # noqa: S104 # listening on all instances is fine for now
HOST = os.environ.get("SCRUBBED_LISTEN_HOST", "127.0.0.1")
PORT = os.environ.get("SCRUBBED_LISTEN_PORT", 8080)
URL = os.environ.get("SCRUBBED_DESTINATION_URL", "http://localhost:6725")
TIMEOUT = 60


def redact_fields(fields: dict[str, str], keys_to_keep: list[str]):
Expand Down Expand Up @@ -75,14 +76,12 @@ def webhook():

logger.debug("sending: \n%s", alert)

session = requests.Session()

# Copy headers
session.headers.clear()
for h in request.headers:
session.headers[h] = request.headers.get(h)

r = session.post(URL, json=alert)
r = requests.post(
URL,
json=alert,
headers=request.headers,
timeout=TIMEOUT,
)
msg = "alert received and processed"
response = {
"status": "success",
Expand All @@ -108,12 +107,12 @@ def webhook():


@app.route("/healthz")
def health_check():
def health_check(): # pragma: nocover
"""Endpoint for health probes."""
return "OK", 200


if __name__ == "__main__":
if __name__ == "__main__": # pragma: nocover
from waitress import serve

serve(app, host=HOST, port=PORT)
128 changes: 128 additions & 0 deletions tests/test_webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import json

import pytest
from werkzeug.test import Client

from scrubbed.main import app


@pytest.fixture()
def client() -> Client:
"""Test client for calling server endpoints."""
return Client(app)


def test_invalid_get(client):
response = client.get("/webhook")

assert response.status == "405 METHOD NOT ALLOWED"
assert response.content_type == "text/html; charset=utf-8"


def test_invalid_content_type(client):
response = client.post(
"/webhook", content_type="text/html", data="<h1>Hello World!</h1>"
)

assert response.status == "400 BAD REQUEST"
assert response.content_type == "application/json"
assert response.json.get("message") == "request must be in JSON format"
assert response.json.get("status") == "error"


def test_invalid_data(client):
response = client.post(
"/webhook", content_type="application/json", data="<h1>Hello World!</h1>"
)

assert response.status == "500 INTERNAL SERVER ERROR"
assert response.content_type == "application/json"
assert (
response.json.get("message")
== "400 Bad Request: The browser (or proxy) sent a request that this server could not understand." # noqa: E501
)
assert response.json.get("status") == "error"


def test_scrubbing_alert(requests_mock, client):
upstream_request = requests_mock.post("http://localhost:6725")

alerts = {
"version": "4",
"groupKey": "groupkey",
"truncatedAlerts": 0,
"status": "firing",
"receiver": "test",
"groupLabels": {
"KEY": "SECRET",
},
"commonLabels": {
"KEY": "SECRET",
},
"commonAnnotations": {
"KEY": "SECRET",
},
"externalURL": "https://SECRET.alertmanager.example.com",
"alerts": [
{
"status": "firing",
"labels": {"KEY": "SECRET"},
"annotations": {"KEY": "SECRET"},
"startsAt": "<rfc3339>",
"endsAt": "<rfc3339>",
"generatorURL": "https://SECRET.generator.example.com",
"fingerprint": "fingerprint",
}
],
}
response = client.post(
"/webhook",
content_type="application/json",
headers={
"KEY": "SECRET",
},
data=json.dumps(alerts),
)

assert response.status == "200 OK"
assert response.content_type == "application/json"
assert upstream_request.call_count == 1
assert "SECRET" not in upstream_request.last_request.text
assert upstream_request.last_request.json() == {
"version": "4",
"groupKey": "REDACTED",
"truncatedAlerts": 0,
"status": "firing",
"receiver": "test",
"groupLabels": {
"KEY": "REDACTED",
},
"commonLabels": {
"KEY": "REDACTED",
},
"commonAnnotations": {
"KEY": "REDACTED",
},
"externalURL": "REDACTED",
"alerts": [
{
"annotations": {
"KEY": "REDACTED",
},
"endsAt": "<rfc3339>",
"fingerprint": "fingerprint",
"generatorURL": "REDACTED",
"labels": {
"KEY": "REDACTED",
},
"startsAt": "<rfc3339>",
"status": "firing",
},
],
}
assert upstream_request.last_request.headers == {
"Host": "localhost",
"Content-Type": "application/json",
"Content-Length": "451",
"Key": "SECRET", # TODO: redact this?

Check failure on line 127 in tests/test_webhook.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (TD002)

tests/test_webhook.py:127:29: TD002 Missing author in TODO; try: `# TODO(<author_name>): ...` or `# TODO @<author_name>: ...`

Check failure on line 127 in tests/test_webhook.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (TD003)

tests/test_webhook.py:127:29: TD003 Missing issue link on the line following this TODO
}

0 comments on commit 6fcfaa0

Please sign in to comment.