From cbe64ce29cb81f23f71c78c311cd4f4d969bb5ba Mon Sep 17 00:00:00 2001 From: zurdi Date: Tue, 23 Dec 2025 16:24:33 +0000 Subject: [PATCH] test: add tests for library structure detection in FSPlatformsHandler --- backend/tests/endpoints/test_heartbeat.py | 256 ++++++++++++++++++ .../filesystem/test_platforms_handler.py | 82 ++++++ 2 files changed, 338 insertions(+) diff --git a/backend/tests/endpoints/test_heartbeat.py b/backend/tests/endpoints/test_heartbeat.py index 834aea6a3..5144708cf 100644 --- a/backend/tests/endpoints/test_heartbeat.py +++ b/backend/tests/endpoints/test_heartbeat.py @@ -1,8 +1,11 @@ +from unittest.mock import patch + import pytest from fastapi import status from fastapi.testclient import TestClient from main import app +from exceptions.fs_exceptions import PlatformAlreadyExistsException from utils import get_version @@ -68,3 +71,256 @@ def test_heartbeat_metadata(client): def test_heartbeat_metadata_unknown_source(client): response = client.get("/api/heartbeat/metadata/unknown") assert response.status_code == status.HTTP_400_BAD_REQUEST + + +def test_get_setup_library_info_structure_a_detected( + self, client, admin_user, access_token +): + """Test get_setup_library_info with Structure A detected""" + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = "A" + + with patch( + "endpoints.heartbeat.fs_platform_handler.get_platforms" + ) as mock_get_platforms: + mock_get_platforms.return_value = ["n64", "psx"] + + with patch("os.path.exists", return_value=True): + with patch("os.listdir") as mock_listdir: + mock_listdir.side_effect = [ + ["game1.z64", "game2.z64"], # n64 roms + ["game1.iso"], # psx roms + ] + + response = client.get( + "/api/setup/library", + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + assert data["detected_structure"] == "A" + assert len(data["existing_platforms"]) == 2 + assert data["existing_platforms"][0]["fs_slug"] == "n64" + assert data["existing_platforms"][0]["rom_count"] == 2 + assert data["existing_platforms"][1]["fs_slug"] == "psx" + assert data["existing_platforms"][1]["rom_count"] == 1 + assert "supported_platforms" in data + + +def test_get_setup_library_info_structure_b_detected( + self, client, admin_user, access_token +): + """Test get_setup_library_info with Structure B detected""" + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = "B" + + with patch( + "endpoints.heartbeat.fs_platform_handler.get_platforms" + ) as mock_get_platforms: + mock_get_platforms.return_value = ["gba"] + + with patch("os.path.exists", return_value=True): + with patch("os.listdir") as mock_listdir: + mock_listdir.return_value = ["game1.gba", "game2.gba", "game3.gba"] + + response = client.get( + "/api/setup/library", + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + assert data["detected_structure"] == "B" + assert len(data["existing_platforms"]) == 1 + assert data["existing_platforms"][0]["fs_slug"] == "gba" + assert data["existing_platforms"][0]["rom_count"] == 3 + + +def test_get_setup_library_info_no_structure_detected( + self, client, admin_user, access_token +): + """Test get_setup_library_info when no structure is detected""" + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = None + + with patch( + "endpoints.heartbeat.fs_platform_handler.get_platforms" + ) as mock_get_platforms: + mock_get_platforms.return_value = [] + + response = client.get( + "/api/setup/library", + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + assert data["detected_structure"] is None + assert data["existing_platforms"] == [] + assert "supported_platforms" in data + + +def test_get_setup_library_info_handles_errors(self, client, admin_user, access_token): + """Test get_setup_library_info handles filesystem errors gracefully""" + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = "A" + + with patch( + "endpoints.heartbeat.fs_platform_handler.get_platforms" + ) as mock_get_platforms: + # Simulate error retrieving platforms + mock_get_platforms.side_effect = Exception("Filesystem error") + + response = client.get( + "/api/setup/library", + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + + # Should return empty platforms list on error + assert data["existing_platforms"] == [] + + +def test_create_setup_platforms_success(self, client, admin_user, access_token): + """Test create_setup_platforms successfully creates platforms""" + platform_slugs = ["n64", "psx", "gba"] + + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = "A" + + with patch( + "endpoints.heartbeat.fs_platform_handler.add_platform" + ) as mock_add_platform: + mock_add_platform.return_value = None # Successful creation + + response = client.post( + "/api/setup/platforms", + json=platform_slugs, + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_201_CREATED + data = response.json() + + assert data["success"] is True + assert data["created_count"] == 3 + assert "Successfully created 3 platform folder(s)" in data["message"] + assert mock_add_platform.call_count == 3 + + +def test_create_setup_platforms_empty_list(self, client, admin_user, access_token): + """Test create_setup_platforms with empty platform list""" + response = client.post( + "/api/setup/platforms", + json=[], + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_201_CREATED + data = response.json() + + assert data["success"] is True + assert data["created_count"] == 0 + assert data["message"] == "No platforms selected" + + +def test_create_setup_platforms_creates_structure_a_when_none_exists( + self, client, admin_user, access_token +): + """Test create_setup_platforms creates Structure A when no structure detected""" + platform_slugs = ["n64"] + + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = None # No structure detected + + with patch("os.makedirs") as mock_makedirs: + with patch("endpoints.heartbeat.fs_platform_handler.add_platform"): + response = client.post( + "/api/setup/platforms", + json=platform_slugs, + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_201_CREATED + # Should create roms folder first + mock_makedirs.assert_called_once() + assert "roms" in str(mock_makedirs.call_args[0][0]) + + +def test_create_setup_platforms_skips_existing_platforms( + self, client, admin_user, access_token +): + """Test create_setup_platforms skips platforms that already exist""" + platform_slugs = ["n64", "psx", "gba"] + + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = "A" + + with patch( + "endpoints.heartbeat.fs_platform_handler.add_platform" + ) as mock_add_platform: + # First platform already exists, second succeeds, third succeeds + mock_add_platform.side_effect = [ + PlatformAlreadyExistsException("n64"), + None, + None, + ] + + response = client.post( + "/api/setup/platforms", + json=platform_slugs, + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_201_CREATED + data = response.json() + + assert data["success"] is True + # Should only count 2 created (psx and gba) + assert data["created_count"] == 2 + + +def test_create_setup_platforms_handles_permission_errors( + self, client, admin_user, access_token +): + """Test create_setup_platforms handles permission errors""" + platform_slugs = ["n64"] + + with patch( + "endpoints.heartbeat.fs_platform_handler.detect_library_structure" + ) as mock_detect: + mock_detect.return_value = "A" + + with patch( + "endpoints.heartbeat.fs_platform_handler.add_platform" + ) as mock_add_platform: + mock_add_platform.side_effect = PermissionError("Permission denied") + + response = client.post( + "/api/setup/platforms", + json=platform_slugs, + headers={"Authorization": f"Bearer {access_token}"}, + ) + + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + assert "Failed to create some platform folders" in response.json()["detail"] diff --git a/backend/tests/handler/filesystem/test_platforms_handler.py b/backend/tests/handler/filesystem/test_platforms_handler.py index 539deb7d0..9913b8e70 100644 --- a/backend/tests/handler/filesystem/test_platforms_handler.py +++ b/backend/tests/handler/filesystem/test_platforms_handler.py @@ -311,3 +311,85 @@ class TestFSPlatformsHandler: for platform in expected_filtered: structure = handler.get_platform_fs_structure(platform) assert structure == f"{platform}/{config.ROMS_FOLDER_NAME}" + + def test_detect_library_structure_structure_a( + self, handler: FSPlatformsHandler, config + ): + """Test detect_library_structure detects Structure A (roms/{platform})""" + roms_path = f"{LIBRARY_BASE_PATH}/{config.ROMS_FOLDER_NAME}" + + with patch( + "handler.filesystem.platforms_handler.cm.get_config", return_value=config + ): + with patch("os.path.exists") as mock_exists: + mock_exists.return_value = True + + result = handler.detect_library_structure() + assert result == "A" + mock_exists.assert_called_once_with(roms_path) + + def test_detect_library_structure_structure_b( + self, handler: FSPlatformsHandler, config + ): + """Test detect_library_structure detects Structure B ({platform}/roms)""" + with patch( + "handler.filesystem.platforms_handler.cm.get_config", return_value=config + ): + with patch("os.path.exists") as mock_exists: + # Roms folder doesn't exist at base level + mock_exists.return_value = False + + with patch("os.listdir") as mock_listdir: + mock_listdir.return_value = ["n64", "psx", "other_folder"] + + with patch("os.path.isdir") as mock_isdir: + # n64 and psx are directories with roms subfolders + def isdir_side_effect(path): + return "n64" in path or "psx" in path + + def exists_side_effect(path): + # n64/roms and psx/roms exist + return ( + f"n64/{config.ROMS_FOLDER_NAME}" in path + or f"psx/{config.ROMS_FOLDER_NAME}" in path + ) + + mock_isdir.side_effect = isdir_side_effect + mock_exists.side_effect = exists_side_effect + + result = handler.detect_library_structure() + assert result == "B" + + def test_detect_library_structure_none(self, handler: FSPlatformsHandler, config): + """Test detect_library_structure returns None when no structure detected""" + with patch( + "handler.filesystem.platforms_handler.cm.get_config", return_value=config + ): + with patch("os.path.exists", return_value=False): + with patch("os.listdir", return_value=[]): + result = handler.detect_library_structure() + assert result is None + + def test_detect_library_structure_handles_os_errors( + self, handler: FSPlatformsHandler, config + ): + """Test detect_library_structure handles OS errors gracefully""" + with patch( + "handler.filesystem.platforms_handler.cm.get_config", return_value=config + ): + with patch("os.path.exists", return_value=False): + with patch("os.listdir", side_effect=OSError("Permission denied")): + result = handler.detect_library_structure() + assert result is None + + def test_detect_library_structure_empty_library( + self, handler: FSPlatformsHandler, config + ): + """Test detect_library_structure with empty library directory""" + with patch( + "handler.filesystem.platforms_handler.cm.get_config", return_value=config + ): + with patch("os.path.exists", return_value=False): + with patch("os.listdir", return_value=[]): + result = handler.detect_library_structure() + assert result is None