diff --git a/tests/test_tileserver.py b/tests/test_tileserver.py index 8a8d784af..c4632b403 100644 --- a/tests/test_tileserver.py +++ b/tests/test_tileserver.py @@ -734,3 +734,44 @@ def test_prop_range(app: TileServer) -> None: assert response.status_code == 200 # should be back to no scaling assert layer.renderer.score_fn(0.5) == 0.5 + + +def test_healthcheck(empty_app: TileServer) -> None: + """Test the /tileserver/healthcheck endpoint.""" + with empty_app.test_client() as client: + response = client.get("/tileserver/healthcheck") + assert response.status_code == 200 + assert response.content_type == "application/json" + assert response.get_json() == {"status": "OK"} + + +def test_sessions_no_slide_loaded(empty_app: TileServer) -> None: + """Test /tileserver/sessions when no slides are loaded.""" + with empty_app.test_client() as client: + setup_app(client) + response = client.get("/tileserver/sessions") + assert response.status_code == 200 + assert response.is_json + assert response.get_json() == {} + + +def test_sessions_one_slide_loaded( + empty_app: TileServer, remote_sample: Callable +) -> None: + """Test /tileserver/sessions after loading one slide.""" + with empty_app.test_client() as client: + setup_app(client) + slide_path = safe_str(remote_sample("svs-1-small")) + + response = client.put( + "/tileserver/slide", + data={"slide_path": slide_path}, + ) + assert response.status_code == 200 + + response = client.get("/tileserver/sessions") + assert response.status_code == 200 + sessions = response.get_json() + + assert isinstance(sessions, dict) + assert len(sessions) == 1 diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index 1587ec984..6267c2f26 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -165,6 +165,8 @@ def __init__( # noqa: PLR0915 self.route("/tileserver/tap_query//")(self.tap_query) self.route("/tileserver/prop_range", methods=["PUT"])(self.prop_range) self.route("/tileserver/shutdown", methods=["POST"])(self.shutdown) + self.route("/tileserver/sessions", methods=["GET"])(self.sessions) + self.route("/tileserver/healthcheck", methods=["GET"])(self.healthcheck) def _get_session_id(self: TileServer) -> str: """Get the session_id from the request. @@ -718,6 +720,36 @@ def prop_range(self: TileServer) -> str: self.renderers[session_id].score_fn = lambda x: (x - minv) / (maxv - minv) return "done" + def sessions(self: TileServer) -> Response: + """Retrieve a mapping of session keys to their corresponding slide file paths. + + Returns: + Response: + A JSON response containing a mapping of session keys + and their respective slide file paths. + + """ + session_paths = {} + for key, layer in self.layers.items(): + slide = layer.get("slide") + if slide is not None: + session_paths[key] = str(slide.info.as_dict().get("file_path", "")) + return jsonify(session_paths) + + @staticmethod + def healthcheck() -> Response: + """Simple health check endpoint to verify the server is running. + + Useful for load balancers or uptime monitoring tools to check + if the service is operational. + + Returns: + Response: + A JSON response with status "OK" and HTTP status code 200. + + """ + return jsonify({"status": "OK"}) + @staticmethod def shutdown() -> None: """Shutdown the tileserver.""" diff --git a/tiatoolbox/visualization/tileserver_api.yml b/tiatoolbox/visualization/tileserver_api.yml index 3f1081c14..bea5826c1 100644 --- a/tiatoolbox/visualization/tileserver_api.yml +++ b/tiatoolbox/visualization/tileserver_api.yml @@ -395,6 +395,24 @@ paths: '200': description: Successful response + /tileserver/sessions: + get: + summary: Returns all session/path mappings + responses: + '200': + description: A jsonified session/path mappings + content: + application/json: + + /tileserver/healthcheck: + get: + summary: Returns Ok if server is online + responses: + '200': + description: A json with status field + content: + application/json: + /tileserver/tap_query/{x}/{y}: get: summary: Query annotation at a point