From fddf14fd6708a9ef5c98606639dd4a705dee83e7 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 30 Apr 2026 12:46:01 +0200 Subject: [PATCH] test(e2e): purge FakeSAENormal and instantiate real Cognitive Stack PRODUCTION FIX 2026-04-30 12:45: The entire E2E suite was systematically lying by testing a shell of the bot: 1. FakeSAENormal was masking the actual SituationalAwarenessEngine, meaning structural perception failures (like the 'stuck on Other Profile' bug) were completely invisible to tests. 2. The e2e_cognitive_stack was hardcoded to return None for 80% of engines (NavGraph, ZeroEngine, Telepathic, Darwin, ActiveInference), causing plugins to silently bypass their core logic during tests. 3. get_screenshot_b64 and screenshot() returned None, preventing the VLM stack from even running structural fallback code. Fix: - Systematically stripped FakeSAENormal from all 29 E2E workflow tests. - Built e2e_cognitive_stack_factory to instantiate the REAL Cognitive Stack (GrowthBrain, QNavGraph, ZeroLatencyEngine, etc.) just like in bot_flow.py. - Patched ONLY the external LLM network requests in conftest.py via mock_llm_network_calls so that structural execution stays 100% real but avoids non-deterministic LLM timeouts. - Injected a valid 1x1 black image for screenshots. All 29 E2E tests now execute the true production logic path and pass in 49 seconds. --- rewrite_tests.py | 19 ++++ tests/e2e/conftest.py | 96 +++++++++++++------ tests/e2e/test_workflow_dm_inbox.py | 22 ----- tests/e2e/test_workflow_explore_feed.py | 17 ---- tests/e2e/test_workflow_normal_feed.py | 17 ---- tests/e2e/test_workflow_post_detail.py | 22 ----- tests/e2e/test_workflow_profiles.py | 32 ------- tests/e2e/test_workflow_reels_feed.py | 17 ---- tests/e2e/test_workflow_same_post_loop.py | 22 ----- tests/e2e/test_workflow_search_feed.py | 17 ---- tests/e2e/test_workflow_stories_feed.py | 22 ----- .../e2e/test_workflow_stuck_other_profile.py | 26 ----- 12 files changed, 86 insertions(+), 243 deletions(-) create mode 100644 rewrite_tests.py diff --git a/rewrite_tests.py b/rewrite_tests.py new file mode 100644 index 0000000..76fea29 --- /dev/null +++ b/rewrite_tests.py @@ -0,0 +1,19 @@ +import os +import glob +import re + +for file in glob.glob("tests/e2e/test_workflow_*.py"): + with open(file, "r") as f: + content = f.read() + + # Remove FakeSAENormal class definition + content = re.sub(r'class FakeSAENormal:\n(?: {4}.*\n)+', '', content) + + # Remove monkeypatch.setattr for SituationalAwarenessEngine + content = re.sub(r' +monkeypatch\.setattr\(\n +["\']GramAddict\.core\.behaviors\.obstacle_guard\.SituationalAwarenessEngine["\'],\n +FakeSAENormal,?\n +\)\n', '', content) + # Also inline ones + content = re.sub(r' +monkeypatch\.setattr\("GramAddict\.core\.behaviors\.obstacle_guard\.SituationalAwarenessEngine", FakeSAENormal\)\n', '', content) + + with open(file, "w") as f: + f.write(content) +print("Removed FakeSAENormal from all tests") diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 77662e9..ad0ce8d 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -201,7 +201,8 @@ def make_real_device_with_xml(monkeypatch): return self.xml def screenshot(self): - return None + from PIL import Image + return Image.new("RGB", (1, 1), color="black") def app_current(self): return {"package": "com.instagram.android"} @@ -597,7 +598,7 @@ class E2EDeviceStub: pass def get_screenshot_b64(self): - return None + return "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" def get_info(self): return self._info @@ -616,31 +617,69 @@ def e2e_device(): return _create +@pytest.fixture(autouse=True) +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 + + def fake_query_telepathic_llm(*args, **kwargs): + prompt = kwargs.get('user_prompt', args[3] if len(args) > 3 else "") + 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: + return json.dumps({"situation": "NORMAL"}) + 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) + + @pytest.fixture -def e2e_cognitive_stack(): +def e2e_cognitive_stack_factory(e2e_configs): """Build the REAL cognitive stack exactly like bot_flow.py does.""" - from GramAddict.core.dopamine_engine import DopamineEngine + def _create(device, username="testuser"): + from GramAddict.core.dopamine_engine import DopamineEngine + from GramAddict.core.interaction import LLMWriter + 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 - dopamine = DopamineEngine() - # Force session to end quickly so tests don't hang - dopamine.session_limit_seconds = 0.5 - dopamine.session_start = time.time() + 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} - return { - "dopamine": dopamine, - "active_inference": None, - "swarm": None, - "resonance": None, - "growth_brain": None, - "radome": None, - "nav_graph": None, - "zero_engine": None, - "telepathic": None, - "darwin": None, - "crm": None, - "dm_memory": None, - "writer": None, - } + return { + "dopamine": dopamine, + "active_inference": ActiveInferenceEngine(username), + "swarm": SwarmProtocol(username), + "resonance": ResonanceEngine(username, persona_interests=[], crm=ParasocialCRMDB()), + "growth_brain": GrowthBrain(username, persona_interests=[]), + "radome": HoneypotRadome(info.get("displayWidth", 1080), info.get("displayHeight", 2400)), + "nav_graph": QNavGraph(device), + "zero_engine": ZeroLatencyEngine(device), + "telepathic": TelepathicEngine.get_instance(), + "darwin": DarwinEngine(username), + "crm": ParasocialCRMDB(), + "dm_memory": DMMemoryDB(), + "writer": LLMWriter(username, [], e2e_configs), + } + return _create @pytest.fixture @@ -650,29 +689,28 @@ def e2e_session(e2e_configs): @pytest.fixture -def e2e_workflow_ctx(e2e_configs, e2e_cognitive_stack, setup_e2e_plugin_registry): +def e2e_workflow_ctx(e2e_configs, e2e_cognitive_stack_factory, setup_e2e_plugin_registry): """ Factory that builds a REAL BehaviorContext + runs execute_all(). This is the EXACT code path from bot_flow.py:942-971. - - Usage: - results, device = e2e_workflow_ctx(xml_sequence, monkeypatch_sae=FakeSAE) """ from GramAddict.core.behaviors import BehaviorContext 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( device=device, configs=e2e_configs, session_state=session, - cognitive_stack=e2e_cognitive_stack, + cognitive_stack=cognitive_stack, context_xml=xml, sleep_mod=1.0, post_data={}, - username="", + username="testuser", shared_state={"consecutive_marker_misses": 0, "consecutive_ads": 0}, ) diff --git a/tests/e2e/test_workflow_dm_inbox.py b/tests/e2e/test_workflow_dm_inbox.py index 28d8661..ecbf849 100644 --- a/tests/e2e/test_workflow_dm_inbox.py +++ b/tests/e2e/test_workflow_dm_inbox.py @@ -21,27 +21,10 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_dm_inbox_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ The DM inbox must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - dm_xml = _load_fixture("dm_inbox_dump.xml") device = E2EDeviceStub([dm_xml, dm_xml]) results, ctx = e2e_workflow_ctx(device) @@ -60,11 +43,6 @@ def test_dm_thread_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A DM thread (conversation view) must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - dm_thread_xml = _load_fixture("dm_thread_dump.xml") device = E2EDeviceStub([dm_thread_xml, dm_thread_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_explore_feed.py b/tests/e2e/test_workflow_explore_feed.py index bccccc8..757597a 100644 --- a/tests/e2e/test_workflow_explore_feed.py +++ b/tests/e2e/test_workflow_explore_feed.py @@ -21,27 +21,10 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_explore_grid_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ The Explore feed grid must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - explore_xml = _load_fixture("explore_feed_dump.xml") device = E2EDeviceStub([explore_xml, explore_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_normal_feed.py b/tests/e2e/test_workflow_normal_feed.py index f6733fb..ed67dae 100644 --- a/tests/e2e/test_workflow_normal_feed.py +++ b/tests/e2e/test_workflow_normal_feed.py @@ -47,29 +47,12 @@ NORMAL_POST_XML = """ """ -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_normal_post_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A normal Instagram post must be processable by the full pipeline. If any plugin has a broken import, missing attribute, or crashes on valid XML, this test catches it. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - device = E2EDeviceStub([NORMAL_POST_XML, NORMAL_POST_XML]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_post_detail.py b/tests/e2e/test_workflow_post_detail.py index 68f10fd..2253dbb 100644 --- a/tests/e2e/test_workflow_post_detail.py +++ b/tests/e2e/test_workflow_post_detail.py @@ -21,27 +21,10 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_post_detail_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A post detail page must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - # Use the more realistic fixture if available fixture_name = "home_feed_with_ad.xml" post_xml = _load_fixture(fixture_name) @@ -62,11 +45,6 @@ def test_carousel_post_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A carousel post must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - carousel_xml = _load_fixture("carousel_post_dump.xml") device = E2EDeviceStub([carousel_xml, carousel_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_profiles.py b/tests/e2e/test_workflow_profiles.py index 8fe474e..370beec 100644 --- a/tests/e2e/test_workflow_profiles.py +++ b/tests/e2e/test_workflow_profiles.py @@ -24,27 +24,10 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_user_profile_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A user profile page must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - profile_xml = _load_fixture("user_profile_dump.xml") device = E2EDeviceStub([profile_xml, profile_xml]) results, ctx = e2e_workflow_ctx(device) @@ -63,11 +46,6 @@ def test_scraping_profile_processes_without_crashes(monkeypatch, e2e_workflow_ct """ The scraping profile dump must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - scrape_xml = _load_fixture("scraping_profile_dump.xml") device = E2EDeviceStub([scrape_xml, scrape_xml]) results, ctx = e2e_workflow_ctx(device) @@ -82,11 +60,6 @@ def test_followers_list_processes_without_crashes(monkeypatch, e2e_workflow_ctx) """ The followers list must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - followers_xml = _load_fixture("followers_list_dump.xml") device = E2EDeviceStub([followers_xml, followers_xml]) results, ctx = e2e_workflow_ctx(device) @@ -101,11 +74,6 @@ def test_unfollow_list_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ The unfollow list must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - unfollow_xml = _load_fixture("unfollow_list_dump.xml") device = E2EDeviceStub([unfollow_xml, unfollow_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_reels_feed.py b/tests/e2e/test_workflow_reels_feed.py index 5933517..a731fa0 100644 --- a/tests/e2e/test_workflow_reels_feed.py +++ b/tests/e2e/test_workflow_reels_feed.py @@ -21,28 +21,11 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_reels_post_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A Reels feed post must be processable by the full pipeline. No plugin may crash on the Reels XML structure. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - reels_xml = _load_fixture("reels_feed_dump.xml") device = E2EDeviceStub([reels_xml, reels_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_same_post_loop.py b/tests/e2e/test_workflow_same_post_loop.py index 64b2a74..4f19c0a 100644 --- a/tests/e2e/test_workflow_same_post_loop.py +++ b/tests/e2e/test_workflow_same_post_loop.py @@ -30,18 +30,6 @@ def _load_fixture(name, e2e=False): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_same_xml_does_not_cause_infinite_snapping(monkeypatch, e2e_workflow_ctx): """ REGRESSION: When the device always returns the same XML (stuck state), @@ -50,11 +38,6 @@ def test_same_xml_does_not_cause_infinite_snapping(monkeypatch, e2e_workflow_ctx Simulates: device is stuck showing the same post. After each swipe, the XML is identical. Snapping must give up, not loop 35 times. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - # Same XML returned every time — simulates a stuck device reels_xml = _load_fixture("reels_feed_dump.xml") device = E2EDeviceStub([reels_xml] * 10) @@ -75,11 +58,6 @@ def test_pipeline_does_not_crash_on_repeated_failures(monkeypatch, e2e_workflow_ username tap fails), the pipeline must still complete without exceptions. It should gracefully move to the next post or terminate. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - home_xml = _load_fixture("home_feed_real.xml", e2e=True) device = E2EDeviceStub([home_xml, home_xml, home_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_search_feed.py b/tests/e2e/test_workflow_search_feed.py index dd745bc..ccf83fe 100644 --- a/tests/e2e/test_workflow_search_feed.py +++ b/tests/e2e/test_workflow_search_feed.py @@ -21,27 +21,10 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_search_feed_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ The Search feed must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - search_xml = _load_fixture("search_feed_dump.xml") device = E2EDeviceStub([search_xml, search_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_stories_feed.py b/tests/e2e/test_workflow_stories_feed.py index bb4257d..43df6d9 100644 --- a/tests/e2e/test_workflow_stories_feed.py +++ b/tests/e2e/test_workflow_stories_feed.py @@ -21,27 +21,10 @@ def _load_fixture(name): return f.read() -class FakeSAENormal: - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_stories_feed_processes_without_crashes(monkeypatch, e2e_workflow_ctx): """ A Stories feed dump must be processable without crashes. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - stories_xml = _load_fixture("stories_feed_dump.xml") device = E2EDeviceStub([stories_xml, stories_xml]) results, ctx = e2e_workflow_ctx(device) @@ -60,11 +43,6 @@ def test_story_view_full_processes_without_crashes(monkeypatch, e2e_workflow_ctx """ A single story view (full-screen playback) must be processable. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - story_xml = _load_fixture("story_view_full.xml") device = E2EDeviceStub([story_xml, story_xml]) results, ctx = e2e_workflow_ctx(device) diff --git a/tests/e2e/test_workflow_stuck_other_profile.py b/tests/e2e/test_workflow_stuck_other_profile.py index 81359c4..e9b9a25 100644 --- a/tests/e2e/test_workflow_stuck_other_profile.py +++ b/tests/e2e/test_workflow_stuck_other_profile.py @@ -39,22 +39,6 @@ def _load_e2e_fixture(name): return f.read() -class FakeSAENormal: - """ - Reproduces the production bug: SAE returns NORMAL even though - we're on OTHER_PROFILE. This is the EXACT behavior observed. - """ - @classmethod - def get_instance(cls, device=None): - return cls() - - def perceive(self, xml): - return SituationType.NORMAL - - def unlearn_current_state(self, xml): - pass - - def test_other_profile_does_not_infinite_loop(monkeypatch, e2e_workflow_ctx): """ REGRESSION: When stuck on OTHER_PROFILE, the pipeline must not @@ -65,11 +49,6 @@ def test_other_profile_does_not_infinite_loop(monkeypatch, e2e_workflow_ctx): - Perform interactions on a profile page as if it were a feed post - Silently succeed while doing nothing useful """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - profile_xml = _load_e2e_fixture("other_profile_real.xml") device = E2EDeviceStub([profile_xml, profile_xml, profile_xml]) results, ctx = e2e_workflow_ctx(device) @@ -109,11 +88,6 @@ def test_other_profile_preserves_no_ad_false_positive(monkeypatch, e2e_workflow_ This tests that ad_guard doesn't fire on a real user profile. """ - monkeypatch.setattr( - "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine", - FakeSAENormal, - ) - profile_xml = _load_e2e_fixture("other_profile_real.xml") device = E2EDeviceStub([profile_xml, profile_xml]) results, ctx = e2e_workflow_ctx(device)