Skip to content

Commit 34c0e28

Browse files
address bot feedback for run denpoint tests, add async mock DB, cleanup
1 parent 4461c63 commit 34c0e28

1 file changed

Lines changed: 47 additions & 15 deletions

File tree

tests/routers/openml/runs_get_test.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import asyncio
44
from http import HTTPStatus
5-
from typing import Any
5+
from typing import Any, NamedTuple
66
from unittest.mock import AsyncMock, patch
77

88
import deepdiff
@@ -17,6 +17,7 @@
1717
# ── Fixtures assume run 24 exists in the test DB (confirmed in research) ──
1818
_RUN_ID = 24
1919
_MISSING_RUN_ID = 999_999_999
20+
_MISSING_USER_ID = 999_999_999
2021
_RUN_NOT_FOUND_CODE = "236"
2122

2223
_RUN_UPLOADER_ID = 1159
@@ -102,6 +103,34 @@ async def test_get_run_known_values(py_api: httpx.AsyncClient) -> None:
102103
assert run["error"] == []
103104

104105

106+
async def test_get_run_non_empty_error(py_api: httpx.AsyncClient) -> None:
107+
"""A run with a non-null error_message is serialized as a single-item error list."""
108+
109+
# Since the test database does not have a run with an error, we mock the DB fetch
110+
class MockRunRow(NamedTuple):
111+
rid: int
112+
uploader: int
113+
setup: int
114+
task_id: int
115+
error_message: str
116+
117+
mock_row = MockRunRow(
118+
rid=_RUN_ID,
119+
uploader=_RUN_UPLOADER_ID,
120+
setup=_RUN_SETUP_ID,
121+
task_id=_RUN_TASK_ID,
122+
error_message="Some error from the backend",
123+
)
124+
125+
with patch("routers.openml.runs.database.runs.get", new_callable=AsyncMock) as mock_get:
126+
mock_get.return_value = mock_row
127+
response = await py_api.get(f"/run/{_RUN_ID}")
128+
assert response.status_code == HTTPStatus.OK
129+
130+
run = response.json()
131+
assert run["error"] == ["Some error from the backend"]
132+
133+
105134
async def test_get_run_input_data_shape(py_api: httpx.AsyncClient) -> None:
106135
"""input_data has the PHP envelope structure {"dataset": [...]}."""
107136
response = await py_api.get(f"/run/{_RUN_ID}")
@@ -291,30 +320,30 @@ async def test_db_get_evaluations(expdb_test: AsyncConnection) -> None:
291320
async def test_db_get_evaluations_empty_engine_list(expdb_test: AsyncConnection) -> None:
292321
"""get_evaluations with no engine IDs returns an empty list (not an error)."""
293322
rows = await database.runs.get_evaluations(_RUN_ID, expdb_test, evaluation_engine_ids=[])
294-
assert isinstance(rows, list)
323+
assert rows == []
295324

296325

297326
async def test_db_get_task_type(expdb_test: AsyncConnection) -> None:
298327
"""database.runs.get_task_type returns 'Supervised Classification' for task 115."""
299-
task_type = await database.runs.get_task_type(115, expdb_test)
328+
task_type = await database.runs.get_task_type(_RUN_TASK_ID, expdb_test)
300329
assert task_type == "Supervised Classification"
301330

302331

303332
async def test_db_get_task_evaluation_measure_missing(expdb_test: AsyncConnection) -> None:
304333
"""get_task_evaluation_measure returns None (not '') when absent."""
305-
measure = await database.runs.get_task_evaluation_measure(115, expdb_test)
334+
measure = await database.runs.get_task_evaluation_measure(_RUN_TASK_ID, expdb_test)
306335
assert measure is None
307336

308337

309338
async def test_db_get_uploader_name(user_test: AsyncConnection) -> None:
310339
"""database.runs.get_uploader_name returns 'Cynthia Glover' for user 1159."""
311-
name = await database.runs.get_uploader_name(1159, user_test)
340+
name = await database.runs.get_uploader_name(_RUN_UPLOADER_ID, user_test)
312341
assert name == "Cynthia Glover"
313342

314343

315344
async def test_db_get_uploader_name_missing(user_test: AsyncConnection) -> None:
316345
"""get_uploader_name returns None for a non-existent user."""
317-
name = await database.runs.get_uploader_name(_MISSING_RUN_ID, user_test)
346+
name = await database.runs.get_uploader_name(_MISSING_USER_ID, user_test)
318347
assert name is None
319348

320349

@@ -377,10 +406,10 @@ async def test_get_run_equal(
377406
py_normalized = _normalize_py_run(py_response.json())
378407
php_json = php_response.json()
379408

380-
# PHP duplicates evaluation entries natively for each fold, and also provides
381-
# an aggregate with `repeat="0"` and `fold="0"`. The Python API correctly provides
382-
# only the aggregate row (and array_data string).
383-
# To match without complex deepdiff matchers, simply verify the base aggregate entries.
409+
# PHP provides evaluation entries natively for each fold (with `repeat` and `fold` keys)
410+
# as well as an aggregate entry (which might or might not have those keys depending on version).
411+
# To match without complex deepdiff matchers, verify base entries without repeat/fold
412+
# and drop the rest.
384413
if (
385414
"run" in php_json
386415
and "output_data" in php_json["run"]
@@ -391,11 +420,11 @@ async def test_get_run_equal(
391420
php_json["run"]["output_data"]["evaluation"] = [
392421
ev for ev in php_evals if "repeat" not in ev and "fold" not in ev
393422
]
394-
elif isinstance(php_evals, dict) and ("repeat" in php_evals or "fold" in php_evals):
395-
# nested_remove_single_element_list removes lists if there's only 1 element, but PHP
396-
# original JSON might have had only 1 base evaluation if no others existed.
397-
# But PHP returns a list anyway if duplicates exist. If they don't, it's a dict.
398-
php_json["run"]["output_data"]["evaluation"] = []
423+
elif isinstance(php_evals, dict):
424+
if "repeat" in php_evals or "fold" in php_evals:
425+
php_json["run"]["output_data"]["evaluation"] = []
426+
else:
427+
php_json["run"]["output_data"]["evaluation"] = [php_evals]
399428

400429
# PHP sometimes includes empty `error` property instead of an empty list when no error occurred
401430
# DeepDiff takes care of it automatically because we didn't see error diffs.
@@ -422,6 +451,7 @@ def __init__(self, name: str, value: object, array_data: str | None = None) -> N
422451
rows = [
423452
MockRow("float_val", 1.0),
424453
MockRow("str_float", "2.0"),
454+
MockRow("str_float_fraction", "1.5"),
425455
MockRow("str_text", "not_a_number"),
426456
MockRow("unhandled_type", ["list"]),
427457
]
@@ -430,7 +460,9 @@ def __init__(self, name: str, value: object, array_data: str | None = None) -> N
430460
values = {e.name: e.value for e in evals}
431461
expected_one = 1
432462
expected_two = 2
463+
expected_fraction = 1.5
433464
assert values["float_val"] == expected_one
434465
assert values["str_float"] == expected_two
466+
assert values["str_float_fraction"] == expected_fraction
435467
assert values["str_text"] is None
436468
assert values["unhandled_type"] is None

0 commit comments

Comments
 (0)