Files
llm-in-text/backend/tests/test_geoip.py
ydy0615 2fdc996af9 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.
2026-04-07 23:38:23 +08:00

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") == ""