更新tjwater-cli network参数;更新metadb health方法

This commit is contained in:
2026-06-03 10:48:01 +08:00
parent f87dd91b2b
commit 4982efba5e
4 changed files with 121 additions and 11 deletions
+18
View File
@@ -1,5 +1,6 @@
import logging import logging
from fastapi import APIRouter, Depends, HTTPException, status, Query, Path from fastapi import APIRouter, Depends, HTTPException, status, Query, Path
import psycopg
from psycopg import AsyncConnection from psycopg import AsyncConnection
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
@@ -58,6 +59,7 @@ async def get_project_metadata(
code=project.code, code=project.code,
description=project.description, description=project.description,
gs_workspace=project.gs_workspace, gs_workspace=project.gs_workspace,
map_extent=project.map_extent,
status=project.status, status=project.status,
project_role=ctx.project_role, project_role=ctx.project_role,
geoserver=geoserver_payload, geoserver=geoserver_payload,
@@ -110,7 +112,23 @@ async def project_db_health(
检查PostgreSQL和TimescaleDB数据库的连接状态 检查PostgreSQL和TimescaleDB数据库的连接状态
""" """
try:
await pg_session.execute(text("SELECT 1")) await pg_session.execute(text("SELECT 1"))
except SQLAlchemyError as exc:
logger.error("Project PostgreSQL health check failed", exc_info=True)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Project PostgreSQL health check failed: {exc}",
) from exc
try:
async with ts_conn.cursor() as cur: async with ts_conn.cursor() as cur:
await cur.execute("SELECT 1") await cur.execute("SELECT 1")
except psycopg.Error as exc:
logger.error("Project TimescaleDB health check failed", exc_info=True)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Project TimescaleDB health check failed: {exc}",
) from exc
return {"postgres": "ok", "timescale": "ok"} return {"postgres": "ok", "timescale": "ok"}
+7 -7
View File
@@ -5,10 +5,10 @@ from pydantic import BaseModel
class GeoServerConfigResponse(BaseModel): class GeoServerConfigResponse(BaseModel):
gs_base_url: Optional[str] gs_base_url: Optional[str] = None
gs_admin_user: Optional[str] gs_admin_user: Optional[str] = None
gs_datastore_name: str gs_datastore_name: str
default_extent: Optional[dict] default_extent: Optional[dict] = None
srid: int srid: int
@@ -16,19 +16,19 @@ class ProjectMetaResponse(BaseModel):
project_id: UUID project_id: UUID
name: str name: str
code: str code: str
description: Optional[str] description: Optional[str] = None
gs_workspace: str gs_workspace: str
map_extent: Optional[dict] map_extent: Optional[dict] = None
status: str status: str
project_role: str project_role: str
geoserver: Optional[GeoServerConfigResponse] geoserver: Optional[GeoServerConfigResponse] = None
class ProjectSummaryResponse(BaseModel): class ProjectSummaryResponse(BaseModel):
project_id: UUID project_id: UUID
name: str name: str
code: str code: str
description: Optional[str] description: Optional[str] = None
gs_workspace: str gs_workspace: str
status: str status: str
project_role: str project_role: str
+1 -1
View File
@@ -119,7 +119,7 @@ def load_auth_context(auth_stdin: bool = False) -> AuthContext:
project_id=_pick(raw, "project_id", "projectId", "x_project_id"), project_id=_pick(raw, "project_id", "projectId", "x_project_id"),
user_id=_pick(raw, "user_id", "userId", "x_user_id"), user_id=_pick(raw, "user_id", "userId", "x_user_id"),
username=_pick(raw, "username", "preferred_username"), username=_pick(raw, "username", "preferred_username"),
network="tjwater", network=_pick(raw, "network", "project_code", "projectCode", "project"),
headers={str(key): str(value) for key, value in headers.items()}, headers={str(key): str(value) for key, value in headers.items()},
) )
+92
View File
@@ -0,0 +1,92 @@
from types import SimpleNamespace
from uuid import uuid4
from fastapi.testclient import TestClient
from sqlalchemy.exc import SQLAlchemyError
from tests.conftest import build_test_app, install_stub, load_module_from_path
def _load_meta_module(monkeypatch):
install_stub(monkeypatch, "app.auth", package=True)
install_stub(
monkeypatch,
"app.auth.project_dependencies",
{
"ProjectContext": object,
"get_project_context": lambda: None,
"get_project_pg_session": lambda: None,
"get_project_timescale_connection": lambda: None,
"get_metadata_repository": lambda: None,
},
)
install_stub(
monkeypatch,
"app.auth.metadata_dependencies",
{"get_current_metadata_user": lambda: None},
)
return load_module_from_path(
"tests_meta_endpoints_module",
"app/api/v1/endpoints/meta.py",
)
def test_meta_project_returns_map_extent(monkeypatch):
module = _load_meta_module(monkeypatch)
project_id = uuid4()
repo = SimpleNamespace(
get_project_by_id=lambda _project_id: None,
get_geoserver_config=lambda _project_id: None,
)
async def get_project_by_id(_project_id):
return SimpleNamespace(
id=project_id,
name="Demo Project",
code="demo",
description="desc",
gs_workspace="workspace",
map_extent={"xmin": 1, "ymin": 2, "xmax": 3, "ymax": 4},
status="active",
)
async def get_geoserver_config(_project_id):
return None
repo.get_project_by_id = get_project_by_id
repo.get_geoserver_config = get_geoserver_config
app = build_test_app(module.router, "/api/v1")
app.dependency_overrides[module.get_project_context] = lambda: SimpleNamespace(
project_id=project_id,
project_role="editor",
)
app.dependency_overrides[module.get_metadata_repository] = lambda: repo
client = TestClient(app)
response = client.get("/api/v1/meta/project")
assert response.status_code == 200
assert response.json()["map_extent"] == {"xmin": 1, "ymin": 2, "xmax": 3, "ymax": 4}
def test_meta_db_health_returns_503_for_postgres_errors(monkeypatch):
module = _load_meta_module(monkeypatch)
class BrokenSession:
async def execute(self, _query):
raise SQLAlchemyError("pg unavailable")
class DummyTimescaleConnection:
def cursor(self):
raise AssertionError("timescale should not be queried after postgres failure")
app = build_test_app(module.router, "/api/v1")
app.dependency_overrides[module.get_project_pg_session] = lambda: BrokenSession()
app.dependency_overrides[module.get_project_timescale_connection] = lambda: DummyTimescaleConnection()
client = TestClient(app)
response = client.get("/api/v1/meta/db/health")
assert response.status_code == 503
assert response.json()["detail"] == "Project PostgreSQL health check failed: pg unavailable"