Skip to content

Commit b6d240d

Browse files
authored
chore: add tests (#7)
1 parent 374b75a commit b6d240d

File tree

9 files changed

+324
-3
lines changed

9 files changed

+324
-3
lines changed

.github/workflows/cicd.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,19 @@ jobs:
1111
- uses: actions/checkout@v4
1212
- uses: actions/setup-python@v3
1313
- uses: pre-commit/[email protected]
14+
15+
test:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-python@v3
21+
22+
- name: Install uv
23+
uses: astral-sh/setup-uv@v4
24+
with:
25+
enable-cache: true
26+
27+
- name: Run tests
28+
run: |
29+
uv run pytest

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"tests"
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.pytestEnabled": true
7+
}

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ known_first_party = ["stac_auth_proxy"]
2929
profile = "black"
3030

3131
[tool.ruff]
32-
ignore = ["E501", "D203", "D212"]
32+
ignore = ["E501", "D205", "D212"]
3333
select = ["D", "E", "F"]
3434

3535
[build-system]
@@ -39,4 +39,5 @@ requires = ["hatchling>=1.12.0"]
3939
[dependency-groups]
4040
dev = [
4141
"pre-commit>=3.5.0",
42+
"pytest>=8.3.3",
4243
]

src/stac_auth_proxy/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@
55
It includes FastAPI routes for handling authentication, authorization, and interaction
66
with some internal STAC API.
77
"""
8+
9+
from .app import create_app
10+
from .config import Settings
11+
12+
__all__ = ["create_app", "Settings"]

src/stac_auth_proxy/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Main module for the STAC Auth Proxy."""
1+
"""Entry point for running the module without customized code."""
22

33
import uvicorn
44
from uvicorn.config import LOGGING_CONFIG

tests/conftest.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Pytest fixtures."""
2+
3+
import threading
4+
5+
import pytest
6+
import uvicorn
7+
from fastapi import FastAPI
8+
9+
10+
@pytest.fixture(scope="session")
11+
def source_api():
12+
"""Create upstream API for testing purposes."""
13+
app = FastAPI(docs_url="/api.html", openapi_url="/api")
14+
15+
for path, methods in {
16+
"/": ["GET"],
17+
"/conformance": ["GET"],
18+
"/queryables": ["GET"],
19+
"/search": ["GET", "POST"],
20+
"/collections": ["GET", "POST"],
21+
"/collections/{collection_id}": ["GET", "PUT", "DELETE"],
22+
"/collections/{collection_id}/items": ["GET", "POST"],
23+
"/collections/{collection_id}/items/{item_id}": [
24+
"GET",
25+
"PUT",
26+
"DELETE",
27+
],
28+
"/collections/{collection_id}/bulk_items": ["POST"],
29+
}.items():
30+
for method in methods:
31+
# NOTE: declare routes per method separately to avoid warning of "Duplicate Operation ID ... for function <lambda>"
32+
app.add_api_route(
33+
path,
34+
lambda: {"id": f"Response from {method}@{path}"},
35+
methods=[method],
36+
)
37+
38+
return app
39+
40+
41+
@pytest.fixture(scope="session")
42+
def source_api_server(source_api):
43+
"""Run the source API in a background thread."""
44+
host, port = "127.0.0.1", 8000
45+
server = uvicorn.Server(
46+
uvicorn.Config(
47+
source_api,
48+
host=host,
49+
port=port,
50+
)
51+
)
52+
thread = threading.Thread(target=server.run)
53+
thread.start()
54+
yield f"http://{host}:{port}"
55+
server.should_exit = True
56+
thread.join()

tests/test_defaults.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Basic test cases for the proxy app."""
2+
3+
import pytest
4+
from fastapi import FastAPI
5+
from fastapi.testclient import TestClient
6+
7+
from stac_auth_proxy import Settings, create_app
8+
9+
10+
@pytest.fixture(scope="module")
11+
def test_app(source_api_server: str) -> FastAPI:
12+
"""Fixture for the proxy app, pointing to the source API."""
13+
return create_app(
14+
Settings.model_validate(
15+
{
16+
"upstream_url": source_api_server,
17+
"oidc_discovery_url": "https://samples.auth0.com/.well-known/openid-configuration",
18+
"default_public": False,
19+
},
20+
)
21+
)
22+
23+
24+
@pytest.mark.parametrize(
25+
"path,method,expected_status",
26+
[
27+
("/", "GET", 200),
28+
("/conformance", "GET", 200),
29+
("/queryables", "GET", 200),
30+
("/search", "GET", 200),
31+
("/search", "POST", 200),
32+
("/collections", "GET", 200),
33+
("/collections", "POST", 403),
34+
("/collections/example-collection", "GET", 200),
35+
("/collections/example-collection", "PUT", 403),
36+
("/collections/example-collection", "DELETE", 403),
37+
("/collections/example-collection/items", "GET", 200),
38+
("/collections/example-collection/items", "POST", 403),
39+
("/collections/example-collection/items/example-item", "GET", 200),
40+
("/collections/example-collection/items/example-item", "PUT", 403),
41+
("/collections/example-collection/items/example-item", "DELETE", 403),
42+
("/collections/example-collection/bulk_items", "POST", 403),
43+
("/api.html", "GET", 200),
44+
("/api", "GET", 200),
45+
],
46+
)
47+
def test_default_public_true(source_api_server, path, method, expected_status):
48+
"""
49+
When default_public=true and private_endpoints aren't set, all endpoints should be
50+
public except for transaction endpoints.
51+
"""
52+
test_app = create_app(
53+
Settings.model_validate(
54+
{
55+
"upstream_url": source_api_server,
56+
"oidc_discovery_url": "https://samples.auth0.com/.well-known/openid-configuration",
57+
"default_public": True,
58+
},
59+
)
60+
)
61+
client = TestClient(test_app)
62+
response = client.request(method=method, url=path)
63+
assert response.status_code == expected_status
64+
65+
66+
@pytest.mark.parametrize(
67+
"path,method,expected_status",
68+
[
69+
("/", "GET", 403),
70+
("/conformance", "GET", 403),
71+
("/queryables", "GET", 403),
72+
("/search", "GET", 403),
73+
("/search", "POST", 403),
74+
("/collections", "GET", 403),
75+
("/collections", "POST", 403),
76+
("/collections/example-collection", "GET", 403),
77+
("/collections/example-collection", "PUT", 403),
78+
("/collections/example-collection", "DELETE", 403),
79+
("/collections/example-collection/items", "GET", 403),
80+
("/collections/example-collection/items", "POST", 403),
81+
("/collections/example-collection/items/example-item", "GET", 403),
82+
("/collections/example-collection/items/example-item", "PUT", 403),
83+
("/collections/example-collection/items/example-item", "DELETE", 403),
84+
("/collections/example-collection/bulk_items", "POST", 403),
85+
("/api.html", "GET", 200),
86+
("/api", "GET", 200),
87+
],
88+
)
89+
def test_default_public_false(source_api_server, path, method, expected_status):
90+
"""
91+
When default_public=false and private_endpoints aren't set, all endpoints should be
92+
public except for transaction endpoints.
93+
"""
94+
test_app = create_app(
95+
Settings.model_validate(
96+
{
97+
"upstream_url": source_api_server,
98+
"oidc_discovery_url": "https://samples.auth0.com/.well-known/openid-configuration",
99+
"default_public": False,
100+
},
101+
)
102+
)
103+
client = TestClient(test_app)
104+
response = client.request(method=method, url=path)
105+
assert response.status_code == expected_status

tests/test_openapi.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Tests for OpenAPI spec handling."""
2+
3+
from fastapi import FastAPI
4+
from fastapi.testclient import TestClient
5+
6+
from stac_auth_proxy import Settings, create_app
7+
8+
9+
def test_no_edit_openapi_spec(source_api_server):
10+
"""When no OpenAPI spec endpoint is set, the proxied OpenAPI spec is unaltered."""
11+
app = create_app(
12+
Settings(
13+
upstream_url=source_api_server,
14+
oidc_discovery_url="https://samples.auth0.com/.well-known/openid-configuration",
15+
openapi_spec_endpoint=None,
16+
)
17+
)
18+
client = TestClient(app)
19+
response = client.get("/api")
20+
assert response.status_code == 200
21+
openapi = response.json()
22+
assert "info" in openapi
23+
assert "openapi" in openapi
24+
assert "paths" in openapi
25+
assert "oidcAuth" not in openapi.get("components", {}).get("securitySchemes", {})
26+
27+
28+
def test_oidc_in_openapi_spec(source_api: FastAPI, source_api_server: str):
29+
"""When OpenAPI spec endpoint is set, the proxied OpenAPI spec is augmented with oidc details."""
30+
app = create_app(
31+
Settings(
32+
upstream_url=source_api_server,
33+
oidc_discovery_url="https://samples.auth0.com/.well-known/openid-configuration",
34+
openapi_spec_endpoint=source_api.openapi_url,
35+
)
36+
)
37+
client = TestClient(app)
38+
response = client.get(source_api.openapi_url)
39+
assert response.status_code == 200
40+
openapi = response.json()
41+
assert "info" in openapi
42+
assert "openapi" in openapi
43+
assert "paths" in openapi
44+
assert "oidcAuth" in openapi.get("components", {}).get("securitySchemes", {})

0 commit comments

Comments
 (0)