From a8f51c9ce91b9d4de587c6ed180ce327210fda8f Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Thu, 30 Apr 2026 16:01:15 -0400 Subject: [PATCH 01/10] Update test_nodenorm_api.py to support alternate OpenAPI locations. --- tests/nodenorm/test_nodenorm_api.py | 35 +++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/nodenorm/test_nodenorm_api.py b/tests/nodenorm/test_nodenorm_api.py index f016350..9701234 100644 --- a/tests/nodenorm/test_nodenorm_api.py +++ b/tests/nodenorm/test_nodenorm_api.py @@ -3,6 +3,7 @@ # These tests are intended to ensure that all the API endpoints on NodeNorm are working as intended. # import urllib.parse +import logging import pytest import requests @@ -13,14 +14,30 @@ def test_openapi_json(target_info): nodenorm_url = target_info['NodeNormURL'] - url = urllib.parse.urljoin(nodenorm_url, 'openapi.json') - response = requests.get(url) - assert response.ok, f"Could not GET {url}: {response}" + # NodeNorm keeps its openapi.json at /openapi.json, but + # NodeNorm ES keeps it at /webapp/openapi.json -- so we should check both. - openapi_json = response.json() - assert openapi_json['info']['x-translator']['infores'] == 'infores:sri-node-normalizer' + openapi_paths = [ + 'openapi.json', + 'webapp/openapi.json', + ] - try: - validate_url(url) - except OpenAPIValidationError as e: - pytest.fail(f"Could not validate OpenAPI at {url}: {e}") + flag_test_passed = False + for openapi_path in openapi_paths: + url = urllib.parse.urljoin(nodenorm_url, openapi_path) + response = requests.get(url) + if not response.ok: + logging.warning(f"Could not GET {url}: {response}") + continue + + openapi_json = response.json() + assert openapi_json['info']['x-translator']['infores'] == 'infores:sri-node-normalizer' + + try: + validate_url(url) + flag_test_passed = True + except OpenAPIValidationError as e: + pytest.fail(f"Could not validate OpenAPI at {url}: {e}") + + if not flag_test_passed: + pytest.fail(f"Could not validate OpenAPI on NodeNorm {nodenorm_url} at any of the expected URLs: {openapi_paths}") From cec7350a9ffa48558da885db9fb054fdbb3d8d80 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Thu, 30 Apr 2026 17:10:13 -0400 Subject: [PATCH 02/10] Updated test for issue #229 so it can run on NodeNorms without /query. --- tests/nodenorm/by_issue/test_nodenorm_229.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/nodenorm/by_issue/test_nodenorm_229.py b/tests/nodenorm/by_issue/test_nodenorm_229.py index f49704c..f3aaefb 100644 --- a/tests/nodenorm/by_issue/test_nodenorm_229.py +++ b/tests/nodenorm/by_issue/test_nodenorm_229.py @@ -1,5 +1,5 @@ # This is a test for https://github.com/TranslatorSRI/NodeNormalization/issues/229 - +import pytest import requests import urllib @@ -7,6 +7,13 @@ def test_nodenorm_229(target_info): nodenorm_url = target_info["NodeNormURL"] + # We can't test this unless NodeNorm has a /query endpoint. + query_url = urllib.parse.urljoin(nodenorm_url, "query") + response = requests.head(query_url) + if response.status_code == 404: + pytest.skip(f"NodeNorm {nodenorm_url} not have a /query endpoint, cannot test issue #229") + return + input_json = { "message": { "query_graph": { @@ -498,8 +505,7 @@ def test_nodenorm_229(target_info): }, } - url = urllib.parse.urljoin(nodenorm_url, "query") - response = requests.post(url, json=input_json) + response = requests.post(query_url, json=input_json) assert response.ok, f"Could not POST test content to {url}: {response.json()}" actual_output = response.json() From eb908054f552d87241ca7e24e8bbba8a1433b734 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 6 May 2026 13:58:54 -0400 Subject: [PATCH 03/10] Add regression test for biothings/NodeNormalizationAPI#14. Tests that NodeNorm ES returns a valid dict for the specific ~300-CURIE list that was reported to cause failures. Co-Authored-By: Claude Sonnet 4.6 --- tests/nodenorm/by_issue/biothings/issue_14.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/nodenorm/by_issue/biothings/issue_14.py diff --git a/tests/nodenorm/by_issue/biothings/issue_14.py b/tests/nodenorm/by_issue/biothings/issue_14.py new file mode 100644 index 0000000..20bf980 --- /dev/null +++ b/tests/nodenorm/by_issue/biothings/issue_14.py @@ -0,0 +1,99 @@ +# Test for https://github.com/biothings/NodeNormalizationAPI/issues/14 +# NodeNorm ES returns an error for this list of CURIEs. +import requests +import urllib.parse + + +CURIES = [ + "PUBCHEM.COMPOUND:5288464", "CHEBI:18332", "CHEBI:29678", "UNII:EVS87XF13W", + "CHEBI:134990", "CHEBI:4027", "CHEBI:65329", "CHEBI:33007", "CHEBI:27584", + "CHEBI:7956", "CHEBI:33364", "CHEBI:28299", "MESH:C000717949", "CHEBI:65408", + "DRUGBANK:DB10510", "CHEBI:192408", "CHEBI:17992", "CHEBI:68656", "MESH:C545474", + "CHEBI:3750", "CHEBI:16240", "CHEBI:34440", "CHEBI:28201", "CHEBI:5132", + "MESH:C097284", "CHEBI:84002", "CHEBI:50867", "CHEBI:41607", "CHEBI:39168", + "CHEBI:33341", "CHEBI:51450", "CHEBI:28757", "CHEBI:87235", "CHEBI:17241", + "CHEBI:37527", "CHEBI:3093", "CHEBI:17574", "PUBCHEM.COMPOUND:16130978", + "CHEBI:28741", "CHEBI:10106", "CHEBI:22652", "UNII:RX077P88RY", "CHEBI:77543", + "CHEBI:2504", "CHEBI:16335", "CHEBI:23414", "CHEBI:27470", "MESH:D014028", + "CHEBI:27214", "CHEBI:59331", "CHEBI:75142", "CHEBI:15365", "CHEBI:3360", + "CHEBI:28939", "CHEBI:16664", "CHEBI:73755", "CHEBI:30513", "CHEBI:138000", + "CHEBI:18145", "PUBCHEM.COMPOUND:95335", "CHEBI:46195", "CHEBI:27568", + "MESH:C000628730", "CHEBI:47807", "CHEBI:34202", "CHEBI:35591", "CHEBI:46677", + "CHEBI:41879", "CHEBI:15366", "CHEBI:2930", "CHEBI:310312", "UNII:Z41TGB4080", + "CHEBI:52726", "CHEBI:17303", "CHEBI:42355", "CHEBI:16973", "CHEBI:28420", + "CHEBI:72544", "CHEBI:9381", "CHEBI:32506", "DRUGBANK:DB10710", "UNII:641ILF0QGZ", + "CHEBI:230644", "CHEBI:4034", "CHEBI:30563", "CHEBI:52492", "UNII:0YO8J06WCR", + "CHEBI:28984", "CHEBI:38705", "CHEBI:61058", "PUBCHEM.COMPOUND:21897015", + "CHEBI:29014", "CHEBI:254496", "CHEBI:2567", "CHEBI:16236", "PUBCHEM.COMPOUND:16014", + "CHEBI:95082", "CHEBI:28262", "CHEBI:27902", "CHEBI:191396", "CHEBI:17243", + "CHEBI:17941", "CHEBI:34873", "UNII:HS813P8QPX", "CHEBI:15379", "CHEBI:48843", + "CHEBI:4470", "CHEBI:8665", "CHEBI:27432", "CHEBI:17747", "CHEBI:26214", + "CHEBI:18227", "CHEBI:17158", "CHEBI:4356", "CHEBI:2922", "CHEBI:28619", + "CHEBI:41774", "CHEBI:17588", "CHEBI:50924", "CHEBI:25322", "CHEBI:22689", + "MESH:D012906", "CHEBI:29287", "CHEBI:8346", "CHEBI:229916", "CHEBI:30621", + "CHEBI:16164", "CHEBI:28119", "CHEBI:30314", "CHEBI:16856", "CHEBI:15971", + "UNII:97PTR2F3Z8", "CHEBI:30114", "CHEBI:16393", "CHEBI:34696", "CHEBI:27385", + "CHEBI:6030", "CHEBI:17549", "CHEBI:64317", "CHEBI:81543", "CHEBI:29866", + "CHEBI:47458", "CHEBI:6078", "CHEBI:34317", "CHEBI:15571", "MESH:C000602261", + "CHEBI:91541", "CHEBI:28260", "CHEBI:1367", "CHEBI:6801", "CHEBI:15756", + "UNII:9G2MP84A8W", "CHEBI:15930", "CHEBI:3962", "CHEBI:34631", "CHEBI:33216", + "CHEBI:30512", "CHEBI:25944", "UNII:24R8721A3S", "CHEBI:35455", "MESH:D013870", + "CHEBI:39170", "CHEBI:3478", "CHEBI:43602", "CHEBI:39176", "CHEBI:34911", + "PUBCHEM.COMPOUND:125922", "CHEBI:32509", "CHEBI:16469", "CHEBI:29035", "CHEBI:7719", + "CHEBI:68642", "CHEBI:25681", "CHEBI:38157", "CHEBI:230671", "CHEBI:88522", + "CHEBI:50122", "DRUGBANK:DB10543", "CHEBI:17051", "CHEBI:34687", "CHEBI:34887", + "CHEBI:28112", "DRUGBANK:DB09337", "CHEBI:5001", "CHEBI:35255", "CHEBI:17688", + "DRUGBANK:DB11010", "CHEBI:75958", "CHEBI:32234", "CHEBI:9495", "CHEBI:5864", + "CHEBI:18721", "CHEBI:16069", "CHEBI:17276", "CHEBI:6842", "CHEBI:27789", + "CHEBI:9288", "CHEBI:28125", "CHEBI:44185", "CHEBI:15948", "CHEBI:4042", + "CHEBI:17230", "PUBCHEM.COMPOUND:9904141", "CHEBI:33262", "CHEBI:5131", + "NCBIGene:2876", "CHEBI:34680", "MESH:C474021", "CHEBI:31653", "CHEBI:17996", + "CHEBI:44445", "CHEBI:81760", "DRUGBANK:DB10429", "CHEBI:28786", "CHEBI:39483", + "MESH:D004041", "CHEBI:3175", "PUBCHEM.COMPOUND:122360683", "CHEBI:27899", + "CHEBI:25805", "CHEBI:48095", "CHEBI:17846", "CHEBI:17310", "DRUGBANK:DB00058", + "CHEBI:27732", "CHEBI:17895", "CHEBI:95089", "CHEBI:30136", "PUBCHEM.COMPOUND:9855813", + "DRUGBANK:DB14377", "CHEBI:22977", "MESH:D019443", "CHEBI:3558", "CHEBI:26523", + "CHEBI:305790", "CHEBI:92360", "CHEBI:144804", "CHEBI:15367", "CHEBI:47808", + "CHEBI:28854", "CHEBI:2663", "CHEBI:22586", "CHEBI:25016", "CHEBI:64163", + "CHEBI:32599", "CHEBI:4791", "CHEBI:27958", "CHEBI:17234", "CHEBI:86368", + "CHEBI:2962", "CHEBI:31348", "CHEBI:28177", "CHEBI:16347", "CHEBI:27744", + "CHEBI:6129", "CHEBI:9249", "CHEBI:30785", "CHEBI:6809", "CHEBI:75955", + "MESH:D001335", "CHEBI:27856", "CHEBI:26401", "CHEBI:16482", "CHEBI:83732", + "CHEBI:31823", "CHEBI:6908", "CHEBI:6605", "PUBCHEM.COMPOUND:3018355", "CHEBI:32497", + "CHEBI:31746", "CHEBI:4806", "CHEBI:16412", "CHEBI:34682", "MESH:D007461", + "CHEBI:39185", "CHEBI:24757", "CHEBI:3179", "CHEBI:28748", "CHEBI:50594", + "PUBCHEM.COMPOUND:3467", "CHEBI:9753", "DRUGBANK:DB13961", "CHEBI:39285", + "MESH:C509867", "CHEBI:34372", "CHEBI:52497", "CHEBI:4903", "CHEBI:8113", + "CHEBI:28694", "DRUGBANK:DB14242", "CHEBI:166520", "MESH:D052638", "CHEBI:641", + "MESH:D000336", "CHEBI:145499", "CHEBI:16908", "CHEBI:28216", "CHEBI:63317", + "CHEBI:73169", "CHEBI:9150", "MESH:C000612485", "CHEBI:37825", "CHEBI:8069", + "CHEBI:6437", "CHEBI:7494", "CHEBI:29865", "CHEBI:28842", "CHEBI:44658", + "CHEBI:38221", "CHEBI:9943", "CHEBI:16796", "CHEBI:82538", "CHEBI:63933", + "CHEBI:16480", "DRUGBANK:DB10575", "CHEBI:46345", +] + + +def test_biothings_issue_14(target_info): + """ + NodeNorm ES returns an error (not a valid JSON dict) when queried with this + specific list of ~300 CURIEs. The endpoint should return 200 with a dict + mapping each CURIE to its normalization result (or null). + """ + nodenorm_url = target_info["NodeNormURL"] + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + + response = requests.post(url, json={"curies": CURIES}) + assert response.ok, ( + f"POST {url} returned HTTP {response.status_code}: {response.text[:500]}" + ) + + result = response.json() + assert isinstance(result, dict), ( + f"Expected a dict response from {url}, got {type(result).__name__}: " + f"{str(result)[:500]}" + ) + + missing = [c for c in CURIES if c not in result] + assert not missing, ( + f"{len(missing)} CURIEs missing from response: {missing[:20]}" + ) From f2d63499441a6439454f728fede5757fcbcd586d Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 6 May 2026 14:01:02 -0400 Subject: [PATCH 04/10] Added drug_chemical_conflate = False, thus triggering the test. --- tests/nodenorm/by_issue/biothings/issue_14.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nodenorm/by_issue/biothings/issue_14.py b/tests/nodenorm/by_issue/biothings/issue_14.py index 20bf980..42dff66 100644 --- a/tests/nodenorm/by_issue/biothings/issue_14.py +++ b/tests/nodenorm/by_issue/biothings/issue_14.py @@ -82,7 +82,7 @@ def test_biothings_issue_14(target_info): nodenorm_url = target_info["NodeNormURL"] url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") - response = requests.post(url, json={"curies": CURIES}) + response = requests.post(url, json={"curies": CURIES, "conflate": False, "drug_chemical_conflate": True}) assert response.ok, ( f"POST {url} returned HTTP {response.status_code}: {response.text[:500]}" ) From c4773b6f19fe2cceebad02157a18556292ddbc8f Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 6 May 2026 14:09:26 -0400 Subject: [PATCH 05/10] Restructure biothings/NodeNormalizationAPI#14 test with bisected reproducer. Three test shapes: all CURIEs minus the bad pair (passes), the minimal failing pair CHEBI:17310 + DRUGBANK:DB00058 (xfail), and each CURIE individually (all pass). Co-Authored-By: Claude Sonnet 4.6 --- tests/nodenorm/by_issue/biothings/issue_14.py | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/tests/nodenorm/by_issue/biothings/issue_14.py b/tests/nodenorm/by_issue/biothings/issue_14.py index 42dff66..83b9097 100644 --- a/tests/nodenorm/by_issue/biothings/issue_14.py +++ b/tests/nodenorm/by_issue/biothings/issue_14.py @@ -1,10 +1,17 @@ # Test for https://github.com/biothings/NodeNormalizationAPI/issues/14 -# NodeNorm ES returns an error for this list of CURIEs. -import requests +# NodeNorm ES returns HTTP 500 when queried with conflate=False and +# drug_chemical_conflate=True for a list that contains both CHEBI:17310 and +# DRUGBANK:DB00058. Bisection confirmed this pair is the minimal reproducer; +# neither CURIE fails on its own. import urllib.parse +import pytest +import requests + +# The two CURIEs that together trigger the 500 under drug_chemical_conflate=True. +FAILING_PAIR = ["CHEBI:17310", "DRUGBANK:DB00058"] -CURIES = [ +ALL_CURIES = [ "PUBCHEM.COMPOUND:5288464", "CHEBI:18332", "CHEBI:29678", "UNII:EVS87XF13W", "CHEBI:134990", "CHEBI:4027", "CHEBI:65329", "CHEBI:33007", "CHEBI:27584", "CHEBI:7956", "CHEBI:33364", "CHEBI:28299", "MESH:C000717949", "CHEBI:65408", @@ -72,28 +79,51 @@ "CHEBI:16480", "DRUGBANK:DB10575", "CHEBI:46345", ] +REMAINING_CURIES = [c for c in ALL_CURIES if c not in FAILING_PAIR] -def test_biothings_issue_14(target_info): - """ - NodeNorm ES returns an error (not a valid JSON dict) when queried with this - specific list of ~300 CURIEs. The endpoint should return 200 with a dict - mapping each CURIE to its normalization result (or null). - """ - nodenorm_url = target_info["NodeNormURL"] + +def _post(nodenorm_url, curies): url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + return requests.post( + url, + json={"curies": curies, "conflate": False, "drug_chemical_conflate": True}, + ) + - response = requests.post(url, json={"curies": CURIES, "conflate": False, "drug_chemical_conflate": True}) +def _assert_ok(response, url, curies): assert response.ok, ( - f"POST {url} returned HTTP {response.status_code}: {response.text[:500]}" + f"POST {url} returned HTTP {response.status_code} for {curies}: " + f"{response.text[:500]}" ) - result = response.json() assert isinstance(result, dict), ( - f"Expected a dict response from {url}, got {type(result).__name__}: " - f"{str(result)[:500]}" + f"Expected a dict response, got {type(result).__name__}: {str(result)[:500]}" ) + missing = [c for c in curies if c not in result] + assert not missing, f"{len(missing)} CURIEs missing from response: {missing}" - missing = [c for c in CURIES if c not in result] - assert not missing, ( - f"{len(missing)} CURIEs missing from response: {missing[:20]}" - ) + +def test_all_curies_except_failing_pair(target_info): + """All CURIEs from the original report, minus the known-bad pair, should succeed.""" + nodenorm_url = target_info["NodeNormURL"] + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + response = _post(nodenorm_url, REMAINING_CURIES) + _assert_ok(response, url, REMAINING_CURIES) + + +@pytest.mark.xfail(strict=True, reason="CHEBI:17310 + DRUGBANK:DB00058 together cause HTTP 500 under drug_chemical_conflate=True (biothings/NodeNormalizationAPI#14)") +def test_failing_pair(target_info): + """CHEBI:17310 and DRUGBANK:DB00058 together trigger HTTP 500 — minimal reproducer.""" + nodenorm_url = target_info["NodeNormURL"] + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + response = _post(nodenorm_url, FAILING_PAIR) + _assert_ok(response, url, FAILING_PAIR) + + +@pytest.mark.parametrize("curie", ALL_CURIES) +def test_individual_curie(target_info, curie): + """Every CURIE from the original report should succeed when queried on its own.""" + nodenorm_url = target_info["NodeNormURL"] + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + response = _post(nodenorm_url, [curie]) + _assert_ok(response, url, [curie]) From 9a2dbdf27e304296090de10035ac47ea393a8dc4 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 6 May 2026 14:23:10 -0400 Subject: [PATCH 06/10] Add ci-biothings target pointing to BioThings NodeNorm CI endpoint. Co-Authored-By: Claude Sonnet 4.6 --- tests/targets.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/targets.ini b/tests/targets.ini index be12baf..f98086d 100644 --- a/tests/targets.ini +++ b/tests/targets.ini @@ -43,6 +43,12 @@ NameResURL = https://name-lookup.ci.transltr.io/ NodeNormURL = https://nodenorm-es.ci.transltr.io/ NameResURL = https://namelookup-es.ci.transltr.io/ +# ci-biothings: Previous release of NodeNorm ES; will be turned off going forward, +# but included here for completeness. +[ci-biothings] +NodeNormURL = https://biothings.ci.transltr.io/nodenorm/ +NameResURL = https://name-lookup.ci.transltr.io/ + [dev] NodeNormURL = https://nodenormalization-sri.renci.org/ NameResURL = https://name-resolution-sri.renci.org/ From e738e4ec995883ea4529fb30babf0513daac16dd Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Wed, 6 May 2026 14:26:36 -0400 Subject: [PATCH 07/10] Add test for biothings/NodeNormalizationAPI#10. UNII:2ZM8CX04RZ triggers HTTP 500 with drug_chemical_conflate=True on both ci and ci-biothings; passes with drug_chemical_conflate=False. Co-Authored-By: Claude Sonnet 4.6 --- tests/nodenorm/by_issue/biothings/issue_10.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/nodenorm/by_issue/biothings/issue_10.py diff --git a/tests/nodenorm/by_issue/biothings/issue_10.py b/tests/nodenorm/by_issue/biothings/issue_10.py new file mode 100644 index 0000000..14dc8bf --- /dev/null +++ b/tests/nodenorm/by_issue/biothings/issue_10.py @@ -0,0 +1,43 @@ +# Test for https://github.com/biothings/NodeNormalizationAPI/issues/10 +# Querying UNII:2ZM8CX04RZ with drug_chemical_conflate=True causes HTTP 500. +# The same query with drug_chemical_conflate=False succeeds. +import urllib.parse + +import pytest +import requests + +CURIE = "UNII:2ZM8CX04RZ" + + +def _post(nodenorm_url, drug_chemical_conflate): + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + return requests.post( + url, + json={"curies": [CURIE], "conflate": True, "drug_chemical_conflate": drug_chemical_conflate}, + ) + + +def _assert_ok(response, nodenorm_url, drug_chemical_conflate): + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + assert response.ok, ( + f"POST {url} returned HTTP {response.status_code} for {CURIE} " + f"(drug_chemical_conflate={drug_chemical_conflate}): {response.text[:500]}" + ) + result = response.json() + assert isinstance(result, dict), ( + f"Expected a dict response, got {type(result).__name__}: {str(result)[:500]}" + ) + assert CURIE in result, f"{CURIE} missing from response" + + +def test_without_drug_chemical_conflate(target_info): + """UNII:2ZM8CX04RZ should succeed when drug_chemical_conflate=False.""" + response = _post(target_info["NodeNormURL"], drug_chemical_conflate=False) + _assert_ok(response, target_info["NodeNormURL"], drug_chemical_conflate=False) + + +@pytest.mark.xfail(strict=True, reason="UNII:2ZM8CX04RZ causes HTTP 500 with drug_chemical_conflate=True (biothings/NodeNormalizationAPI#10)") +def test_with_drug_chemical_conflate(target_info): + """UNII:2ZM8CX04RZ triggers HTTP 500 when drug_chemical_conflate=True.""" + response = _post(target_info["NodeNormURL"], drug_chemical_conflate=True) + _assert_ok(response, target_info["NodeNormURL"], drug_chemical_conflate=True) From fe943028fe924590a73da963c36a4feae1143029 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Fri, 8 May 2026 01:21:55 -0400 Subject: [PATCH 08/10] Add regression test for biothings/pending.api#338. NCBITaxon:2 returns null when queried alongside MESH:C029371, but succeeds alongside MESH:D008795 or on its own. Co-Authored-By: Claude Sonnet 4.6 --- .../by_issue/biothings/pending_api_338.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/nodenorm/by_issue/biothings/pending_api_338.py diff --git a/tests/nodenorm/by_issue/biothings/pending_api_338.py b/tests/nodenorm/by_issue/biothings/pending_api_338.py new file mode 100644 index 0000000..454eeaf --- /dev/null +++ b/tests/nodenorm/by_issue/biothings/pending_api_338.py @@ -0,0 +1,54 @@ +# Test for https://github.com/biothings/pending.api/issues/338 +# NodeNorm returns null for NCBITaxon:2 when queried alongside MESH:C029371, +# but returns it correctly alongside MESH:D008795. The bug triggers only when +# the two CURIEs are in the same request; each CURIE succeeds on its own. +import urllib.parse + +import pytest +import requests + +TRIGGERING_PAIR = ["MESH:C029371", "NCBITaxon:2"] +WORKING_PAIR = ["MESH:D008795", "NCBITaxon:2"] +PARAMS = {"conflate": True, "description": False, "drug_chemical_conflate": False} + + +def _post(nodenorm_url, curies): + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + return requests.post(url, json={"curies": curies, **PARAMS}) + + +def _assert_ok(response, nodenorm_url, curies): + url = urllib.parse.urljoin(nodenorm_url, "get_normalized_nodes") + assert response.ok, ( + f"POST {url} returned HTTP {response.status_code} for {curies}: " + f"{response.text[:500]}" + ) + result = response.json() + assert isinstance(result, dict), ( + f"Expected a dict response, got {type(result).__name__}: {str(result)[:500]}" + ) + null_curies = [c for c in curies if result.get(c) is None] + assert not null_curies, f"CURIEs returned as null: {null_curies}" + + +def test_working_pair(target_info): + """MESH:D008795 and NCBITaxon:2 together should both be non-null (positive control).""" + nodenorm_url = target_info["NodeNormURL"] + response = _post(nodenorm_url, WORKING_PAIR) + _assert_ok(response, nodenorm_url, WORKING_PAIR) + + +@pytest.mark.xfail(strict=True, reason="MESH:C029371 alongside NCBITaxon:2 causes NCBITaxon:2 to return null (biothings/pending.api#338)") +def test_triggering_pair(target_info): + """MESH:C029371 alongside NCBITaxon:2 causes NCBITaxon:2 to return null.""" + nodenorm_url = target_info["NodeNormURL"] + response = _post(nodenorm_url, TRIGGERING_PAIR) + _assert_ok(response, nodenorm_url, TRIGGERING_PAIR) + + +@pytest.mark.parametrize("curie", sorted(set(TRIGGERING_PAIR + WORKING_PAIR))) +def test_individual_curie(target_info, curie): + """Each CURIE should succeed when queried on its own.""" + nodenorm_url = target_info["NodeNormURL"] + response = _post(nodenorm_url, [curie]) + _assert_ok(response, nodenorm_url, [curie]) From ad4dce079c26bc1a1cad0b71be24c95d3774ad81 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Tue, 12 May 2026 16:55:00 -0400 Subject: [PATCH 09/10] Renamed tests with `test_` prefix so they are run. --- .../nodenorm/by_issue/biothings/{issue_10.py => test_issue_10.py} | 0 .../nodenorm/by_issue/biothings/{issue_14.py => test_issue_14.py} | 0 .../biothings/{pending_api_338.py => test_pending_api_338.py} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/nodenorm/by_issue/biothings/{issue_10.py => test_issue_10.py} (100%) rename tests/nodenorm/by_issue/biothings/{issue_14.py => test_issue_14.py} (100%) rename tests/nodenorm/by_issue/biothings/{pending_api_338.py => test_pending_api_338.py} (100%) diff --git a/tests/nodenorm/by_issue/biothings/issue_10.py b/tests/nodenorm/by_issue/biothings/test_issue_10.py similarity index 100% rename from tests/nodenorm/by_issue/biothings/issue_10.py rename to tests/nodenorm/by_issue/biothings/test_issue_10.py diff --git a/tests/nodenorm/by_issue/biothings/issue_14.py b/tests/nodenorm/by_issue/biothings/test_issue_14.py similarity index 100% rename from tests/nodenorm/by_issue/biothings/issue_14.py rename to tests/nodenorm/by_issue/biothings/test_issue_14.py diff --git a/tests/nodenorm/by_issue/biothings/pending_api_338.py b/tests/nodenorm/by_issue/biothings/test_pending_api_338.py similarity index 100% rename from tests/nodenorm/by_issue/biothings/pending_api_338.py rename to tests/nodenorm/by_issue/biothings/test_pending_api_338.py From ecad2259d1ba8d662b8a8895e91b7147a841b1ed Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Tue, 12 May 2026 16:57:21 -0400 Subject: [PATCH 10/10] Removed xfails (they work now!). --- tests/nodenorm/by_issue/biothings/test_issue_10.py | 1 - tests/nodenorm/by_issue/biothings/test_issue_14.py | 1 - tests/nodenorm/by_issue/biothings/test_pending_api_338.py | 1 - 3 files changed, 3 deletions(-) diff --git a/tests/nodenorm/by_issue/biothings/test_issue_10.py b/tests/nodenorm/by_issue/biothings/test_issue_10.py index 14dc8bf..18143ad 100644 --- a/tests/nodenorm/by_issue/biothings/test_issue_10.py +++ b/tests/nodenorm/by_issue/biothings/test_issue_10.py @@ -36,7 +36,6 @@ def test_without_drug_chemical_conflate(target_info): _assert_ok(response, target_info["NodeNormURL"], drug_chemical_conflate=False) -@pytest.mark.xfail(strict=True, reason="UNII:2ZM8CX04RZ causes HTTP 500 with drug_chemical_conflate=True (biothings/NodeNormalizationAPI#10)") def test_with_drug_chemical_conflate(target_info): """UNII:2ZM8CX04RZ triggers HTTP 500 when drug_chemical_conflate=True.""" response = _post(target_info["NodeNormURL"], drug_chemical_conflate=True) diff --git a/tests/nodenorm/by_issue/biothings/test_issue_14.py b/tests/nodenorm/by_issue/biothings/test_issue_14.py index 83b9097..1183188 100644 --- a/tests/nodenorm/by_issue/biothings/test_issue_14.py +++ b/tests/nodenorm/by_issue/biothings/test_issue_14.py @@ -111,7 +111,6 @@ def test_all_curies_except_failing_pair(target_info): _assert_ok(response, url, REMAINING_CURIES) -@pytest.mark.xfail(strict=True, reason="CHEBI:17310 + DRUGBANK:DB00058 together cause HTTP 500 under drug_chemical_conflate=True (biothings/NodeNormalizationAPI#14)") def test_failing_pair(target_info): """CHEBI:17310 and DRUGBANK:DB00058 together trigger HTTP 500 — minimal reproducer.""" nodenorm_url = target_info["NodeNormURL"] diff --git a/tests/nodenorm/by_issue/biothings/test_pending_api_338.py b/tests/nodenorm/by_issue/biothings/test_pending_api_338.py index 454eeaf..9306529 100644 --- a/tests/nodenorm/by_issue/biothings/test_pending_api_338.py +++ b/tests/nodenorm/by_issue/biothings/test_pending_api_338.py @@ -38,7 +38,6 @@ def test_working_pair(target_info): _assert_ok(response, nodenorm_url, WORKING_PAIR) -@pytest.mark.xfail(strict=True, reason="MESH:C029371 alongside NCBITaxon:2 causes NCBITaxon:2 to return null (biothings/pending.api#338)") def test_triggering_pair(target_info): """MESH:C029371 alongside NCBITaxon:2 causes NCBITaxon:2 to return null.""" nodenorm_url = target_info["NodeNormURL"]