Initial commit
This commit is contained in:
commit
9c7df76fbd
91 files changed
+36572
No files matched your search
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
test_app_config.py — Tests for app_config.py.
|
||||
|
||||
Covers:
|
||||
- LANG loading and key access
|
||||
- Article 9 keyword detection (_check_special_category)
|
||||
- Config load/save round-trip
|
||||
- Admin PIN hash/verify
|
||||
- Profile CRUD (_profile_save, _profile_get, _profile_delete)
|
||||
- SMTP password encryption/decryption round-trip
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
import app_config
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 1. i18n
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestLang:
|
||||
|
||||
def test_lang_dict_loaded(self):
|
||||
assert isinstance(app_config.LANG, dict)
|
||||
assert len(app_config.LANG) > 0
|
||||
|
||||
def test_lang_has_lang_code(self):
|
||||
assert "_lang_code" in app_config.LANG
|
||||
|
||||
def test_load_lang_returns_dict(self):
|
||||
lang = app_config._load_lang()
|
||||
assert isinstance(lang, dict)
|
||||
|
||||
def test_load_lang_forced_en(self):
|
||||
lang = app_config._load_lang_forced("en")
|
||||
assert isinstance(lang, dict)
|
||||
assert len(lang) > 0
|
||||
|
||||
def test_load_lang_forced_da(self):
|
||||
lang = app_config._load_lang_forced("da")
|
||||
assert isinstance(lang, dict)
|
||||
assert len(lang) > 0
|
||||
|
||||
def test_load_lang_forced_de(self):
|
||||
lang = app_config._load_lang_forced("de")
|
||||
assert isinstance(lang, dict)
|
||||
assert len(lang) > 0
|
||||
|
||||
def test_missing_lang_falls_back(self):
|
||||
# Unknown lang code should fall back without raising
|
||||
lang = app_config._load_lang_forced("xx")
|
||||
assert isinstance(lang, dict)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 2. Article 9 keyword detection
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestCheckSpecialCategory:
|
||||
|
||||
def _cats(self, text):
|
||||
cprs = [{"raw": "290472-1234"}]
|
||||
return app_config._check_special_category(text, cprs)
|
||||
|
||||
def test_health_keyword_detected(self):
|
||||
cats = self._cats("CPR: 290472-1234 har diagnosen diabetes og behandling")
|
||||
assert "health" in cats
|
||||
|
||||
def test_trade_union_keyword_detected(self):
|
||||
cats = self._cats("CPR: 290472-1234 er fagforeningsmedlem tillidsrepræsentant")
|
||||
assert "trade_union" in cats
|
||||
|
||||
def test_religion_keyword_detected(self):
|
||||
cats = self._cats("CPR: 290472-1234 kirke konfirmation")
|
||||
assert "religion" in cats
|
||||
|
||||
def test_no_keyword_returns_empty(self):
|
||||
cats = self._cats("CPR: 290472-1234 bor i Aarhus")
|
||||
assert cats == []
|
||||
|
||||
def test_empty_text_returns_empty(self):
|
||||
cats = app_config._check_special_category("", [])
|
||||
assert cats == []
|
||||
|
||||
def test_keyword_without_cpr_still_detected(self):
|
||||
# No CPR — keyword still triggers if no CPR list given
|
||||
cats = app_config._check_special_category("diagnose sygemelding behandling", [])
|
||||
assert "health" in cats
|
||||
|
||||
def test_returns_sorted_list(self):
|
||||
cats = self._cats("CPR 290472-1234 diabetes fagforening")
|
||||
assert cats == sorted(cats)
|
||||
|
||||
def test_compiled_keywords_populated(self):
|
||||
assert len(app_config._compiled_keywords) > 0
|
||||
|
||||
def test_keyword_flat_has_entries(self):
|
||||
assert len(app_config._keyword_flat) > 0
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 3. Config load / save
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestConfig:
|
||||
|
||||
def test_load_config_returns_dict(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "config.json")
|
||||
cfg = app_config._load_config()
|
||||
assert isinstance(cfg, dict)
|
||||
|
||||
def test_save_and_load_round_trip(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "config.json")
|
||||
app_config._save_config({"client_id": "test-id", "tenant_id": "test-tid"})
|
||||
cfg = app_config._load_config()
|
||||
assert cfg["client_id"] == "test-id"
|
||||
assert cfg["tenant_id"] == "test-tid"
|
||||
|
||||
def test_save_config_creates_file(self, tmp_path, monkeypatch):
|
||||
cfg_path = tmp_path / "config.json"
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", cfg_path)
|
||||
app_config._save_config({"x": 1})
|
||||
assert cfg_path.exists()
|
||||
|
||||
def test_load_missing_file_returns_empty(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "nonexistent.json")
|
||||
cfg = app_config._load_config()
|
||||
assert cfg == {}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 4. Admin PIN
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestAdminPin:
|
||||
|
||||
def test_pin_not_set_initially(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "config.json")
|
||||
# Fresh config — no PIN
|
||||
app_config._save_config({})
|
||||
assert app_config._admin_pin_is_set() is False
|
||||
|
||||
def test_set_and_verify_pin(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "config.json")
|
||||
app_config._save_config({})
|
||||
app_config._set_admin_pin("1234")
|
||||
assert app_config._verify_admin_pin("1234") is True
|
||||
|
||||
def test_wrong_pin_fails(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "config.json")
|
||||
app_config._save_config({})
|
||||
app_config._set_admin_pin("1234")
|
||||
assert app_config._verify_admin_pin("9999") is False
|
||||
|
||||
def test_pin_is_set_after_setting(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_CONFIG_FILE", tmp_path / "config.json")
|
||||
app_config._save_config({})
|
||||
app_config._set_admin_pin("5678")
|
||||
assert app_config._admin_pin_is_set() is True
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 5. Profiles
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestProfiles:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_SETTINGS_PATH", tmp_path / "settings.json")
|
||||
|
||||
def test_profiles_load_returns_list(self):
|
||||
profiles = app_config._profiles_load()
|
||||
assert isinstance(profiles, list)
|
||||
|
||||
def test_save_and_get_profile(self):
|
||||
profile = {
|
||||
"id": "test-uuid-1",
|
||||
"name": "Test Profile",
|
||||
"sources": ["email"],
|
||||
"user_ids": "all",
|
||||
"options": {},
|
||||
}
|
||||
app_config._profile_save(profile)
|
||||
loaded = app_config._profile_get("Test Profile")
|
||||
assert loaded is not None
|
||||
assert loaded["name"] == "Test Profile"
|
||||
|
||||
def test_profile_get_by_id(self):
|
||||
profile = {"id": "uid-42", "name": "By ID", "sources": [], "options": {}}
|
||||
app_config._profile_save(profile)
|
||||
loaded = app_config._profile_get("uid-42")
|
||||
assert loaded is not None
|
||||
|
||||
def test_profile_delete(self):
|
||||
profile = {"id": "del-1", "name": "To Delete", "sources": [], "options": {}}
|
||||
app_config._profile_save(profile)
|
||||
deleted = app_config._profile_delete("To Delete")
|
||||
assert deleted is True
|
||||
assert app_config._profile_get("To Delete") is None
|
||||
|
||||
def test_delete_nonexistent_returns_false(self):
|
||||
assert app_config._profile_delete("Does Not Exist") is False
|
||||
|
||||
def test_profiles_load_after_save(self):
|
||||
app_config._profile_save({"id": "p1", "name": "P1", "sources": [], "options": {}})
|
||||
app_config._profile_save({"id": "p2", "name": "P2", "sources": [], "options": {}})
|
||||
profiles = app_config._profiles_load()
|
||||
names = [p["name"] for p in profiles]
|
||||
assert "P1" in names
|
||||
assert "P2" in names
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 6. SMTP password encryption
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestFernet:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(app_config, "_MACHINE_ID_PATH", tmp_path / "machine_id")
|
||||
|
||||
def test_encrypt_decrypt_round_trip(self):
|
||||
fernet = app_config._get_fernet()
|
||||
if fernet is None:
|
||||
pytest.skip("cryptography not installed")
|
||||
plaintext = "my-secret-smtp-password"
|
||||
encrypted = app_config._encrypt_password(plaintext)
|
||||
decrypted = app_config._decrypt_password(encrypted)
|
||||
assert decrypted == plaintext
|
||||
|
||||
def test_encrypt_returns_string(self):
|
||||
fernet = app_config._get_fernet()
|
||||
if fernet is None:
|
||||
pytest.skip("cryptography not installed")
|
||||
result = app_config._encrypt_password("test")
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_encrypted_differs_from_plaintext(self):
|
||||
fernet = app_config._get_fernet()
|
||||
if fernet is None:
|
||||
pytest.skip("cryptography not installed")
|
||||
enc = app_config._encrypt_password("password123")
|
||||
assert enc != "password123"
|
||||
|
||||
def test_decrypt_empty_returns_empty(self):
|
||||
result = app_config._decrypt_password("")
|
||||
assert result == ""
|
||||
Reference in new issue
Block a user