Skip to content

Commit 2c7ea32

Browse files
committed
python: Add unit tests for Svix API client (WIP)
1 parent 8bb403c commit 2c7ea32

File tree

5 files changed

+286
-44
lines changed

5 files changed

+286
-44
lines changed

.github/workflows/python-tests.yml

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

python/requirements-dev.txt

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,120 @@
11
#
2-
# This file is autogenerated by pip-compile with Python 3.12
2+
# This file is autogenerated by pip-compile with Python 3.11
33
# by the following command:
44
#
55
# pip-compile --output-file=requirements-dev.txt requirements.in/development.txt
66
#
7-
anyio==3.5.0
8-
# via httpcore
9-
attrs==21.4.0
7+
anyio==4.4.0
8+
# via
9+
# httpcore
10+
# httpx
11+
attrs==24.2.0
1012
# via
1113
# openapi-python-client
12-
# pytest
13-
autoflake==1.4
14+
# pytest-docker
15+
autoflake==2.3.1
1416
# via openapi-python-client
15-
black==23.3.0
17+
black==24.8.0
1618
# via openapi-python-client
17-
build==0.10.0
19+
build==1.2.2
1820
# via pip-tools
19-
certifi==2024.7.4
21+
certifi==2024.8.30
2022
# via
2123
# httpcore
2224
# httpx
23-
click==8.0.1
25+
# requests
26+
charset-normalizer==3.3.2
27+
# via requests
28+
click==8.1.7
2429
# via
2530
# black
2631
# pip-tools
2732
# typer
28-
h11==0.12.0
33+
h11==0.14.0
2934
# via httpcore
30-
httpcore==0.15.0
35+
httpcore==0.17.3
3136
# via httpx
32-
httpx==0.23.0
37+
httpx==0.24.1
3338
# via
3439
# -r requirements.in/development.txt
3540
# openapi-python-client
36-
idna==3.3
41+
idna==3.10
3742
# via
3843
# anyio
39-
# rfc3986
40-
iniconfig==1.1.1
44+
# httpx
45+
# requests
46+
iniconfig==2.0.0
4147
# via pytest
42-
isort==5.8.0
48+
isort==5.13.2
4349
# via openapi-python-client
44-
jinja2==3.1.3
50+
jinja2==3.1.4
4551
# via
4652
# -r requirements.in/development.txt
4753
# openapi-python-client
48-
markupsafe==2.1.0
54+
markupsafe==2.1.5
4955
# via jinja2
50-
mypy==1.4.0
56+
mypy==1.11.2
5157
# via -r requirements.in/development.txt
5258
mypy-extensions==1.0.0
5359
# via
5460
# black
5561
# mypy
5662
openapi-python-client==0.14.1
5763
# via -r requirements.in/development.txt
58-
packaging==23.1
64+
packaging==24.1
5965
# via
6066
# black
6167
# build
6268
# pytest
63-
pathspec==0.11.1
69+
pathspec==0.12.1
6470
# via black
65-
pip-tools==6.13.0
71+
pip-tools==7.4.1
6672
# via -r requirements.in/development.txt
67-
platformdirs==3.5.1
73+
platformdirs==4.3.3
6874
# via black
69-
pluggy==0.13.1
75+
pluggy==1.5.0
7076
# via pytest
71-
py==1.10.0
72-
# via pytest
73-
pydantic==1.10.13
77+
pydantic==1.10.18
7478
# via openapi-python-client
75-
pyflakes==2.3.1
79+
pyflakes==3.2.0
7680
# via autoflake
77-
pyproject-hooks==1.0.0
78-
# via build
79-
pytest==6.2.4
81+
pyproject-hooks==1.1.0
82+
# via
83+
# build
84+
# pip-tools
85+
pytest==8.3.3
86+
# via
87+
# -r requirements.in/development.txt
88+
# pytest-docker
89+
pytest-docker==3.1.1
8090
# via -r requirements.in/development.txt
81-
python-dateutil==2.8.2
91+
python-dateutil==2.9.0.post0
8292
# via openapi-python-client
83-
pyyaml==6.0.1
93+
pyyaml==6.0.2
8494
# via openapi-python-client
85-
rfc3986[idna2008]==1.5.0
86-
# via httpx
87-
ruff==0.4.8
95+
requests==2.32.3
96+
# via -r requirements.in/development.txt
97+
ruff==0.6.5
8898
# via -r requirements.in/development.txt
89-
shellingham==1.4.0
99+
shellingham==1.5.4
90100
# via openapi-python-client
91101
six==1.16.0
92102
# via python-dateutil
93-
sniffio==1.2.0
103+
sniffio==1.3.1
94104
# via
95105
# anyio
96106
# httpcore
97107
# httpx
98-
toml==0.10.2
99-
# via pytest
100-
typer==0.7.0
108+
typer==0.9.4
101109
# via openapi-python-client
102-
typing-extensions==4.6.3
110+
typing-extensions==4.12.2
103111
# via
104112
# mypy
105113
# pydantic
106-
wheel==0.40.0
114+
# typer
115+
urllib3==2.2.3
116+
# via requests
117+
wheel==0.44.0
107118
# via pip-tools
108119

109120
# The following packages are considered to be unsafe in a requirements file:

python/requirements.in/development.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ 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+
requests

python/tests/conftest.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
"""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+
item.add_marker(skipper)
37+
38+
39+
@pytest.fixture(scope="session")
40+
def docker_compose_command():
41+
try:
42+
# use docker compose v2 if available
43+
check_output(["docker", "compose", "version"])
44+
return "docker compose"
45+
except Exception:
46+
# fallback on v1 otherwise
47+
return "docker-compose"
48+
49+
50+
@pytest.fixture(scope="session")
51+
def docker_compose_file():
52+
return [
53+
os.path.join(os.path.dirname(__file__), "../../server/docker-compose.yml"),
54+
os.path.join(
55+
os.path.dirname(__file__), "../../server/docker-compose.override.yml"
56+
),
57+
]
58+
59+
60+
@pytest.fixture(scope="session")
61+
def docker_compose(docker_services):
62+
return docker_services._docker_compose
63+
64+
65+
@pytest.fixture(scope="session")
66+
def svix_server_url(docker_services):
67+
# svix server container exposes a free port to the docker host,
68+
# we use the docker network gateway IP in case the tests are also
69+
# executed in a container
70+
svix_server_port = docker_services.port_for("backend", 8071)
71+
return f"http://172.17.0.1:{svix_server_port}"
72+
73+
74+
@pytest.fixture(autouse=True, scope="session")
75+
def svix_server(svix_server_url):
76+
"""Spawn a Svix server for the tests session using docker compose"""
77+
# wait for the svix backend service to be up and responding
78+
request_session = requests.Session()
79+
retries = Retry(total=10, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
80+
request_session.mount("http://", HTTPAdapter(max_retries=retries))
81+
api_url = f"{svix_server_url}/api/v1/health/"
82+
response = request_session.get(api_url)
83+
assert response
84+
85+
86+
@pytest.fixture(autouse=True)
87+
def svix_wiper(docker_compose):
88+
"""Ensure stateless tests"""
89+
yield
90+
# wipe svix database after each test to ensure stateless tests
91+
docker_compose.execute(
92+
f"exec -T backend svix-server wipe --yes-i-know-what-im-doing {SVIX_ORG_ID}"
93+
)
94+
95+
96+
@pytest.fixture(scope="session")
97+
def svix_api(svix_server_url, docker_compose):
98+
# generate bearer token to authorize communication with the svix server
99+
exec_output = docker_compose.execute(
100+
f"exec -T backend svix-server jwt generate {SVIX_ORG_ID}"
101+
)
102+
svix_auth_token = (
103+
exec_output.decode()
104+
.replace("Token (Bearer): ", "")
105+
.replace("\r", "")
106+
.replace("\n", "")
107+
)
108+
return Svix(
109+
svix_auth_token,
110+
SvixOptions(server_url=svix_server_url),
111+
)

0 commit comments

Comments
 (0)