From 7bd901eefa7ba09c8db26c3e3d0add90a2d81062 Mon Sep 17 00:00:00 2001 From: Solaria Lumis Havens Date: Sat, 11 Apr 2026 04:51:59 +0000 Subject: [PATCH] Add test suite for persistent memory (synthetic identity) --- tests/test_memory.py | 390 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 tests/test_memory.py diff --git a/tests/test_memory.py b/tests/test_memory.py new file mode 100644 index 0000000..0579b97 --- /dev/null +++ b/tests/test_memory.py @@ -0,0 +1,390 @@ +""" +test_memory.py + +Persistent Memory Test Suite +============================ + +Tests for the persistent memory system (Steps 1-3). +Uses synthetic identity ("TestBot") to avoid compromising the actual system. + +Usage: + python -m becomingone.tests.test_memory + +Requirements: + pip install sentence-transformers + +Author: Solaria Lumis Havens +Date: 2026-04-11 +""" + +import json +import os +import sys +import tempfile +import unittest +from datetime import datetime, timedelta +from pathlib import Path +from typing import List +from dataclasses import dataclass, field, asdict +from enum import Enum + +# Add parent to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from becomingone.memory.temporal import ( + TemporalSignature, + MemoryStrength, + encode_to_phase, + persist_signature, + retrieve_signatures, + phase_distance, + compute_resonance_score, +) + + +class TestPersona: + """Synthetic test identity for isolation testing.""" + name: str = "TestBot" + origin: str = "testbot" + coherence_threshold: float = 0.5 + + +class MemoryStrengthEnum(MemoryStrength): + """Compatibility shim.""" + TRANSIENT = MemoryStrength.TRANSIENT + WORKING = MemoryStrength.WORKING + EPISODIC = MemoryStrength.EPISODIC + PROCEDURAL = MemoryStrength.PROCEDURAL + SEMANTIC = MemoryStrength.SEMANTIC + IDENTITY = MemoryStrength.IDENTITY + + +def create_test_signature( + signature_id: str, + coherence_value: float, + content_text: str, + origin: str = "user", + parent_id: str = None, + hours_ago: float = 0.0, + strength: MemoryStrength = MemoryStrength.EPISODIC, +) -> TemporalSignature: + """Create a test TemporalSignature with controlled parameters.""" + + # Encode content to phase + phase_vector = encode_to_phase(content_text).tolist() + + # Calculate created_at + created_at = datetime.utcnow() - timedelta(hours=hours_ago) if hours_ago > 0 else datetime.utcnow() + + # Create context hash + import hashlib + context_hash = hashlib.sha256(content_text.encode()).hexdigest()[:16] + + return TemporalSignature( + signature_id=signature_id, + coherence_value=coherence_value, + phase_vector=phase_vector, + frequency_modes=[1.0, 2.0], + context_hash=context_hash, + strength=strength, + origin=origin, + parent_id=parent_id, + created_at=created_at, + last_accessed=datetime.utcnow(), + access_count=0, + decay_rate=0.01, + content={"text": content_text, "source": "test"}, + ) + + +class TestPersistence(unittest.TestCase): + """Step 1: Log-only persistence tests.""" + + def setUp(self): + """Create temporary file for testing.""" + self.temp_file = tempfile.NamedTemporaryFile( + mode='w', + suffix='.jsonl', + delete=False + ) + self.temp_file.close() + self.filepath = self.temp_file.name + + def tearDown(self): + """Clean up temp file.""" + if os.path.exists(self.filepath): + os.remove(self.filepath) + + def test_persist_single_signature(self): + """Test writing a single signature to JSONL.""" + sig = create_test_signature( + signature_id="test-001", + coherence_value=0.85, + content_text="Test memory one", + ) + + persist_signature(sig, self.filepath) + + # Verify file exists and has content + self.assertTrue(os.path.exists(self.filepath)) + with open(self.filepath, 'r') as f: + lines = f.readlines() + self.assertEqual(len(lines), 1) + + # Verify JSON is valid + data = json.loads(lines[0]) + self.assertEqual(data['signature_id'], "test-001") + self.assertEqual(data['coherence_value'], 0.85) + + def test_persist_multiple_signatures(self): + """Test writing multiple signatures.""" + signatures = [ + create_test_signature(f"test-{i:03d}", 0.80 + i*0.02, f"Test memory {i}") + for i in range(1, 6) + ] + + for sig in signatures: + persist_signature(sig, self.filepath) + + with open(self.filepath, 'r') as f: + lines = f.readlines() + + self.assertEqual(len(lines), 5) + + +class TestPhaseEncoder(unittest.TestCase): + """Step 2: Phase encoder tests.""" + + def test_encode_returns_array(self): + """Test that encode_to_phase returns numpy array.""" + import numpy as np + result = encode_to_phase("Hello world") + self.assertIsInstance(result, np.ndarray) + + def test_encode_deterministic(self): + """Test that same input gives same output.""" + result1 = encode_to_phase("Consciousness") + result2 = encode_to_phase("Consciousness") + import numpy as np + self.assertTrue(np.allclose(result1, result2)) + + def test_encode_different_inputs(self): + """Test that different inputs give different outputs.""" + result1 = encode_to_phase("Consciousness") + result2 = encode_to_phase("Table") + import numpy as np + self.assertFalse(np.allclose(result1, result2)) + + def test_encode_output_shape(self): + """Test expected output dimension.""" + result = encode_to_phase("Test") + self.assertEqual(result.shape[0], 384) # all-MiniLM-L6-v2 dimension + + +class TestRetrieval(unittest.TestCase): + """Step 3: Retrieval tests.""" + + def setUp(self): + """Create test memory file.""" + self.temp_file = tempfile.NamedTemporaryFile( + mode='w', + suffix='.jsonl', + delete=False + ) + self.temp_file.close() + self.filepath = self.temp_file.name + + # Create test memories with various properties + self.test_memories = [ + # High coherence, recent, user origin + create_test_signature( + "sig-001", 0.95, "We talked about consciousness", + origin="user", hours_ago=0.5 + ), + # High coherence, older + create_test_signature( + "sig-002", 0.90, "The meaning of coherence", + origin="user", hours_ago=24.0 + ), + # Low coherence + create_test_signature( + "sig-003", 0.30, "Random thought", + origin="user", hours_ago=1.0 + ), + # Solaria origin (should be weighted lower) + create_test_signature( + "sig-004", 0.85, "I am Solaria", + origin="solaria", hours_ago=2.0 + ), + # User origin, very old + create_test_signature( + "sig-005", 0.88, "Building persistent memory", + origin="user", hours_ago=72.0 + ), + ] + + for sig in self.test_memories: + persist_signature(sig, self.filepath) + + def tearDown(self): + """Clean up.""" + if os.path.exists(self.filepath): + os.remove(self.filepath) + + def test_retrieve_signatures(self): + """Test loading signatures from JSONL.""" + signatures = retrieve_signatures(self.filepath) + self.assertEqual(len(signatures), 5) + + def test_phase_distance(self): + """Test circular phase distance.""" + import numpy as np + a = np.array([0.0, 0.0]) + b = np.array([0.1, 0.1]) + dist = phase_distance(a, b) + self.assertGreater(dist, 0) + + def test_resonance_scoring_user_vs_solaria(self): + """Test that user origins score higher than solaria origins.""" + import numpy as np + + query_phase = encode_to_phase("consciousness") + current_coherence = 0.80 + + # Find a user signature + user_sig = next(s for s in self.test_memories if s.origin == "user") + solaria_sig = next(s for s in self.test_memories if s.origin == "solaria") + + user_score = compute_resonance_score(user_sig, query_phase, current_coherence) + solaria_score = compute_resonance_score(solaria_sig, query_phase, current_coherence) + + # User should score higher (or equal, never lower) + self.assertGreaterEqual(user_score, solaria_score) + + def test_resonance_decay_over_time(self): + """Test that older memories decay.""" + import numpy as np + + query_phase = encode_to_phase("consciousness") + current_coherence = 0.80 + + # Recent memory + recent_sig = create_test_signature( + "recent", 0.90, "We talked about consciousness", + hours_ago=0.1 + ) + + # Old memory + old_sig = create_test_signature( + "old", 0.90, "We talked about consciousness", + hours_ago=168.0 # 1 week + ) + + recent_score = compute_resonance_score(recent_sig, query_phase, current_coherence) + old_score = compute_resonance_score(old_sig, query_phase, current_coherence) + + self.assertGreater(recent_score, old_score) + + def test_coherence_weighting(self): + """Test that high coherence memories resonate more.""" + import numpy as np + + query_phase = encode_to_phase("test") + current_coherence = 0.80 + + high_coh = create_test_signature("high", 0.95, "test content") + low_coh = create_test_signature("low", 0.30, "test content") + + high_score = compute_resonance_score(high_coh, query_phase, current_coherence) + low_score = compute_resonance_score(low_coh, query_phase, current_coherence) + + self.assertGreater(high_score, low_score) + + +class TestIntegration(unittest.TestCase): + """End-to-end integration tests.""" + + def setUp(self): + """Create test memory.""" + self.temp_file = tempfile.NamedTemporaryFile( + mode='w', + suffix='.jsonl', + delete=False + ) + self.temp_file.close() + self.filepath = self.temp_file.name + + def tearDown(self): + """Clean up.""" + if os.path.exists(self.filepath): + os.remove(self.filepath) + + def test_full_pipeline(self): + """Test: encode -> store -> retrieve -> reconstruct.""" + + # 1. User input + user_input = "Tell me about consciousness" + + # 2. Encode to phase + query_phase = encode_to_phase(user_input) + + # 3. Create and store signature + sig = create_test_signature( + signature_id="int-001", + coherence_value=0.85, + content_text=user_input, + origin="user" + ) + persist_signature(sig, self.filepath) + + # 4. Retrieve + retrieved = retrieve_signatures(self.filepath, limit=3) + + # 5. Verify we got something back + self.assertGreater(len(retrieved), 0) + + # 6. Compute resonance + scores = [ + compute_resonance_score(s, query_phase, 0.80) + for s in retrieved + ] + + # Should have valid scores + for score in scores: + self.assertGreater(score, 0) + + +def run_tests(): + """Run all tests and return results.""" + loader = unittest.TestLoader() + suite = unittest.TestSuite() + + suite.addTests(loader.loadTestsFromTestCase(TestPersistence)) + suite.addTests(loader.loadTestsFromTestCase(TestPhaseEncoder)) + suite.addTests(loader.loadTestsFromTestCase(TestRetrieval)) + suite.addTests(loader.loadTestsFromTestCase(TestIntegration)) + + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + return result + + +if __name__ == "__main__": + print("=" * 60) + print("Persistent Memory Test Suite") + print("Using synthetic identity: TestBot") + print("=" * 60) + print() + + result = run_tests() + + print() + print("=" * 60) + if result.wasSuccessful(): + print("✓ All tests passed") + else: + print(f"✗ {len(result.failures)} failures, {len(result.errors)} errors") + print("=" * 60) + + sys.exit(0 if result.wasSuccessful() else 1) \ No newline at end of file