from __future__ import annotations import json import time from yuxi.channels.adapters.nextcloudtalk.dedup import NextcloudTalkDedupGuard class TestDedupGuardGC: def test_gc_removes_expired_entries(self): guard = NextcloudTalkDedupGuard(ttl=-1, max_entries=1000) guard.commit("token", "msg-1") assert guard.is_duplicate("token", "msg-1") is False def test_gc_enforces_max_entries(self): guard = NextcloudTalkDedupGuard(ttl=3600, max_entries=5) for i in range(10): guard.commit("token", f"msg-{i}") stats = guard.stats() assert stats["committed"] <= 5 class TestDedupGuardMakeKey: def test_make_key_format(self): guard = NextcloudTalkDedupGuard() key = guard._make_key("token-abc", "msg-123") assert key == "token-abc:msg-123" class TestDedupGuardClaimCommitCycle: def test_claim_then_commit(self): guard = NextcloudTalkDedupGuard() assert guard.claim("token", "msg-1") is True assert guard.claim("token", "msg-1") is False guard.commit("token", "msg-1") assert guard.is_duplicate("token", "msg-1") is True assert guard.claim("token", "msg-1") is False def test_claim_then_release(self): guard = NextcloudTalkDedupGuard() assert guard.claim("token", "msg-1") is True guard.release("token", "msg-1") assert guard.claim("token", "msg-1") is True def test_claim_then_mark_invalid(self): guard = NextcloudTalkDedupGuard() assert guard.claim("token", "msg-1") is True guard.mark_invalid("token", "msg-1") assert guard.is_invalid("token", "msg-1") is True assert guard.claim("token", "msg-1") is True class TestDedupGuardInvalid: def test_mark_and_check_invalid(self): guard = NextcloudTalkDedupGuard() guard.mark_invalid("token", "msg-1") assert guard.is_invalid("token", "msg-1") is True def test_invalid_not_duplicate(self): guard = NextcloudTalkDedupGuard() guard.mark_invalid("token", "msg-1") assert guard.is_duplicate("token", "msg-1") is False class TestDedupGuardStats: def test_initial_stats(self): guard = NextcloudTalkDedupGuard() stats = guard.stats() assert stats["committed"] == 0 assert stats["pending"] == 0 assert stats["invalid"] == 0 def test_stats_after_operations(self): guard = NextcloudTalkDedupGuard() guard.commit("token", "msg-1") guard.commit("token", "msg-2") guard.claim("token", "msg-3") guard.mark_invalid("token", "msg-4") stats = guard.stats() assert stats["committed"] == 2 assert stats["pending"] == 1 assert stats["invalid"] == 1 class TestDedupGuardPersist: def test_flush_saves_to_file(self, tmp_path, monkeypatch): import yuxi.channels.adapters.nextcloudtalk.dedup as dedup_mod persist_file = tmp_path / ".dedup_cache_test.json" monkeypatch.setattr(dedup_mod, "_PERSIST_PATH", str(persist_file)) guard = NextcloudTalkDedupGuard(ttl=3600, max_entries=1000) guard.commit("token", "msg-1") guard.commit("token", "msg-2") guard.flush() assert persist_file.exists() with open(persist_file, encoding="utf-8") as f: data = json.load(f) assert data["token:msg-1"] > 0 assert data["token:msg-2"] > 0 def test_load_from_persist(self, tmp_path, monkeypatch): import yuxi.channels.adapters.nextcloudtalk.dedup as dedup_mod persist_file = tmp_path / ".dedup_cache_load.json" now = time.time() data = { "token:msg-1": now, "token:msg-2": now, } with open(persist_file, "w", encoding="utf-8") as f: json.dump(data, f) monkeypatch.setattr(dedup_mod, "_PERSIST_PATH", str(persist_file)) guard = NextcloudTalkDedupGuard(ttl=3600, max_entries=1000) assert guard.is_duplicate("token", "msg-1") is True assert guard.is_duplicate("token", "msg-2") is True assert guard.is_duplicate("token", "msg-3") is False def test_load_expired_from_persist(self, tmp_path, monkeypatch): import yuxi.channels.adapters.nextcloudtalk.dedup as dedup_mod persist_file = tmp_path / ".dedup_cache_expired.json" old_time = time.time() - 99999 data = { "token:msg-old": old_time, } with open(persist_file, "w", encoding="utf-8") as f: json.dump(data, f) monkeypatch.setattr(dedup_mod, "_PERSIST_PATH", str(persist_file)) guard = NextcloudTalkDedupGuard(ttl=10, max_entries=1000) assert guard.is_duplicate("token", "msg-old") is False