import pytest from store_assistant.bot import split_message, to_plain_text from store_assistant.config import ConfigError, Settings from store_assistant.memory import ConversationMemory from store_assistant.prompts import build_system_prompt from store_assistant.ratelimit import RateLimiter class FakeClock: def __init__(self) -> None: self.now = 1000.0 def __call__(self) -> float: return self.now def test_rate_limiter_window() -> None: clock = FakeClock() limiter = RateLimiter(limit=2, window_s=60, clock=clock) assert limiter.check(1) == 0 assert limiter.check(1) == 0 assert limiter.check(1) == pytest.approx(60) assert limiter.check(2) == 0 # other users are not affected clock.now += 61 assert limiter.check(1) == 0 def test_memory_evicts_least_recent_chat() -> None: memory = ConversationMemory(max_messages=4, max_chats=2) memory.add_exchange(1, "q", "a") memory.add_exchange(2, "q", "a") memory.add_exchange(1, "q2", "a2") # chat 1 becomes the most recent memory.add_exchange(3, "q", "a") assert memory.history(2) == [] assert len(memory.history(1)) == 4 def test_split_message_short() -> None: assert split_message("hello") == ["hello"] def test_split_message_respects_limit_and_boundaries() -> None: text = "\n\n".join(["word " * 300] * 5) chunks = split_message(text, limit=4096) assert all(len(c) <= 4096 for c in chunks) assert "".join(chunks).replace(" ", "").replace("\n", "") == text.replace(" ", "").replace("\n", "") def test_split_message_without_spaces() -> None: chunks = split_message("x" * 10_000, limit=4096) assert [len(c) for c in chunks] == [4096, 4096, 1808] def test_system_prompt_names_store() -> None: assert "Nour Shop" in build_system_prompt("Nour Shop") def test_settings_require_api_key(monkeypatch) -> None: monkeypatch.setattr("store_assistant.config.load_dotenv", lambda *a, **k: None) monkeypatch.delenv("DEVUP_API_KEY", raising=False) with pytest.raises(ConfigError): Settings.from_env() assert Settings.from_env(require_api_key=False).devup_api_key == "" def test_settings_validate_numbers(monkeypatch) -> None: monkeypatch.setattr("store_assistant.config.load_dotenv", lambda *a, **k: None) monkeypatch.setenv("DEVUP_API_KEY", "test") monkeypatch.setenv("RATE_LIMIT_PER_MINUTE", "zero") with pytest.raises(ConfigError): Settings.from_env() monkeypatch.setenv("RATE_LIMIT_PER_MINUTE", "5") monkeypatch.setenv("ADMIN_CHAT_ID", "-1001234") settings = Settings.from_env() assert settings.rate_limit_per_minute == 5 assert settings.admin_chat_id == -1001234 def test_settings_require_telegram_token_for_bot(monkeypatch) -> None: monkeypatch.setattr("store_assistant.config.load_dotenv", lambda *a, **k: None) monkeypatch.setenv("DEVUP_API_KEY", "test") monkeypatch.delenv("TELEGRAM_BOT_TOKEN", raising=False) with pytest.raises(ConfigError): Settings.from_env(require_telegram=True) def test_to_plain_text() -> None: raw = "## Commande\nStatut : **expédiée**\n- Total : 9 550 DA\nCode #TRK-1" assert to_plain_text(raw) == "Commande\nStatut : expédiée\n- Total : 9 550 DA\nCode #TRK-1"