diff --git a/pkgs/clan-app/clan_app/deps/http/swagger.html b/pkgs/clan-app/clan_app/deps/http/swagger.html index 9a1106cb8..5b52cc7e7 100644 --- a/pkgs/clan-app/clan_app/deps/http/swagger.html +++ b/pkgs/clan-app/clan_app/deps/http/swagger.html @@ -46,7 +46,7 @@ */ requestInterceptor: (request) => { console.log("Intercepting request:", request); - + // Only modify requests that have a body (like POST, PUT) if (request.body) { try { @@ -56,19 +56,24 @@ // Create the new, nested structure. const newBody = { body: originalBody, - header: {} // Add an empty header object as per your example + header: {}, // Add an empty header object as per your example }; // Replace the original body with the new, stringified, nested structure. request.body = JSON.stringify(newBody); - + // Update the 'Content-Length' header to match the new body size. - request.headers['Content-Length'] = new Blob([request.body]).size; + request.headers["Content-Length"] = new Blob([ + request.body, + ]).size; console.log("Modified request body:", request.body); } catch (e) { // If the user's input isn't valid JSON, don't modify the request. - console.error("Request Interceptor: Could not parse body as JSON.", e); + console.error( + "Request Interceptor: Could not parse body as JSON.", + e, + ); } } return request; // Always return the request object @@ -90,9 +95,11 @@ const fullResponse = JSON.parse(response.data); // Check if the expected 'body' property exists. - if (fullResponse && typeof fullResponse.body !== 'undefined') { - console.log("Found nested 'body' property. Un-nesting for display."); - + if (fullResponse && typeof fullResponse.body !== "undefined") { + console.log( + "Found nested 'body' property. Un-nesting for display.", + ); + // Replace the response's data with JUST the nested 'body' object. // We stringify it with pretty-printing (2-space indentation) for readability in the UI. response.data = JSON.stringify(fullResponse.body, null, 2); @@ -101,15 +108,18 @@ } catch (e) { // If the response isn't the expected JSON structure, do nothing. // This prevents errors on other endpoints that have a normal response. - console.error("Response Interceptor: Could not parse response or un-nest data.", e); + console.error( + "Response Interceptor: Could not parse response or un-nest data.", + e, + ); } } return response; // Always return the response object - } + }, // --- INTERCEPTORS END HERE --- }); }; - \ No newline at end of file + diff --git a/pkgs/clan-app/clan_app/deps/http/test_http_api.py b/pkgs/clan-app/clan_app/deps/http/test_http_api.py index 753f6afcb..65b419099 100644 --- a/pkgs/clan-app/clan_app/deps/http/test_http_api.py +++ b/pkgs/clan-app/clan_app/deps/http/test_http_api.py @@ -1,13 +1,16 @@ """Tests for HTTP API components.""" import json +import logging import time from unittest.mock import Mock from urllib.request import Request, urlopen - +import threading import pytest from clan_lib.api import MethodRegistry +from clan_lib.async_run import is_async_cancelled from clan_lib.log_manager import LogManager +from clan_lib.api import tasks from clan_app.api.middleware import ( ArgumentParsingMiddleware, @@ -16,6 +19,8 @@ from clan_app.api.middleware import ( ) from clan_app.deps.http.http_server import HttpApiServer +log = logging.getLogger(__name__) + @pytest.fixture def mock_api() -> MethodRegistry: @@ -31,6 +36,21 @@ def mock_api() -> MethodRegistry: msg = "Test error" raise ValueError(msg) + @api.register + def run_task_blocking(wtime: int) -> str: + """A long blocking task that simulates a long-running operation.""" + time.sleep(1) + + for i in range(wtime): + if is_async_cancelled(): + log.debug("Task was cancelled") + return "Task was cancelled" + log.debug( + f"Processing {i} for {wtime}" + ) + time.sleep(1) + return f"Task completed with wtime: {wtime}" + return api @@ -282,5 +302,77 @@ class TestIntegration: server.stop() + def test_blocking_task( + self, mock_api: MethodRegistry, mock_log_manager: Mock + ) -> None: + + shared_threads: dict[str, tasks.WebThread] = {} + tasks.BAKEND_THREADS = shared_threads + + """Test a long-running blocking task.""" + server: HttpApiServer = HttpApiServer( + api=mock_api, + host="127.0.0.1", + port=8083, + shared_threads=shared_threads, + ) + + # Add middleware + server.add_middleware(ArgumentParsingMiddleware(api=mock_api)) + server.add_middleware(LoggingMiddleware(log_manager=mock_log_manager)) + server.add_middleware(MethodExecutionMiddleware(api=mock_api)) + + # Start server + server.start() + time.sleep(0.1) # Give server time to start + + + sucess = threading.Event() + def parallel_task() -> None: + + time.sleep(1) + request_data: dict = { + "body": {"message": "Integration"}, + } + req: Request = Request( + "http://127.0.0.1:8083/api/v1/test_method", + data=json.dumps(request_data).encode(), + headers={"Content-Type": "application/json"}, + ) + response = urlopen(req) + data: dict = json.loads(response.read().decode()) + + assert "body" in data + assert "header" in data + assert data["body"]["status"] == "success" + assert data["body"]["data"] == {"response": "Hello Integration!"} + sucess.set() + + thread = threading.Thread( + target=parallel_task, + name="ParallelTaskThread", + daemon=True, + ) + thread.start() + + # Make API call + request_data: dict = { + "body": {"wtime": 3}, + } + req: Request = Request( + "http://127.0.0.1:8083/api/v1/run_task_blocking", + data=json.dumps(request_data).encode(), + headers={"Content-Type": "application/json"}, + ) + response = urlopen(req) + data: dict = json.loads(response.read().decode()) + + # thread.join() + assert "body" in data + assert data["body"]["status"] == "success" + assert data["body"]["data"] == "Task completed with wtime: 3" + assert sucess.is_set(), "Parallel task did not complete successfully" + + if __name__ == "__main__": pytest.main([__file__, "-v"])