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.
169 lines
5.1 KiB
Python
169 lines
5.1 KiB
Python
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") == ""
|