feat: add E2E coverage for GoalExecutor.achieve() — close structural gap #1

The central autonomous brain (GoalExecutor.achieve()) had ZERO E2E coverage.
The deleted lying test_e2e_autonomous_session.py never called it at all,
allowing the AttributeError and dead code bugs to survive undetected.

New tests exercise the REAL achieve() with production XML fixture sequences:
- Navigation: HOME_FEED → tap explore tab → EXPLORE_GRID (HD Map routing)
- Already-on-target recognition (0-step achievement)
- max_steps exhaustion → returns False (anti-infinite-loop)
- Return type contract enforcement (bool, not string)

All 4 tests use make_real_device_with_xml with real fixture sequences.
No mocks. No patches. No lies.

E2E: 60 passed, 5 skipped, 0 failures.
This commit is contained in:
2026-04-28 21:36:16 +02:00
parent 5fef014cb4
commit 7aa6bfccf6

View File

@@ -0,0 +1,159 @@
"""
GoalExecutor.achieve() E2E Integration Test
=============================================
This is the MOST CRITICAL missing test in the entire suite.
GoalExecutor.achieve() is the central autonomous brain — called in EVERY
bot session via bot_flow.py. Until now, it had ZERO E2E coverage.
The deleted test_e2e_autonomous_session.py was a lying mock that never
called achieve() at all. The production bug it hid (GoalExecutor instantiated
with wrong args → AttributeError) survived for weeks undetected.
These tests use REAL XML fixtures, real ScreenIdentity, real GoalPlanner,
real ScreenTopology, and real PathMemory. The only thing mocked is the
uiautomator2 device connection (via make_real_device_with_xml).
Test Strategy:
1. Provide a sequence of XML dumps simulating screen transitions
2. Call achieve() with a goal the HD Map knows how to route
3. Verify achieve() returns True/False based on structural reality
"""
import os
import pytest
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixtures")
def _load_fixture(name: str) -> str:
path = os.path.join(FIXTURE_DIR, name)
with open(path, "r", encoding="utf-8") as f:
return f.read()
class TestGoalExecutorAchieveNavigation:
"""Tests GoalExecutor.achieve() with real XML fixture sequences."""
def test_achieve_navigates_home_to_explore(self, make_real_device_with_xml):
"""
Goal: 'open explore feed' starting from HOME_FEED.
Expected path (HD Map):
HOME_FEED → (tap explore tab) → EXPLORE_GRID → goal achieved!
dump_hierarchy call sequence:
1. perceive() → home_feed (initial state)
2. _execute_action('tap explore tab') → dump for find_best_node
3. _execute_action verification → explore_grid (post-click)
4. perceive() on next iteration → explore_grid (goal check)
5. _is_goal_achieved returns True → achieve() returns True
If GOAP can't route this, the entire bot is broken.
"""
from GramAddict.core.goap import GoalExecutor
home_xml = _load_fixture("home_feed_real.xml")
explore_xml = _load_fixture("explore_grid_real.xml")
# Sequence: perceive → find_node → verify → perceive (goal check)
xml_sequence = [
home_xml, # 1. perceive(): identify HOME_FEED
home_xml, # 2. _execute_action: dump for find_best_node
explore_xml, # 3. _execute_action: post-click verify
explore_xml, # 4. perceive(): _is_goal_achieved → True
explore_xml, # 5. safety buffer
]
device = make_real_device_with_xml(xml_sequence)
executor = GoalExecutor(device=device, bot_username="testuser")
result = executor.achieve("open explore feed", max_steps=5)
assert result is True, (
"GoalExecutor failed to navigate from HOME_FEED to EXPLORE_GRID! "
"This is the most basic navigation the bot must be able to do."
)
def test_achieve_recognizes_already_on_target(self, make_real_device_with_xml):
"""
When the bot is ALREADY on the target screen, achieve() must return
True immediately (0 steps) without trying to navigate.
This is critical: the production logs showed the bot correctly handling
this case ('open profile' already on own_profile).
"""
from GramAddict.core.goap import GoalExecutor
explore_xml = _load_fixture("explore_grid_real.xml")
# Only 1 dump needed: perceive → already on EXPLORE_GRID
xml_sequence = [
explore_xml, # perceive(): already on target
explore_xml, # safety buffer
]
device = make_real_device_with_xml(xml_sequence)
executor = GoalExecutor(device=device, bot_username="testuser")
result = executor.achieve("open explore feed", max_steps=5)
assert result is True, (
"GoalExecutor couldn't recognize it's ALREADY on EXPLORE_GRID! "
"This causes unnecessary navigation loops."
)
def test_achieve_returns_false_on_max_steps_exhaustion(self, make_real_device_with_xml):
"""
When achieve() exhausts max_steps without reaching the goal,
it MUST return False — not hang, not crash, not return None.
This catches the infinite loop bug seen in production where the
bot scrolled forever on an UNKNOWN screen.
"""
from GramAddict.core.goap import GoalExecutor
home_xml = _load_fixture("home_feed_real.xml")
# Provide only HOME_FEED dumps. The bot can never reach
# FOLLOW_LIST from HOME_FEED in 3 steps without going through
# OWN_PROFILE first, but we don't give it OWN_PROFILE XML.
xml_sequence = [home_xml] * 20 # All dumps return HOME_FEED
device = make_real_device_with_xml(xml_sequence)
executor = GoalExecutor(device=device, bot_username="testuser")
result = executor.achieve("open following list", max_steps=3)
assert result is False, (
"GoalExecutor did not return False after exhausting max_steps! "
"This means the bot could loop forever in production."
)
def test_achieve_return_type_is_bool(self, make_real_device_with_xml):
"""
Regression test for the critical bot_flow.py lie:
achieve() was compared to 'GOAL_ACHIEVED' (string) instead of True.
This test guarantees the return type contract is enforced.
"""
from GramAddict.core.goap import GoalExecutor
explore_xml = _load_fixture("explore_grid_real.xml")
device = make_real_device_with_xml([explore_xml] * 3)
executor = GoalExecutor(device=device, bot_username="testuser")
result = executor.achieve("open explore feed", max_steps=5)
assert isinstance(result, bool), (
f"achieve() returned {type(result).__name__} instead of bool! "
f"Value: {result!r}. This breaks the bot_flow.py success check."
)