Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions api-server/tests/test_middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import uuid
import pytest
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient

# Import your middleware classes here
from app.middlewares.request_id_middleware import RequestIdMiddleware
from app.middlewares.unhandled_exceptions_middleware import UnhandledExceptionsMiddleware

# --- Dummy endpoint that returns OK JSON ---
async def ok_endpoint(request: Request):
# When called, respond with JSON {"status": "ok"}
return JSONResponse({"status": "ok"})

# --- Dummy endpoint that raises an exception ---
async def fail_endpoint(request: Request):
# When called, raise a ValueError to simulate an error
raise ValueError("oops")

@pytest.fixture
def client():
"""
This pytest fixture sets up a test app instance with the middlewares added,
and returns a TestClient which simulates HTTP requests to the app.
"""
app = FastAPI()

# Add middlewares. Order matters! Exceptions should be handled outermost.
app.add_middleware(UnhandledExceptionsMiddleware)
app.add_middleware(RequestIdMiddleware)
Comment on lines +30 to +31

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea we test these middlewares separately instead of testing them together.


# Add routes tied to the dummy endpoints above
app.add_api_route("/ok", ok_endpoint, methods=["GET"])
app.add_api_route("/fail", fail_endpoint, methods=["GET"])

# Return a test client wrapping the ASGI app for test requests
return TestClient(app)

def test_request_id_auto_generated(client):
"""
Test that when no 'x-exosphere-request-id' header is sent, the RequestIdMiddleware
automatically generates a valid UUID and returns it in the response headers.
"""
resp = client.get("/ok") # Make GET request without headers
assert resp.status_code == 200 # Should return HTTP 200 OK

rid = resp.headers["x-exosphere-request-id"] # Get the request ID from headers
uuid.UUID(rid) # Validate that it is a valid UUID (will raise an exception if not)

assert resp.json() == {"status": "ok"} # Response body should be unchanged

def test_request_id_echoed_when_valid(client):
"""
Test that when a valid UUID is provided in header, middleware preserves (echoes) it.
"""
provided = str(uuid.uuid4()) # Generate a valid UUID string
resp = client.get("/ok", headers={"x-exosphere-request-id": provided}) # Send header

assert resp.headers["x-exosphere-request-id"] == provided # Header echoed exactly

def test_request_id_replaced_when_invalid(client):
"""
Test that when an invalid request ID is sent, middleware replaces it with a valid UUID.
"""
resp = client.get("/ok", headers={"x-exosphere-request-id": "invalid-id"}) # Send bad header

new_rid = resp.headers["x-exosphere-request-id"]
assert new_rid != "invalid-id" # Confirm the invalid ID was replaced
uuid.UUID(new_rid) # Confirm replacement is a valid UUID

def test_unhandled_exception_caught(client):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to a different file, let's keep the test per logical unit separately

"""
Test that any unhandled exception during request processing is caught by the middleware,
returning a JSON error with HTTP status 500, and still providing a request ID header.
"""
resp = client.get("/fail") # This route raises an exception

assert resp.status_code == 500 # Middleware converts error to HTTP 500 response

body = resp.json()
assert "error" in body or "detail" in body # Error info present in JSON response body

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a fixed error body, I think it is better to assert that the whole body in asset equal condition.

Ref: https://github.com/exospherehost/exospherehost/blob/main/api-server%2Fapp%2Fmiddlewares%2Funhandled_exceptions_middleware.py#L25-L28


assert "x-exosphere-request-id" in resp.headers # Request ID header is still present