Skip to content

Commit 9f8da53

Browse files
committed
Add tests for the API prefix and use it.
The previous commit laid the groundwork but failed to actually set the API prefix. This is now fixed. The API prefix is tested in a couple of places: validation is tested in `test_server_config_model`, and the endpoints are checked in `test_server` explicitly, and `test_thing_client` implicitly (because we use a prefix for the thing that's tested).
1 parent 5d2e00e commit 9f8da53

File tree

5 files changed

+43
-3
lines changed

5 files changed

+43
-3
lines changed

src/labthings_fastapi/server/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def __init__(
9494
self._config = ThingServerConfig(
9595
things=things,
9696
settings_folder=settings_folder,
97+
api_prefix=api_prefix,
9798
application_config=application_config,
9899
)
99100
self.app = FastAPI(lifespan=self.lifespan)

src/labthings_fastapi/server/config_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def thing_configs(self) -> Mapping[ThingName, ThingConfig]:
182182

183183
api_prefix: str = Field(
184184
default="",
185-
pattern="(\/[\w-]+)*",
185+
pattern=r"^(\/[\w-]+)*$",
186186
description=(
187187
"""A prefix added to all endpoints, including Things.
188188

tests/test_server.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pytest
88
import labthings_fastapi as lt
99
from fastapi.testclient import TestClient
10+
from starlette.routing import Route
1011

1112

1213
def test_server_from_config_non_thing_error():
@@ -63,3 +64,31 @@ def test_server_thing_descriptions():
6364
prop = thing_description["properties"][prop_name]
6465
expected_href = thing_name + "/" + prop_name
6566
assert prop["forms"][0]["href"] == expected_href
67+
68+
69+
def test_api_prefix():
70+
"""Check we can add a prefix to the URLs on a server."""
71+
72+
class Example(lt.Thing):
73+
"""An example Thing"""
74+
75+
server = lt.ThingServer(things={"example": Example}, api_prefix="/api/v3")
76+
paths = [route.path for route in server.app.routes if isinstance(route, Route)]
77+
for expected_path in [
78+
"/api/v3/action_invocations",
79+
"/api/v3/action_invocations/{id}",
80+
"/api/v3/action_invocations/{id}/output",
81+
"/api/v3/action_invocations/{id}",
82+
"/api/v3/blob/{blob_id}",
83+
"/api/v3/thing_descriptions/",
84+
"/api/v3/example/",
85+
]:
86+
assert expected_path in paths
87+
88+
unprefixed_paths = {p for p in paths if not p.startswith("/api/v3/")}
89+
assert unprefixed_paths == {
90+
"/openapi.json",
91+
"/docs",
92+
"/docs/oauth2-redirect",
93+
"/redoc",
94+
}

tests/test_server_config_model.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ def test_ThingServerConfig():
100100
with pytest.raises(ValidationError):
101101
ThingServerConfig(things={name: MyThing})
102102

103+
# Check some good prefixes
104+
for prefix in ["", "/api", "/api/v2", "/api-v2"]:
105+
config = ThingServerConfig(things={}, api_prefix=prefix)
106+
assert config.api_prefix == prefix
107+
108+
# Check some bad prefixes
109+
for prefix in ["api", "/api/", "api/v2", "/badchars!"]:
110+
with pytest.raises(ValidationError):
111+
ThingServerConfig(things={}, api_prefix=prefix)
112+
103113

104114
def test_unimportable_modules():
105115
"""Test that unimportable modules raise errors as expected."""

tests/test_thing_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ def throw_value_error(self) -> None:
6161
@pytest.fixture
6262
def thing_client():
6363
"""Yield a test client connected to a ThingServer."""
64-
server = lt.ThingServer({"test_thing": ThingToTest})
64+
server = lt.ThingServer({"test_thing": ThingToTest}, api_prefix="/api/v1")
6565
with TestClient(server.app) as client:
66-
yield lt.ThingClient.from_url("/test_thing/", client=client)
66+
yield lt.ThingClient.from_url("/api/v1/test_thing/", client=client)
6767

6868

6969
def test_reading_and_setting_properties(thing_client):

0 commit comments

Comments
 (0)