test(e2e): eliminate all legacy mocks and establish real-world sim suite
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>"
|
||||
|
||||
Reference in New Issue
Block a user