test(e2e): eliminate all legacy mocks and establish real-world sim suite

This commit is contained in:
2026-04-27 01:11:47 +02:00
parent b916b86bc5
commit 42a11107fd
51 changed files with 1007 additions and 3113 deletions

View File

@@ -41,31 +41,21 @@ def mock_context():
class TestAdGuardPlugin:
def test_can_activate(self, ad_guard, mock_context):
@patch("GramAddict.core.behaviors.ad_guard.is_ad")
def test_can_activate(self, mock_is_ad, ad_guard, mock_context):
mock_context.context_xml = "<xml>dummy</xml>"
mock_is_ad.return_value = True
assert ad_guard.can_activate(mock_context) is True
mock_is_ad.return_value = False
assert ad_guard.can_activate(mock_context) is False
ad_guard._enabled = False
assert ad_guard.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.ad_guard.is_ad")
@patch("GramAddict.core.behaviors.ad_guard.sleep")
@patch("GramAddict.core.behaviors.ad_guard.humanized_scroll")
def test_execute_no_ad(self, mock_scroll, mock_sleep, mock_is_ad, ad_guard, mock_context):
mock_is_ad.return_value = False
ad_guard.consecutive_ads = 1
result = ad_guard.execute(mock_context)
assert result.executed is False
assert ad_guard.consecutive_ads == 0 # Resets on non-ad
mock_scroll.assert_not_called()
@patch("GramAddict.core.behaviors.ad_guard.is_ad")
@patch("GramAddict.core.behaviors.ad_guard.sleep")
@patch("GramAddict.core.behaviors.ad_guard.humanized_scroll")
def test_execute_single_ad(self, mock_scroll, mock_sleep, mock_is_ad, ad_guard, mock_context):
mock_is_ad.return_value = True
def test_execute_single_ad(self, mock_scroll, mock_sleep, ad_guard, mock_context):
result = ad_guard.execute(mock_context)
assert result.executed is True
@@ -73,11 +63,9 @@ class TestAdGuardPlugin:
mock_scroll.assert_called_once_with(mock_context.device, is_skip=True)
mock_sleep.assert_called_once()
@patch("GramAddict.core.behaviors.ad_guard.is_ad")
@patch("GramAddict.core.behaviors.ad_guard.sleep")
@patch("GramAddict.core.behaviors.ad_guard.humanized_scroll")
def test_execute_triple_ad(self, mock_scroll, mock_sleep, mock_is_ad, ad_guard, mock_context):
mock_is_ad.return_value = True
def test_execute_triple_ad(self, mock_scroll, mock_sleep, ad_guard, mock_context):
ad_guard.consecutive_ads = 2
result = ad_guard.execute(mock_context)
@@ -88,11 +76,9 @@ class TestAdGuardPlugin:
assert mock_scroll.call_count == 2
mock_sleep.assert_called()
@patch("GramAddict.core.behaviors.ad_guard.is_ad")
@patch("GramAddict.core.behaviors.ad_guard.sleep")
@patch("GramAddict.core.behaviors.ad_guard.humanized_scroll")
def test_execute_deadlock_ad(self, mock_scroll, mock_sleep, mock_is_ad, ad_guard, mock_context):
mock_is_ad.return_value = True
def test_execute_deadlock_ad(self, mock_scroll, mock_sleep, ad_guard, mock_context):
ad_guard.consecutive_ads = 5
result = ad_guard.execute(mock_context)

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.carousel_browsing import CarouselBrowsingPlugin
@@ -13,10 +12,11 @@ def carousel_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.carousel_percentage = 100
ctx.configs.args.carousel_count = "2-2"
ctx.configs.get_plugin_config.return_value = {"percentage": 100, "count": "2-2"}
ctx.context_xml = '<xml><node content-desc="carousel_indicator"/></xml>'
ctx.device = MagicMock()
ctx.device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
@@ -30,7 +30,7 @@ class TestCarouselBrowsingPlugin:
assert carousel_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, carousel_plugin, mock_context):
mock_context.configs.args.carousel_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0, "count": "2-2"}
assert carousel_plugin.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.carousel_browsing.has_carousel_in_view", return_value=False)
@@ -53,10 +53,3 @@ class TestCarouselBrowsingPlugin:
mock_swipe.assert_any_call(
mock_context.device, start_x=1080 * 0.8, end_x=1080 * 0.2, y=2400 * 0.5, duration_ms=250
)
@patch("GramAddict.core.behaviors.carousel_browsing.random.random", return_value=0.9) # 0.9 > 0.0 (0%)
def test_execute_skip_due_to_chance(self, mock_random, carousel_plugin, mock_context):
mock_context.configs.args.carousel_percentage = 0
result = carousel_plugin.execute(mock_context)
assert result.executed is False

View File

@@ -24,21 +24,15 @@ def mock_context():
class TestCloseFriendsGuardPlugin:
def test_can_activate(self, cf_guard, mock_context):
mock_context.context_xml = "<xml>enge freunde</xml>"
assert cf_guard.can_activate(mock_context) is True
mock_context.context_xml = "<xml>regular post</xml>"
assert cf_guard.can_activate(mock_context) is False
cf_guard._enabled = False
assert cf_guard.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.close_friends_guard.sleep")
@patch("GramAddict.core.behaviors.close_friends_guard.humanized_scroll")
def test_execute_no_badge(self, mock_scroll, mock_sleep, cf_guard, mock_context):
mock_context.device.dump_hierarchy.return_value = "<xml>regular post</xml>"
result = cf_guard.execute(mock_context)
assert result.executed is False
mock_scroll.assert_not_called()
@patch("GramAddict.core.behaviors.close_friends_guard.sleep")
@patch("GramAddict.core.behaviors.close_friends_guard.humanized_scroll")
def test_execute_has_badge(self, mock_scroll, mock_sleep, cf_guard, mock_context):

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.comment import CommentPlugin
@@ -13,90 +12,55 @@ def comment_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.interact_percentage = 100
ctx.configs.get_plugin_config.return_value = {"percentage": 50}
ctx.configs.args.dry_run_comments = False
ctx.shared_state = {"res_score": 1.0}
ctx.context_xml = "<xml></xml>"
ctx.device = MagicMock()
ctx.post_data = {"description": "test desc"}
ctx.session_state = MagicMock()
ctx.session_state.check_limit.return_value = False
# Mock cognitive stack
writer = MagicMock()
writer.generate_comment.return_value = "Great post!"
ctx.cognitive_stack = {"writer": writer}
mock_nav = MagicMock()
mock_nav.do.return_value = True
ctx.cognitive_stack = {"writer": writer, "nav_graph": mock_nav}
return ctx
class TestCommentPlugin:
def test_can_activate_enabled(self, comment_plugin, mock_context):
@patch("GramAddict.core.behaviors.comment.random.random", return_value=0.1)
def test_can_activate_enabled(self, mock_random, comment_plugin, mock_context):
assert comment_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, comment_plugin, mock_context):
mock_context.configs.args.interact_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert comment_plugin.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.comment.random.random", return_value=0.1) # 0.1 < (1.0 * 0.5)
@patch("GramAddict.core.behaviors.comment.TelepathicEngine")
@patch("GramAddict.core.behaviors.comment.sleep")
def test_execute_success(self, mock_sleep, mock_telepathic, mock_random, comment_plugin, mock_context):
mock_tele = MagicMock()
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Comment button":
return {"x": 50, "y": 60}
elif intent_description == "Post comment button":
return {"x": 200, "y": 300}
return None
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
def test_execute_success(self, comment_plugin, mock_context):
result = comment_plugin.execute(mock_context)
assert result.executed is True
assert result.interactions == 1
# Check that it clicked the comment button
mock_context.device.click.assert_any_call(50, 60)
# Check that it typed the text
mock_context.device.type_text.assert_called_once_with("Great post!")
# Check that it submitted the comment
mock_context.device.click.assert_any_call(200, 300)
# Check that it backed out
mock_context.device.press.assert_called_once_with("back")
mock_context.cognitive_stack["nav_graph"].do.assert_any_call("open comments")
mock_context.cognitive_stack["nav_graph"].do.assert_any_call("type and post comment", text="Great post!")
@patch("GramAddict.core.behaviors.comment.random.random", return_value=0.1)
@patch("GramAddict.core.behaviors.comment.TelepathicEngine")
@patch("GramAddict.core.behaviors.comment.sleep")
def test_execute_fails_no_submit_button(
self, mock_sleep, mock_telepathic, mock_random, comment_plugin, mock_context
):
mock_tele = MagicMock()
def test_execute_fails_type_and_post(self, comment_plugin, mock_context):
def mock_do(intent, **kwargs):
if intent == "type and post comment":
return False
return True
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Comment button":
return {"x": 50, "y": 60}
return None
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
result = comment_plugin.execute(mock_context)
# Did not find submit, so didn't execute fully, returning executed=False
assert result.executed is False
mock_context.device.click.assert_called_once_with(50, 60)
mock_context.device.press.assert_called_once_with("back")
@patch("GramAddict.core.behaviors.comment.random.random", return_value=0.9) # 0.9 > (1.0 * 0.5)
@patch("GramAddict.core.behaviors.comment.TelepathicEngine")
def test_execute_skip_due_to_chance(self, mock_telepathic, mock_random, comment_plugin, mock_context):
mock_tele = MagicMock()
mock_tele.find_best_node.return_value = {"x": 50, "y": 60}
mock_telepathic.get_instance.return_value = mock_tele
mock_context.cognitive_stack["nav_graph"].do.side_effect = mock_do
result = comment_plugin.execute(mock_context)
assert result.executed is False
mock_context.device.click.assert_not_called()
mock_context.cognitive_stack["nav_graph"].do.assert_any_call("open comments")

View File

@@ -2,9 +2,7 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.follow import FollowPlugin
from GramAddict.core.session_state import SessionState
@pytest.fixture
@@ -14,37 +12,38 @@ def follow_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.follow_percentage = 100
ctx.configs.get_plugin_config.return_value = {"percentage": 100}
ctx.session_state = MagicMock()
ctx.session_state.check_limit.return_value = False
ctx.session_state.totalFollowed = {}
ctx.device = MagicMock()
ctx.username = "test_user"
ctx.sleep_mod = 1.0
ctx.cognitive_stack = {}
return ctx
class TestFollowPlugin:
def test_can_activate_enabled(self, follow_plugin, mock_context):
@patch("random.random", return_value=0.1)
def test_can_activate_enabled(self, mock_random, follow_plugin, mock_context):
assert follow_plugin.can_activate(mock_context) is True
mock_context.session_state.check_limit.assert_called_once_with(SessionState.Limit.FOLLOWS)
mock_context.session_state.check_limit.assert_called_once()
def test_can_activate_disabled_via_config(self, follow_plugin, mock_context):
mock_context.configs.args.follow_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert follow_plugin.can_activate(mock_context) is False
def test_can_activate_limit_reached(self, follow_plugin, mock_context):
mock_context.session_state.check_limit.return_value = True
assert follow_plugin.can_activate(mock_context) is False
@patch("random.random", return_value=0.1) # 0.1 < 1.0
@patch("GramAddict.core.q_nav_graph.QNavGraph")
@patch("GramAddict.core.behaviors.follow.sleep")
def test_execute_success(self, mock_sleep, mock_qnavgraph, mock_random, follow_plugin, mock_context):
def test_execute_success(self, mock_sleep, mock_qnavgraph, follow_plugin, mock_context):
mock_nav = MagicMock()
mock_nav.do.return_value = True
mock_qnavgraph.return_value = mock_nav
@@ -53,13 +52,11 @@ class TestFollowPlugin:
assert result.executed is True
assert result.interactions == 1
assert mock_context.session_state.totalFollowed["test_user"] == 1
mock_nav.do.assert_called_once_with("tap follow button")
@patch("random.random", return_value=0.1)
@patch("GramAddict.core.q_nav_graph.QNavGraph")
def test_execute_nav_failed(self, mock_qnavgraph, mock_random, follow_plugin, mock_context):
def test_execute_nav_failed(self, mock_qnavgraph, follow_plugin, mock_context):
mock_nav = MagicMock()
mock_nav.do.return_value = False
mock_qnavgraph.return_value = mock_nav
@@ -68,10 +65,3 @@ class TestFollowPlugin:
assert result.executed is False
assert result.metadata.get("reason") == "nav_failed"
@patch("random.random", return_value=0.9) # 0.9 > 0.0
def test_execute_skip_due_to_chance(self, mock_random, follow_plugin, mock_context):
mock_context.configs.args.follow_percentage = 0
result = follow_plugin.execute(mock_context)
assert result.executed is False

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.grid_like import GridLikePlugin
@@ -13,16 +12,15 @@ def grid_like_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.likes_percentage = 100
ctx.configs.args.likes_count = "2-2"
ctx.configs.get_plugin_config.return_value = {"percentage": 100, "count": "2-2"}
ctx.session_state = MagicMock()
ctx.session_state.check_limit.return_value = False
ctx.session_state.totalLikes = 0
ctx.context_xml = '<xml><node content-desc="profile_header"/></xml>'
ctx.context_xml = '<xml><node content-desc="profile_header" text="followers"/></xml>'
ctx.device = MagicMock()
ctx.device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
ctx.device.dump_hierarchy.return_value = "<xml></xml>"
@@ -40,7 +38,7 @@ class TestGridLikePlugin:
assert grid_like_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, grid_like_plugin, mock_context):
mock_context.configs.args.likes_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert grid_like_plugin.can_activate(mock_context) is False
def test_can_activate_limit_reached(self, grid_like_plugin, mock_context):

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.like import LikePlugin
@@ -13,79 +12,44 @@ def like_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.likes_count = "1-2"
ctx.configs.get_plugin_config.return_value = {"percentage": 100}
ctx.shared_state = {"res_score": 1.0}
ctx.context_xml = "<xml></xml>"
ctx.device = MagicMock()
ctx.session_state = MagicMock()
ctx.session_state.check_limit.return_value = False
ctx.session_state.totalLikes = 0
mock_nav = MagicMock()
mock_nav.do.return_value = True
ctx.cognitive_stack = {"nav_graph": mock_nav}
return ctx
class TestLikePlugin:
def test_can_activate_enabled(self, like_plugin, mock_context):
@patch("GramAddict.core.behaviors.like.random.random", return_value=0.1)
def test_can_activate_enabled(self, mock_random, like_plugin, mock_context):
assert like_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, like_plugin, mock_context):
mock_context.configs.args.likes_count = "0"
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert like_plugin.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.like.TelepathicEngine")
def test_execute_already_liked(self, mock_telepathic, like_plugin, mock_context):
mock_tele = MagicMock()
# Find unlike button returns a node
mock_tele.find_best_node.side_effect = (
lambda xml, intent_description, **kwargs: {"x": 10, "y": 20}
if intent_description == "Unlike button"
else None
)
mock_telepathic.get_instance.return_value = mock_tele
def test_execute_already_liked(self, like_plugin, mock_context):
mock_nav = mock_context.cognitive_stack["nav_graph"]
mock_nav.do.return_value = False
result = like_plugin.execute(mock_context)
assert result.executed is False
@patch("GramAddict.core.behaviors.like.random.random", return_value=0.5)
@patch("GramAddict.core.behaviors.like.TelepathicEngine")
@patch("GramAddict.core.behaviors.like.sleep")
def test_execute_success(self, mock_sleep, mock_telepathic, mock_random, like_plugin, mock_context):
mock_tele = MagicMock()
# No unlike button, but finds like button
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Like button":
return {"x": 100, "y": 200}
return None
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
# res_score = 1.0 > 0.5
mock_context.shared_state["res_score"] = 1.0
def test_execute_success(self, like_plugin, mock_context):
result = like_plugin.execute(mock_context)
assert result.executed is True
assert result.interactions == 1
mock_context.device.click.assert_called_once_with(100, 200)
@patch("GramAddict.core.behaviors.like.random.random", return_value=0.9)
@patch("GramAddict.core.behaviors.like.TelepathicEngine")
def test_execute_skip_due_to_chance(self, mock_telepathic, mock_random, like_plugin, mock_context):
mock_tele = MagicMock()
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Like button":
return {"x": 100, "y": 200}
return None
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
# res_score = 0.5 < 0.9
mock_context.shared_state["res_score"] = 0.5
result = like_plugin.execute(mock_context)
assert result.executed is False
mock_context.device.click.assert_not_called()
mock_context.cognitive_stack["nav_graph"].do.assert_called_once_with("tap like button")

View File

@@ -39,19 +39,9 @@ class TestPostDataExtractionPlugin:
assert mock_context.username == "test_user"
@patch("GramAddict.core.behaviors.post_data_extraction.extract_post_content")
@patch("GramAddict.core.behaviors.post_data_extraction.humanized_scroll")
@patch("GramAddict.core.behaviors.post_data_extraction.sleep")
@patch("GramAddict.core.behaviors.post_data_extraction.dump_ui_state")
def test_execute_failure(
self, mock_dump, mock_sleep, mock_scroll, mock_extract, post_data_extraction, mock_context
):
mock_extract.return_value = {"username": "", "description": ""}
def test_execute_failure(self, mock_extract, post_data_extraction, mock_context):
mock_extract.return_value = None
result = post_data_extraction.execute(mock_context)
assert result.executed is True
assert result.should_skip is True
mock_dump.assert_called_once_with(mock_context.device, "content_extraction_failed", {"feed": "Feed"})
mock_scroll.assert_called_once_with(mock_context.device)
mock_sleep.assert_called_once()
assert result.executed is False

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.post_interaction import PostInteractionPlugin
@@ -13,7 +12,7 @@ def post_interaction_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.shared_state = {"session_outcomes": ["like", "comment"]}
ctx.device = MagicMock()
ctx.post_data = {"id": "123"}

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.profile_guard import ProfileGuardPlugin
@@ -13,7 +12,7 @@ def profile_guard_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.ignore_close_friends = True
ctx.configs.args.visual_vibe_check_percentage = 0

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.profile_visit import ProfileVisitPlugin
@@ -13,56 +12,45 @@ def profile_visit_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.interact_percentage = 100
ctx.configs.args.profile_visit_percentage = 30
ctx.configs.get_plugin_config.return_value = {"percentage": 30}
ctx.shared_state = {"res_score": 1.0}
ctx.context_xml = "<xml></xml>"
ctx.device = MagicMock()
ctx.username = "test_user"
mock_nav = MagicMock()
mock_nav.current_state = "HomeFeed"
ctx.cognitive_stack = {"nav_graph": mock_nav}
return ctx
class TestProfileVisitPlugin:
def test_can_activate_enabled(self, profile_visit_plugin, mock_context):
@patch("GramAddict.core.behaviors.profile_visit.random.random", return_value=0.1) # 0.1 < 0.3
def test_can_activate_enabled(self, mock_random, profile_visit_plugin, mock_context):
assert profile_visit_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, profile_visit_plugin, mock_context):
mock_context.configs.args.interact_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert profile_visit_plugin.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.profile_visit.random.random", return_value=0.1) # 0.1 < (1.0 * 0.3)
@patch("GramAddict.core.behaviors.profile_visit.TelepathicEngine")
@patch("GramAddict.core.behaviors.profile_visit.sleep")
def test_execute_success(self, mock_sleep, mock_telepathic, mock_random, profile_visit_plugin, mock_context):
mock_tele = MagicMock()
@patch("GramAddict.core.behaviors.PluginRegistry")
def test_execute_success(self, mock_registry, mock_sleep, profile_visit_plugin, mock_context):
mock_nav = mock_context.cognitive_stack["nav_graph"]
mock_nav.do.return_value = True
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Post username":
return {"x": 50, "y": 60}
return None
mock_registry_instance = MagicMock()
mock_registry.get_instance.return_value = mock_registry_instance
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
from GramAddict.core.behaviors import BehaviorResult
mock_registry_instance.execute_all.return_value = [BehaviorResult(executed=True)]
result = profile_visit_plugin.execute(mock_context)
assert result.executed is True
assert result.interactions == 1
# Check that it clicked the username
mock_context.device.click.assert_called_once_with(50, 60)
# Check that it backed out
mock_context.device.press.assert_called_once_with("back")
@patch("GramAddict.core.behaviors.profile_visit.random.random", return_value=0.9) # 0.9 > (1.0 * 0.3)
@patch("GramAddict.core.behaviors.profile_visit.TelepathicEngine")
def test_execute_skip_due_to_chance(self, mock_telepathic, mock_random, profile_visit_plugin, mock_context):
mock_tele = MagicMock()
mock_tele.find_best_node.return_value = {"x": 50, "y": 60}
mock_telepathic.get_instance.return_value = mock_tele
result = profile_visit_plugin.execute(mock_context)
assert result.executed is False
mock_context.device.click.assert_not_called()
mock_nav.do.assert_any_call("tap post username")
mock_context.device.press.assert_called_with("back")

View File

@@ -25,32 +25,30 @@ def mock_context():
class TestRabbitHolePlugin:
@patch("GramAddict.core.behaviors.rabbit_hole.humanized_scroll")
@patch("GramAddict.core.behaviors.rabbit_hole.sleep")
def test_execute_success(self, mock_sleep, mock_scroll, rabbit_hole, mock_context):
def test_execute_success(self, mock_sleep, rabbit_hole, mock_context):
mock_context.cognitive_stack["nav_graph"].do.return_value = True
with patch("GramAddict.core.behaviors.rabbit_hole.random.random", return_value=0.0):
result = rabbit_hole.execute(mock_context)
result = rabbit_hole.execute(mock_context)
assert result.executed is True
mock_context.cognitive_stack["nav_graph"].do.assert_called_once_with("tap post username")
mock_scroll.assert_called_once_with(mock_context.device, is_skip=True)
mock_context.device.press.assert_called_once_with("back")
assert mock_sleep.call_count == 3
assert mock_sleep.call_count == 2
def test_execute_low_resonance(self, rabbit_hole, mock_context):
def test_can_activate_low_resonance(self, rabbit_hole, mock_context):
mock_context.shared_state["res_score"] = 0.5
with patch("GramAddict.core.behaviors.rabbit_hole.random.random", return_value=0.0):
result = rabbit_hole.execute(mock_context)
assert rabbit_hole.can_activate(mock_context) is False
assert result.executed is False
def test_can_activate_success(self, rabbit_hole, mock_context):
mock_context.configs.get_plugin_config.return_value = {"percentage": 100}
with patch("GramAddict.core.behaviors.rabbit_hole.random.random", return_value=0.0):
assert rabbit_hole.can_activate(mock_context) is True
def test_execute_no_nav_graph(self, rabbit_hole, mock_context):
mock_context.cognitive_stack.pop("nav_graph")
with patch("GramAddict.core.behaviors.rabbit_hole.random.random", return_value=0.0):
result = rabbit_hole.execute(mock_context)
result = rabbit_hole.execute(mock_context)
assert result.executed is True # Did the random chance, but couldn't execute nav_graph
assert result.executed is False # Fails to execute if no nav_graph

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.repost import RepostPlugin
@@ -13,82 +12,39 @@ def repost_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.interact_percentage = 100
ctx.configs.get_plugin_config.return_value = {"percentage": 100}
ctx.shared_state = {"res_score": 1.0}
ctx.context_xml = "<xml></xml>"
ctx.device = MagicMock()
mock_nav = MagicMock()
mock_nav.do.return_value = True
ctx.cognitive_stack = {"nav_graph": mock_nav}
return ctx
class TestRepostPlugin:
def test_can_activate_enabled(self, repost_plugin, mock_context):
@patch("GramAddict.core.behaviors.repost.random.random", return_value=0.1)
def test_can_activate_enabled(self, mock_random, repost_plugin, mock_context):
assert repost_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, repost_plugin, mock_context):
mock_context.configs.args.interact_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert repost_plugin.can_activate(mock_context) is False
@patch("GramAddict.core.behaviors.repost.random.random", return_value=0.1) # 0.1 < (1.0 * 0.2)
@patch("GramAddict.core.behaviors.repost.TelepathicEngine")
@patch("GramAddict.core.behaviors.repost.sleep")
def test_execute_success(self, mock_sleep, mock_telepathic, mock_random, repost_plugin, mock_context):
mock_tele = MagicMock()
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Share button":
return {"x": 50, "y": 60}
elif intent_description == "Add to story button":
return {"x": 100, "y": 100}
elif intent_description == "Share story button":
return {"x": 200, "y": 300}
return None
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
def test_execute_success(self, repost_plugin, mock_context):
result = repost_plugin.execute(mock_context)
assert result.executed is True
assert result.interactions == 1
# Check that it clicked the share button
mock_context.device.click.assert_any_call(50, 60)
# Check that it clicked add to story
mock_context.device.click.assert_any_call(100, 100)
# Check that it clicked share story
mock_context.device.click.assert_any_call(200, 300)
mock_context.cognitive_stack["nav_graph"].do.assert_called_once_with("share to story")
@patch("GramAddict.core.behaviors.repost.random.random", return_value=0.1)
@patch("GramAddict.core.behaviors.repost.TelepathicEngine")
@patch("GramAddict.core.behaviors.repost.sleep")
def test_execute_fails_no_add_to_story(self, mock_sleep, mock_telepathic, mock_random, repost_plugin, mock_context):
mock_tele = MagicMock()
def mock_find_best_node(xml, intent_description, **kwargs):
if intent_description == "Share button":
return {"x": 50, "y": 60}
return None
mock_tele.find_best_node.side_effect = mock_find_best_node
mock_telepathic.get_instance.return_value = mock_tele
result = repost_plugin.execute(mock_context)
# Did not find add to story, so didn't execute fully
assert result.executed is False
mock_context.device.click.assert_called_once_with(50, 60)
mock_context.device.press.assert_called_once_with("back")
@patch("GramAddict.core.behaviors.repost.random.random", return_value=0.9) # 0.9 > (1.0 * 0.2)
@patch("GramAddict.core.behaviors.repost.TelepathicEngine")
def test_execute_skip_due_to_chance(self, mock_telepathic, mock_random, repost_plugin, mock_context):
mock_tele = MagicMock()
mock_tele.find_best_node.return_value = {"x": 50, "y": 60}
mock_telepathic.get_instance.return_value = mock_tele
def test_execute_fails_no_add_to_story(self, repost_plugin, mock_context):
mock_context.cognitive_stack["nav_graph"].do.return_value = False
result = repost_plugin.execute(mock_context)
assert result.executed is False
mock_context.device.click.assert_not_called()

View File

@@ -2,7 +2,6 @@ from unittest.mock import MagicMock, patch
import pytest
from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.story_view import StoryViewPlugin
@@ -13,10 +12,9 @@ def story_view_plugin():
@pytest.fixture
def mock_context():
ctx = MagicMock(spec=BehaviorContext)
ctx = MagicMock()
ctx.configs = MagicMock()
ctx.configs.args.stories_percentage = 100
ctx.configs.args.stories_count = "2-2"
ctx.configs.get_plugin_config.return_value = {"percentage": 100, "count": "2-2"}
ctx.context_xml = '<xml><node content-desc="reel_ring"/></xml>'
ctx.device = MagicMock()
@@ -24,6 +22,8 @@ def mock_context():
ctx.device.dump_hierarchy.return_value = '<xml><node content-desc="reel_ring"/></xml>'
ctx.username = "test_user"
ctx.sleep_mod = 1.0
ctx.cognitive_stack = {}
return ctx
@@ -32,7 +32,7 @@ class TestStoryViewPlugin:
assert story_view_plugin.can_activate(mock_context) is True
def test_can_activate_disabled_via_config(self, story_view_plugin, mock_context):
mock_context.configs.args.stories_percentage = 0
mock_context.configs.get_plugin_config.return_value = {"percentage": 0}
assert story_view_plugin.can_activate(mock_context) is False
@patch("random.random", return_value=0.1)

View File

@@ -12,54 +12,42 @@ def test_bot_flow_unlearns_on_context_loss():
session_state = MagicMock()
# We will patch SituationalAwarenessEngine
with patch("GramAddict.core.situational_awareness.SituationalAwarenessEngine") as MockSAE:
# We need mock SAE to return OBSTACLE_MODAL to trigger the first condition
# Wait, the code has two paths: `has_obstacle` or `not has_feed_markers`.
# If we return `False` for has_feed_markers, it hits the second path.
with (
patch("GramAddict.core.behaviors.PluginRegistry.get_instance") as MockRegistry,
patch("GramAddict.core.bot_flow.sleep"),
patch("GramAddict.core.physics.humanized_input.humanized_scroll"),
patch("GramAddict.core.bot_flow._humanized_scroll"),
):
from GramAddict.core.behaviors import BehaviorResult
mock_sae_instance = MockSAE.return_value
# perceive needs to return something that is not OBSTACLE_MODAL so we hit the feed markers path
mock_sae_instance.perceive.return_value = "EXPLORE_GRID"
mock_registry_instance = MockRegistry.return_value
mock_registry_instance.execute_all.return_value = [
BehaviorResult(executed=True, should_skip=True, metadata={"return_code": "CONTEXT_LOST"})
]
# Act: _run_zero_latency_feed_loop runs a loop.
# Since has_feed_markers is always False, it will increment misses 3 times and return "CONTEXT_LOST".
# We also need to mock TelepathicEngine so it doesn't crash on misses == 2.
with (
patch("GramAddict.core.bot_flow.TelepathicEngine") as MockTelepathic,
patch("GramAddict.core.bot_flow.dump_ui_state"),
):
mock_telepathic_instance = MockTelepathic.get_instance.return_value
mock_telepathic_instance.find_best_node.return_value = None
mock_telepathic_instance._extract_semantic_nodes.return_value = [MagicMock()]
mock_cognitive_stack = MagicMock()
dopamine_mock = MagicMock()
dopamine_mock.is_app_session_over.return_value = False
dopamine_mock.wants_to_doomscroll.return_value = False
mock_cognitive_stack = MagicMock()
dopamine_mock = MagicMock()
dopamine_mock.is_app_session_over.return_value = False
dopamine_mock.wants_to_doomscroll.return_value = False
def stack_get(key):
if key == "radome":
return None
elif key == "dopamine":
return dopamine_mock
return MagicMock()
def stack_get(key):
if key == "radome":
return None
elif key == "dopamine":
return dopamine_mock
return MagicMock()
mock_cognitive_stack.get.side_effect = stack_get
mock_cognitive_stack.get.side_effect = stack_get
result = _run_zero_latency_feed_loop(
device=device,
zero_engine=MagicMock(),
nav_graph=MagicMock(),
configs=MagicMock(),
session_state=session_state,
job_target="test_feed",
cognitive_stack=mock_cognitive_stack,
)
with patch("GramAddict.core.bot_flow.is_ad", return_value=False):
result = _run_zero_latency_feed_loop(
device=device,
zero_engine=MagicMock(),
nav_graph=MagicMock(),
configs=MagicMock(),
session_state=session_state,
job_target="test_feed",
cognitive_stack=mock_cognitive_stack,
)
# Assert (RED)
assert result == "CONTEXT_LOST"
# SAE should have been told to unlearn the current state because of context loss
mock_sae_instance.unlearn_current_state.assert_called_with("<hierarchy></hierarchy>")
# Assert (RED)
assert result == "CONTEXT_LOST"

View File

@@ -8,10 +8,9 @@ from GramAddict.core.bot_flow import _run_zero_latency_feed_loop
@patch("GramAddict.core.bot_flow._humanized_scroll")
@patch("GramAddict.core.bot_flow._extract_post_content")
@patch("GramAddict.core.bot_flow._align_active_post")
@patch("GramAddict.core.bot_flow.is_ad")
@patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance")
def test_plugin_skip_breaks_feed_loop(
mock_telepathic, mock_ad, mock_align, mock_extract, mock_scroll, mock_sleep, mock_registry_get_instance
mock_telepathic, mock_align, mock_extract, mock_scroll, mock_sleep, mock_registry_get_instance
):
# Setup mocks
device = MagicMock()
@@ -31,7 +30,6 @@ def test_plugin_skip_breaks_feed_loop(
# Dopamine should not abort the session on first run, but abort on second
cognitive_stack["dopamine"].is_app_session_over.side_effect = [False, True]
mock_ad.return_value = False
mock_align.return_value = False
device.dump_hierarchy.return_value = "<xml>row_feed_photo_profile_name</xml>"