""" tests/test_remember.py — C1.2 Tests for remember(), forget(), and jaccard_similarity(). """ import sys import os import datetime import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_kv(tmp_path): from db import StateKV os.environ.pop("AGENTCACHE_SECRET", None) return StateKV(db_path=str(tmp_path / "test.db")) def _now(): return datetime.datetime.utcnow().isoformat() + "Z" # --------------------------------------------------------------------------- # jaccard_similarity # --------------------------------------------------------------------------- class TestJaccardSimilarity: def test_identical_strings(self): from functions import jaccard_similarity assert jaccard_similarity("hello world foo", "hello world foo") == 1.0 def test_completely_different(self): from functions import jaccard_similarity score = jaccard_similarity("apple banana cherry", "xyz uvw qrs") assert score == 0.0 def test_partial_overlap(self): from functions import jaccard_similarity score = jaccard_similarity("authentication security token", "authentication bearer token") assert 0.0 < score < 1.0 def test_empty_strings(self): from functions import jaccard_similarity assert jaccard_similarity("", "") == 1.0 def test_high_similarity_above_threshold(self): from functions import jaccard_similarity # These two should meet or exceed the 0.7 threshold used in remember() a = "Use parameterised queries to prevent SQL injection in all database calls" b = "Use parameterised queries to prevent SQL injection in database operations" assert jaccard_similarity(a, b) >= 0.7 def test_low_similarity_below_threshold(self): from functions import jaccard_similarity a = "Configure Redis as the session cache backend" b = "Deploy the React frontend to Vercel using GitHub Actions CI" assert jaccard_similarity(a, b) < 0.7 # --------------------------------------------------------------------------- # remember() # --------------------------------------------------------------------------- class TestRemember: def test_creates_memory_with_is_latest_true(self, tmp_path): from functions import remember kv = _make_kv(tmp_path) result = remember(kv, {"content": "Always use type hints in Python functions"}) assert result["success"] is True mem = result["memory"] assert mem["isLatest"] is True assert mem["id"].startswith("mem_") assert "Always use type hints" in mem["content"] def test_memory_has_required_fields(self, tmp_path): from functions import remember kv = _make_kv(tmp_path) result = remember(kv, { "content": "Prefer composition over inheritance", "type": "architecture", "concepts": ["design", "patterns"], }) mem = result["memory"] assert "id" in mem assert "content" in mem assert "createdAt" in mem assert mem["type"] == "architecture" assert "design" in mem["concepts"] def test_supersedes_memory_with_high_jaccard_similarity(self, tmp_path): from functions import remember, KV kv = _make_kv(tmp_path) first = remember(kv, {"content": "Always use parameterised SQL queries to prevent injection attacks in every database call"}) first_id = first["memory"]["id"] # Highly similar content — should supersede the first second = remember(kv, {"content": "Always use parameterised SQL queries to prevent injection attacks in every database operation"}) second_mem = second["memory"] # Old memory should be marked as not latest old_mem = kv.get(KV.memories, first_id) assert old_mem is not None assert old_mem.get("isLatest") is False # New memory should be latest and point to old via parentId assert second_mem["isLatest"] is True assert second_mem.get("parentId") == first_id def test_independent_memory_with_low_jaccard_similarity(self, tmp_path): from functions import remember, KV kv = _make_kv(tmp_path) first = remember(kv, {"content": "Configure Redis as the session cache backend for high throughput"}) first_id = first["memory"]["id"] # Very different content — should be independent second = remember(kv, {"content": "Deploy the React frontend to Vercel using GitHub Actions continuous deployment"}) second_mem = second["memory"] # Old memory should remain latest old_mem = kv.get(KV.memories, first_id) assert old_mem is not None assert old_mem.get("isLatest") is True # New memory has no parentId assert second_mem["isLatest"] is True assert second_mem.get("parentId") is None # Both exist independently all_mems = kv.list(KV.memories) assert len(all_mems) == 2 def test_remember_raises_on_empty_content(self, tmp_path): from functions import remember kv = _make_kv(tmp_path) with pytest.raises(ValueError, match="content is required"): remember(kv, {"content": ""}) def test_remember_strips_private_data(self, tmp_path): from functions import remember kv = _make_kv(tmp_path) result = remember(kv, { "content": "API key is sk-proj-abc123def456ghi789jkl012mno345pqr678 for production" }) assert "sk-proj-" not in result["memory"]["content"] assert "[REDACTED" in result["memory"]["content"] def test_remember_with_project_scoping(self, tmp_path): from functions import remember kv = _make_kv(tmp_path) # Two very similar memories for different projects should not supersede each other first = remember(kv, { "content": "Always use parameterised queries in all database operations", "project": "project-alpha", }) second = remember(kv, { "content": "Always use parameterised queries in all database operations", "project": "project-beta", }) # Both should remain independent (different projects) assert first["memory"]["isLatest"] is True assert second["memory"]["isLatest"] is True # --------------------------------------------------------------------------- # forget() # --------------------------------------------------------------------------- class TestForget: def test_forget_memory_by_id(self, tmp_path): from functions import remember, forget, KV kv = _make_kv(tmp_path) result = remember(kv, {"content": "This memory will be forgotten"}) mem_id = result["memory"]["id"] forget_result = forget(kv, {"memoryId": mem_id}) assert forget_result["deleted"] >= 1 assert kv.get(KV.memories, mem_id) is None def test_forget_returns_zero_for_nonexistent_memory(self, tmp_path): from functions import forget kv = _make_kv(tmp_path) result = forget(kv, {"memoryId": "mem_nonexistent_id"}) # Should still succeed (memory was already gone) — deleted may be 0 or 1 assert "deleted" in result