diff --git a/app/api.py b/app/api.py index 30ddc56..d9d265e 100644 --- a/app/api.py +++ b/app/api.py @@ -7,6 +7,7 @@ from __future__ import annotations import hashlib import json +import yaml from pathlib import Path from datetime import datetime, timezone @@ -16,6 +17,7 @@ from pydantic import BaseModel _ROOT = Path(__file__).parent.parent _DATA_DIR: Path = _ROOT / "data" # overridable in tests via set_data_dir() +_CONFIG_DIR: Path | None = None # None = use real path def set_data_dir(path: Path) -> None: @@ -24,6 +26,18 @@ def set_data_dir(path: Path) -> None: _DATA_DIR = path +def set_config_dir(path: Path | None) -> None: + """Override config directory — used by tests.""" + global _CONFIG_DIR + _CONFIG_DIR = path + + +def _config_file() -> Path: + if _CONFIG_DIR is not None: + return _CONFIG_DIR / "label_tool.yaml" + return _ROOT / "config" / "label_tool.yaml" + + def reset_last_action() -> None: """Reset undo state — used by tests.""" global _last_action @@ -206,6 +220,31 @@ def get_labels(): return _LABEL_META +@app.get("/api/config") +def get_config(): + f = _config_file() + if not f.exists(): + return {"accounts": [], "max_per_account": 500} + raw = yaml.safe_load(f.read_text(encoding="utf-8")) or {} + return {"accounts": raw.get("accounts", []), "max_per_account": raw.get("max_per_account", 500)} + + +class ConfigPayload(BaseModel): + accounts: list[dict] + max_per_account: int = 500 + + +@app.post("/api/config") +def post_config(payload: ConfigPayload): + f = _config_file() + f.parent.mkdir(parents=True, exist_ok=True) + tmp = f.with_suffix(".tmp") + tmp.write_text(yaml.dump(payload.model_dump(), allow_unicode=True, sort_keys=False), + encoding="utf-8") + tmp.rename(f) + return {"ok": True} + + # Static SPA — MUST be last (catches all unmatched paths) _DIST = _ROOT / "web" / "dist" if _DIST.exists(): diff --git a/tests/test_api.py b/tests/test_api.py index 2d5b8bc..d51b208 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -152,3 +152,58 @@ def test_config_labels_returns_metadata(client): assert "emoji" in labels[0] assert "color" in labels[0] assert "name" in labels[0] + + +# ── /api/config ────────────────────────────────────────────────────────────── + +@pytest.fixture +def config_dir(tmp_path): + """Give the API a writable config directory.""" + from app import api as api_module + api_module.set_config_dir(tmp_path) + yield tmp_path + api_module.set_config_dir(None) # reset to default + + +@pytest.fixture +def data_dir(): + """Expose the current _DATA_DIR set by the autouse reset_globals fixture.""" + from app import api as api_module + return api_module._DATA_DIR + + +def test_get_config_returns_empty_when_no_file(client, config_dir): + r = client.get("/api/config") + assert r.status_code == 200 + data = r.json() + assert data["accounts"] == [] + assert data["max_per_account"] == 500 + + +def test_post_config_writes_yaml(client, config_dir): + import yaml + payload = { + "accounts": [{"name": "Test", "host": "imap.test.com", "port": 993, + "use_ssl": True, "username": "u@t.com", "password": "pw", + "folder": "INBOX", "days_back": 30}], + "max_per_account": 200, + } + r = client.post("/api/config", json=payload) + assert r.status_code == 200 + assert r.json()["ok"] is True + cfg_file = config_dir / "label_tool.yaml" + assert cfg_file.exists() + saved = yaml.safe_load(cfg_file.read_text()) + assert saved["max_per_account"] == 200 + assert saved["accounts"][0]["name"] == "Test" + + +def test_get_config_round_trips(client, config_dir): + payload = {"accounts": [{"name": "R", "host": "h", "port": 993, "use_ssl": True, + "username": "u", "password": "p", "folder": "INBOX", + "days_back": 90}], "max_per_account": 300} + client.post("/api/config", json=payload) + r = client.get("/api/config") + data = r.json() + assert data["max_per_account"] == 300 + assert data["accounts"][0]["name"] == "R"