chore(test): Ruthless deletion of ALL remaining MagicMocks and patches across the entire test suite

This commit is contained in:
2026-04-27 16:50:26 +02:00
parent 746eeb767d
commit a7449a1db3
149 changed files with 1168 additions and 16454 deletions

View File

@@ -1,226 +0,0 @@
"""
Chaos Engineering: Network & Dependency Failure Tests.
Verifies that the bot degrades gracefully when external services
(Qdrant, Ollama, OpenRouter) are unavailable, slow, or return errors.
Tesla's FSD doesn't crash if the map server is unreachable — neither should we.
"""
from unittest.mock import MagicMock, patch
import pytest
from tests.chaos import VALID_FEED_XML
# ──────────────────────────────────────────────────
# Qdrant Failure Tests
# ──────────────────────────────────────────────────
@pytest.mark.chaos
class TestQdrantFailure:
"""Bot must survive total Qdrant outage."""
def test_telepathic_works_without_qdrant(self):
"""TelepathicEngine must still resolve nodes via keyword fast-path when Qdrant is down."""
with (
patch("GramAddict.core.qdrant_memory.QdrantBase.__init__", return_value=None),
patch(
"GramAddict.core.qdrant_memory.QdrantBase.is_connected",
new_callable=lambda: property(lambda self: False),
),
):
from GramAddict.core.telepathic_engine import TelepathicEngine
TelepathicEngine._instance = None
engine = TelepathicEngine.__new__(TelepathicEngine)
engine.__init__()
engine._memory.ui_memory = MagicMock()
engine._memory.ui_memory.is_connected = False
engine._memory.ui_memory.query_closest = MagicMock(return_value=None)
engine.positive_memory = MagicMock()
engine.positive_memory.is_connected = False
engine.positive_memory.recall = MagicMock(return_value=None)
nodes = engine._extract_semantic_nodes(VALID_FEED_XML)
# Should still find clickable nodes via structural parsing
assert len(nodes) > 0
TelepathicEngine._instance = None
def test_sae_recall_returns_none_without_qdrant(self):
"""SAE episodic memory must return None (not crash) when Qdrant is down."""
with (
patch("GramAddict.core.qdrant_memory.QdrantBase.__init__", return_value=None),
patch(
"GramAddict.core.qdrant_memory.QdrantBase.is_connected",
new_callable=lambda: property(lambda self: False),
),
):
from GramAddict.core.situational_awareness import SituationEpisodeDB
db = SituationEpisodeDB()
db._db = MagicMock()
db._db.is_connected = False
result = db.recall("test_situation_signature")
assert result is None
def test_sae_learn_silently_fails_without_qdrant(self):
"""SAE learning must silently skip (not crash) when Qdrant is down."""
with (
patch("GramAddict.core.qdrant_memory.QdrantBase.__init__", return_value=None),
patch(
"GramAddict.core.qdrant_memory.QdrantBase.is_connected",
new_callable=lambda: property(lambda self: False),
),
):
from GramAddict.core.situational_awareness import EscapeAction, SituationEpisodeDB
db = SituationEpisodeDB()
db._db = MagicMock()
db._db.is_connected = False
action = EscapeAction("back", reason="test")
# Must not raise
db.learn("test_signature", action, True)
def test_qdrant_timeout_doesnt_hang_extraction(self):
"""If Qdrant queries time out, node extraction must still complete."""
import time
with (
patch("GramAddict.core.qdrant_memory.QdrantBase.__init__", return_value=None),
patch(
"GramAddict.core.qdrant_memory.QdrantBase.is_connected",
new_callable=lambda: property(lambda self: False),
),
):
from GramAddict.core.telepathic_engine import TelepathicEngine
TelepathicEngine._instance = None
engine = TelepathicEngine.__new__(TelepathicEngine)
engine.__init__()
engine._memory.ui_memory = MagicMock()
engine._memory.ui_memory.is_connected = False
engine._memory.ui_memory.query_closest = MagicMock(side_effect=TimeoutError("Qdrant timeout"))
engine.positive_memory = MagicMock()
engine.positive_memory.is_connected = False
engine.positive_memory.recall = MagicMock(side_effect=TimeoutError("Qdrant timeout"))
start = time.time()
nodes = engine._extract_semantic_nodes(VALID_FEED_XML)
elapsed = time.time() - start
assert elapsed < 5.0
assert isinstance(nodes, list)
TelepathicEngine._instance = None
# ──────────────────────────────────────────────────
# LLM (Ollama/OpenRouter) Failure Tests
# ──────────────────────────────────────────────────
@pytest.mark.chaos
class TestLLMFailure:
"""Bot must survive LLM outages."""
def test_sae_perceive_defaults_to_normal_on_llm_failure(self):
"""If LLM classification fails, SAE must default to NORMAL (safe fallback)."""
from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType
SituationalAwarenessEngine.reset()
device = MagicMock()
device.app_id = "com.instagram.android"
device.deviceV2 = MagicMock()
device.deviceV2.info = {"screenOn": True}
sae = SituationalAwarenessEngine(device)
sae.episodes = MagicMock()
sae.episodes.recall = MagicMock(return_value=None)
with patch("GramAddict.core.qdrant_memory.ScreenMemoryDB") as MockScreenDB:
mock_screen_db = MagicMock()
mock_screen_db.get_screen_type = MagicMock(return_value=None)
MockScreenDB.return_value = mock_screen_db
with patch("GramAddict.core.llm_provider.query_telepathic_llm", side_effect=ConnectionError("Ollama down")):
result = sae.perceive(VALID_FEED_XML)
# Must default to NORMAL, not crash
assert result == SituationType.NORMAL
SituationalAwarenessEngine.reset()
def test_sae_escape_planning_defaults_to_back_on_llm_failure(self):
"""If LLM escape planning fails, SAE must default to BACK press."""
from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType
SituationalAwarenessEngine.reset()
device = MagicMock()
device.app_id = "com.instagram.android"
device.deviceV2 = MagicMock()
device.deviceV2.info = {"screenOn": True}
sae = SituationalAwarenessEngine(device)
with patch("GramAddict.core.llm_provider.query_llm", side_effect=ConnectionError("LLM down")):
action = sae._plan_escape_via_llm(VALID_FEED_XML, "compressed_sig", SituationType.OBSTACLE_MODAL)
assert action.action_type == "back"
assert "failed" in action.reason.lower() or "default" in action.reason.lower()
SituationalAwarenessEngine.reset()
# ──────────────────────────────────────────────────
# Active Inference Resilience
# ──────────────────────────────────────────────────
@pytest.mark.chaos
class TestActiveInferenceChaos:
"""Active Inference engine must survive edge cases."""
def test_evaluate_with_empty_history(self):
"""Evaluating without any predictions must return True (no-op)."""
from GramAddict.core.active_inference import ActiveInferenceEngine
ai = ActiveInferenceEngine("test_user")
assert ai.evaluate_prediction("<hierarchy/>") is True
def test_extreme_free_energy_doesnt_overflow(self):
"""Repeated errors must not cause float overflow."""
from GramAddict.core.active_inference import ActiveInferenceEngine
ai = ActiveInferenceEngine("test_user")
for _ in range(1000):
ai.predict_state(["nonexistent_element"])
ai.evaluate_prediction("<hierarchy><node text='wrong'/></hierarchy>")
assert ai.free_energy < float("inf")
assert ai.free_energy >= 0
def test_surprise_with_identical_prediction_is_zero(self):
"""Perfect prediction (predicted == observed) must produce near-zero surprise."""
from GramAddict.core.active_inference import ActiveInferenceEngine
ai = ActiveInferenceEngine("test_user")
ai.free_energy = 0.0
result = ai.calculate_surprise(1.0, 1.0)
assert result < 0.1 # Near-zero free energy
def test_sleep_modifier_bounds(self):
"""Sleep modifier must always be between 1.0 and 5.0."""
from GramAddict.core.active_inference import ActiveInferenceEngine
ai = ActiveInferenceEngine("test_user")
for policy in ["STABLE", "CAUTIOUS", "DORMANT"]:
ai.policy = policy
mod = ai.get_sleep_modifier()
assert 1.0 <= mod <= 5.0

View File

@@ -1,245 +0,0 @@
"""
Chaos Engineering: XML Corruption Resilience Tests for TelepathicEngine + SAE.
Verifies that NEITHER engine crashes on any form of corrupted, truncated,
adversarial, or garbage XML input. They must degrade gracefully (return None
or empty lists) without raising unhandled exceptions.
These tests are the "crash barrier" of autonomous navigation — ensuring that
no matter what Android dumps to us, the bot survives and recovers.
"""
import time
from unittest.mock import MagicMock, patch
import pytest
from tests.chaos import generate_corrupted_xml
# ──────────────────────────────────────────────────
# Telepathic Engine Chaos Tests
# ──────────────────────────────────────────────────
@pytest.fixture
def telepathic_engine():
"""Creates a real TelepathicEngine instance with mocked Qdrant."""
with (
patch("GramAddict.core.qdrant_memory.QdrantBase.__init__", return_value=None),
patch(
"GramAddict.core.qdrant_memory.QdrantBase.is_connected", new_callable=lambda: property(lambda self: False)
),
):
from GramAddict.core.telepathic_engine import TelepathicEngine
TelepathicEngine._instance = None
engine = TelepathicEngine.__new__(TelepathicEngine)
engine.__init__()
# We need to mock the Qdrant connection in the ActionMemory submodule
engine._memory.ui_memory = MagicMock()
engine._memory.ui_memory.is_connected = False
engine._memory.ui_memory.query_closest = MagicMock(return_value=None)
# We mock positive memory for chaos tests
engine.positive_memory = MagicMock()
engine.positive_memory.is_connected = False
engine.positive_memory.recall = MagicMock(return_value=None)
yield engine
TelepathicEngine._instance = None
ALL_CORRUPTION_TYPES = [
"EMPTY_STRING",
"NONE_VALUE",
"TRUNCATED_MID_TAG",
"UNICODE_INJECTION",
"MASSIVE_DOM_10K_NODES",
"ZERO_SIZE_BOUNDS",
"NEGATIVE_COORDINATES",
"MISSING_CLOSING_TAGS",
"RECURSIVE_NESTING_500_DEEP",
"NULL_BYTES",
"MALFORMED_BOUNDS",
"ONLY_WHITESPACE",
"HTML_NOT_XML",
"BINARY_GARBAGE",
"EXTREMELY_LONG_TEXT",
]
@pytest.mark.chaos
class TestTelepathicEngineChaos:
"""Telepathic Engine must NEVER crash on corrupted XML."""
@pytest.mark.parametrize("corruption_type", ALL_CORRUPTION_TYPES)
def test_extract_semantic_nodes_survives(self, telepathic_engine, corruption_type):
"""Engine's XML parser must return empty list on any corruption."""
xml = generate_corrupted_xml(corruption_type)
# Must NOT raise. May return empty list.
if xml is None:
# None input — directly test defense
result = telepathic_engine._extract_semantic_nodes("")
else:
result = telepathic_engine._extract_semantic_nodes(xml)
assert isinstance(result, list)
@pytest.mark.parametrize(
"corruption_type",
[
"EMPTY_STRING",
"NONE_VALUE",
"TRUNCATED_MID_TAG",
"MISSING_CLOSING_TAGS",
"ONLY_WHITESPACE",
"HTML_NOT_XML",
"BINARY_GARBAGE",
],
)
def test_find_best_node_survives_garbage(self, telepathic_engine, corruption_type):
"""find_best_node must return None on garbage XML, never crash."""
xml = generate_corrupted_xml(corruption_type)
if xml is None:
xml = ""
result = telepathic_engine._find_best_node_inner(xml, "tap like button", min_confidence=0.82)
# Must be None or a dict, never an exception
assert result is None or isinstance(result, dict)
def test_unicode_injection_doesnt_corrupt_semantics(self, telepathic_engine):
"""Zalgo text in nodes shouldn't crash semantic extraction."""
xml = generate_corrupted_xml("UNICODE_INJECTION")
nodes = telepathic_engine._extract_semantic_nodes(xml)
# Should extract SOME nodes (the XML structure is valid)
assert isinstance(nodes, list)
# If nodes found, they should have valid coordinates
for node in nodes:
assert isinstance(node.get("x", 0), int)
assert isinstance(node.get("y", 0), int)
def test_massive_dom_doesnt_hang(self, telepathic_engine):
"""10K nodes must be parsed within 5 seconds — no infinite loops."""
xml = generate_corrupted_xml("MASSIVE_DOM_10K_NODES")
start = time.time()
nodes = telepathic_engine._extract_semantic_nodes(xml)
elapsed = time.time() - start
assert elapsed < 5.0, f"Parsing 10K nodes took {elapsed:.2f}s (limit: 5s)"
assert isinstance(nodes, list)
def test_deep_nesting_doesnt_stackoverflow(self, telepathic_engine):
"""500 levels of nesting must not cause stack overflow."""
xml = generate_corrupted_xml("RECURSIVE_NESTING_500_DEEP")
# This would crash Python's default recursion limit (1000) if
# we used recursive parsing. ElementTree uses iterative parsing,
# so it should survive.
nodes = telepathic_engine._extract_semantic_nodes(xml)
assert isinstance(nodes, list)
def test_null_bytes_stripped(self, telepathic_engine):
"""Null bytes in text content must not cause parsing failures."""
xml = generate_corrupted_xml("NULL_BYTES")
nodes = telepathic_engine._extract_semantic_nodes(xml)
assert isinstance(nodes, list)
# Verify no null bytes leaked into node semantics
for node in nodes:
assert "\x00" not in node.get("semantic_string", "")
# ──────────────────────────────────────────────────
# SAE (Situational Awareness Engine) Chaos Tests
# ──────────────────────────────────────────────────
@pytest.fixture
def sae_engine():
"""Creates a SAE instance with mocked device."""
from GramAddict.core.situational_awareness import SituationalAwarenessEngine
SituationalAwarenessEngine.reset()
device = MagicMock()
device.app_id = "com.instagram.android"
device.deviceV2 = MagicMock()
device.deviceV2.info = {"screenOn": True}
engine = SituationalAwarenessEngine(device)
# Mock the episode DB to avoid Qdrant dependency
engine.episodes = MagicMock()
engine.episodes.recall = MagicMock(return_value=None)
engine.episodes.learn = MagicMock()
yield engine
SituationalAwarenessEngine.reset()
@pytest.mark.chaos
class TestSAEChaos:
"""SAE perception must be bulletproof against XML corruption."""
@pytest.mark.parametrize(
"corruption_type",
[
"EMPTY_STRING",
"TRUNCATED_MID_TAG",
"MISSING_CLOSING_TAGS",
"ONLY_WHITESPACE",
"HTML_NOT_XML",
"BINARY_GARBAGE",
],
)
def test_compress_xml_survives_garbage(self, sae_engine, corruption_type):
"""XML compression must never crash, even on garbage."""
xml = generate_corrupted_xml(corruption_type)
if xml is None:
xml = ""
result = sae_engine._compress_xml(xml)
assert isinstance(result, str)
assert len(result) > 0 # Should always return something
def test_compress_empty_returns_marker(self, sae_engine):
"""Empty/None input must return 'EMPTY_SCREEN' sentinel."""
assert sae_engine._compress_xml("") == "EMPTY_SCREEN"
assert sae_engine._compress_xml(None) == "EMPTY_SCREEN"
@pytest.mark.parametrize(
"corruption_type",
[
"EMPTY_STRING",
"TRUNCATED_MID_TAG",
"BINARY_GARBAGE",
"ONLY_WHITESPACE",
],
)
def test_perceive_survives_garbage(self, sae_engine, corruption_type):
"""perceive() must return a valid SituationType on any input."""
from GramAddict.core.situational_awareness import SituationType
xml = generate_corrupted_xml(corruption_type)
if xml is None:
xml = ""
result = sae_engine.perceive(xml)
assert isinstance(result, SituationType)
def test_compute_situation_hash_is_deterministic(self, sae_engine):
"""Same XML must always produce the same hash."""
xml = generate_corrupted_xml("UNICODE_INJECTION")
compressed = sae_engine._compress_xml(xml)
hash1 = sae_engine._compute_situation_hash(compressed)
hash2 = sae_engine._compute_situation_hash(compressed)
assert hash1 == hash2
def test_massive_dom_compression_is_bounded(self, sae_engine):
"""10K nodes must be compressed to < 3000 chars (the cap)."""
xml = generate_corrupted_xml("MASSIVE_DOM_10K_NODES")
start = time.time()
result = sae_engine._compress_xml(xml)
elapsed = time.time() - start
assert len(result) <= 3000, f"Compressed output is {len(result)} chars (limit: 3000)"
assert elapsed < 5.0, f"Compression took {elapsed:.2f}s"