test(backend): add comprehensive test coverage for backend modules
Added a new `.coveragerc` file configuring coverage thresholds and exclusions. Included `pytest.ini` to enable coverage reporting for multiple backend modules (`main`, `llm`, `prompt`, `geoip`, `tts_asr`) with a 90 % fail‑under requirement and detailed HTML output. Implemented a suite of unit tests: * `test_geoip.py` – validates geo‑location lookup logic. * `test_llm_extended.py` – tests LLm response extraction and Ollama interactions. * `test_main_endpoints.py` – covers API endpoints for completions, OCR, and TTS. * `test_prompt_extended.py` – verifies language sanitization, timestamp generation, and prompt building. * `test_tts_asr_coverage.py` – checks device detection, cache clearing, and model loading under various environment configurations. * `test_tts_asr_extended.py` – further tests TTS/ASR device selection and time‑outs. Updated `backend/requirements.txt` to use newer, compatible packages, removed obsolete testing dependencies, and added `qwen-tts`. Modified `backend/tts_asr.py` to work with the new `Qwen3TTSModel`, simplified imports, and adjusted device mapping logic. Additionally, frontend changes added a new `TreeNodeItem` component, updated Markdown rendering, added TTS instruction fields, and reworked context menu handling. No breaking changes were introduced.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import sys
|
||||
import os
|
||||
import types
|
||||
import pathlib
|
||||
import pytest
|
||||
|
||||
# Ensure the backend directory is on sys.path so we can import the geoip module directly
|
||||
BACKEND_DIR = pathlib.Path(__file__).resolve().parents[1] # backend/ folder
|
||||
if str(BACKEND_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(BACKEND_DIR))
|
||||
|
||||
import geoip as geoip
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_geoip_reader():
|
||||
# Ensure each test starts with a clean cache
|
||||
geoip._geoip_reader = None
|
||||
yield
|
||||
geoip._geoip_reader = None
|
||||
|
||||
|
||||
def test_get_reader_import_error(monkeypatch):
|
||||
import builtins
|
||||
real_import = getattr(builtins, "__import__")
|
||||
|
||||
def fake_import(name, globals=None, locals=None, fromlist=(), level=0):
|
||||
if name == "geoip2.database":
|
||||
raise ImportError("simulate missing geoip2")
|
||||
return real_import(name, globals, locals, fromlist, level)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", fake_import)
|
||||
|
||||
geoip._geoip_reader = None
|
||||
assert geoip._get_reader() is None
|
||||
|
||||
|
||||
def test_get_reader_db_missing(monkeypatch):
|
||||
# Provide a fake geoip2 module, but force the database file to be considered missing
|
||||
fake_db_module = types.ModuleType("geoip2.database")
|
||||
class FakeReader:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
fake_db_module.Reader = FakeReader
|
||||
|
||||
fake_geoip2 = types.ModuleType("geoip2")
|
||||
fake_geoip2.database = fake_db_module
|
||||
|
||||
sys.modules["geoip2"] = fake_geoip2
|
||||
sys.modules["geoip2.database"] = fake_db_module
|
||||
|
||||
# Ensure path existence check returns False
|
||||
monkeypatch.setattr(geoip.os.path, "exists", lambda p: False)
|
||||
|
||||
geoip._geoip_reader = None
|
||||
assert geoip._get_reader() is None
|
||||
|
||||
# Clean up injected modules
|
||||
del sys.modules["geoip2"]
|
||||
del sys.modules["geoip2.database"]
|
||||
|
||||
|
||||
def test_get_reader_loads_and_caches(monkeypatch):
|
||||
fake_db_module = types.ModuleType("geoip2.database")
|
||||
class FakeReader:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
fake_db_module.Reader = FakeReader
|
||||
|
||||
fake_geoip2 = types.ModuleType("geoip2")
|
||||
fake_geoip2.database = fake_db_module
|
||||
|
||||
sys.modules["geoip2"] = fake_geoip2
|
||||
sys.modules["geoip2.database"] = fake_db_module
|
||||
|
||||
# Simulate that the database file exists
|
||||
monkeypatch.setattr(geoip.os.path, "exists", lambda p: True)
|
||||
|
||||
geoip._geoip_reader = None
|
||||
r1 = geoip._get_reader()
|
||||
assert isinstance(r1, FakeReader)
|
||||
# Second call should return the same cached instance
|
||||
r2 = geoip._get_reader()
|
||||
assert r1 is r2
|
||||
# Clean up injected modules
|
||||
del sys.modules["geoip2"]
|
||||
del sys.modules["geoip2.database"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ip", [None, "", "127.0.0.1", "localhost", "::1"])
|
||||
def test_get_ip_location_none_inputs(ip):
|
||||
assert geoip.get_ip_location(ip) is None
|
||||
|
||||
|
||||
def test_get_ip_location_reader_none(monkeypatch):
|
||||
# When there is no reader (no database), return None
|
||||
monkeypatch.setattr(geoip, "_get_reader", lambda: None)
|
||||
assert geoip.get_ip_location("1.2.3.4") is None
|
||||
|
||||
|
||||
def test_get_ip_location_successful_lookup(monkeypatch):
|
||||
from types import SimpleNamespace
|
||||
|
||||
country = SimpleNamespace(name="United States")
|
||||
region = SimpleNamespace(name="California")
|
||||
resp = SimpleNamespace(
|
||||
country=country,
|
||||
subdivisions=SimpleNamespace(most_specific=region),
|
||||
city=SimpleNamespace(name="Mountain View"),
|
||||
)
|
||||
|
||||
class FakeReader:
|
||||
def city(self, ip):
|
||||
return resp
|
||||
|
||||
monkeypatch.setattr(geoip, "_get_reader", lambda: FakeReader())
|
||||
loc = geoip.get_ip_location("1.2.3.4")
|
||||
assert loc == {
|
||||
"country": "United States",
|
||||
"region": "California",
|
||||
"city": "Mountain View",
|
||||
"display": "United States California Mountain View",
|
||||
}
|
||||
|
||||
|
||||
def test_get_ip_location_reader_exception(monkeypatch):
|
||||
class FakeReader:
|
||||
def city(self, ip):
|
||||
raise Exception("boom")
|
||||
|
||||
monkeypatch.setattr(geoip, "_get_reader", lambda: FakeReader())
|
||||
assert geoip.get_ip_location("1.2.3.4") is None
|
||||
|
||||
|
||||
def test_get_ip_location_no_location_parts(monkeypatch):
|
||||
from types import SimpleNamespace
|
||||
resp = SimpleNamespace(country=SimpleNamespace(name=None), subdivisions=None, city=None)
|
||||
|
||||
class FakeReader:
|
||||
def city(self, ip):
|
||||
return resp
|
||||
|
||||
monkeypatch.setattr(geoip, "_get_reader", lambda: FakeReader())
|
||||
assert geoip.get_ip_location("1.2.3.4") is None
|
||||
|
||||
|
||||
def test_get_ip_location_text_valid(monkeypatch):
|
||||
from types import SimpleNamespace
|
||||
country = SimpleNamespace(name="United States")
|
||||
region = SimpleNamespace(name="California")
|
||||
resp = SimpleNamespace(
|
||||
country=country,
|
||||
subdivisions=SimpleNamespace(most_specific=region),
|
||||
city=SimpleNamespace(name="Mountain View"),
|
||||
)
|
||||
|
||||
class FakeReader:
|
||||
def city(self, ip):
|
||||
return resp
|
||||
|
||||
monkeypatch.setattr(geoip, "_get_reader", lambda: FakeReader())
|
||||
assert geoip.get_ip_location_text("1.2.3.4") == "United States California Mountain View"
|
||||
|
||||
|
||||
def test_get_ip_location_text_none_when_no_location(monkeypatch):
|
||||
# Force get_ip_location to return None
|
||||
monkeypatch.setattr(geoip, "get_ip_location", lambda ip: None)
|
||||
assert geoip.get_ip_location_text("1.2.3.4") == ""
|
||||
Reference in New Issue
Block a user