Skip to content

Commit 4f7a445

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 3f702c6 commit 4f7a445

5 files changed

Lines changed: 43 additions & 3 deletions

File tree

src/labthings_fastapi/server/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def __init__(
9898
self._config = ThingServerConfig(
9999
things=things,
100100
settings_folder=settings_folder,
101+
api_prefix=api_prefix,
101102
application_config=application_config,
102103
)
103104
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():
@@ -67,3 +68,31 @@ def test_server_thing_descriptions():
6768
prop = thing_description["properties"][prop_name]
6869
expected_href = thing_name + "/" + prop_name
6970
assert prop["forms"][0]["href"] == expected_href
71+
72+
73+
def test_api_prefix():
74+
"""Check we can add a prefix to the URLs on a server."""
75+
76+
class Example(lt.Thing):
77+
"""An example Thing"""
78+
79+
server = lt.ThingServer(things={"example": Example}, api_prefix="/api/v3")
80+
paths = [route.path for route in server.app.routes if isinstance(route, Route)]
81+
for expected_path in [
82+
"/api/v3/action_invocations",
83+
"/api/v3/action_invocations/{id}",
84+
"/api/v3/action_invocations/{id}/output",
85+
"/api/v3/action_invocations/{id}",
86+
"/api/v3/blob/{blob_id}",
87+
"/api/v3/thing_descriptions/",
88+
"/api/v3/example/",
89+
]:
90+
assert expected_path in paths
91+
92+
unprefixed_paths = {p for p in paths if not p.startswith("/api/v3/")}
93+
assert unprefixed_paths == {
94+
"/openapi.json",
95+
"/docs",
96+
"/docs/oauth2-redirect",
97+
"/redoc",
98+
}

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
@@ -63,9 +63,9 @@ def throw_value_error(self) -> None:
6363
@pytest.fixture
6464
def thing_client_and_thing():
6565
"""Yield a test client connected to a ThingServer and the Thing itself."""
66-
server = lt.ThingServer({"test_thing": ThingToTest})
66+
server = lt.ThingServer({"test_thing": ThingToTest}, api_prefix="/api/v1")
6767
with TestClient(server.app) as client:
68-
thing_client = lt.ThingClient.from_url("/test_thing/", client=client)
68+
thing_client = lt.ThingClient.from_url("/api/v1/test_thing/", client=client)
6969
thing = server.things["test_thing"]
7070
yield thing_client, thing
7171

0 commit comments

Comments
 (0)