136 lines
4.7 KiB
Python
136 lines
4.7 KiB
Python
|
|
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
|