hermes-agent/tests/gateway/test_message_deduplicator.py
kshitijk4poor 66827f8947 chore: prune unused imports and duplicate import redefinitions
Remove unused imports (F401) and duplicate/shadowed import
redefinitions (F811) across the codebase using ruff's safe
autofixes. No behavioral changes -- imports only.

- ~1400 safe autofixes applied across 644 files (net -1072 lines)
- __init__.py re-exports preserved (excluded from F401 removal so
  public re-export surfaces stay intact)
- Re-exports that are imported or monkeypatched by tests but look
  unused in their defining module are kept with explicit # noqa:
  F401 (gateway/run.py load_dotenv; run_agent re-exports from
  agent.message_sanitization, agent.context_compressor,
  agent.retry_utils, agent.prompt_builder, agent.process_bootstrap,
  agent.codex_responses_adapter)
- Unsafe F841 (unused-variable) fixes deliberately skipped -- those
  can change behavior when the RHS has side effects
- ruff lints remain disabled in pyproject.toml (only PLW1514 is
  selected); this is a one-time cleanup, not a config change

Verification:
- python -m compileall: clean
- pytest --collect-only: all 27161 tests collect (zero import errors)
- core entry points import clean (run_agent, model_tools, cli,
  toolsets, hermes_state, batch_runner, gateway)
- static scan: every name any test imports directly from an edited
  module still resolves
2026-05-28 22:26:25 -07:00

101 lines
4.1 KiB
Python

"""Tests for MessageDeduplicator TTL enforcement (#10306).
Previously, is_duplicate() returned True for any previously seen ID without
checking its age — expired entries were only purged when cache size exceeded
max_size. Normal workloads never overflowed, so messages stayed "duplicate"
forever.
The fix checks TTL at query time: if the entry's timestamp plus TTL is in
the past, the entry is treated as expired and the message is allowed through.
"""
import time
from gateway.platforms.helpers import MessageDeduplicator
class TestMessageDeduplicatorTTL:
"""TTL-based expiration must work regardless of cache size."""
def test_duplicate_within_ttl(self):
"""Same message within TTL window is duplicate."""
dedup = MessageDeduplicator(ttl_seconds=60)
assert dedup.is_duplicate("msg-1") is False
assert dedup.is_duplicate("msg-1") is True
def test_not_duplicate_after_ttl_expires(self):
"""Same message AFTER TTL expires should NOT be duplicate."""
dedup = MessageDeduplicator(ttl_seconds=5)
assert dedup.is_duplicate("msg-1") is False
# Fast-forward time past TTL
dedup._seen["msg-1"] = time.time() - 10 # 10s ago, TTL is 5s
assert dedup.is_duplicate("msg-1") is False, \
"Expired entry should not be treated as duplicate"
def test_expired_entry_gets_refreshed(self):
"""After an expired entry is allowed through, it should be re-tracked."""
dedup = MessageDeduplicator(ttl_seconds=5)
assert dedup.is_duplicate("msg-1") is False
# Expire the entry
dedup._seen["msg-1"] = time.time() - 10
# Should be allowed through (expired)
assert dedup.is_duplicate("msg-1") is False
# Now should be duplicate again (freshly tracked)
assert dedup.is_duplicate("msg-1") is True
def test_different_messages_not_confused(self):
"""Different message IDs are independent."""
dedup = MessageDeduplicator(ttl_seconds=60)
assert dedup.is_duplicate("msg-1") is False
assert dedup.is_duplicate("msg-2") is False
assert dedup.is_duplicate("msg-1") is True
assert dedup.is_duplicate("msg-2") is True
def test_empty_id_never_duplicate(self):
"""Empty/None message IDs are never treated as duplicate."""
dedup = MessageDeduplicator(ttl_seconds=60)
assert dedup.is_duplicate("") is False
assert dedup.is_duplicate("") is False
def test_max_size_eviction_prunes_expired(self):
"""Cache pruning on overflow removes expired entries."""
dedup = MessageDeduplicator(max_size=5, ttl_seconds=60)
# Add 6 entries, with the first 3 expired
now = time.time()
for i in range(3):
dedup._seen[f"old-{i}"] = now - 120 # expired (2 min ago, TTL 60s)
for i in range(3):
dedup.is_duplicate(f"new-{i}")
# Now we have 6 entries. Next insert triggers pruning.
dedup.is_duplicate("trigger")
# The 3 expired entries should be gone, leaving 4 fresh ones
assert len(dedup._seen) == 4
assert "old-0" not in dedup._seen
assert "new-0" in dedup._seen
def test_max_size_eviction_caps_fresh_entries(self):
"""Fresh entries must still be capped to max_size on overflow."""
dedup = MessageDeduplicator(max_size=2, ttl_seconds=60)
dedup.is_duplicate("msg-1")
dedup.is_duplicate("msg-2")
dedup.is_duplicate("msg-3")
assert len(dedup._seen) == 2
assert "msg-1" not in dedup._seen
assert "msg-2" in dedup._seen
assert "msg-3" in dedup._seen
def test_ttl_zero_means_no_dedup(self):
"""With TTL=0, all entries expire immediately."""
dedup = MessageDeduplicator(ttl_seconds=0)
assert dedup.is_duplicate("msg-1") is False
# Entry was just added at time.time(), and TTL is 0,
# so now - seen_time >= 0 = ttl, meaning it's expired
# But time.time() might be the exact same float, so
# the check is `now - ts < ttl` which is `0 < 0` = False
# This means TTL=0 effectively disables dedup
assert dedup.is_duplicate("msg-1") is False