feat: implement API versioning /api/v1/
- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -54,9 +54,9 @@ async def geo_client(tmp_path: Path) -> AsyncClient: # type: ignore[misc]
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
||||
setup_payload = _SETUP_PAYLOAD.copy()
|
||||
setup_payload["database_path"] = settings.database_path
|
||||
await ac.post("/api/setup", json=setup_payload)
|
||||
await ac.post("/api/v1/setup", json=setup_payload)
|
||||
login = await ac.post(
|
||||
"/api/auth/login",
|
||||
"/api/v1/auth/login",
|
||||
json={"password": _SETUP_PAYLOAD["master_password"]},
|
||||
)
|
||||
assert login.status_code == 200
|
||||
@@ -85,7 +85,7 @@ class TestGeoLookup:
|
||||
"app.routers.geo.jail_service.lookup_ip",
|
||||
AsyncMock(return_value=result),
|
||||
):
|
||||
resp = await geo_client.get("/api/geo/lookup/1.2.3.4")
|
||||
resp = await geo_client.get("/api/v1/geo/lookup/1.2.3.4")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
@@ -107,7 +107,7 @@ class TestGeoLookup:
|
||||
"app.routers.geo.jail_service.lookup_ip",
|
||||
AsyncMock(return_value=result),
|
||||
):
|
||||
resp = await geo_client.get("/api/geo/lookup/8.8.8.8")
|
||||
resp = await geo_client.get("/api/v1/geo/lookup/8.8.8.8")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["currently_banned_in"] == []
|
||||
@@ -123,7 +123,7 @@ class TestGeoLookup:
|
||||
"app.routers.geo.jail_service.lookup_ip",
|
||||
AsyncMock(return_value=result),
|
||||
):
|
||||
resp = await geo_client.get("/api/geo/lookup/1.2.3.4")
|
||||
resp = await geo_client.get("/api/v1/geo/lookup/1.2.3.4")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["geo"] is None
|
||||
@@ -134,7 +134,7 @@ class TestGeoLookup:
|
||||
"app.routers.geo.jail_service.lookup_ip",
|
||||
AsyncMock(side_effect=ValueError("Invalid IP address: 'bad_ip'")),
|
||||
):
|
||||
resp = await geo_client.get("/api/geo/lookup/bad_ip")
|
||||
resp = await geo_client.get("/api/v1/geo/lookup/bad_ip")
|
||||
|
||||
assert resp.status_code == 400
|
||||
assert "detail" in resp.json()
|
||||
@@ -145,7 +145,7 @@ class TestGeoLookup:
|
||||
resp = await AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
).get("/api/geo/lookup/1.2.3.4")
|
||||
).get("/api/v1/geo/lookup/1.2.3.4")
|
||||
assert resp.status_code == 401
|
||||
|
||||
async def test_ipv6_address(self, geo_client: AsyncClient) -> None:
|
||||
@@ -159,7 +159,7 @@ class TestGeoLookup:
|
||||
"app.routers.geo.jail_service.lookup_ip",
|
||||
AsyncMock(return_value=result),
|
||||
):
|
||||
resp = await geo_client.get("/api/geo/lookup/2001:db8::1")
|
||||
resp = await geo_client.get("/api/v1/geo/lookup/2001:db8::1")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["ip"] == "2001:db8::1"
|
||||
@@ -179,7 +179,7 @@ class TestReResolve:
|
||||
"app.routers.geo.geo_service.re_resolve_all",
|
||||
AsyncMock(return_value={"resolved": 0, "total": 0}),
|
||||
):
|
||||
resp = await geo_client.post("/api/geo/re-resolve")
|
||||
resp = await geo_client.post("/api/v1/geo/re-resolve")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
@@ -188,7 +188,7 @@ class TestReResolve:
|
||||
|
||||
async def test_empty_when_no_unresolved_ips(self, geo_client: AsyncClient) -> None:
|
||||
"""Returns resolved=0, total=0 when geo_cache has no NULL country_code rows."""
|
||||
resp = await geo_client.post("/api/geo/re-resolve")
|
||||
resp = await geo_client.post("/api/v1/geo/re-resolve")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"resolved": 0, "total": 0}
|
||||
@@ -209,7 +209,7 @@ class TestReResolve:
|
||||
"lookup_batch",
|
||||
new_callable=lambda: AsyncMock(return_value=geo_result),
|
||||
):
|
||||
resp = await geo_client.post("/api/geo/re-resolve")
|
||||
resp = await geo_client.post("/api/v1/geo/re-resolve")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
@@ -222,7 +222,7 @@ class TestReResolve:
|
||||
resp = await AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
).post("/api/geo/re-resolve")
|
||||
).post("/api/v1/geo/re-resolve")
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ class TestGeoStats:
|
||||
"app.routers.geo.geo_service.cache_stats",
|
||||
AsyncMock(return_value=stats),
|
||||
):
|
||||
resp = await geo_client.get("/api/geo/stats")
|
||||
resp = await geo_client.get("/api/v1/geo/stats")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
@@ -257,7 +257,7 @@ class TestGeoStats:
|
||||
|
||||
async def test_stats_empty_cache(self, geo_client: AsyncClient) -> None:
|
||||
"""GET /api/geo/stats returns all zeros on a fresh database."""
|
||||
resp = await geo_client.get("/api/geo/stats")
|
||||
resp = await geo_client.get("/api/v1/geo/stats")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
@@ -274,7 +274,7 @@ class TestGeoStats:
|
||||
await db.execute("INSERT OR IGNORE INTO geo_cache (ip) VALUES (?)", ("8.8.8.8",))
|
||||
await db.commit()
|
||||
|
||||
resp = await geo_client.get("/api/geo/stats")
|
||||
resp = await geo_client.get("/api/v1/geo/stats")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["unresolved"] >= 2
|
||||
@@ -285,5 +285,5 @@ class TestGeoStats:
|
||||
resp = await AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
).get("/api/geo/stats")
|
||||
).get("/api/v1/geo/stats")
|
||||
assert resp.status_code == 401
|
||||
|
||||
Reference in New Issue
Block a user