feat: complete modular plugin refactor with 100% E2E coverage for interactions
This commit is contained in:
@@ -7,16 +7,16 @@ not just specific examples. Think of them as mathematical proofs of correctness.
|
||||
Tesla validates that steering never exceeds max torque for ANY speed —
|
||||
we validate that scroll never exceeds screen bounds for ANY device size.
|
||||
"""
|
||||
import pytest
|
||||
import re
|
||||
from hypothesis import given, strategies as st, settings, assume
|
||||
from tests.chaos import VALID_FEED_XML
|
||||
|
||||
import pytest
|
||||
from hypothesis import assume, given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
# ──────────────────────────────────────────────────
|
||||
# XML Parsing Properties
|
||||
# ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.property
|
||||
class TestXMLParsingProperties:
|
||||
"""Universal properties of the XML extraction pipeline."""
|
||||
@@ -29,11 +29,23 @@ class TestXMLParsingProperties:
|
||||
def test_extracted_nodes_always_have_valid_coordinates(self, text, desc):
|
||||
"""PROPERTY: Any extracted node must have integer x, y >= 0."""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
# Escape XML special chars
|
||||
safe_text = text.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace("'", "'")
|
||||
safe_desc = desc.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace("'", "'")
|
||||
|
||||
safe_text = (
|
||||
text.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
.replace("'", "'")
|
||||
)
|
||||
safe_desc = (
|
||||
desc.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
.replace("'", "'")
|
||||
)
|
||||
|
||||
xml = (
|
||||
f'<hierarchy rotation="0">'
|
||||
f'<node index="0" text="{safe_text}" '
|
||||
@@ -43,12 +55,18 @@ class TestXMLParsingProperties:
|
||||
f'content-desc="{safe_desc}" '
|
||||
f'clickable="true" '
|
||||
f'bounds="[100,200][300,400]" />'
|
||||
f'</hierarchy>'
|
||||
f"</hierarchy>"
|
||||
)
|
||||
|
||||
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)):
|
||||
|
||||
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.ui_memory = MagicMock()
|
||||
@@ -57,20 +75,20 @@ class TestXMLParsingProperties:
|
||||
engine.positive_memory.is_connected = False
|
||||
engine._edge_model = None
|
||||
engine._edge_tokenizer = None
|
||||
|
||||
|
||||
try:
|
||||
nodes = engine._extract_semantic_nodes(xml)
|
||||
except Exception:
|
||||
# If the generated text breaks XML parsing, that's OK —
|
||||
# the parser should return empty list, not crash
|
||||
nodes = []
|
||||
|
||||
|
||||
for node in nodes:
|
||||
assert isinstance(node["x"], int)
|
||||
assert isinstance(node["y"], int)
|
||||
assert node["x"] >= 0
|
||||
assert node["y"] >= 0
|
||||
|
||||
|
||||
TelepathicEngine._instance = None
|
||||
|
||||
@given(
|
||||
@@ -84,10 +102,10 @@ class TestXMLParsingProperties:
|
||||
"""PROPERTY: Calculated center must lie within the bounding rectangle."""
|
||||
right = min(left + width, 2160)
|
||||
bottom = min(top + height, 3200)
|
||||
|
||||
|
||||
center_x = (left + right) // 2
|
||||
center_y = (top + bottom) // 2
|
||||
|
||||
|
||||
assert left <= center_x <= right
|
||||
assert top <= center_y <= bottom
|
||||
|
||||
@@ -96,6 +114,7 @@ class TestXMLParsingProperties:
|
||||
# SAE Compression Properties
|
||||
# ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.property
|
||||
class TestSAECompressionProperties:
|
||||
"""Universal properties of XML compression."""
|
||||
@@ -107,14 +126,16 @@ class TestSAECompressionProperties:
|
||||
def test_compression_output_bounded(self, n_nodes):
|
||||
"""PROPERTY: Compressed output must ALWAYS be <= 3000 characters."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from GramAddict.core.situational_awareness import SituationalAwarenessEngine
|
||||
|
||||
SituationalAwarenessEngine.reset()
|
||||
|
||||
|
||||
device = MagicMock()
|
||||
device.deviceV2 = MagicMock()
|
||||
device.deviceV2.info = {"screenOn": True}
|
||||
sae = SituationalAwarenessEngine(device)
|
||||
|
||||
|
||||
# Generate XML with n_nodes
|
||||
parts = ['<hierarchy rotation="0">']
|
||||
for i in range(n_nodes):
|
||||
@@ -125,12 +146,12 @@ class TestSAECompressionProperties:
|
||||
f'package="com.instagram.android" '
|
||||
f'clickable="true" bounds="[0,{i*50}][100,{i*50+40}]" />'
|
||||
)
|
||||
parts.append('</hierarchy>')
|
||||
parts.append("</hierarchy>")
|
||||
xml = "".join(parts)
|
||||
|
||||
|
||||
result = sae._compress_xml(xml)
|
||||
assert len(result) <= 3000
|
||||
|
||||
|
||||
SituationalAwarenessEngine.reset()
|
||||
|
||||
@given(
|
||||
@@ -141,20 +162,22 @@ class TestSAECompressionProperties:
|
||||
def test_different_inputs_produce_different_hashes(self, text1, text2):
|
||||
"""PROPERTY: Distinct inputs should (almost always) produce distinct hashes."""
|
||||
assume(text1 != text2)
|
||||
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from GramAddict.core.situational_awareness import SituationalAwarenessEngine
|
||||
|
||||
SituationalAwarenessEngine.reset()
|
||||
|
||||
|
||||
device = MagicMock()
|
||||
device.deviceV2 = MagicMock()
|
||||
device.deviceV2.info = {"screenOn": True}
|
||||
sae = SituationalAwarenessEngine(device)
|
||||
|
||||
|
||||
hash1 = sae._compute_situation_hash(text1)
|
||||
hash2 = sae._compute_situation_hash(text2)
|
||||
assert hash1 != hash2
|
||||
|
||||
|
||||
SituationalAwarenessEngine.reset()
|
||||
|
||||
|
||||
@@ -162,6 +185,7 @@ class TestSAECompressionProperties:
|
||||
# Active Inference Properties
|
||||
# ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.property
|
||||
class TestActiveInferenceProperties:
|
||||
"""Universal properties of the Active Inference engine."""
|
||||
@@ -174,8 +198,9 @@ class TestActiveInferenceProperties:
|
||||
def test_free_energy_always_non_negative(self, predicted, observed):
|
||||
"""PROPERTY: Free energy must NEVER go negative."""
|
||||
from GramAddict.core.active_inference import ActiveInferenceEngine
|
||||
|
||||
ai = ActiveInferenceEngine("test_user")
|
||||
|
||||
|
||||
result = ai.calculate_surprise(predicted, observed)
|
||||
assert result >= 0.0
|
||||
|
||||
@@ -187,8 +212,9 @@ class TestActiveInferenceProperties:
|
||||
def test_policy_always_valid(self, predicted, observed):
|
||||
"""PROPERTY: Policy must always be one of the valid states."""
|
||||
from GramAddict.core.active_inference import ActiveInferenceEngine
|
||||
|
||||
ai = ActiveInferenceEngine("test_user")
|
||||
|
||||
|
||||
ai.calculate_surprise(predicted, observed)
|
||||
assert ai.policy in ("STABLE", "CAUTIOUS", "DORMANT")
|
||||
|
||||
@@ -199,10 +225,11 @@ class TestActiveInferenceProperties:
|
||||
def test_sleep_modifier_always_bounded(self, modifier_count):
|
||||
"""PROPERTY: Sleep modifier must always be in [1.0, 5.0] range."""
|
||||
from GramAddict.core.active_inference import ActiveInferenceEngine
|
||||
|
||||
ai = ActiveInferenceEngine("test_user")
|
||||
|
||||
|
||||
for _ in range(modifier_count):
|
||||
ai.calculate_surprise(1.0, 0.0) # Max surprise
|
||||
|
||||
|
||||
mod = ai.get_sleep_modifier()
|
||||
assert 1.0 <= mod <= 5.0
|
||||
|
||||
Reference in New Issue
Block a user