Merge pull request #3473 from Spinnich/fix/rom-content-404-on-stale-file-ids

fix(roms): return 404 when content file_ids match no files
This commit is contained in:
Georges-Antoine Assi
2026-06-03 15:52:28 -04:00
committed by GitHub
3 changed files with 80 additions and 0 deletions

View File

@@ -868,6 +868,12 @@ async def head_rom_content(
files = [f for f in files if f.id in file_id_values]
files.sort(key=lambda x: x.file_name)
if not files:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"No files found for ROM {id}",
)
# Serve the file directly in development mode for emulatorjs
if DEV_MODE:
if len(files) == 1:
@@ -945,6 +951,12 @@ async def get_rom_content(
files = [f for f in files if f.id in file_id_values]
files.sort(key=lambda x: x.file_name)
if not files:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"No files found for ROM {id}",
)
log.info(
f"User {hl(current_username, color=BLUE)} is downloading {hl(rom.fs_name)}"
)

View File

@@ -89,6 +89,18 @@ def rom(admin_user: User, platform: Platform):
return rom
@pytest.fixture
def rom_file(rom: Rom):
"""A single content file attached to the `rom` fixture."""
rom_file = RomFile(
rom_id=rom.id,
file_name="test_rom.zip",
file_path=rom.fs_path,
file_size_bytes=1000,
)
return db_rom_handler.add_rom_file(rom_file)
@pytest.fixture
def multi_file_rom(admin_user: User, platform: Platform):
"""A ROM stored as a game folder with multiple files (e.g. multi-disc).

View File

@@ -82,6 +82,62 @@ def test_get_all_roms(
assert items[0]["id"] == rom.id
def test_get_rom_content_requires_auth(client: TestClient, rom: Rom, rom_file):
response = client.get(f"/api/roms/{rom.id}/content/test_rom.zip")
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_get_rom_content_single_file(
client: TestClient, access_token: str, rom: Rom, rom_file
):
response = client.get(
f"/api/roms/{rom.id}/content/test_rom.zip",
headers={"Authorization": f"Bearer {access_token}"},
follow_redirects=False,
)
assert response.status_code == status.HTTP_200_OK
# Single-file roms are proxied through nginx via X-Accel-Redirect.
assert "X-Accel-Redirect" in response.headers
def test_get_rom_content_valid_file_id(
client: TestClient, access_token: str, rom: Rom, rom_file
):
response = client.get(
f"/api/roms/{rom.id}/content/test_rom.zip",
headers={"Authorization": f"Bearer {access_token}"},
params={"file_ids": str(rom_file.id)},
follow_redirects=False,
)
assert response.status_code == status.HTTP_200_OK
assert "X-Accel-Redirect" in response.headers
def test_get_rom_content_stale_file_id_returns_404(
client: TestClient, access_token: str, rom: Rom, rom_file
):
# Regression test for #3470: a remembered file id that no longer exists
# (e.g. after a rename gave the file a new id) must return a clean 404
# instead of an empty-.m3u ZIP that nginx aborts as a 0-byte response.
stale_file_id = rom_file.id + 999
response = client.get(
f"/api/roms/{rom.id}/content/test_rom.zip",
headers={"Authorization": f"Bearer {access_token}"},
params={"file_ids": str(stale_file_id)},
follow_redirects=False,
)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_get_rom_content_missing_rom_returns_404(client: TestClient, access_token: str):
response = client.get(
"/api/roms/999999/content/missing.zip",
headers={"Authorization": f"Bearer {access_token}"},
follow_redirects=False,
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@patch.object(FSRomsHandler, "rename_fs_rom")
@patch.object(IGDBHandler, "get_rom_by_id", return_value=IGDBRom(igdb_id=None))
def test_update_rom(