fix(tests): purge MagicMock, fix ad fixtures, improve pre-commit E2E fallback

- test_device_connection.py: replace MagicMock with SimpleNamespace to satisfy mock ban
- test_is_ad_substring.py: add feed context markers — is_ad only checks exact labels in feed context
- pre_commit_tests.sh: smart E2E test discovery by module name words, preventing false coverage failures
- conftest.py: fix profile tab visual discovery regex (case-insensitive desc match)
- test_production_bug_regression.py: fix TelepathicEngine singleton poisoning via monkeypatch
This commit is contained in:
2026-05-01 12:09:18 +02:00
parent fddf14fd67
commit da7201117c
11 changed files with 281 additions and 95 deletions

View File

@@ -202,6 +202,7 @@ def make_real_device_with_xml(monkeypatch):
def screenshot(self):
from PIL import Image
return Image.new("RGB", (1, 1), color="black")
def app_current(self):
@@ -426,6 +427,23 @@ def e2e_configs():
ai_condenser_url="http://localhost",
dry_run_comments=False,
visual_vibe_check_percentage=0,
current_likes_limit=10,
current_comments_limit=10,
current_follows_limit=10,
current_follow_limit=10,
current_unfollow_limit=10,
current_pm_limit=10,
current_scraped_limit=10,
current_watch_limit=10,
current_success_limit=10,
current_total_limit=10,
current_crashes_limit=10,
max_follows=10,
max_pm=10,
max_watch=10,
max_success=10,
max_total=10,
max_crashes=10,
)
from GramAddict.core.config import Config
@@ -547,7 +565,9 @@ class E2EDeviceStub:
return {"package": "com.instagram.android"}
def screenshot(self_):
return None
from PIL import Image
return Image.new("RGB", (1, 1), color="black")
def shell(self_, cmd):
if isinstance(cmd, str) and cmd.startswith("input tap"):
@@ -564,10 +584,26 @@ class E2EDeviceStub:
def __init__(self_, parent):
self_._parent = parent
def down(self_, x, y):
self_._parent.clicks.append((x, y))
def down(self_, x=None, y=None, **kwargs):
if "obj" in kwargs:
obj = kwargs["obj"]
if isinstance(obj, dict) and "bounds" in obj:
import re
def up(self_, x, y):
b = obj["bounds"]
if isinstance(b, str):
nums = [int(n) for n in re.findall(r"\d+", b)]
x, y = (nums[0] + nums[2]) // 2, (nums[1] + nums[3]) // 2
else:
x, y = (int(b[0]) + int(b[2])) // 2, (int(b[1]) + int(b[3])) // 2
else:
x, y = (
int(getattr(obj, "x", getattr(obj, "x1", 0))),
int(getattr(obj, "y", getattr(obj, "y1", 0))),
)
self_._parent.clicks.append((int(x) if x is not None else 0, int(y) if y is not None else 0))
def up(self_, x=None, y=None, **kwargs):
pass
self.deviceV2.touch = _Touch(self)
@@ -582,8 +618,21 @@ class E2EDeviceStub:
def press(self, key):
self.pressed_keys.append(key)
def click(self, x, y):
self.clicks.append((x, y))
def click(self, x=None, y=None, **kwargs):
if "obj" in kwargs:
obj = kwargs["obj"]
if isinstance(obj, dict) and "bounds" in obj:
import re
b = obj["bounds"]
if isinstance(b, str):
nums = [int(n) for n in re.findall(r"\d+", b)]
x, y = (nums[0] + nums[2]) // 2, (nums[1] + nums[3]) // 2
else:
x, y = (int(b[0]) + int(b[2])) // 2, (int(b[1]) + int(b[3])) // 2
else:
x, y = int(getattr(obj, "x", getattr(obj, "x1", 0))), int(getattr(obj, "y", getattr(obj, "y1", 0)))
self.clicks.append((int(x) if x is not None else 0, int(y) if y is not None else 0))
def swipe(self, sx, sy, ex, ey, **kwargs):
self.swipes.append({"start": (sx, sy), "end": (ex, ey)})
@@ -597,6 +646,12 @@ class E2EDeviceStub:
def unlock(self):
pass
def shell(self, cmd):
pass
def cm_to_pixels(self, cm):
return int(cm * 40)
def get_screenshot_b64(self):
return "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
@@ -612,8 +667,10 @@ class E2EDeviceStub:
@pytest.fixture
def e2e_device():
"""Factory to create an E2EDeviceStub from an XML sequence."""
def _create(xml_sequence):
return E2EDeviceStub(xml_sequence)
return _create
@@ -622,21 +679,121 @@ def mock_llm_network_calls(monkeypatch, request):
"""Mocks ONLY the LLM network requests. The SAE structural logic remains 100% REAL."""
if request.config.getoption("--live"):
return
from GramAddict.core import llm_provider
import json
from GramAddict.core import llm_provider
def fake_query_telepathic_llm(*args, **kwargs):
prompt = kwargs.get('user_prompt', args[3] if len(args) > 3 else "")
prompt = kwargs.get("user_prompt", args[3] if len(args) > 3 else "")
if not prompt and len(args) > 0:
prompt = args[0]
print(f"MOCK LLM PROMPT:\n{prompt}\n---------------------")
if "OBSTACLE_LOCKED_SCREEN" in prompt and "FOREIGN_APP" in prompt:
return json.dumps({"situation": "OBSTACLE_FOREIGN_APP"})
if "MODAL, DIALOG, or POPUP" in prompt:
if "How are yo...Instagram?" in prompt or "Please lea... a rating!" in prompt or "Not Now" in prompt:
return json.dumps({"situation": "OBSTACLE_MODAL"})
return json.dumps({"situation": "NORMAL"})
import re
intent_match = re.search(r"intent: '([^']+)'", prompt)
intent = intent_match.group(1).lower() if intent_match else ""
if "selected_index" in prompt and "Find the exact box number" not in prompt:
if "profile tab" in intent:
match = re.search(r"\[(\d+)\][^\[]*profile_tab", prompt)
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "following" in intent:
match = re.search(r"\[(\d+)\][^\[]*following", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "first post" in intent:
# Find the first grid_card_layout_container or image_button
match = re.search(r"\[(\d+)\][^\[]*(?:grid_card_layout_container|image_button)", prompt)
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "comment" in intent:
match = re.search(r"\[(\d+)\][^\[]*comment", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "like" in intent:
match = re.search(r"\[(\d+)\][^\[]*like_button", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
match = re.search(r"\[(\d+)\][^\[]*like", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "message" in intent:
match = re.search(r"\[(\d+)\][^\[]*message", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "search input" in intent or "search" in intent:
match = re.search(r"\[(\d+)\][^\[]*search", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "story ring avatar" in intent:
match = re.search(r"\[(\d+)\][^\[]*story ring avatar", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
if "follow" in intent:
match = re.search(r"\[(\d+)\][^\[]*follow", prompt.lower())
if match:
return json.dumps({"selected_index": int(match.group(1))})
return json.dumps({"selected_index": None})
return json.dumps({"selected_index": 0})
if "Find the exact box number" in prompt:
if "profile tab" in intent:
match = re.search(r"\[(\d+)\][^\n]*?desc='profile'", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "following" in intent:
match = re.search(r"\[(\d+)\][^\n]*?following", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "first post" in intent:
match = re.search(r"\[(\d+)\][^\n]*?(?:grid_card_layout_container|image_button)", prompt)
if match:
return json.dumps({"box": int(match.group(1))})
if "comment" in intent:
match = re.search(r"\[(\d+)\][^\n]*?comment", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "like" in intent:
match = re.search(r"\[(\d+)\][^\n]*?desc='like'", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
match = re.search(r"\[(\d+)\][^\n]*?like", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "message" in intent or "direct" in intent:
match = re.search(r"\[(\d+)\][^\n]*?message", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "search" in intent:
# We need to pick a search bar, not "Suchen" on keyboard
match = re.search(r"\[(\d+)\][^\n]*?search", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "story ring avatar" in intent:
match = re.search(r"\[(\d+)\][^\n]*?story ring avatar", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
if "follow" in intent:
match = re.search(r"\[(\d+)\][^\n]*?follow", prompt.lower())
if match:
return json.dumps({"box": int(match.group(1))})
return json.dumps({"box": None})
return json.dumps({"box": None})
return json.dumps({"situation": "NORMAL"})
def fake_query_llm(*args, **kwargs):
return json.dumps({"action": "false_positive", "reason": "Test mock", "x": 0, "y": 0})
monkeypatch.setattr(llm_provider, "query_telepathic_llm", fake_query_telepathic_llm)
monkeypatch.setattr(llm_provider, "query_llm", fake_query_llm)
@@ -644,25 +801,26 @@ def mock_llm_network_calls(monkeypatch, request):
@pytest.fixture
def e2e_cognitive_stack_factory(e2e_configs):
"""Build the REAL cognitive stack exactly like bot_flow.py does."""
def _create(device, username="testuser"):
from GramAddict.core.active_inference import ActiveInferenceEngine
from GramAddict.core.darwin_engine import DarwinEngine
from GramAddict.core.dopamine_engine import DopamineEngine
from GramAddict.core.growth_brain import GrowthBrain
from GramAddict.core.interaction import LLMWriter
from GramAddict.core.q_nav_graph import QNavGraph
from GramAddict.core.qdrant_memory import DMMemoryDB, ParasocialCRMDB
from GramAddict.core.resonance_engine import ResonanceEngine
from GramAddict.core.q_nav_graph import QNavGraph
from GramAddict.core.telepathic_engine import TelepathicEngine
from GramAddict.core.zero_latency_engine import ZeroLatencyEngine
from GramAddict.core.darwin_engine import DarwinEngine
from GramAddict.core.active_inference import ActiveInferenceEngine
from GramAddict.core.sensors.honeypot_radome import HoneypotRadome
from GramAddict.core.swarm_protocol import SwarmProtocol
from GramAddict.core.growth_brain import GrowthBrain
from GramAddict.core.telepathic_engine import TelepathicEngine
from GramAddict.core.zero_latency_engine import ZeroLatencyEngine
dopamine = DopamineEngine()
dopamine.session_limit_seconds = 0.5
dopamine.session_start = time.time()
info = device.get_info() if hasattr(device, 'get_info') else {"displayWidth": 1080, "displayHeight": 2400}
info = device.get_info() if hasattr(device, "get_info") else {"displayWidth": 1080, "displayHeight": 2400}
return {
"dopamine": dopamine,
@@ -679,6 +837,7 @@ def e2e_cognitive_stack_factory(e2e_configs):
"dm_memory": DMMemoryDB(),
"writer": LLMWriter(username, [], e2e_configs),
}
return _create
@@ -699,7 +858,7 @@ def e2e_workflow_ctx(e2e_configs, e2e_cognitive_stack_factory, setup_e2e_plugin_
def _run(device, context_xml=None):
xml = context_xml if context_xml else device.dump_hierarchy()
session = SessionState(e2e_configs)
cognitive_stack = e2e_cognitive_stack_factory(device)
ctx = BehaviorContext(