import sys import os import pytest import time from unittest.mock import MagicMock from GramAddict.core import utils # Force Qdrant mocking globally across ALL E2E tests so we never # block on connection refused trying to hit localhost:6344 mock_qdrant = MagicMock() # Setup correct return types for dimension check warnings in qdrant_memory mock_collection = MagicMock() mock_collection.config.params.vectors.size = 768 mock_qdrant.get_collection.return_value = mock_collection sys.modules["qdrant_client"].QdrantClient = MagicMock(return_value=mock_qdrant) @pytest.fixture def e2e_device_dump_injector(): """ Provides a factory to mock device.deviceV2.dump_hierarchy using real XML files. Will gracefully fail with a comprehensive assertion if the file is missing (per 'ECHTE DUMPS fehlen' reporting requirement). """ def _inject_dump(device_mock, xml_filename): fix_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "fixtures") xml_path = os.path.join(fix_dir, xml_filename) if not os.path.exists(xml_path): pytest.fail(f"MISSING REAL DUMP: required XML fixture '{xml_filename}' for full E2E workflow testing could not be found at {xml_path}. FAKE_NOTHING policy implies dropping this test execution until it is captured.", pytrace=False) with open(xml_path, "r") as f: real_xml = f.read() device_mock.deviceV2.dump_hierarchy.return_value = real_xml return real_xml return _inject_dump @pytest.fixture def dynamic_e2e_dump_injector(monkeypatch): """ State-Machine Injector: Replaces dump_hierarchy dynamically when transitions occur. Validates that the Telepathic Engine's pathfinding truly worked. """ def _inject(device_mock, state_map, initial_xml): fix_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "fixtures") def load_xml(filename): path = os.path.join(fix_dir, filename) if not os.path.exists(path): pytest.fail(f"MISSING REAL DUMP: {filename} not found.") with open(path, "r") as f: return f.read() device_mock.deviceV2.dump_hierarchy.return_value = load_xml(initial_xml) from GramAddict.core.q_nav_graph import QNavGraph original_execute = QNavGraph._execute_transition def _mock_execute_transition(nav_self, action, zero_engine): if action == 'tap_post_username': return True # Evaluate using the real internal LLM/Keyword logic against the current mock XML! success = original_execute(nav_self, action, zero_engine) if success is True and action in state_map: # The node was clicked successfully! Swap the XML to the target state. device_mock.deviceV2.dump_hierarchy.return_value = load_xml(state_map[action]) return success monkeypatch.setattr(QNavGraph, "_execute_transition", _mock_execute_transition) return _inject @pytest.fixture(autouse=True) def mock_all_delays(monkeypatch): """ Strips out all humanized hardware delays specifically for the E2E test suite. Ensures loops evaluate instantly using the injected dumps. """ monkeypatch.setattr(time, "sleep", lambda x: None) monkeypatch.setattr(utils, "random_sleep", lambda *args, **kwargs: None) monkeypatch.setattr(utils, "sleep", lambda x: None) # Standardize DarwinEngine across tests to prevent mockup math errors on session end try: from GramAddict.core.darwin_engine import DarwinEngine monkeypatch.setattr(DarwinEngine, "evaluate_session_end", lambda *args, **kwargs: None) except ImportError: pass @pytest.fixture def e2e_configs(): import argparse configs = MagicMock() configs.args = argparse.Namespace( username="testuser", device="emulator-5554", app_id="com.instagram.android", debug=True, feed=None, carousel_percentage=0, carousel_count="1", explore=None, reels=None, stories=None, interact_percentage=0, likes_percentage=0, follow_percentage=0, comment_percentage=0, working_hours=[0.0, 24.0], time_delta_session=0, speed_multiplier=1.0, disable_filters=False, interaction_users_amount="1", scrape_profiles=False, disable_ai_messaging=True, total_unfollows_limit=0, ai_telepathic_url="http://localhost", ai_telepathic_model="llama3", ai_condenser_url="http://localhost", ai_condenser_model="llama3" ) return configs