import pytest from unittest.mock import patch, AsyncMock @pytest.mark.asyncio async def test_get_health_ok(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (0, "All checks passed\n", "") response = test_app.get("/api/media/health", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "ok" assert data["exit_code"] == 0 assert "All checks passed" in data["stdout"] @pytest.mark.asyncio async def test_get_health_warning(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (1, "Disk >85%\n", "") response = test_app.get("/api/media/health", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "warning" @pytest.mark.asyncio async def test_get_health_error(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (2, "", "Immich API unreachable") response = test_app.get("/api/media/health", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "error" @pytest.mark.asyncio async def test_get_health_unavailable(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (999, "", "SSH connection refused") response = test_app.get("/api/media/health", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "unavailable" assert "NAS unreachable" in data["error"] @pytest.mark.asyncio async def test_get_storage(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = ( 0, '{"timestamp": "2026-07-11T05:37:57Z", "volume": {"path": "/volume1", "total": "14T", "used": "12T", "available": "2.2T", "used_pct": 85}}', "", ) response = test_app.get("/api/media/storage", headers=admin_headers) assert response.status_code == 200 data = response.json() assert "volume" in data assert data["volume"]["total"] == "14T" @pytest.mark.asyncio async def test_get_storage_unavailable(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (999, "", "SSH timeout") response = test_app.get("/api/media/storage", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "unavailable" @pytest.mark.asyncio async def test_get_storage_json_parse_error(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (0, "not valid json", "") response = test_app.get("/api/media/storage", headers=admin_headers) assert response.status_code == 200 data = response.json() assert "error" in data assert "Failed to parse JSON" in data["error"] @pytest.mark.asyncio async def test_get_shuttle_status_idle(test_app, admin_headers): """Shuttle not running, no log file.""" with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: # First call: cat log, Second call: pgrep -f mock_run.side_effect = [ (0, "NO_LOG", ""), # cat log (1, "", ""), # pgrep -f (not running) ] response = test_app.get("/api/media/shuttle/status", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["running"] is False assert data["queue_depth"] == 0 @pytest.mark.asyncio async def test_get_shuttle_status_running(test_app, admin_headers): """Shuttle running with log content.""" log_content = """=== Shuttle Started at 2026-07-11 10:00 === --source /Volumes/SD_CARD --dest /Volumes/SSD Stage 1: copy [10:01] rsync: 1.2GB copied from SD_CARD to SSD Stage 2: verify [10:05] ✅ Verification passed — all files match [10:06] --stage ship --source /Volumes/SSD --dest naslan:/volume1/video """ with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.side_effect = [ (0, log_content, ""), # cat log (0, "12345\n", ""), # pgrep -f (running) ] response = test_app.get("/api/media/shuttle/status", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["running"] is True assert data["pid"] == "12345" assert data["queue_depth"] == 2 # --source entries in log assert data["last_transfer"] is not None assert "1.2GB" in data.get("last_transfer_size", "") @pytest.mark.asyncio async def test_get_shuttle_status_with_errors(test_app, admin_headers): """Shuttle log has error lines.""" log_content = """[10:01] rsync: OK [10:02] ❌ rsync: failed to transfer file XYZ [10:03] rsync: 500MB copied [10:04] ❌ connection reset by peer """ with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.side_effect = [ (0, log_content, ""), # cat log (1, "", ""), # pgrep -f (not running) ] response = test_app.get("/api/media/shuttle/status", headers=admin_headers) assert response.status_code == 200 data = response.json() assert len(data["recent_errors"]) == 2 @pytest.mark.asyncio async def test_get_shuttle_status_unavailable(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (999, "", "SSH timeout") response = test_app.get("/api/media/shuttle/status", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "unavailable" @pytest.mark.asyncio async def test_run_scan(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (0, "", "") response = test_app.post("/api/media/maintenance/scan?library=DJI-Action6", headers=admin_headers) assert response.status_code == 200 data = response.json() assert "Scan started" in data["message"] mock_run.assert_called_once() assert "scan-library.sh DJI-Action6" in mock_run.call_args[0][0] @pytest.mark.asyncio async def test_run_scan_unavailable(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (999, "", "SSH timeout") response = test_app.post("/api/media/maintenance/scan", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "unavailable" @pytest.mark.asyncio async def test_get_active_status(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: def side_effect(cmd): if "scan-library.sh" in cmd: return (0, "1234\n", "") return (1, "", "") mock_run.side_effect = side_effect response = test_app.get("/api/media/status", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["scan"]["running"] is True assert data["scan"]["pid"] == "1234" assert data["requeue"]["running"] is False @pytest.mark.asyncio async def test_get_active_status_unavailable(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (999, "", "SSH timeout") response = test_app.get("/api/media/status", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "unavailable" @pytest.mark.asyncio async def test_get_logs_shuttle(test_app, admin_headers): """Fetch shuttle logs successfully.""" with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (0, "line1\nline2\nline3\n", "") response = test_app.get("/api/media/logs/shuttle", headers=admin_headers) assert response.status_code == 200 data = response.json() assert "line1" in data["content"] @pytest.mark.asyncio async def test_get_logs_invalid_task(test_app, admin_headers): """Invalid task name returns 400.""" response = test_app.get("/api/media/logs/invalid-task", headers=admin_headers) assert response.status_code == 400 @pytest.mark.asyncio async def test_get_logs_unavailable(test_app, admin_headers): with patch("routers.media_mgmt.run_host_command", new_callable=AsyncMock) as mock_run: mock_run.return_value = (999, "", "SSH timeout") response = test_app.get("/api/media/logs/scan", headers=admin_headers) assert response.status_code == 200 data = response.json() assert data["status"] == "unavailable"