Skip to content

Commit 17baaf3

Browse files
add paging links tests (#195)
* add paging links tests * fix url links with root-path * fix * update requirements * update changelog
1 parent e063785 commit 17baaf3

File tree

5 files changed

+120
-12
lines changed

5 files changed

+120
-12
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Fixed
6+
7+
- fix links when app is mounted behind proxy or has router-prefix ([#195](https://github.com/stac-utils/stac-fastapi-pgstac/pull/195))
8+
59
## [4.0.2] - 2025-02-18
610

711
### Fixed

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
"orjson",
1111
"pydantic",
1212
"stac_pydantic==3.1.*",
13-
"stac-fastapi.api~=5.0",
14-
"stac-fastapi.extensions~=5.0",
15-
"stac-fastapi.types~=5.0",
13+
"stac-fastapi.api>=5.0,<5.1",
14+
"stac-fastapi.extensions>=5.0,<5.1",
15+
"stac-fastapi.types>=5.0,<5.1",
1616
"asyncpg",
1717
"buildpg",
1818
"brotli_asgi",

stac_fastapi/pgstac/models/links.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ def base_url(self):
5151
@property
5252
def url(self):
5353
"""Get the current request url."""
54-
return str(self.request.url)
54+
url = urljoin(str(self.request.base_url), self.request.url.path.lstrip("/"))
55+
if qs := self.request.url.query:
56+
url += f"?{qs}"
57+
58+
return url
5559

5660
def resolve(self, url):
5761
"""Resolve url to the current request url."""
@@ -143,7 +147,7 @@ def link_next(self) -> Optional[Dict[str, Any]]:
143147
"rel": Relations.next.value,
144148
"type": MimeTypes.geojson.value,
145149
"method": method,
146-
"href": f"{self.request.url}",
150+
"href": self.url,
147151
"body": {**self.request.postbody, "token": f"next:{self.next}"},
148152
}
149153

@@ -167,7 +171,7 @@ def link_prev(self) -> Optional[Dict[str, Any]]:
167171
"rel": Relations.previous.value,
168172
"type": MimeTypes.geojson.value,
169173
"method": method,
170-
"href": f"{self.request.url}",
174+
"href": self.url,
171175
"body": {**self.request.postbody, "token": f"prev:{self.prev}"},
172176
}
173177
return None

tests/api/test_links.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import pytest
2+
from fastapi import APIRouter, FastAPI
3+
from starlette.requests import Request
4+
from starlette.testclient import TestClient
5+
6+
from stac_fastapi.pgstac.models import links as app_links
7+
8+
9+
@pytest.mark.parametrize("root_path", ["", "/api/v1"])
10+
@pytest.mark.parametrize("prefix", ["", "/stac"])
11+
def tests_app_links(prefix, root_path): # noqa: C901
12+
endpoint_prefix = root_path + prefix
13+
url_prefix = "http://stac.io" + endpoint_prefix
14+
15+
app = FastAPI(root_path=root_path)
16+
router = APIRouter(prefix=prefix)
17+
app.state.router_prefix = router.prefix
18+
19+
@router.get("/search")
20+
@router.post("/search")
21+
async def search(request: Request):
22+
links = app_links.PagingLinks(request, next="yo:2", prev="yo:1")
23+
return {
24+
"url": links.url,
25+
"base_url": links.base_url,
26+
"links": await links.get_links(),
27+
}
28+
29+
@router.get("/collections")
30+
async def collections(request: Request):
31+
pgstac_next = {
32+
"rel": "next",
33+
"body": {"offset": 1},
34+
"href": "./collections",
35+
"type": "application/json",
36+
"merge": True,
37+
"method": "GET",
38+
}
39+
pgstac_prev = {
40+
"rel": "prev",
41+
"body": {"offset": 0},
42+
"href": "./collections",
43+
"type": "application/json",
44+
"merge": True,
45+
"method": "GET",
46+
}
47+
links = app_links.CollectionSearchPagingLinks(
48+
request, next=pgstac_next, prev=pgstac_prev
49+
)
50+
return {
51+
"url": links.url,
52+
"base_url": links.base_url,
53+
"links": await links.get_links(),
54+
}
55+
56+
app.include_router(router)
57+
58+
with TestClient(
59+
app,
60+
base_url="http://stac.io",
61+
root_path=root_path,
62+
) as client:
63+
response = client.get(f"{prefix}/search")
64+
assert response.status_code == 200
65+
assert response.json()["url"] == url_prefix + "/search"
66+
assert response.json()["base_url"].rstrip("/") == url_prefix
67+
links = response.json()["links"]
68+
for link in links:
69+
if link["rel"] in ["previous", "next"]:
70+
assert link["method"] == "GET"
71+
assert link["href"].startswith(url_prefix)
72+
assert {"next", "previous", "root", "self"} == {link["rel"] for link in links}
73+
74+
response = client.get(f"{prefix}/search", params={"limit": 1})
75+
assert response.status_code == 200
76+
assert response.json()["url"] == url_prefix + "/search?limit=1"
77+
assert response.json()["base_url"].rstrip("/") == url_prefix
78+
links = response.json()["links"]
79+
for link in links:
80+
if link["rel"] in ["previous", "next"]:
81+
assert link["method"] == "GET"
82+
assert "limit=1" in link["href"]
83+
assert link["href"].startswith(url_prefix)
84+
assert {"next", "previous", "root", "self"} == {link["rel"] for link in links}
85+
86+
response = client.post(f"{prefix}/search", json={})
87+
assert response.status_code == 200
88+
assert response.json()["url"] == url_prefix + "/search"
89+
assert response.json()["base_url"].rstrip("/") == url_prefix
90+
links = response.json()["links"]
91+
for link in links:
92+
if link["rel"] in ["previous", "next"]:
93+
assert link["method"] == "POST"
94+
assert link["href"].startswith(url_prefix)
95+
assert {"next", "previous", "root", "self"} == {link["rel"] for link in links}
96+
97+
response = client.get(f"{prefix}/collections")
98+
assert response.status_code == 200
99+
assert response.json()["url"] == url_prefix + "/collections"
100+
assert response.json()["base_url"].rstrip("/") == url_prefix
101+
links = response.json()["links"]
102+
for link in links:
103+
if link["rel"] in ["previous", "next"]:
104+
assert link["method"] == "GET"
105+
assert link["href"].startswith(url_prefix)
106+
assert {"next", "previous", "root", "self"} == {link["rel"] for link in links}

tests/conftest.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import json
32
import logging
43
import os
@@ -62,11 +61,6 @@
6261
)
6362

6463

65-
@pytest.fixture(scope="session")
66-
def event_loop():
67-
return asyncio.get_event_loop()
68-
69-
7064
@pytest.fixture(scope="session")
7165
def database(postgresql_proc):
7266
with DatabaseJanitor(

0 commit comments

Comments
 (0)