From 21aff4beeb53a6bcfc759f18c301451b9c17e15d Mon Sep 17 00:00:00 2001 From: anhad Date: Sat, 2 Aug 2025 12:49:36 +0530 Subject: [PATCH 01/17] Add separated middleware tests and CI workflow --- .github/workflows/ci.yml | 0 api-server/tests/test_request_id_middleware.py | 0 api-server/tests/test_unhandled_exceptions_middleware.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 api-server/tests/test_request_id_middleware.py create mode 100644 api-server/tests/test_unhandled_exceptions_middleware.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e69de29b diff --git a/api-server/tests/test_request_id_middleware.py b/api-server/tests/test_request_id_middleware.py new file mode 100644 index 00000000..e69de29b diff --git a/api-server/tests/test_unhandled_exceptions_middleware.py b/api-server/tests/test_unhandled_exceptions_middleware.py new file mode 100644 index 00000000..e69de29b From 18902abf5fdf3d8d6112b9ac84d15f9855696a72 Mon Sep 17 00:00:00 2001 From: anhad Date: Sat, 2 Aug 2025 12:54:01 +0530 Subject: [PATCH 02/17] Update middleware tests, dependencies, and CI workflow --- .github/workflows/ci.yml | 33 +++++++++++ api-server/pyproject.toml | 3 +- .../tests/test_request_id_middleware.py | 37 ++++++++++++ .../test_unhandled_exceptions_middleware.py | 34 +++++++++++ api-server/uv.lock | 56 ++++++++++++++++++- 5 files changed, 161 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e69de29b..36f806f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: Python API Server Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install uv + uses: astral-sh/setup-uv@v2 + + - name: Install dev dependencies with uv + working-directory: api-server + run: | + uv sync --group dev + + - name: Run tests with pytest + working-directory: api-server + run: | + uv run pytest --maxfail=1 --disable-warnings -v diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index a74d5997..525406b3 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -15,7 +15,8 @@ dependencies = [ "python-dotenv>=1.1.1", "structlog>=25.4.0", "uvicorn>=0.35.0", - "redis[hiredis]>=6.0.0" + "redis[hiredis]>=6.0.0", + "pytest>=8.0.0" ] [dependency-groups] diff --git a/api-server/tests/test_request_id_middleware.py b/api-server/tests/test_request_id_middleware.py index e69de29b..77897ea7 100644 --- a/api-server/tests/test_request_id_middleware.py +++ b/api-server/tests/test_request_id_middleware.py @@ -0,0 +1,37 @@ +import uuid +import pytest +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient +from app.middlewares.request_id_middleware import RequestIdMiddleware + +# Minimal endpoint to test request ID logic +async def handler(request: Request): + return JSONResponse({"msg": "ok"}) + +@pytest.fixture +def client(): + app = FastAPI() + app.add_middleware(RequestIdMiddleware) # Only the request ID middleware + app.add_api_route("/test", handler, methods=["GET"]) + return TestClient(app) + +def test_generates_uuid_when_missing(client): + resp = client.get("/test") + assert resp.status_code == 200 + rid = resp.headers["x-exosphere-request-id"] + uuid.UUID(rid) + assert resp.json() == {"msg": "ok"} + +def test_preserves_valid_id(client): + valid_id = str(uuid.uuid4()) + resp = client.get("/test", headers={"x-exosphere-request-id": valid_id}) + assert resp.status_code == 200 + assert resp.headers["x-exosphere-request-id"] == valid_id + +def test_replaces_invalid_id(client): + resp = client.get("/test", headers={"x-exosphere-request-id": "bad-id"}) + assert resp.status_code == 200 + new_id = resp.headers["x-exosphere-request-id"] + assert new_id != "bad-id" + uuid.UUID(new_id) diff --git a/api-server/tests/test_unhandled_exceptions_middleware.py b/api-server/tests/test_unhandled_exceptions_middleware.py index e69de29b..5365bf24 100644 --- a/api-server/tests/test_unhandled_exceptions_middleware.py +++ b/api-server/tests/test_unhandled_exceptions_middleware.py @@ -0,0 +1,34 @@ +import pytest +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient +from app.middlewares.unhandled_exceptions_middleware import UnhandledExceptionsMiddleware + +# Endpoint that will always raise an error to test error handling +async def fail(request: Request): + raise RuntimeError("boom") + +# Endpoint for a healthy request +async def ok(request: Request): + return JSONResponse({"ok": True}) + +@pytest.fixture +def client(): + app = FastAPI() + app.add_middleware(UnhandledExceptionsMiddleware) # Only exception middleware + app.add_api_route("/fail", fail, methods=["GET"]) + app.add_api_route("/ok", ok, methods=["GET"]) + return TestClient(app) + +def test_exception_returns_expected_json(client): + resp = client.get("/fail") + assert resp.status_code == 500 + expected = { + "detail": "Internal server error. Please contact the admin if this persists." + } + assert resp.json() == expected + +def test_normal_request_passes_through(client): + resp = client.get("/ok") + assert resp.status_code == 200 + assert resp.json() == {"ok": True} diff --git a/api-server/uv.lock b/api-server/uv.lock index db8867fc..de7636a1 100644 --- a/api-server/uv.lock +++ b/api-server/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" [[package]] @@ -37,6 +37,7 @@ dependencies = [ { name = "fastapi" }, { name = "jsonschema" }, { name = "pyjwt" }, + { name = "pytest" }, { name = "python-dotenv" }, { name = "redis", extra = ["hiredis"] }, { name = "structlog" }, @@ -57,6 +58,7 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.115.14" }, { name = "jsonschema", specifier = ">=4.24.0" }, { name = "pyjwt", specifier = ">=2.10.1" }, + { name = "pytest", specifier = ">=8.0.0" }, { name = "python-dotenv", specifier = ">=1.1.1" }, { name = "redis", extras = ["hiredis"], specifier = ">=6.0.0" }, { name = "structlog", specifier = ">=25.4.0" }, @@ -266,6 +268,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + [[package]] name = "jsonschema" version = "4.25.0" @@ -305,6 +316,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/a4/55bb305df9fe0d343ff8f0dd4da25b2cc33ba65f8596238aa7a4ecbe9777/lazy_model-0.3.0-py3-none-any.whl", hash = "sha256:67c112cad3fbc1816d32c070bf3b3ac1f48aefeb4e46e9eb70e12acc92c6859d", size = 13719, upload-time = "2025-04-22T17:03:34.764Z" }, ] +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pydantic" version = "2.11.7" @@ -362,6 +391,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pyjwt" version = "2.10.1" @@ -409,6 +447,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/9c/00301a6df26f0f8d5c5955192892241e803742e7c3da8c2c222efabc0df6/pymongo-4.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c38168263ed94a250fc5cf9c6d33adea8ab11c9178994da1c3481c2a49d235f8", size = 1011057, upload-time = "2025-06-16T18:16:07.917Z" }, ] +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + [[package]] name = "python-dotenv" version = "1.1.1" From ddd9953dfb93ddc1ea44dd1f091d6d5b7cf2e24f Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 13:17:46 +0530 Subject: [PATCH 03/17] Update .github/workflows/ci.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36f806f5..44f14173 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 From 80ba9f947a8af12dc08630c92627bfc1c744fd28 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 13:18:20 +0530 Subject: [PATCH 04/17] Update .github/workflows/ci.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44f14173..4b994659 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,9 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.13' - name: Install uv uses: astral-sh/setup-uv@v2 From a0fa2698f66c47da0359c8384d150e42819e0209 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 14:24:07 +0530 Subject: [PATCH 05/17] Update api-server/pyproject.toml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- api-server/pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index 525406b3..af24ce0a 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -16,7 +16,15 @@ dependencies = [ "structlog>=25.4.0", "uvicorn>=0.35.0", "redis[hiredis]>=6.0.0", - "pytest>=8.0.0" +dependencies = [ + "redis[hiredis]>=6.0.0" +] + +[dependency-groups] +dev = [ + "ruff>=0.12.2", + "pytest>=8.0.0", +] ] [dependency-groups] From f596ced7898bffc2d07d64a75c32f27efc13d240 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 14:29:37 +0530 Subject: [PATCH 06/17] Update api-server/pyproject.toml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- api-server/pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index af24ce0a..49d76a75 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -16,9 +16,6 @@ dependencies = [ "structlog>=25.4.0", "uvicorn>=0.35.0", "redis[hiredis]>=6.0.0", -dependencies = [ - "redis[hiredis]>=6.0.0" -] [dependency-groups] dev = [ From 1aac48dbb04f8278c947ca997e4756c4bbd812b0 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:03:33 +0530 Subject: [PATCH 07/17] Update pyproject.toml --- api-server/pyproject.toml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index 49d76a75..6ca6bff9 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -15,16 +15,14 @@ dependencies = [ "python-dotenv>=1.1.1", "structlog>=25.4.0", "uvicorn>=0.35.0", - "redis[hiredis]>=6.0.0", + "redis[hiredis]>=6.0.0",] [dependency-groups] dev = [ "ruff>=0.12.2", "pytest>=8.0.0", ] -] -[dependency-groups] -dev = [ - "ruff>=0.12.2", -] + + + From 55f1ec1fa61d10689df82af40f36eda3ccd9d14e Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:21:55 +0530 Subject: [PATCH 08/17] Update .github/workflows/ci.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b994659..ee26915a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,12 +21,13 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v2 + with: + cache: true # enables automatic actions/cache reuse - name: Install dev dependencies with uv working-directory: api-server run: | uv sync --group dev - - name: Run tests with pytest working-directory: api-server run: | From 9e822820189724bd63661309c7356114f9ef4080 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:30:27 +0530 Subject: [PATCH 09/17] Update pyproject.toml --- api-server/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index 6ca6bff9..eb9ef13e 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ dev = [ "ruff>=0.12.2", "pytest>=8.0.0", + "httpx>=0.27.0", ] From e46b6d38a905a391f446e8ec1408de86f2490427 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:30:56 +0530 Subject: [PATCH 10/17] Update .github/workflows/ci.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee26915a..d3b7cdc4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.13' + # Prefer released versions or test a matrix of supported ones + python-version: | + 3.11 + 3.12 - name: Install uv uses: astral-sh/setup-uv@v2 From ae1aa93fd5cf1e967bfea366df087d1daf790b7b Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:39:27 +0530 Subject: [PATCH 11/17] Update api-server/tests/test_request_id_middleware.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- api-server/tests/test_request_id_middleware.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api-server/tests/test_request_id_middleware.py b/api-server/tests/test_request_id_middleware.py index 77897ea7..b928f1c6 100644 --- a/api-server/tests/test_request_id_middleware.py +++ b/api-server/tests/test_request_id_middleware.py @@ -20,7 +20,9 @@ def test_generates_uuid_when_missing(client): resp = client.get("/test") assert resp.status_code == 200 rid = resp.headers["x-exosphere-request-id"] - uuid.UUID(rid) + # Validate that a proper UUID was generated + parsed_uuid = uuid.UUID(rid) + assert str(parsed_uuid) == rid assert resp.json() == {"msg": "ok"} def test_preserves_valid_id(client): From 9f185e28e6505e9a44b24e4ad120711f2a60f029 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:39:59 +0530 Subject: [PATCH 12/17] Update api-server/tests/test_request_id_middleware.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- api-server/tests/test_request_id_middleware.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api-server/tests/test_request_id_middleware.py b/api-server/tests/test_request_id_middleware.py index b928f1c6..9a64f8a9 100644 --- a/api-server/tests/test_request_id_middleware.py +++ b/api-server/tests/test_request_id_middleware.py @@ -36,4 +36,6 @@ def test_replaces_invalid_id(client): assert resp.status_code == 200 new_id = resp.headers["x-exosphere-request-id"] assert new_id != "bad-id" - uuid.UUID(new_id) + # Validate that a proper UUID was generated + parsed_uuid = uuid.UUID(new_id) + assert str(parsed_uuid) == new_id From 624d2ca4a928255bc3b7891cdef199f85c237ef4 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 15:56:14 +0530 Subject: [PATCH 13/17] Update api-server/tests/test_unhandled_exceptions_middleware.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- api-server/tests/test_unhandled_exceptions_middleware.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api-server/tests/test_unhandled_exceptions_middleware.py b/api-server/tests/test_unhandled_exceptions_middleware.py index 5365bf24..afefaef6 100644 --- a/api-server/tests/test_unhandled_exceptions_middleware.py +++ b/api-server/tests/test_unhandled_exceptions_middleware.py @@ -23,10 +23,9 @@ def client(): def test_exception_returns_expected_json(client): resp = client.get("/fail") assert resp.status_code == 500 - expected = { - "detail": "Internal server error. Please contact the admin if this persists." - } - assert resp.json() == expected + response_json = resp.json() + assert "detail" in response_json + assert "server error" in response_json["detail"].lower() def test_normal_request_passes_through(client): resp = client.get("/ok") From 77d09e72e7d8922a548d66c9a6f93fcfb77969a2 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 18:24:55 +0530 Subject: [PATCH 14/17] Address reviewer feedback and CI errors: Python version, error response test, Redis CI service and artefacts --- api-server/pyproject.toml | 1 + .../test_unhandled_exceptions_middleware.py | 37 ++++- api-server/uv.lock | 127 +++++++++++++++++- 3 files changed, 158 insertions(+), 7 deletions(-) diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index eb9ef13e..6bd5f96e 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -22,6 +22,7 @@ dev = [ "ruff>=0.12.2", "pytest>=8.0.0", "httpx>=0.27.0", + "pytest-cov>=5.0.0" ] diff --git a/api-server/tests/test_unhandled_exceptions_middleware.py b/api-server/tests/test_unhandled_exceptions_middleware.py index afefaef6..0951e107 100644 --- a/api-server/tests/test_unhandled_exceptions_middleware.py +++ b/api-server/tests/test_unhandled_exceptions_middleware.py @@ -4,26 +4,55 @@ from fastapi.testclient import TestClient from app.middlewares.unhandled_exceptions_middleware import UnhandledExceptionsMiddleware -# Endpoint that will always raise an error to test error handling +# Handlers for various exception types + +# 1. RuntimeError async def fail(request: Request): raise RuntimeError("boom") -# Endpoint for a healthy request +# 2. ValueError +async def fail_value_error(request: Request): + raise ValueError("Invalid value test") + +# 3. KeyError +async def fail_key_error(request: Request): + raise KeyError("Missing key test") + +# 4. Healthy endpoint async def ok(request: Request): return JSONResponse({"ok": True}) @pytest.fixture def client(): app = FastAPI() - app.add_middleware(UnhandledExceptionsMiddleware) # Only exception middleware + app.add_middleware(UnhandledExceptionsMiddleware) app.add_api_route("/fail", fail, methods=["GET"]) + app.add_api_route("/fail_value_error", fail_value_error, methods=["GET"]) + app.add_api_route("/fail_key_error", fail_key_error, methods=["GET"]) app.add_api_route("/ok", ok, methods=["GET"]) return TestClient(app) -def test_exception_returns_expected_json(client): +def test_runtime_error_returns_expected_json(client): resp = client.get("/fail") assert resp.status_code == 500 response_json = resp.json() + assert response_json.get("success") is False + assert "detail" in response_json + assert "server error" in response_json["detail"].lower() + +def test_value_error_returns_expected_json(client): + resp = client.get("/fail_value_error") + assert resp.status_code == 500 + response_json = resp.json() + assert response_json.get("success") is False + assert "detail" in response_json + assert "server error" in response_json["detail"].lower() + +def test_key_error_returns_expected_json(client): + resp = client.get("/fail_key_error") + assert resp.status_code == 500 + response_json = resp.json() + assert response_json.get("success") is False assert "detail" in response_json assert "server error" in response_json["detail"].lower() diff --git a/api-server/uv.lock b/api-server/uv.lock index de7636a1..1c3f2c62 100644 --- a/api-server/uv.lock +++ b/api-server/uv.lock @@ -37,7 +37,6 @@ dependencies = [ { name = "fastapi" }, { name = "jsonschema" }, { name = "pyjwt" }, - { name = "pytest" }, { name = "python-dotenv" }, { name = "redis", extra = ["hiredis"] }, { name = "structlog" }, @@ -46,6 +45,9 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "httpx" }, + { name = "pytest" }, + { name = "pytest-cov" }, { name = "ruff" }, ] @@ -58,7 +60,6 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.115.14" }, { name = "jsonschema", specifier = ">=4.24.0" }, { name = "pyjwt", specifier = ">=2.10.1" }, - { name = "pytest", specifier = ">=8.0.0" }, { name = "python-dotenv", specifier = ">=1.1.1" }, { name = "redis", extras = ["hiredis"], specifier = ">=6.0.0" }, { name = "structlog", specifier = ">=25.4.0" }, @@ -66,7 +67,12 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.12.2" }] +dev = [ + { name = "httpx", specifier = ">=0.27.0" }, + { name = "pytest", specifier = ">=8.0.0" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, + { name = "ruff", specifier = ">=0.12.2" }, +] [[package]] name = "attrs" @@ -143,6 +149,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/36/c40577bc8e3564639b89db32aff1e9e8af14c990e3a7ed85a79b74ec4b78/beanie-2.0.0-py3-none-any.whl", hash = "sha256:0d5c0e0de09f2a316c74d17bbba1ceb68ebcbfd3046ae5be69038b2023682372", size = 87051, upload-time = "2025-07-20T06:55:25.944Z" }, ] +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -164,6 +179,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/0e/66dbd4c6a7f0758a8d18044c048779ba21fb94856e1edcf764bd5403e710/coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57", size = 819938, upload-time = "2025-07-27T14:13:39.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/3f/b051feeb292400bd22d071fdf933b3ad389a8cef5c80c7866ed0c7414b9e/coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4", size = 214934, upload-time = "2025-07-27T14:11:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e4/a61b27d5c4c2d185bdfb0bfe9d15ab4ac4f0073032665544507429ae60eb/coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e", size = 215173, upload-time = "2025-07-27T14:11:38.005Z" }, + { url = "https://files.pythonhosted.org/packages/8a/01/40a6ee05b60d02d0bc53742ad4966e39dccd450aafb48c535a64390a3552/coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4", size = 246190, upload-time = "2025-07-27T14:11:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/11/ef/a28d64d702eb583c377255047281305dc5a5cfbfb0ee36e721f78255adb6/coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a", size = 248618, upload-time = "2025-07-27T14:11:41.841Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ad/73d018bb0c8317725370c79d69b5c6e0257df84a3b9b781bda27a438a3be/coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe", size = 250081, upload-time = "2025-07-27T14:11:43.705Z" }, + { url = "https://files.pythonhosted.org/packages/2d/dd/496adfbbb4503ebca5d5b2de8bed5ec00c0a76558ffc5b834fd404166bc9/coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386", size = 247990, upload-time = "2025-07-27T14:11:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/18/3c/a9331a7982facfac0d98a4a87b36ae666fe4257d0f00961a3a9ef73e015d/coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6", size = 246191, upload-time = "2025-07-27T14:11:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/62/0c/75345895013b83f7afe92ec595e15a9a525ede17491677ceebb2ba5c3d85/coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f", size = 247400, upload-time = "2025-07-27T14:11:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/98b268cfc5619ef9df1d5d34fee408ecb1542d9fd43d467e5c2f28668cd4/coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca", size = 217338, upload-time = "2025-07-27T14:11:50.258Z" }, + { url = "https://files.pythonhosted.org/packages/fe/31/22a5440e4d1451f253c5cd69fdcead65e92ef08cd4ec237b8756dc0b20a7/coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3", size = 218125, upload-time = "2025-07-27T14:11:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2b/40d9f0ce7ee839f08a43c5bfc9d05cec28aaa7c9785837247f96cbe490b9/coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4", size = 216523, upload-time = "2025-07-27T14:11:53.965Z" }, + { url = "https://files.pythonhosted.org/packages/ef/72/135ff5fef09b1ffe78dbe6fcf1e16b2e564cd35faeacf3d63d60d887f12d/coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39", size = 214960, upload-time = "2025-07-27T14:11:55.959Z" }, + { url = "https://files.pythonhosted.org/packages/b1/aa/73a5d1a6fc08ca709a8177825616aa95ee6bf34d522517c2595484a3e6c9/coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7", size = 215220, upload-time = "2025-07-27T14:11:57.899Z" }, + { url = "https://files.pythonhosted.org/packages/8d/40/3124fdd45ed3772a42fc73ca41c091699b38a2c3bd4f9cb564162378e8b6/coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892", size = 245772, upload-time = "2025-07-27T14:12:00.422Z" }, + { url = "https://files.pythonhosted.org/packages/42/62/a77b254822efa8c12ad59e8039f2bc3df56dc162ebda55e1943e35ba31a5/coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7", size = 248116, upload-time = "2025-07-27T14:12:03.099Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/8101f062f472a3a6205b458d18ef0444a63ae5d36a8a5ed5dd0f6167f4db/coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994", size = 249554, upload-time = "2025-07-27T14:12:04.668Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7b/e51bc61573e71ff7275a4f167aecbd16cb010aefdf54bcd8b0a133391263/coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0", size = 247766, upload-time = "2025-07-27T14:12:06.234Z" }, + { url = "https://files.pythonhosted.org/packages/4b/71/1c96d66a51d4204a9d6d12df53c4071d87e110941a2a1fe94693192262f5/coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7", size = 245735, upload-time = "2025-07-27T14:12:08.305Z" }, + { url = "https://files.pythonhosted.org/packages/13/d5/efbc2ac4d35ae2f22ef6df2ca084c60e13bd9378be68655e3268c80349ab/coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7", size = 247118, upload-time = "2025-07-27T14:12:09.903Z" }, + { url = "https://files.pythonhosted.org/packages/d1/22/073848352bec28ca65f2b6816b892fcf9a31abbef07b868487ad15dd55f1/coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7", size = 217381, upload-time = "2025-07-27T14:12:11.535Z" }, + { url = "https://files.pythonhosted.org/packages/b7/df/df6a0ff33b042f000089bd11b6bb034bab073e2ab64a56e78ed882cba55d/coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e", size = 218152, upload-time = "2025-07-27T14:12:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/30/e3/5085ca849a40ed6b47cdb8f65471c2f754e19390b5a12fa8abd25cbfaa8f/coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4", size = 216559, upload-time = "2025-07-27T14:12:14.807Z" }, + { url = "https://files.pythonhosted.org/packages/cc/93/58714efbfdeb547909feaabe1d67b2bdd59f0597060271b9c548d5efb529/coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72", size = 215677, upload-time = "2025-07-27T14:12:16.68Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0c/18eaa5897e7e8cb3f8c45e563e23e8a85686b4585e29d53cacb6bc9cb340/coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af", size = 215899, upload-time = "2025-07-27T14:12:18.758Z" }, + { url = "https://files.pythonhosted.org/packages/84/c1/9d1affacc3c75b5a184c140377701bbf14fc94619367f07a269cd9e4fed6/coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7", size = 257140, upload-time = "2025-07-27T14:12:20.357Z" }, + { url = "https://files.pythonhosted.org/packages/3d/0f/339bc6b8fa968c346df346068cca1f24bdea2ddfa93bb3dc2e7749730962/coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759", size = 259005, upload-time = "2025-07-27T14:12:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/89390864b92ea7c909079939b71baba7e5b42a76bf327c1d615bd829ba57/coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324", size = 261143, upload-time = "2025-07-27T14:12:23.746Z" }, + { url = "https://files.pythonhosted.org/packages/2c/56/3d04d89017c0c41c7a71bd69b29699d919b6bbf2649b8b2091240b97dd6a/coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53", size = 258735, upload-time = "2025-07-27T14:12:25.73Z" }, + { url = "https://files.pythonhosted.org/packages/cb/40/312252c8afa5ca781063a09d931f4b9409dc91526cd0b5a2b84143ffafa2/coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f", size = 256871, upload-time = "2025-07-27T14:12:27.767Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2b/564947d5dede068215aaddb9e05638aeac079685101462218229ddea9113/coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd", size = 257692, upload-time = "2025-07-27T14:12:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/93/1b/c8a867ade85cb26d802aea2209b9c2c80613b9c122baa8c8ecea6799648f/coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c", size = 218059, upload-time = "2025-07-27T14:12:31.076Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fe/cd4ab40570ae83a516bf5e754ea4388aeedd48e660e40c50b7713ed4f930/coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18", size = 219150, upload-time = "2025-07-27T14:12:32.746Z" }, + { url = "https://files.pythonhosted.org/packages/8d/16/6e5ed5854be6d70d0c39e9cb9dd2449f2c8c34455534c32c1a508c7dbdb5/coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4", size = 217014, upload-time = "2025-07-27T14:12:34.406Z" }, + { url = "https://files.pythonhosted.org/packages/54/8e/6d0bfe9c3d7121cf936c5f8b03e8c3da1484fb801703127dba20fb8bd3c7/coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c", size = 214951, upload-time = "2025-07-27T14:12:36.069Z" }, + { url = "https://files.pythonhosted.org/packages/f2/29/e3e51a8c653cf2174c60532aafeb5065cea0911403fa144c9abe39790308/coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e", size = 215229, upload-time = "2025-07-27T14:12:37.759Z" }, + { url = "https://files.pythonhosted.org/packages/e0/59/3c972080b2fa18b6c4510201f6d4dc87159d450627d062cd9ad051134062/coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b", size = 245738, upload-time = "2025-07-27T14:12:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/2e/04/fc0d99d3f809452654e958e1788454f6e27b34e43f8f8598191c8ad13537/coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41", size = 248045, upload-time = "2025-07-27T14:12:41.387Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2e/afcbf599e77e0dfbf4c97197747250d13d397d27e185b93987d9eaac053d/coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f", size = 249666, upload-time = "2025-07-27T14:12:43.056Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/bc47f7f8ecb7a06cbae2bf86a6fa20f479dd902bc80f57cff7730438059d/coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1", size = 247692, upload-time = "2025-07-27T14:12:44.83Z" }, + { url = "https://files.pythonhosted.org/packages/b6/26/cbfa3092d31ccba8ba7647e4d25753263e818b4547eba446b113d7d1efdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2", size = 245536, upload-time = "2025-07-27T14:12:46.527Z" }, + { url = "https://files.pythonhosted.org/packages/56/77/9c68e92500e6a1c83d024a70eadcc9a173f21aadd73c4675fe64c9c43fdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4", size = 246954, upload-time = "2025-07-27T14:12:49.279Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/ba96671c5a669672aacd9877a5987c8551501b602827b4e84256da2a30a7/coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613", size = 217616, upload-time = "2025-07-27T14:12:51.214Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/e1e1eb95fc1585f15a410208c4795db24a948e04d9bde818fe4eb893bc85/coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e", size = 218412, upload-time = "2025-07-27T14:12:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/b0/85/7e1e5be2cb966cba95566ba702b13a572ca744fbb3779df9888213762d67/coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652", size = 216776, upload-time = "2025-07-27T14:12:55.482Z" }, + { url = "https://files.pythonhosted.org/packages/62/0f/5bb8f29923141cca8560fe2217679caf4e0db643872c1945ac7d8748c2a7/coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894", size = 215698, upload-time = "2025-07-27T14:12:57.225Z" }, + { url = "https://files.pythonhosted.org/packages/80/29/547038ffa4e8e4d9e82f7dfc6d152f75fcdc0af146913f0ba03875211f03/coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5", size = 215902, upload-time = "2025-07-27T14:12:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8a/7aaa8fbfaed900147987a424e112af2e7790e1ac9cd92601e5bd4e1ba60a/coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2", size = 257230, upload-time = "2025-07-27T14:13:01.248Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1d/c252b5ffac44294e23a0d79dd5acf51749b39795ccc898faeabf7bee903f/coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb", size = 259194, upload-time = "2025-07-27T14:13:03.247Z" }, + { url = "https://files.pythonhosted.org/packages/16/ad/6c8d9f83d08f3bac2e7507534d0c48d1a4f52c18e6f94919d364edbdfa8f/coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b", size = 261316, upload-time = "2025-07-27T14:13:04.957Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4e/f9bbf3a36c061e2e0e0f78369c006d66416561a33d2bee63345aee8ee65e/coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea", size = 258794, upload-time = "2025-07-27T14:13:06.715Z" }, + { url = "https://files.pythonhosted.org/packages/87/82/e600bbe78eb2cb0541751d03cef9314bcd0897e8eea156219c39b685f869/coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd", size = 256869, upload-time = "2025-07-27T14:13:08.933Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/2fc9a9236c5268f68ac011d97cd3a5ad16cc420535369bedbda659fdd9b7/coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d", size = 257765, upload-time = "2025-07-27T14:13:10.778Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/b4e00b2bd48a2dc8e1c7d2aea7455f40af2e36484ab2ef06deb85883e9fe/coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47", size = 218420, upload-time = "2025-07-27T14:13:12.882Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d21d05f33ea27ece327422240e69654b5932b0b29e7fbc40fbab3cf199bf/coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651", size = 219536, upload-time = "2025-07-27T14:13:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/a6/68/7fea94b141281ed8be3d1d5c4319a97f2befc3e487ce33657fc64db2c45e/coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab", size = 217190, upload-time = "2025-07-27T14:13:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/0f/64/922899cff2c0fd3496be83fa8b81230f5a8d82a2ad30f98370b133c2c83b/coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7", size = 206597, upload-time = "2025-07-27T14:13:37.221Z" }, +] + [[package]] name = "dnspython" version = "2.7.0" @@ -259,6 +338,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/6e/e76341d68aa717a705a2ee3be6da9f4122a0d1e3f3ad93a7104ed7a81bea/hiredis-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:b5b1653ad7263a001f2e907e81a957d6087625f9700fa404f1a2268c0a4f9059", size = 22136, upload-time = "2025-05-23T11:40:51.497Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -463,6 +570,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + [[package]] name = "python-dotenv" version = "1.1.1" From 4c6031b271150be63d76019683c22db4e1e52e72 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 18:38:53 +0530 Subject: [PATCH 15/17] Enable coverage reporting via Codecov and cleanup nits --- .github/workflows/ci.yml | 33 ++++++++++++++++++++++++++------- api-server/pyproject.toml | 9 ++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3b7cdc4..359198ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,16 @@ on: jobs: test: runs-on: ubuntu-latest - + services: + redis: + image: redis:7 + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout code uses: actions/checkout@v4 @@ -18,20 +27,30 @@ jobs: uses: actions/setup-python@v5 with: # Prefer released versions or test a matrix of supported ones - python-version: | - 3.11 - 3.12 + python-version: "3.12" + + - name: Install uv uses: astral-sh/setup-uv@v2 with: - cache: true # enables automatic actions/cache reuse + enable-cache: true # enables automatic actions/cache reuse - name: Install dev dependencies with uv working-directory: api-server run: | uv sync --group dev - - name: Run tests with pytest + - name: Run tests with pytest and coverage working-directory: api-server run: | - uv run pytest --maxfail=1 --disable-warnings -v + uv run pytest --cov=app --cov-report=xml --cov-report=term-missing -v -q --junitxml=pytest-report.xml + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: exospherehost/exospherehost + files: api-server/coverage.xml + flags: unittests + name: codecov-coverage-report + fail_ci_if_error: true + diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index 6bd5f96e..cab1ed15 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -15,16 +15,11 @@ dependencies = [ "python-dotenv>=1.1.1", "structlog>=25.4.0", "uvicorn>=0.35.0", - "redis[hiredis]>=6.0.0",] + "redis[hiredis]>=6.0.0"] [dependency-groups] dev = [ "ruff>=0.12.2", "pytest>=8.0.0", "httpx>=0.27.0", - "pytest-cov>=5.0.0" -] - - - - + "pytest-cov>=5.0.0"] \ No newline at end of file From 9a1fb6b3170dc9f9b7c7e551224d7d6c584f9192 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 18:52:12 +0530 Subject: [PATCH 16/17] Enable coverage reporting via Codecov and cleanup nits remaining changes --- .github/workflows/ci.yml | 2 +- api-server/pyproject.toml | 6 +++-- .../test_unhandled_exceptions_middleware.py | 26 +++++++------------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 359198ef..27d265f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: test: runs-on: ubuntu-latest - services: + services: redis: image: redis:7 ports: diff --git a/api-server/pyproject.toml b/api-server/pyproject.toml index cab1ed15..0604b376 100644 --- a/api-server/pyproject.toml +++ b/api-server/pyproject.toml @@ -15,11 +15,13 @@ dependencies = [ "python-dotenv>=1.1.1", "structlog>=25.4.0", "uvicorn>=0.35.0", - "redis[hiredis]>=6.0.0"] + "redis[hiredis]>=6.0.0" + ] [dependency-groups] dev = [ "ruff>=0.12.2", "pytest>=8.0.0", "httpx>=0.27.0", - "pytest-cov>=5.0.0"] \ No newline at end of file + "pytest-cov>=5.0.0" + ] \ No newline at end of file diff --git a/api-server/tests/test_unhandled_exceptions_middleware.py b/api-server/tests/test_unhandled_exceptions_middleware.py index 0951e107..7d645078 100644 --- a/api-server/tests/test_unhandled_exceptions_middleware.py +++ b/api-server/tests/test_unhandled_exceptions_middleware.py @@ -4,21 +4,17 @@ from fastapi.testclient import TestClient from app.middlewares.unhandled_exceptions_middleware import UnhandledExceptionsMiddleware -# Handlers for various exception types +# Exception handlers to trigger different exceptions -# 1. RuntimeError async def fail(request: Request): raise RuntimeError("boom") -# 2. ValueError async def fail_value_error(request: Request): raise ValueError("Invalid value test") -# 3. KeyError async def fail_key_error(request: Request): raise KeyError("Missing key test") -# 4. Healthy endpoint async def ok(request: Request): return JSONResponse({"ok": True}) @@ -32,29 +28,25 @@ def client(): app.add_api_route("/ok", ok, methods=["GET"]) return TestClient(app) +ERROR_RESPONSE = { + "success": False, + "detail": "internal server error, please reach out to support team at nivedit@exosphere.host" +} + def test_runtime_error_returns_expected_json(client): resp = client.get("/fail") assert resp.status_code == 500 - response_json = resp.json() - assert response_json.get("success") is False - assert "detail" in response_json - assert "server error" in response_json["detail"].lower() + assert resp.json() == ERROR_RESPONSE def test_value_error_returns_expected_json(client): resp = client.get("/fail_value_error") assert resp.status_code == 500 - response_json = resp.json() - assert response_json.get("success") is False - assert "detail" in response_json - assert "server error" in response_json["detail"].lower() + assert resp.json() == ERROR_RESPONSE def test_key_error_returns_expected_json(client): resp = client.get("/fail_key_error") assert resp.status_code == 500 - response_json = resp.json() - assert response_json.get("success") is False - assert "detail" in response_json - assert "server error" in response_json["detail"].lower() + assert resp.json() == ERROR_RESPONSE def test_normal_request_passes_through(client): resp = client.get("/ok") From d4a6a1d33539eb98eece065d5fbd0673fac95b0b Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 2 Aug 2025 19:13:05 +0530 Subject: [PATCH 17/17] Fix YAML structure/codecov step; cleanup pyproject formatting --- .github/workflows/ci.yml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27d265f2..c879d0cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + steps: - name: Checkout code uses: actions/checkout@v4 @@ -26,31 +27,29 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - # Prefer released versions or test a matrix of supported ones - python-version: "3.12" - - + python-version: '3.12' - name: Install uv uses: astral-sh/setup-uv@v2 with: - enable-cache: true # enables automatic actions/cache reuse + cache: true - name: Install dev dependencies with uv working-directory: api-server run: | uv sync --group dev + - name: Run tests with pytest and coverage working-directory: api-server run: | - uv run pytest --cov=app --cov-report=xml --cov-report=term-missing -v -q --junitxml=pytest-report.xml - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: exospherehost/exospherehost - files: api-server/coverage.xml - flags: unittests - name: codecov-coverage-report - fail_ci_if_error: true + uv run pytest --cov=app --cov-report=xml --cov-report=term-missing -v --junitxml=pytest-report.xml + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: exospherehost/exospherehost + files: api-server/coverage.xml + flags: unittests + name: codecov-coverage-report + fail_ci_if_error: true \ No newline at end of file