From 56f92142a0c1b27760e632a28d8987b8d9c0f7e3 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:06:45 -0400 Subject: [PATCH 1/7] Add openapi_version and backend to /status output Closes #377. Surface the app's OpenAPI info.version and a backend key (currently always "redis") at the top of /status. Co-Authored-By: Claude Opus 4.8 --- node_normalizer/server.py | 2 ++ tests/frontend/__init__.py | 1 + tests/frontend/test_status.py | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 tests/frontend/__init__.py create mode 100644 tests/frontend/test_status.py diff --git a/node_normalizer/server.py b/node_normalizer/server.py index 9af313ca..6af3f63f 100644 --- a/node_normalizer/server.py +++ b/node_normalizer/server.py @@ -121,6 +121,8 @@ async def status() -> Dict: return { "status": "running", + "openapi_version": app.openapi_schema["info"]["version"], + "backend": "redis", "nodenorm_version": nodenorm_version, "babel_version": babel_version, "babel_version_url": babel_version_url, diff --git a/tests/frontend/__init__.py b/tests/frontend/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/frontend/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/frontend/test_status.py b/tests/frontend/test_status.py new file mode 100644 index 00000000..1f813c65 --- /dev/null +++ b/tests/frontend/test_status.py @@ -0,0 +1,24 @@ +"""Test the /status endpoint (issue #377: openapi_version + backend keys).""" +from node_normalizer.server import app +from fastapi.testclient import TestClient + + +class MockStatusRedis: + async def dbsize(self): + return 0 + + async def used_memory_rss_human(self): + return "0B" + + +def test_status_openapi_version_and_backend(): + for db in ( + "eq_id_to_id_db", "id_to_eqids_db", "id_to_type_db", "curie_to_bl_type_db", + "info_content_db", "gene_protein_db", "chemical_drug_db", + ): + setattr(app.state, db, MockStatusRedis()) + + body = TestClient(app).get("/status").json() + + assert body["backend"] == "redis" + assert body["openapi_version"] == app.openapi_schema["info"]["version"] From a67f780d0210b7d2bb58d82a233cc422d2b00e19 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:07:12 -0400 Subject: [PATCH 2/7] Move frontend tests into tests/frontend/ Group the frontend (server/normalizer) tests under tests/frontend/, leaving the loader tests at tests/ for a future tests/loader/ move. Relative imports (..helpers) and resources paths (parent.parent) updated for the new depth; shared helpers/, resources/, and conftest.py stay at tests/. Co-Authored-By: Claude Opus 4.8 --- tests/{ => frontend}/test_callback.py | 2 +- tests/{ => frontend}/test_endpoints.py | 14 +++++++------- tests/{ => frontend}/test_norm.py | 2 +- tests/{ => frontend}/test_normalizer.py | 6 +++--- tests/{ => frontend}/test_redis_adapter.py | 2 +- tests/{ => frontend}/test_setid.py | 0 6 files changed, 13 insertions(+), 13 deletions(-) rename tests/{ => frontend}/test_callback.py (94%) rename tests/{ => frontend}/test_endpoints.py (88%) rename tests/{ => frontend}/test_norm.py (98%) rename tests/{ => frontend}/test_normalizer.py (97%) rename tests/{ => frontend}/test_redis_adapter.py (98%) rename tests/{ => frontend}/test_setid.py (100%) diff --git a/tests/test_callback.py b/tests/frontend/test_callback.py similarity index 94% rename from tests/test_callback.py rename to tests/frontend/test_callback.py index e81f2465..7d634196 100644 --- a/tests/test_callback.py +++ b/tests/frontend/test_callback.py @@ -11,7 +11,7 @@ logger = LoggingUtil.init_logging() -premerged_response = Path(__file__).parent / "resources" / "premerged_response.json" +premerged_response = Path(__file__).parent.parent / "resources" / "premerged_response.json" def test_async_query_callback(session): diff --git a/tests/test_endpoints.py b/tests/frontend/test_endpoints.py similarity index 88% rename from tests/test_endpoints.py rename to tests/frontend/test_endpoints.py index 11ff8b7a..b7e85da2 100644 --- a/tests/test_endpoints.py +++ b/tests/frontend/test_endpoints.py @@ -13,16 +13,16 @@ from reasoner_pydantic import Response # Need to add to sources root to avoid linter warnings -from .helpers.redis_mocks import mock_get_equivalent_curies -from .helpers.redis_mocks import mock_get_ic +from ..helpers.redis_mocks import mock_get_equivalent_curies +from ..helpers.redis_mocks import mock_get_ic -premerged_response = Path(__file__).parent / "resources" / "premerged_response.json" -postmerged_response = Path(__file__).parent / "resources" / "postmerged_response.json" +premerged_response = Path(__file__).parent.parent / "resources" / "premerged_response.json" +postmerged_response = Path(__file__).parent.parent / "resources" / "postmerged_response.json" -premerged_dupe_edge = Path(__file__).parent / "resources" / "premerged_dupe_edge.json" -postmerged_dupe_edge = Path(__file__).parent / "resources" / "postmerged_dupe_edge.json" +premerged_dupe_edge = Path(__file__).parent.parent / "resources" / "premerged_dupe_edge.json" +postmerged_dupe_edge = Path(__file__).parent.parent / "resources" / "postmerged_dupe_edge.json" -input_set = Path(__file__).parent / "resources" / "input_set.json" +input_set = Path(__file__).parent.parent / "resources" / "input_set.json" class TestServer: diff --git a/tests/test_norm.py b/tests/frontend/test_norm.py similarity index 98% rename from tests/test_norm.py rename to tests/frontend/test_norm.py index ab136bbf..3cf3689b 100644 --- a/tests/test_norm.py +++ b/tests/frontend/test_norm.py @@ -3,7 +3,7 @@ from node_normalizer.server import app from fastapi.testclient import TestClient from unittest.mock import Mock, patch -from .helpers.redis_mocks import mock_get_equivalent_curies, mock_get_ic +from ..helpers.redis_mocks import mock_get_equivalent_curies, mock_get_ic from pathlib import Path import os from bmt import Toolkit diff --git a/tests/test_normalizer.py b/tests/frontend/test_normalizer.py similarity index 97% rename from tests/test_normalizer.py rename to tests/frontend/test_normalizer.py index d7a27f56..8e8034bc 100644 --- a/tests/test_normalizer.py +++ b/tests/frontend/test_normalizer.py @@ -12,7 +12,7 @@ from unittest.mock import Mock, patch # Need to add to sources root to avoid linter warnings -from .helpers.redis_mocks import mock_get_equivalent_curies, mock_get_ic +from ..helpers.redis_mocks import mock_get_equivalent_curies, mock_get_ic from types import SimpleNamespace from node_normalizer.normalizer import ( @@ -121,8 +121,8 @@ def float_or_none(x): return None -premerged_graph = Path(__file__).parent / "resources" / "premerged_kgraph.json" -postmerged_graph = Path(__file__).parent / "resources" / "postmerged_kgraph.json" +premerged_graph = Path(__file__).parent.parent / "resources" / "premerged_kgraph.json" +postmerged_graph = Path(__file__).parent.parent / "resources" / "postmerged_kgraph.json" class TestNormalizer: diff --git a/tests/test_redis_adapter.py b/tests/frontend/test_redis_adapter.py similarity index 98% rename from tests/test_redis_adapter.py rename to tests/frontend/test_redis_adapter.py index 78cbe367..7f7212fb 100644 --- a/tests/test_redis_adapter.py +++ b/tests/frontend/test_redis_adapter.py @@ -4,7 +4,7 @@ from pathlib import Path from unittest.mock import patch -REDIS_CONF_PATH = Path(__file__).parent / 'resources' / 'redis-config.yaml' +REDIS_CONF_PATH = Path(__file__).parent.parent / 'resources' / 'redis-config.yaml' @pytest.fixture def config_dict(): diff --git a/tests/test_setid.py b/tests/frontend/test_setid.py similarity index 100% rename from tests/test_setid.py rename to tests/frontend/test_setid.py From 2f8351892a850798ba86d6f6ce12f14eea95220c Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:08:53 -0400 Subject: [PATCH 3/7] Update tests/CLAUDE.md paths for moved frontend tests Co-Authored-By: Claude Opus 4.8 --- tests/CLAUDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CLAUDE.md b/tests/CLAUDE.md index 97612da6..e3476628 100644 --- a/tests/CLAUDE.md +++ b/tests/CLAUDE.md @@ -7,7 +7,7 @@ requests/docker/testcontainers landmine) are in `documentation/Loader.md`. ## The shared-app-singleton pitfall -`test_norm.py` and `test_setid.py` assign to `app.state.*` at **import time**, +`frontend/test_norm.py` and `frontend/test_setid.py` assign to `app.state.*` at **import time**, mutating the shared `node_normalizer.server.app` singleton for the rest of the process. Don't assume that app is pristine in a later test, and don't add more import-time mutation. New tests should build their own fake app @@ -38,7 +38,7 @@ mock — it avoids reimplementing the loader's canonical-id/db-5 keying. See `create_node` reads Redis beyond these dbs, so the stored-`preferred_name` path needs nothing more. -For a pure-unit version that skips Docker entirely, `test_normalizer.py` drives +For a pure-unit version that skips Docker entirely, `frontend/test_normalizer.py` drives `get_normalized_nodes` / `create_node` against a tiny `_MgetRedis` mock — cheaper, but you must construct the redis values (canonical-id keys, db-5 JSON, gene-first conflation list) by hand, so it's easy to encode an assumption the loader doesn't actually make. From 6048a1ee3bc4a3c7201411a772c8bb4ce626446e Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:09:06 -0400 Subject: [PATCH 4/7] Renamed openapi_version to version, reused existing logic. --- node_normalizer/server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/node_normalizer/server.py b/node_normalizer/server.py index 6af3f63f..37a19a90 100644 --- a/node_normalizer/server.py +++ b/node_normalizer/server.py @@ -121,9 +121,8 @@ async def status() -> Dict: return { "status": "running", - "openapi_version": app.openapi_schema["info"]["version"], + "version": nodenorm_version, "backend": "redis", - "nodenorm_version": nodenorm_version, "babel_version": babel_version, "babel_version_url": babel_version_url, "biolink_model": { From 481cbb5f33a437690a5adfe4207e059e33e1362d Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:09:50 -0400 Subject: [PATCH 5/7] Update status test for consolidated version key The /status output now surfaces the app version as "version" (e.g. "v2.4.1") rather than a separate "openapi_version" key. Co-Authored-By: Claude Opus 4.8 --- tests/frontend/test_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/frontend/test_status.py b/tests/frontend/test_status.py index 1f813c65..0d637f8d 100644 --- a/tests/frontend/test_status.py +++ b/tests/frontend/test_status.py @@ -21,4 +21,4 @@ def test_status_openapi_version_and_backend(): body = TestClient(app).get("/status").json() assert body["backend"] == "redis" - assert body["openapi_version"] == app.openapi_schema["info"]["version"] + assert body["version"] == "v" + app.openapi_schema["info"]["version"] From be31980157772992d886a62c3728a09e3fcd995b Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:12:35 -0400 Subject: [PATCH 6/7] Removed leading 'v', which is probably more confusing than anything. --- node_normalizer/server.py | 2 +- tests/frontend/test_status.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node_normalizer/server.py b/node_normalizer/server.py index 37a19a90..c0ace631 100644 --- a/node_normalizer/server.py +++ b/node_normalizer/server.py @@ -117,7 +117,7 @@ async def status() -> Dict: # Can we figure out the NodeNorm version? nodenorm_version = "unknown" if "version" in app.openapi_schema["info"]: - nodenorm_version = "v" + app.openapi_schema["info"]["version"] + nodenorm_version = app.openapi_schema["info"]["version"] return { "status": "running", diff --git a/tests/frontend/test_status.py b/tests/frontend/test_status.py index 0d637f8d..b4cac77c 100644 --- a/tests/frontend/test_status.py +++ b/tests/frontend/test_status.py @@ -21,4 +21,4 @@ def test_status_openapi_version_and_backend(): body = TestClient(app).get("/status").json() assert body["backend"] == "redis" - assert body["version"] == "v" + app.openapi_schema["info"]["version"] + assert body["version"] == app.openapi_schema["info"]["version"] From ecd32c00bd2097919fe377c7615b73fc68f5b5c8 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 15 Jul 2026 07:19:01 -0400 Subject: [PATCH 7/7] Mark test_endpoints.py/test_callback.py as skipped in-file instead of CI --ignore pytestmark = pytest.mark.skip travels with the file, so it survives moves (e.g. tests/ -> tests/frontend/) instead of relying on the workflow to keep --ignore paths in sync. --- .github/workflows/test.yml | 5 +---- tests/frontend/test_callback.py | 5 +++++ tests/frontend/test_endpoints.py | 5 +++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e9205322..6edc1722 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,11 +18,8 @@ jobs: - name: Install dependencies run: pip install -r requirements.txt -r requirements-loader.txt -r requirements-test.txt - # test_endpoints.py and test_callback.py use an older docker-compose harness - # that builds the full app image; they are not yet wired up for CI (tracked - # in issue #383). Everything else runs headless. - name: Unit tests - run: pytest -m "not integration" --ignore=tests/test_endpoints.py --ignore=tests/test_callback.py + run: pytest -m "not integration" # GitHub's ubuntu runners have Docker, so testcontainers can start a real # Redis for the loader integration test. diff --git a/tests/frontend/test_callback.py b/tests/frontend/test_callback.py index 7d634196..759547e2 100644 --- a/tests/frontend/test_callback.py +++ b/tests/frontend/test_callback.py @@ -2,6 +2,7 @@ import json import time +import pytest import reasoner_pydantic import requests import fastapi @@ -11,6 +12,10 @@ logger = LoggingUtil.init_logging() +# Uses an older docker-compose harness that builds the full app image; not yet +# wired up for CI (tracked in issue #383). +pytestmark = pytest.mark.skip(reason="requires docker-compose harness, see #383") + premerged_response = Path(__file__).parent.parent / "resources" / "premerged_response.json" diff --git a/tests/frontend/test_endpoints.py b/tests/frontend/test_endpoints.py index b7e85da2..c5012ed9 100644 --- a/tests/frontend/test_endpoints.py +++ b/tests/frontend/test_endpoints.py @@ -1,6 +1,7 @@ """Test node_normalizer server.py""" import json +import pytest import reasoner_pydantic from node_normalizer.server import app @@ -16,6 +17,10 @@ from ..helpers.redis_mocks import mock_get_equivalent_curies from ..helpers.redis_mocks import mock_get_ic +# Uses an older docker-compose harness that builds the full app image; not yet +# wired up for CI (tracked in issue #383). +pytestmark = pytest.mark.skip(reason="requires docker-compose harness, see #383") + premerged_response = Path(__file__).parent.parent / "resources" / "premerged_response.json" postmerged_response = Path(__file__).parent.parent / "resources" / "postmerged_response.json"