Skip to content

Commit

Permalink
Feature/sqlite repo: Identify type signature and annotations for inte…
Browse files Browse the repository at this point in the history
…rface and sqlite implementation (#66)

Prepares testing support for #26 

- adds support for test markers
- `make test` works. added test-integration marker and is currently WIP.
- refactored unit tests to have dummy functions move to conftest. fix
their type annotaitons. added e2e api testing support in start_server
and conftest. added unit test configuration setup in pyproject.
  • Loading branch information
codecakes authored Sep 9, 2024
1 parent b277616 commit 953e5de
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 57 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/stage_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ jobs:
uses: ./.github/actions

- name: Run tests
run: |
poetry run pytest
run: make test

pre-commit:
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ coverage.xml
*.mo
*.pot

# Django stuff:
# Miscellaneuos configuration stuff:
*.log
local_settings.py
db.sqlite3
*.db

# Flask stuff:
instance/
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ pip-install:
pip install --prefer-binary --use-pep517 --check-build-dependencies .[dev]

test:
pytest -s xcov19/tests/
APP_ENV=test APP_DB_ENGINE_URL="sqlite+aiosqlite://" pytest -s xcov19/tests/ -m "not integration"

test-integration:
APP_ENV=test APP_DB_ENGINE_URL="sqlite+aiosqlite://" pytest -s xcov19/tests/ -m "integration"

todos:
@grep -rn "TODO:" xcov19/ --exclude-dir=node_modules --include="*.py"
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ testpaths = [
"xcov19/tests",
]
asyncio_mode = "auto"
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
"unit: marks tests as unit tests",
# Add more markers as needed
]
# Add env vars when running pytest
env = [
"APP_ENV=test",
"APP_DB_ENGINE_URL=sqlite+aiosqlite://"
]

[tool.pyright]
pythonVersion = "3.12"
Expand Down
2 changes: 1 addition & 1 deletion run.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

poetry run python3 -m xcov19.dev
APP_ENV=dev APP_DB_ENGINE_URL="sqlite+aiosqlite:///xcov19.db" poetry run python3 -m xcov19.dev
12 changes: 8 additions & 4 deletions xcov19/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
https://docs.pydantic.dev/latest/usage/settings/
"""

from typing import Annotated
from blacksheep import FromHeader
from pydantic import BaseModel
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict


Expand All @@ -26,6 +27,8 @@ class Site(BaseModel):


class Settings(BaseSettings):
db_engine_url: Annotated[str, "database connection string"] = Field(default=...)

# to override info:
# export app_info='{"title": "x", "version": "0.0.2"}'
info: APIInfo = APIInfo()
Expand All @@ -34,13 +37,14 @@ class Settings(BaseSettings):
# export app_app='{"show_error_details": True}'
app: App = App()

db_engine_url: str = "sqlite+aiosqlite:///" # "sqlite+aiosqlite:///xcov19.db"

model_config = SettingsConfigDict(env_prefix="APP_")


def load_settings() -> Settings:
return Settings()
settings = Settings()
if not settings.db_engine_url:
raise ValueError("Missing environment variable: APP_DB_ENGINE_URL")
return settings


class FromOriginMatchHeader(FromHeader[str]):
Expand Down
63 changes: 59 additions & 4 deletions xcov19/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from collections.abc import Callable
from typing import List

from blacksheep.testing import TestClient
import pytest

from blacksheep import Application
from xcov19.dto import (
AnonymousId,
GeoLocation,
Expand All @@ -14,6 +16,10 @@
from xcov19.services.geolocation import LocationQueryServiceInterface
from xcov19.utils.mixins import InterfaceProtocolCheckMixin

import random

RANDOM_SEED = random.seed(1)

# Same as using @pytest.mark.anyio
pytestmark = pytest.mark.anyio

Expand All @@ -29,12 +35,12 @@ def anyio_backend(request):


@pytest.fixture(scope="class")
def dummy_coordinates():
def dummy_coordinates() -> GeoLocation:
return GeoLocation(lat=0, lng=0)


@pytest.fixture(scope="class")
def dummy_geolocation_query_json(dummy_coordinates):
def dummy_geolocation_query_json(dummy_coordinates) -> LocationQueryJSON:
return LocationQueryJSON(
location=dummy_coordinates,
cust_id=AnonymousId(cust_id="test_cust_id"),
Expand All @@ -43,8 +49,43 @@ def dummy_geolocation_query_json(dummy_coordinates):


@pytest.fixture(scope="class")
def stub_location_srvc():
return StubLocationQueryServiceImpl
def dummy_reverse_geo_lookup_svc() -> Callable[[LocationQueryJSON], dict]:
def callback(query: LocationQueryJSON) -> dict:
return {}

return callback


@pytest.fixture(scope="class")
def dummy_patient_query_lookup_svc_none() -> (
Callable[[Address, LocationQueryJSON], list]
):
def callback(address: Address, query: LocationQueryJSON) -> list:
return []

return callback


@pytest.fixture(scope="class")
def dummy_patient_query_lookup_svc() -> Callable[[Address, LocationQueryJSON], list]:
def callback(address: Address, query: LocationQueryJSON) -> list:
return [
FacilitiesResult(
name="Test facility",
address=Address(),
geolocation=GeoLocation(lat=0.0, lng=0.0),
contact="+919999999999",
facility_type="nursing",
ownership="charity",
specialties=["surgery", "pediatrics"],
stars=4,
reviews=120,
rank=random.randint(1, 20),
estimated_time=20,
)
]

return callback


class StubLocationQueryServiceImpl(
Expand Down Expand Up @@ -82,3 +123,17 @@ async def fetch_facilities(
estimated_time=20,
)
]


@pytest.fixture(scope="class")
def stub_location_srvc() -> LocationQueryServiceInterface:
return StubLocationQueryServiceImpl


@pytest.fixture(scope="function", name="client")
async def test_client():
# Create a test client
async def start_client(app: Application) -> TestClient:
return TestClient(app)

return start_client
13 changes: 13 additions & 0 deletions xcov19/tests/start_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from collections.abc import AsyncGenerator
from xcov19.app.main import app
from blacksheep import Application


async def start_server() -> AsyncGenerator[Application, None]:
"""Start a test server for automated testing."""
try:
await app.start()
yield app
finally:
if app.started:
await app.stop()
37 changes: 37 additions & 0 deletions xcov19/tests/test_geolocation_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import json
import pytest
from xcov19.dto import LocationQueryJSON, GeoLocation, AnonymousId, QueryId
from blacksheep import Content, Response


@pytest.mark.integration
@pytest.mark.usefixtures("client")
class TestGeolocationAPI:
async def test_location_query_endpoint(self, client):
# Prepare the request payload
location_query = LocationQueryJSON(
location=GeoLocation(lat=0, lng=0),
cust_id=AnonymousId(cust_id="test_cust_id"),
query_id=QueryId(query_id="test_query_id"),
)

# Send a POST request to the /geo endpoint
query = location_query.model_dump(round_trip=True)
binary_data = json.dumps(query).encode("utf-8")
print("binary data", binary_data, type(binary_data))
response: Response = await client.post(
"/geo",
content=Content(b"application/json", binary_data),
# Add the required header
headers={
"X-Origin-Match-Header": "secret",
},
)

# The current implementation returns ok(), which is null in JSON
# response_text = await response.text()
# assert response_text.lower() == "resource not found"
# Assert the response
assert response.content_type() == b"text/plain; charset=utf-8"
# assert response.content == b''
assert response.status == 200
Loading

0 comments on commit 953e5de

Please sign in to comment.