import pytest def test_health_contains_build_info(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main payload = main.health() assert payload["status"] == "ok" assert payload["name"] == "Importarr" assert payload["version"] def test_build_date_is_rendered_in_local_time(monkeypatch): import importarr.build_info as build_info monkeypatch.setenv("TZ", "Europe/Copenhagen") import time time.tzset() monkeypatch.setenv("IMPORTARR_BUILD_DATE", "2026-07-29T12:00:00Z") assert build_info.build_info()["build_date"] == "2026-07-29T14:00:00+02:00" def test_build_info_prefers_installed_release_provenance(monkeypatch): import importarr.build_info as build_info monkeypatch.setenv("IMPORTARR_VERSION", "v1.2.3") monkeypatch.setenv("IMPORTARR_GIT_SHA", "abc123") assert build_info.build_info()["version"] == "v1.2.3" assert build_info.build_info()["git_sha"] == "abc123" def test_status_contains_service_configuration(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main payload = main.status() assert payload["build"]["name"] == "Importarr" assert "download_root" in payload assert "movies_root" in payload assert "tv_root" in payload assert "auth_enabled" in payload def test_index_serves_react_application(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient response = TestClient(main.app).get("/") assert response.status_code == 200 assert '
' in response.text assert '/static/assets/app.js' in response.text assert '/static/assets/app.css' in response.text def test_frontend_uses_required_stack_and_capabilities(): from pathlib import Path app = Path("frontend/src/main.jsx").read_text() package = Path("frontend/package.json").read_text() assert "lucide-react" in package assert "@radix-ui/react-dialog" in package assert "tailwindcss" in package for endpoint in ("/api/jobs", "/api/settings", "/api/manual-batches", "/api/control/update", "/api/import/run-now"): assert endpoint in app def test_frontend_defines_exact_light_and_dark_tokens(): from pathlib import Path css = Path("frontend/src/globals.css").read_text() for value in ("#F8FAFC", "#FFFFFF", "#4A43EC", "#7171FF", "#2AD1ED", "#1A1D2E", "#64748B", "#E2E8F0", "#252B42", "#303753", "#23283B", "#42ECF5", "#8B95B7", "#3D4668"): assert value in css assert ".dark" in css def test_settings_endpoint_persists_arr_connection_values(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient response = TestClient(main.app).post( "/api/settings", json={ "sab_url": "http://sab:8080", "sab_api_key": "sab-secret", "radarr_url": "http://radarr:7878", "radarr_api_key": "radarr-secret", "sonarr_url": "http://sonarr:8989", "sonarr_api_key": "sonarr-secret", }, ) assert response.status_code == 200 assert response.json() == { "sab_url": "http://sab:8080", "sab_api_key_configured": True, "radarr_url": "http://radarr:7878", "radarr_api_key_configured": True, "sonarr_url": "http://sonarr:8989", "sonarr_api_key_configured": True, } assert main.settings.sab_api_key == "sab-secret" assert main.state.get_app_state("radarr_api_key") == "radarr-secret" def test_sab_connection_test_reports_success(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient async def fake_queue(self): return {"queue": {"slots": [{"name": "one"}, {"name": "two"}]}} monkeypatch.setattr(main.SabnzbdClient, "queue", fake_queue) response = TestClient(main.app).post( "/api/settings/test-connection", json={"service": "sabnzbd", "url": "http://sab:8080", "api_key": "secret"}, ) assert response.status_code == 200 assert response.json() == {"ok": True, "service": "sabnzbd", "message": "Connected to SABnzbd; 2 queued jobs visible."} def test_connection_test_rejects_unknown_service(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient response = TestClient(main.app).post( "/api/settings/test-connection", json={"service": "lidarr", "url": "http://lidarr:8686"}, ) assert response.status_code == 400 def test_control_endpoints_require_configured_bearer_token(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient monkeypatch.setattr(main.settings, "auth_token", "test-token") client = TestClient(main.app) for method, endpoint in (("get", "/api/control/update-check"), ("post", "/api/control/update"), ("post", "/api/control/restart")): assert getattr(client, method)(endpoint).status_code == 401 assert getattr(client, method)(endpoint, headers={"Authorization": "Bearer wrong"}).status_code == 401 def test_write_endpoint_fails_closed_without_token_on_non_loopback_bind(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient monkeypatch.setattr(main.settings, "auth_token", None) monkeypatch.setattr(main.settings, "bind_host", "0.0.0.0") assert TestClient(main.app).post("/api/control/restart").status_code == 503 def test_tokenless_local_development_remains_available(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main monkeypatch.setattr(main.settings, "auth_token", None) monkeypatch.setattr(main.settings, "bind_host", "127.0.0.1") main.require_write_auth() def test_update_schedules_exact_latest_release_tag(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main monkeypatch.setattr(main, "check_update_available", lambda: {"status": "update_available", "current_version": "v1.0.0", "latest_version": "v1.2.3", "update_available": True}) monkeypatch.setattr(main.settings, "update_command", ["/opt/importarr/repo-upgrade.sh"]) scheduled = [] monkeypatch.setattr(main, "_schedule_update", scheduled.append) response = main.update_service(expected_tag="v1.2.3") assert scheduled == [["/opt/importarr/repo-upgrade.sh", "v1.2.3"]] assert response["status"] == "update_scheduled" def test_update_endpoint_requires_expected_tag(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi.testclient import TestClient monkeypatch.setattr(main.settings, "auth_token", "test-token") monkeypatch.setattr(main, "check_update_available", lambda: pytest.fail("release lookup must not run")) response = TestClient(main.app).post( "/api/control/update", headers={"Authorization": "Bearer test-token"}, ) assert response.status_code == 422 assert response.json()["detail"][0]["loc"] == ["query", "expected_tag"] def test_update_rejects_unexpected_latest_release(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main from fastapi import HTTPException monkeypatch.setattr(main, "check_update_available", lambda: {"latest_version": "v1.2.4", "update_available": True}) scheduled = [] monkeypatch.setattr(main, "_schedule_update", scheduled.append) with pytest.raises(HTTPException) as exc_info: main.update_service(expected_tag="v1.2.3") assert exc_info.value.status_code == 409 assert scheduled == []