diff --git a/GramAddict/core/bot_flow.py b/GramAddict/core/bot_flow.py index bc9d6d2..b82d81c 100644 --- a/GramAddict/core/bot_flow.py +++ b/GramAddict/core/bot_flow.py @@ -184,6 +184,9 @@ def start_bot(**kwargs): active_inference = ActiveInferenceEngine(username) # Core Autonomous Engines + from GramAddict.core.goap import GoalExecutor + + GoalExecutor.get_instance(device, username) zero_engine = ZeroLatencyEngine(device) nav_graph = QNavGraph(device) growth_brain = GrowthBrain(username, persona_interests=persona_interests) diff --git a/GramAddict/core/qdrant_memory.py b/GramAddict/core/qdrant_memory.py index fd7f734..f002b05 100644 --- a/GramAddict/core/qdrant_memory.py +++ b/GramAddict/core/qdrant_memory.py @@ -205,11 +205,13 @@ class QdrantBase: point_id = self.generate_uuid(seed_string) try: - self.client.delete(collection_name=self.collection_name, points_selector=[point_id]) - logger.info( - f"🗑️ [Qdrant] Purged poisoned memory vector from {self.collection_name} (UUID: {point_id[:8]}...)", - extra={"color": "\x1b[31m"}, - ) + res = self.client.retrieve(collection_name=self.collection_name, ids=[point_id]) + if res: + self.client.delete(collection_name=self.collection_name, points_selector=[point_id]) + logger.info( + f"🗑️ [Qdrant] Purged poisoned memory vector from {self.collection_name} (UUID: {point_id[:8]}...)", + extra={"color": "\x1b[31m"}, + ) return True except Exception as e: self._handle_error(e, f"Failed to delete point {point_id}") diff --git a/tests/unit/test_bot_flow_singleton.py b/tests/unit/test_bot_flow_singleton.py new file mode 100644 index 0000000..ab06a46 --- /dev/null +++ b/tests/unit/test_bot_flow_singleton.py @@ -0,0 +1,28 @@ +from unittest.mock import MagicMock, patch +import pytest + +def test_bot_flow_goal_executor_init_order(): + # We want to prove that without explicit get_instance(device, username), + # QNavGraph(device) initializes GoalExecutor with an empty username. + + # We have to reset GoalExecutor singleton first + from GramAddict.core.goap import GoalExecutor + from GramAddict.core.q_nav_graph import QNavGraph + + if hasattr(GoalExecutor, "_instance"): + GoalExecutor._instance = None + + device = MagicMock() + + # Action: Instantiate QNavGraph + # Without the fix, this will initialize GoalExecutor with bot_username="" + # and PathMemory will get bound to "goap_paths_v1" + + with patch("GramAddict.core.qdrant_memory.QdrantClient") as mock_qdrant: + # FIX: Pre-initialize with username (as done in bot_flow.py) + executor_pre = GoalExecutor.get_instance(device, "marisaundmarc") + nav_graph = QNavGraph(device) + executor = GoalExecutor.get_instance(device, "marisaundmarc") + + # Now it should be bound correctly + assert executor.path_memory._db.collection_name == "goap_paths_v1_marisaundmarc", "PathMemory collection name does not contain the username suffix! Initialization leak occurred." diff --git a/tests/unit/test_delete_point_logging.py b/tests/unit/test_delete_point_logging.py new file mode 100644 index 0000000..75f511c --- /dev/null +++ b/tests/unit/test_delete_point_logging.py @@ -0,0 +1,42 @@ +from unittest.mock import MagicMock, patch, PropertyMock +from GramAddict.core.qdrant_memory import UIMemoryDB + +def test_delete_point_logs_only_if_exists(caplog): + # Setup + db = UIMemoryDB() + type(db).is_connected = PropertyMock(return_value=True) + db.client = MagicMock() + db.collection_name = "test_collection" + + # RED: Force the mock to return an empty list (point does NOT exist) + db.client.retrieve.return_value = [] + + # Action + result = db.delete_point("fake_seed") + + # Assertions + assert result is True # Should still return True as it didn't crash + # It should NOT call delete if it wasn't found + db.client.delete.assert_not_called() + # It should NOT log the "Purged poisoned memory" line + assert "Purged poisoned memory vector" not in caplog.text + +def test_delete_point_logs_if_found(caplog): + # Setup + db = UIMemoryDB() + type(db).is_connected = PropertyMock(return_value=True) + db.client = MagicMock() + db.collection_name = "test_collection" + + # RED: Force the mock to return a result (point DOES exist) + db.client.retrieve.return_value = [{"id": "some_uuid"}] + + # Action + with patch("GramAddict.core.qdrant_memory.logger") as mock_logger: + result = db.delete_point("fake_seed") + + # Assertions + assert result is True + db.client.delete.assert_called_once() + mock_logger.info.assert_called_once() + assert "Purged poisoned memory vector" in mock_logger.info.call_args[0][0]