Compare commits
2 Commits
refactor/p
...
fix-memory
| Author | SHA1 | Date | |
|---|---|---|---|
| ddbe8f8e99 | |||
| 5b53a7e4c0 |
@@ -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)
|
||||
|
||||
@@ -178,6 +178,8 @@ class ScreenIdentity:
|
||||
return ScreenType.FOLLOW_LIST
|
||||
|
||||
if "profile_header_container" in ids:
|
||||
if selected_tab == "profile_tab":
|
||||
return ScreenType.OWN_PROFILE
|
||||
return ScreenType.OTHER_PROFILE
|
||||
|
||||
# Reels structural markers — present even when Instagram hides the tab bar
|
||||
|
||||
@@ -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}")
|
||||
|
||||
28
tests/unit/test_bot_flow_singleton.py
Normal file
28
tests/unit/test_bot_flow_singleton.py
Normal file
@@ -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."
|
||||
42
tests/unit/test_delete_point_logging.py
Normal file
42
tests/unit/test_delete_point_logging.py
Normal file
@@ -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]
|
||||
39
tests/unit/test_screen_identity_profile.py
Normal file
39
tests/unit/test_screen_identity_profile.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import pytest
|
||||
from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType
|
||||
|
||||
def test_screen_identity_own_profile_vs_other_profile():
|
||||
identity = ScreenIdentity("marisaundmarc")
|
||||
|
||||
# When we are on our OWN profile, 'profile_tab' is selected,
|
||||
# but 'profile_header_container' is ALSO present.
|
||||
# The bug is that 'profile_header_container' shadows 'profile_tab' selected=True.
|
||||
|
||||
# Let's create an XML dump that mimics this scenario:
|
||||
own_profile_xml = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<hierarchy>
|
||||
<node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_container" text="" content-desc="" clickable="false" bounds="[0,0][100,100]" />
|
||||
<node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab" selected="true" text="" content-desc="Profile" clickable="true" bounds="[0,0][100,100]" />
|
||||
</hierarchy>
|
||||
"""
|
||||
|
||||
result = identity.identify(own_profile_xml)
|
||||
|
||||
assert result["screen_type"] == ScreenType.OWN_PROFILE, "Failed! own profile was classified as OTHER_PROFILE because profile_header_container shadowed it."
|
||||
|
||||
def test_screen_identity_other_profile_vs_own_profile():
|
||||
identity = ScreenIdentity("marisaundmarc")
|
||||
|
||||
# When we are on someone ELSE's profile, 'profile_tab' is NOT selected
|
||||
# (or maybe 'feed_tab' or 'search_tab' is selected, or none).
|
||||
# And 'profile_header_container' is present.
|
||||
|
||||
other_profile_xml = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<hierarchy>
|
||||
<node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_container" text="" content-desc="" clickable="false" bounds="[0,0][100,100]" />
|
||||
<node package="com.instagram.android" resource-id="com.instagram.android:id/feed_tab" selected="true" text="" content-desc="Home" clickable="true" bounds="[0,0][100,100]" />
|
||||
</hierarchy>
|
||||
"""
|
||||
|
||||
result = identity.identify(other_profile_xml)
|
||||
|
||||
assert result["screen_type"] == ScreenType.OTHER_PROFILE, "Failed! other profile was not classified as OTHER_PROFILE."
|
||||
Reference in New Issue
Block a user