Skip to content

Commit 5075885

Browse files
committed
python: Add unit tests for Svix API client (WIP)
1 parent 7416137 commit 5075885

File tree

6 files changed

+443
-6
lines changed

6 files changed

+443
-6
lines changed

.github/workflows/python-tests.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Python Tests
2+
on:
3+
push:
4+
branches:
5+
- main
6+
paths:
7+
- "python/**"
8+
- "openapi.json"
9+
- ".github/workflows/python-tests.yml"
10+
pull_request:
11+
paths:
12+
- "python/**"
13+
- "openapi.json"
14+
- ".github/workflows/python-tests.yml"
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Build svix server image
22+
run: docker compose build
23+
working-directory: ./server
24+
25+
- uses: actions/setup-python@v5
26+
name: Install Python
27+
with:
28+
python-version: "3.11"
29+
30+
- name: Install deps
31+
run: |
32+
python -m pip install --upgrade pip
33+
python -m pip install -r requirements.txt .
34+
python -m pip install -r requirements-dev.txt .
35+
working-directory: ./python
36+
37+
- name: Regen openapi libs
38+
run: ./scripts/generate_openapi.sh
39+
working-directory: ./python
40+
41+
- name: Check typing on client tests
42+
run: mypy tests/test_client.py
43+
working-directory: ./python
44+
45+
- name: Run Python tests
46+
run: pytest -sv
47+
working-directory: ./python

python/requirements-dev.txt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#
2-
# This file is autogenerated by pip-compile with Python 3.11
2+
# This file is autogenerated by pip-compile with Python 3.12
33
# by the following command:
44
#
55
# pip-compile --output-file=requirements-dev.txt requirements.in/development.txt
66
#
7-
anyio==4.4.0
7+
anyio==4.6.0
88
# via httpcore
99
attrs==24.2.0
10-
# via openapi-python-client
10+
# via
11+
# openapi-python-client
12+
# pytest-docker
1113
autoflake==2.3.1
1214
# via openapi-python-client
1315
black==24.8.0
@@ -18,6 +20,9 @@ certifi==2024.8.30
1820
# via
1921
# httpcore
2022
# httpx
23+
# requests
24+
charset-normalizer==3.3.2
25+
# via requests
2126
click==8.1.7
2227
# via
2328
# black
@@ -35,6 +40,7 @@ idna==3.10
3540
# via
3641
# anyio
3742
# httpx
43+
# requests
3844
iniconfig==2.0.0
3945
# via pytest
4046
isort==5.13.2
@@ -44,7 +50,9 @@ jinja2==3.1.4
4450
# -r requirements.in/development.txt
4551
# openapi-python-client
4652
markupsafe==2.1.5
47-
# via jinja2
53+
# via
54+
# jinja2
55+
# werkzeug
4856
mypy==1.11.2
4957
# via -r requirements.in/development.txt
5058
mypy-extensions==1.0.0
@@ -62,7 +70,7 @@ pathspec==0.12.1
6270
# via black
6371
pip-tools==7.4.1
6472
# via -r requirements.in/development.txt
65-
platformdirs==4.3.3
73+
platformdirs==4.3.6
6674
# via black
6775
pluggy==1.5.0
6876
# via pytest
@@ -75,12 +83,20 @@ pyproject-hooks==1.1.0
7583
# build
7684
# pip-tools
7785
pytest==8.3.3
86+
# via
87+
# -r requirements.in/development.txt
88+
# pytest-docker
89+
pytest-docker==3.1.1
90+
# via -r requirements.in/development.txt
91+
pytest-httpserver==1.1.0
7892
# via -r requirements.in/development.txt
7993
python-dateutil==2.9.0.post0
8094
# via openapi-python-client
8195
pyyaml==6.0.2
8296
# via openapi-python-client
83-
ruff==0.6.5
97+
requests==2.32.3
98+
# via -r requirements.in/development.txt
99+
ruff==0.6.8
84100
# via -r requirements.in/development.txt
85101
shellingham==1.5.4
86102
# via openapi-python-client
@@ -98,6 +114,10 @@ typing-extensions==4.12.2
98114
# mypy
99115
# pydantic
100116
# typer
117+
urllib3==2.2.3
118+
# via requests
119+
werkzeug==3.0.4
120+
# via pytest-httpserver
101121
wheel==0.44.0
102122
# via pip-tools
103123

python/requirements.in/development.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ pytest
55
httpx>=0.23.0
66
openapi-python-client>=0.14.1,<0.15 # I think version 0.15 is now dangerous for us? https://github.com/openapi-generators/openapi-python-client/pull/775#issuecomment-1646977834
77
jinja2>=3.1.3
8+
pytest-docker
9+
pytest-httpserver
10+
requests

python/tests/conftest.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import os
2+
import shutil
3+
from subprocess import CalledProcessError, check_output
4+
5+
import pytest
6+
import requests
7+
from requests.adapters import HTTPAdapter
8+
from svix.api import Svix, SvixOptions
9+
from urllib3.util.retry import Retry
10+
11+
SVIX_ORG_ID = "org_svix_python_tests"
12+
13+
14+
def pytest_collection_modifyitems(config, items):
15+
"""Client tests require docker compose (v2 or v1) so skip them if it is not
16+
installed on host."""
17+
skipper = None
18+
if shutil.which("docker") is None:
19+
skipper = pytest.mark.skip(reason="skipping test as docker command is missing")
20+
else:
21+
docker_compose_available = False
22+
try:
23+
# check if docker compose v2 if available
24+
check_output(["docker", "compose", "version"])
25+
docker_compose_available = True
26+
except CalledProcessError:
27+
# check if docker compose v1 if available
28+
docker_compose_available = shutil.which("docker-compose") is not None
29+
finally:
30+
if not docker_compose_available:
31+
skipper = pytest.mark.skip(
32+
reason="skipping test as docker compose is missing"
33+
)
34+
if skipper is not None:
35+
for item in items:
36+
if item.module.__name__ == "tests.test_client":
37+
item.add_marker(skipper)
38+
39+
40+
@pytest.fixture(scope="session")
41+
def docker_compose_command():
42+
try:
43+
# use docker compose v2 if available
44+
check_output(["docker", "compose", "version"])
45+
return "docker compose"
46+
except Exception:
47+
# fallback on v1 otherwise
48+
return "docker-compose"
49+
50+
51+
@pytest.fixture(scope="session")
52+
def docker_compose_file():
53+
return [
54+
os.path.join(os.path.dirname(__file__), "../../server/docker-compose.yml"),
55+
os.path.join(os.path.dirname(__file__), "docker-compose.override.yml"),
56+
]
57+
58+
59+
@pytest.fixture(scope="session")
60+
def docker_compose(docker_services):
61+
return docker_services._docker_compose
62+
63+
64+
@pytest.fixture(scope="session")
65+
def httpserver_listen_address():
66+
# Use IP address in the docker bridge network as server hostname in order for
67+
# the svix server executed in a docker container to successfully send webhooks
68+
# to the HTTP server executed on the host
69+
return ("172.17.0.1", 0)
70+
71+
72+
@pytest.fixture(scope="session")
73+
def svix_server_url(docker_services):
74+
# svix server container exposes a free port to the docker host,
75+
# we use the docker network gateway IP in case the tests are also
76+
# executed in a container
77+
svix_server_port = docker_services.port_for("backend", 8071)
78+
return f"http://172.17.0.1:{svix_server_port}"
79+
80+
81+
@pytest.fixture(autouse=True, scope="session")
82+
def svix_server(svix_server_url):
83+
"""Spawn a Svix server for the tests session using docker compose"""
84+
# wait for the svix backend service to be up and responding
85+
request_session = requests.Session()
86+
retries = Retry(total=10, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
87+
request_session.mount("http://", HTTPAdapter(max_retries=retries))
88+
api_url = f"{svix_server_url}/api/v1/health/"
89+
response = request_session.get(api_url)
90+
assert response
91+
92+
93+
@pytest.fixture(autouse=True)
94+
def svix_wiper(docker_compose):
95+
"""Ensure stateless tests"""
96+
yield
97+
# wipe svix database after each test to ensure stateless tests
98+
docker_compose.execute(
99+
f"exec -T backend svix-server wipe --yes-i-know-what-im-doing {SVIX_ORG_ID}"
100+
)
101+
102+
103+
@pytest.fixture(scope="session")
104+
def svix_api(svix_server_url, docker_compose):
105+
# generate bearer token to authorize communication with the svix server
106+
exec_output = docker_compose.execute(
107+
f"exec -T backend svix-server jwt generate {SVIX_ORG_ID}"
108+
)
109+
svix_auth_token = (
110+
exec_output.decode()
111+
.replace("Token (Bearer): ", "")
112+
.replace("\r", "")
113+
.replace("\n", "")
114+
)
115+
return Svix(
116+
svix_auth_token,
117+
SvixOptions(server_url=svix_server_url),
118+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: "3.7"
2+
services:
3+
backend:
4+
environment:
5+
SVIX_JWT_SECRET: "x"
6+
SVIX_ENDPOINT_HTTPS_ONLY: "false"
7+
SVIX_WHITELIST_SUBNETS: "[127.0.0.1/32, 172.17.0.0/16]"
8+
pgbouncer:
9+
ports:
10+
- "8079:5432" # Needed for sqlx
11+
redis:
12+
ports:
13+
- "8078:6379" # Needed for sqlx

0 commit comments

Comments
 (0)