Skip to content

Commit af7d0e6

Browse files
committed
test: Improve cross-platform file handling in existing tests and add new tests for selector and grid logger utilities.
1 parent 2122c9a commit af7d0e6

8 files changed

Lines changed: 1617 additions & 103 deletions

File tree

tests/api_utils/routers/test_static.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ def test_read_index_not_found(client):
4848
测试场景: index.html 不存在
4949
预期: 返回 404 错误 (lines 18-20)
5050
"""
51-
MagicMock()
5251

5352
with patch("api_utils.routers.static.os.path.exists", return_value=False):
5453
response = client.get("/")
@@ -74,7 +73,6 @@ def test_get_css_not_found(client):
7473
测试场景: webui.css 不存在
7574
预期: 返回 404 错误 (lines 26-28)
7675
"""
77-
MagicMock()
7876

7977
with patch("api_utils.routers.static.os.path.exists", return_value=False):
8078
response = client.get("/css")
@@ -100,7 +98,6 @@ def test_get_js_not_found(client):
10098
测试场景: webui.js 不存在
10199
预期: 返回 404 错误 (lines 34-36)
102100
"""
103-
MagicMock()
104101

105102
with patch("api_utils.routers.static.os.path.exists", return_value=False):
106103
response = client.get("/js")

tests/api_utils/test_utils.py

Lines changed: 131 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def mock_logger():
3131

3232
@pytest.fixture
3333
def mock_file_utils():
34+
"""Fixture providing mocked file utility functions for cross-platform testing."""
3435
with (
3536
patch("api_utils.utils.extract_data_url_to_local") as mock_extract,
3637
patch("api_utils.utils.save_blob_to_local") as mock_save,
@@ -438,28 +439,39 @@ def test_prepare_combined_prompt_input_video_processing(mock_file_utils, mock_lo
438439
mock_save.assert_called()
439440

440441

441-
def test_prepare_combined_prompt_complex_nested_dict(mock_file_utils, mock_logger):
442-
"""Test nested dictionary content with specific attachment keys."""
442+
def test_prepare_combined_prompt_complex_nested_dict(
443+
mock_file_utils, mock_logger, tmp_path
444+
):
445+
"""Test nested dictionary content with specific attachment keys.
446+
447+
Uses platform-appropriate paths via tmp_path fixture for cross-platform compatibility.
448+
"""
443449
mock_extract, _, mock_exists = mock_file_utils
444450
mock_exists.return_value = True
445451

452+
# Create actual temp files for cross-platform path handling
453+
img_file = tmp_path / "img1.png"
454+
doc_file = tmp_path / "doc1.pdf"
455+
img_file.touch()
456+
doc_file.touch()
457+
446458
content = {
447459
"text": "Look at these files",
448-
"images": [{"url": "file:///c:/img1.png"}],
449-
"files": [{"path": "c:/doc1.pdf"}], # absolute path
460+
"images": [{"url": f"file://{img_file}"}], # Platform-appropriate file URL
461+
"files": [{"path": str(doc_file)}], # Platform-appropriate absolute path
450462
"media": [{"url": "data:video..."}], # data url
451463
}
452464

453-
mock_extract.return_value = "/tmp/media.mp4"
465+
mock_extract.return_value = str(tmp_path / "media.mp4")
454466

455467
messages = [Message.model_construct(role="user", content=content)]
456468

457469
prompt, files = prepare_combined_prompt(messages, "req1")
458470

459471
assert "Look at these files" in prompt
460-
assert "/tmp/media.mp4" in files
472+
assert str(tmp_path / "media.mp4") in files
461473
assert any(f.endswith("img1.png") for f in files)
462-
assert "c:/doc1.pdf" in files or "/c:/doc1.pdf" in files or "c:\\doc1.pdf" in files
474+
assert any(f.endswith("doc1.pdf") for f in files)
463475

464476

465477
@pytest.mark.asyncio
@@ -860,13 +872,38 @@ def test_prepare_combined_prompt_non_existent_local_file(mock_file_utils, mock_l
860872
assert len(files) == 0
861873

862874

863-
def test_collect_and_validate_attachments_detailed(mock_file_utils, mock_logger):
864-
"""Test detailed attachment collection logic including top-level and various message keys."""
875+
def test_collect_and_validate_attachments_detailed(
876+
mock_file_utils, mock_logger, tmp_path
877+
):
878+
"""Test detailed attachment collection logic including top-level and various message keys.
879+
880+
Uses tmp_path for platform-compatible paths.
881+
"""
865882
mock_extract, _, mock_exists = mock_file_utils
866883

867-
# Mock file existence
884+
# Create actual temp files for cross-platform path handling
885+
valid_initial = tmp_path / "valid_initial.png"
886+
valid_top_level = tmp_path / "valid_top_level.png"
887+
valid_file_url = tmp_path / "valid_file_url.txt"
888+
valid_image = tmp_path / "valid_image.png"
889+
valid_file = tmp_path / "valid_file.pdf"
890+
valid_media = tmp_path / "valid_media.mp4"
891+
missing_initial = tmp_path / "missing_initial.png"
892+
missing = tmp_path / "missing.png"
893+
894+
# Create "valid" files, leave "missing" files uncreated
895+
valid_initial.touch()
896+
valid_top_level.touch()
897+
valid_file_url.touch()
898+
valid_image.touch()
899+
valid_file.touch()
900+
valid_media.touch()
901+
902+
# Mock file existence based on actual file existence
868903
def side_effect(path):
869-
return "existing" in path or "valid" in path
904+
from pathlib import Path
905+
906+
return Path(path).exists()
870907

871908
mock_exists.side_effect = side_effect
872909

@@ -875,10 +912,10 @@ def side_effect(path):
875912
# Mock request object with various attachment fields
876913
class MockRequest:
877914
attachments = [
878-
"c:/tmp/valid_top_level.png",
879-
"c:/tmp/missing.png",
915+
str(valid_top_level),
916+
str(missing),
880917
{"url": "data:image/png;base64,data1"},
881-
{"url": "file:///c:/valid_file_url.txt"},
918+
{"url": f"file://{valid_file_url}"},
882919
"", # Empty string
883920
None, # None
884921
{"url": ""}, # Empty URL in dict
@@ -887,31 +924,31 @@ class MockRequest:
887924
Message.model_construct(
888925
role="user",
889926
content="msg1",
890-
images=["c:/tmp/valid_image.png"],
891-
files=[{"path": "c:/tmp/valid_file.pdf"}],
892-
media=["file:///c:/valid_media.mp4"],
927+
images=[str(valid_image)],
928+
files=[{"path": str(valid_file)}],
929+
media=[f"file://{valid_media}"],
893930
)
894931
]
895932

896933
req = MockRequest()
897-
initial_list = ["c:/tmp/valid_initial.png", "c:/tmp/missing_initial.png"]
934+
initial_list = [str(valid_initial), str(missing_initial)]
898935

899936
result = collect_and_validate_attachments(req, "req1", initial_list)
900937

901938
# Check initial list filtering
902-
assert "c:/tmp/valid_initial.png" in result
903-
assert "c:/tmp/missing_initial.png" not in result
939+
assert str(valid_initial) in result
940+
assert str(missing_initial) not in result
904941

905942
# Check top-level attachments
906-
assert "c:/tmp/valid_top_level.png" in result
907-
assert "c:/tmp/missing.png" not in result
943+
assert str(valid_top_level) in result
944+
assert str(missing) not in result
908945
# data: URL -> extracted (mock extract returns /tmp/...)
909946
assert mock_extract.called
910947

911948
# Check message-level attachments
912-
assert "c:/tmp/valid_image.png" in result
913-
assert "c:/tmp/valid_file.pdf" in result
914-
# file:///c:/valid_media.mp4 -> unquoted path
949+
assert str(valid_image) in result
950+
assert str(valid_file) in result
951+
# file:// -> unquoted path
915952
assert any("valid_media.mp4" in f for f in result)
916953

917954

@@ -1034,15 +1071,24 @@ def test_prepare_combined_prompt_dict_image_url_with_detail(
10341071
assert "[图像细节: detail=auto]" in prompt
10351072

10361073

1037-
def test_prepare_combined_prompt_audio_absolute_path(mock_file_utils, mock_logger):
1038-
"""Test audio with absolute path (lines 427-431)."""
1074+
def test_prepare_combined_prompt_audio_absolute_path(
1075+
mock_file_utils, mock_logger, tmp_path
1076+
):
1077+
"""Test audio with absolute path (lines 427-431).
1078+
1079+
Uses tmp_path for platform-compatible paths.
1080+
"""
10391081
_, _, mock_exists = mock_file_utils
10401082
mock_exists.return_value = True
10411083

1084+
# Create actual temp file for cross-platform path handling
1085+
audio_file = tmp_path / "test.mp3"
1086+
audio_file.touch()
1087+
10421088
# Absolute path for audio
10431089
content_item = {
10441090
"type": "input_audio",
1045-
"input_audio": {"url": "c:/audio/test.mp3"},
1091+
"input_audio": {"url": str(audio_file)},
10461092
}
10471093

10481094
messages = [
@@ -1051,7 +1097,7 @@ def test_prepare_combined_prompt_audio_absolute_path(mock_file_utils, mock_logge
10511097

10521098
prompt, files = prepare_combined_prompt(messages, "req1")
10531099

1054-
assert "c:/audio/test.mp3" in files
1100+
assert str(audio_file) in files
10551101

10561102

10571103
def test_prepare_combined_prompt_video_absolute_path(mock_file_utils, mock_logger):
@@ -1185,36 +1231,47 @@ def __iter__(self):
11851231

11861232

11871233
def test_collect_and_validate_attachments_empty_url_handling(
1188-
mock_file_utils, mock_logger
1234+
mock_file_utils, mock_logger, tmp_path
11891235
):
1190-
"""Test attachment collection with empty URL strings (lines 802, 831, 834)."""
1236+
"""Test attachment collection with empty URL strings (lines 802, 831, 834).
1237+
1238+
Uses tmp_path for platform-compatible paths.
1239+
"""
11911240
_, _, mock_exists = mock_file_utils
11921241
mock_exists.return_value = True
11931242

1243+
# Create actual temp files for cross-platform path handling
1244+
valid_png = tmp_path / "valid.png"
1245+
valid_image = tmp_path / "valid_image.png"
1246+
valid_file = tmp_path / "valid_file.pdf"
1247+
valid_png.touch()
1248+
valid_image.touch()
1249+
valid_file.touch()
1250+
11941251
class MockRequest:
11951252
attachments = [
11961253
"", # Empty string
11971254
" ", # Whitespace only
11981255
{"url": ""}, # Empty URL in dict
11991256
{"url": " "}, # Whitespace URL in dict
1200-
"c:/valid.png", # Valid path
1257+
str(valid_png), # Valid path
12011258
]
12021259
messages = [
12031260
Message.model_construct(
12041261
role="user",
12051262
content="test",
1206-
images=["", "c:/valid_image.png"],
1207-
files=[{"path": ""}, {"path": "c:/valid_file.pdf"}],
1263+
images=["", str(valid_image)],
1264+
files=[{"path": ""}, {"path": str(valid_file)}],
12081265
)
12091266
]
12101267

12111268
req = MockRequest()
12121269
result = collect_and_validate_attachments(req, "req1", [])
12131270

12141271
# Only valid paths should be included
1215-
assert "c:/valid.png" in result
1216-
assert "c:/valid_image.png" in result
1217-
assert "c:/valid_file.pdf" in result
1272+
assert str(valid_png) in result
1273+
assert str(valid_image) in result
1274+
assert str(valid_file) in result
12181275

12191276
# Empty strings should be filtered out
12201277
assert "" not in result
@@ -1246,23 +1303,32 @@ def test_prepare_combined_prompt_dict_input_image_with_detail(
12461303
assert "[图像细节: detail=low]" in prompt
12471304

12481305

1249-
def test_prepare_combined_prompt_dict_content_file_field(mock_file_utils, mock_logger):
1250-
"""Test dict content with generic 'file' field (lines 313-322)."""
1306+
def test_prepare_combined_prompt_dict_content_file_field(
1307+
mock_file_utils, mock_logger, tmp_path
1308+
):
1309+
"""Test dict content with generic 'file' field (lines 313-322).
1310+
1311+
Uses tmp_path for platform-compatible paths.
1312+
"""
12511313
_, _, mock_exists = mock_file_utils
12521314
mock_exists.return_value = True
12531315

1316+
# Create actual temp file for cross-platform path handling
1317+
file_path = tmp_path / "from_file_field.png"
1318+
file_path.touch()
1319+
12541320
content = [
12551321
{
12561322
"type": "image_url",
1257-
"file": {"url": "c:/from_file_field.png"},
1323+
"file": {"url": str(file_path)},
12581324
}
12591325
]
12601326

12611327
messages = [Message.model_construct(role="user", content=content)]
12621328

12631329
prompt, files = prepare_combined_prompt(messages, "req1")
12641330

1265-
assert "c:/from_file_field.png" in files
1331+
assert str(file_path) in files
12661332

12671333

12681334
def test_prepare_combined_prompt_dict_content_file_path(mock_file_utils, mock_logger):
@@ -1284,20 +1350,29 @@ def test_prepare_combined_prompt_dict_content_file_path(mock_file_utils, mock_lo
12841350
assert "/absolute/path/file.pdf" in files
12851351

12861352

1287-
def test_prepare_combined_prompt_object_url_attribute(mock_file_utils, mock_logger):
1288-
"""Test content item with direct url attribute (lines 249-250)."""
1353+
def test_prepare_combined_prompt_object_url_attribute(
1354+
mock_file_utils, mock_logger, tmp_path
1355+
):
1356+
"""Test content item with direct url attribute (lines 249-250).
1357+
1358+
Uses tmp_path for platform-compatible paths.
1359+
"""
12891360
_, _, mock_exists = mock_file_utils
12901361
mock_exists.return_value = True
12911362

1363+
# Create actual temp file for cross-platform path handling
1364+
url_path = tmp_path / "direct_url.png"
1365+
url_path.touch()
1366+
12921367
class UrlItem:
12931368
type = "image_url"
1294-
url = "c:/direct_url.png"
1369+
url = str(url_path)
12951370

12961371
messages = [Message.model_construct(role="user", content=[UrlItem()])]
12971372

12981373
prompt, files = prepare_combined_prompt(messages, "req1")
12991374

1300-
assert "c:/direct_url.png" in files
1375+
assert str(url_path) in files
13011376

13021377

13031378
def test_prepare_combined_prompt_dict_attachments_nested_input_image(
@@ -1323,25 +1398,32 @@ def test_prepare_combined_prompt_dict_attachments_nested_input_image(
13231398

13241399

13251400
def test_prepare_combined_prompt_content_item_input_image_string(
1326-
mock_file_utils, mock_logger
1401+
mock_file_utils, mock_logger, tmp_path
13271402
):
1328-
"""Test content item with input_image as string (lines 283-284)."""
1403+
"""Test content item with input_image as string (lines 283-284).
1404+
1405+
Uses tmp_path for platform-compatible paths.
1406+
"""
13291407
_, _, mock_exists = mock_file_utils
13301408
mock_exists.return_value = True
13311409

1410+
# Create actual temp file for cross-platform path handling
1411+
img_path = tmp_path / "string_input_image.png"
1412+
img_path.touch()
1413+
13321414
# This tests the case where item has input_image as a string directly
13331415
content = [
13341416
{
13351417
"type": "image_url",
1336-
"input_image": "c:/string_input_image.png", # String, not dict
1418+
"input_image": str(img_path), # String, not dict
13371419
}
13381420
]
13391421

13401422
messages = [Message.model_construct(role="user", content=content)]
13411423

13421424
prompt, files = prepare_combined_prompt(messages, "req1")
13431425

1344-
assert "c:/string_input_image.png" in files
1426+
assert str(img_path) in files
13451427

13461428

13471429
def test_prepare_combined_prompt_audio_video_data_base64(mock_file_utils, mock_logger):

0 commit comments

Comments
 (0)