Skip to content

Commit

Permalink
python: Add unit tests for Svix API client (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
anlambert committed Sep 16, 2024
1 parent 8bb403c commit 2c7ea32
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 44 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Python Tests
on:
push:
paths:
- "python/**"
- "openapi.json"
pull_request:
paths:
- "python/**"
- "openapi.json"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build svix server image
run: docker compose build
working-directory: ./server

- uses: actions/setup-python@v2
name: Install Python
with:
python-version: "3.11"

- name: Install deps
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt .
python -m pip install -r requirements-dev.txt .
working-directory: ./python

- name: Regen openapi libs
run: ./scripts/generate_openapi.sh
working-directory: ./python

- name: Check typing on client tests
run: mypy tests/test_client.py
working-directory: ./python

- name: Run Python tests
run: pytest -sv
working-directory: ./python
99 changes: 55 additions & 44 deletions python/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,109 +1,120 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --output-file=requirements-dev.txt requirements.in/development.txt
#
anyio==3.5.0
# via httpcore
attrs==21.4.0
anyio==4.4.0
# via
# httpcore
# httpx
attrs==24.2.0
# via
# openapi-python-client
# pytest
autoflake==1.4
# pytest-docker
autoflake==2.3.1
# via openapi-python-client
black==23.3.0
black==24.8.0
# via openapi-python-client
build==0.10.0
build==1.2.2
# via pip-tools
certifi==2024.7.4
certifi==2024.8.30
# via
# httpcore
# httpx
click==8.0.1
# requests
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via
# black
# pip-tools
# typer
h11==0.12.0
h11==0.14.0
# via httpcore
httpcore==0.15.0
httpcore==0.17.3
# via httpx
httpx==0.23.0
httpx==0.24.1
# via
# -r requirements.in/development.txt
# openapi-python-client
idna==3.3
idna==3.10
# via
# anyio
# rfc3986
iniconfig==1.1.1
# httpx
# requests
iniconfig==2.0.0
# via pytest
isort==5.8.0
isort==5.13.2
# via openapi-python-client
jinja2==3.1.3
jinja2==3.1.4
# via
# -r requirements.in/development.txt
# openapi-python-client
markupsafe==2.1.0
markupsafe==2.1.5
# via jinja2
mypy==1.4.0
mypy==1.11.2
# via -r requirements.in/development.txt
mypy-extensions==1.0.0
# via
# black
# mypy
openapi-python-client==0.14.1
# via -r requirements.in/development.txt
packaging==23.1
packaging==24.1
# via
# black
# build
# pytest
pathspec==0.11.1
pathspec==0.12.1
# via black
pip-tools==6.13.0
pip-tools==7.4.1
# via -r requirements.in/development.txt
platformdirs==3.5.1
platformdirs==4.3.3
# via black
pluggy==0.13.1
pluggy==1.5.0
# via pytest
py==1.10.0
# via pytest
pydantic==1.10.13
pydantic==1.10.18
# via openapi-python-client
pyflakes==2.3.1
pyflakes==3.2.0
# via autoflake
pyproject-hooks==1.0.0
# via build
pytest==6.2.4
pyproject-hooks==1.1.0
# via
# build
# pip-tools
pytest==8.3.3
# via
# -r requirements.in/development.txt
# pytest-docker
pytest-docker==3.1.1
# via -r requirements.in/development.txt
python-dateutil==2.8.2
python-dateutil==2.9.0.post0
# via openapi-python-client
pyyaml==6.0.1
pyyaml==6.0.2
# via openapi-python-client
rfc3986[idna2008]==1.5.0
# via httpx
ruff==0.4.8
requests==2.32.3
# via -r requirements.in/development.txt
ruff==0.6.5
# via -r requirements.in/development.txt
shellingham==1.4.0
shellingham==1.5.4
# via openapi-python-client
six==1.16.0
# via python-dateutil
sniffio==1.2.0
sniffio==1.3.1
# via
# anyio
# httpcore
# httpx
toml==0.10.2
# via pytest
typer==0.7.0
typer==0.9.4
# via openapi-python-client
typing-extensions==4.6.3
typing-extensions==4.12.2
# via
# mypy
# pydantic
wheel==0.40.0
# typer
urllib3==2.2.3
# via requests
wheel==0.44.0
# via pip-tools

# The following packages are considered to be unsafe in a requirements file:
Expand Down
2 changes: 2 additions & 0 deletions python/requirements.in/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pytest
httpx>=0.23.0
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
jinja2>=3.1.3
pytest-docker
requests
111 changes: 111 additions & 0 deletions python/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import os
import shutil
from subprocess import CalledProcessError, check_output

import pytest
import requests
from requests.adapters import HTTPAdapter
from svix.api import Svix, SvixOptions
from urllib3.util.retry import Retry

SVIX_ORG_ID = "org_svix_python_tests"


def pytest_collection_modifyitems(config, items):
"""Tests require docker compose (v2 or v1) so skip them if it is not
installed on host."""
skipper = None
if shutil.which("docker") is None:
skipper = pytest.mark.skip(reason="skipping test as docker command is missing")
else:
docker_compose_available = False
try:
# check if docker compose v2 if available
check_output(["docker", "compose", "version"])
docker_compose_available = True
except CalledProcessError:
# check if docker compose v1 if available
docker_compose_available = shutil.which("docker-compose") is not None
finally:
if not docker_compose_available:
skipper = pytest.mark.skip(
reason="skipping test as docker compose is missing"
)
if skipper is not None:
for item in items:
item.add_marker(skipper)


@pytest.fixture(scope="session")
def docker_compose_command():
try:
# use docker compose v2 if available
check_output(["docker", "compose", "version"])
return "docker compose"
except Exception:
# fallback on v1 otherwise
return "docker-compose"


@pytest.fixture(scope="session")
def docker_compose_file():
return [
os.path.join(os.path.dirname(__file__), "../../server/docker-compose.yml"),
os.path.join(
os.path.dirname(__file__), "../../server/docker-compose.override.yml"
),
]


@pytest.fixture(scope="session")
def docker_compose(docker_services):
return docker_services._docker_compose


@pytest.fixture(scope="session")
def svix_server_url(docker_services):
# svix server container exposes a free port to the docker host,
# we use the docker network gateway IP in case the tests are also
# executed in a container
svix_server_port = docker_services.port_for("backend", 8071)
return f"http://172.17.0.1:{svix_server_port}"


@pytest.fixture(autouse=True, scope="session")
def svix_server(svix_server_url):
"""Spawn a Svix server for the tests session using docker compose"""
# wait for the svix backend service to be up and responding
request_session = requests.Session()
retries = Retry(total=10, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
request_session.mount("http://", HTTPAdapter(max_retries=retries))
api_url = f"{svix_server_url}/api/v1/health/"
response = request_session.get(api_url)
assert response


@pytest.fixture(autouse=True)
def svix_wiper(docker_compose):
"""Ensure stateless tests"""
yield
# wipe svix database after each test to ensure stateless tests
docker_compose.execute(
f"exec -T backend svix-server wipe --yes-i-know-what-im-doing {SVIX_ORG_ID}"
)


@pytest.fixture(scope="session")
def svix_api(svix_server_url, docker_compose):
# generate bearer token to authorize communication with the svix server
exec_output = docker_compose.execute(
f"exec -T backend svix-server jwt generate {SVIX_ORG_ID}"
)
svix_auth_token = (
exec_output.decode()
.replace("Token (Bearer): ", "")
.replace("\r", "")
.replace("\n", "")
)
return Svix(
svix_auth_token,
SvixOptions(server_url=svix_server_url),
)
Loading

0 comments on commit 2c7ea32

Please sign in to comment.