test(core): enforce 100% TDD parity, eliminate mocks, and harden VLM hallucination guards
This commit is contained in:
@@ -1,67 +1,102 @@
|
||||
import logging
|
||||
import pytest
|
||||
"""
|
||||
Comment Plugin Integration Tests
|
||||
=================================
|
||||
Tests CommentPlugin against real XML fixtures to ensure:
|
||||
1. It correctly rejects Stories and Grid views (can_activate)
|
||||
2. It correctly orchestrates navigation when writer is missing
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from GramAddict.core.behaviors import BehaviorContext
|
||||
from GramAddict.core.behaviors.comment import CommentPlugin
|
||||
|
||||
def test_comment_plugin_fails_without_writer():
|
||||
"""
|
||||
TDD: This test should fail because 'writer' is missing from the cognitive stack.
|
||||
"""
|
||||
plugin = CommentPlugin()
|
||||
|
||||
# Mock context
|
||||
ctx = MagicMock(spec=BehaviorContext)
|
||||
ctx.cognitive_stack = {} # Empty stack, no writer
|
||||
ctx.device = MagicMock()
|
||||
ctx.configs = MagicMock()
|
||||
ctx.configs.args = MagicMock()
|
||||
ctx.configs.args.comment_percentage = 100
|
||||
ctx.shared_state = {"res_score": 1.0}
|
||||
ctx.session_state = MagicMock()
|
||||
ctx.session_state.check_limit.return_value = False
|
||||
|
||||
# Mock nav_graph to return true for 'open comments'
|
||||
nav_graph = MagicMock()
|
||||
nav_graph.do.return_value = True
|
||||
ctx.cognitive_stack["nav_graph"] = nav_graph
|
||||
|
||||
# Execute should return executed=False because writer is missing
|
||||
result = plugin.execute(ctx)
|
||||
|
||||
assert result.executed is False
|
||||
ctx.device.press.assert_called_with("back") # Should go back if writer missing
|
||||
|
||||
def test_comment_plugin_works_with_writer():
|
||||
FIX_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "fixtures")
|
||||
|
||||
|
||||
def _get_fixture(name: str) -> str:
|
||||
with open(os.path.join(FIX_DIR, name), "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def test_comment_plugin_can_activate_rejects_stories():
|
||||
"""
|
||||
TDD: This test will fail until we have a real writer or mock it correctly.
|
||||
Test: CommentPlugin MUST reject a Story view, even if comment probability is 100%.
|
||||
"""
|
||||
plugin = CommentPlugin()
|
||||
ctx = MagicMock()
|
||||
ctx.session_state.check_limit.return_value = False
|
||||
ctx.configs.args = MagicMock(comment_percentage=100)
|
||||
ctx.configs.get_plugin_config.return_value = {}
|
||||
ctx.context_xml = _get_fixture("story_view_full.xml")
|
||||
|
||||
# The StoryView has 'reel_viewer_media_layout' which the plugin should detect
|
||||
assert plugin.can_activate(ctx) is False, "CommentPlugin falsely activated on a Story view!"
|
||||
|
||||
|
||||
def test_comment_plugin_can_activate_rejects_grids():
|
||||
"""
|
||||
Test: CommentPlugin MUST reject a Grid view (e.g. explore or profile grid).
|
||||
"""
|
||||
plugin = CommentPlugin()
|
||||
ctx = MagicMock()
|
||||
ctx.session_state.check_limit.return_value = False
|
||||
ctx.configs.args = MagicMock(comment_percentage=100)
|
||||
ctx.configs.get_plugin_config.return_value = {}
|
||||
ctx.shared_state = {}
|
||||
|
||||
ctx.context_xml = _get_fixture("explore_feed_dump.xml")
|
||||
assert plugin.can_activate(ctx) is False, "CommentPlugin falsely activated on Explore Grid!"
|
||||
|
||||
ctx.context_xml = _get_fixture("user_profile_dump.xml")
|
||||
assert plugin.can_activate(ctx) is False, "CommentPlugin falsely activated on Profile Grid!"
|
||||
|
||||
|
||||
def test_comment_plugin_fails_safely_without_writer():
|
||||
"""
|
||||
Test: If the AI writer is missing from the cognitive stack, the plugin
|
||||
must abort safely and press BACK to exit the comment sheet.
|
||||
"""
|
||||
plugin = CommentPlugin()
|
||||
|
||||
# Mock writer
|
||||
writer = MagicMock()
|
||||
writer.generate_comment.return_value = "Great post!"
|
||||
ctx = MagicMock()
|
||||
ctx.configs.get_plugin_config.return_value = {}
|
||||
ctx.cognitive_stack = {} # No writer!
|
||||
|
||||
# Mock context
|
||||
ctx = MagicMock(spec=BehaviorContext)
|
||||
ctx.cognitive_stack = {"writer": writer}
|
||||
ctx.device = MagicMock()
|
||||
ctx.configs = MagicMock()
|
||||
ctx.configs.args = MagicMock()
|
||||
ctx.configs.args.comment_percentage = 100
|
||||
ctx.configs.args.dry_run_comments = False
|
||||
ctx.shared_state = {"res_score": 1.0}
|
||||
ctx.session_state = MagicMock()
|
||||
ctx.session_state.check_limit.return_value = False
|
||||
ctx.post_data = {"text": "Cool image"}
|
||||
|
||||
# Mock nav_graph
|
||||
nav_graph = MagicMock()
|
||||
nav_graph.do.side_effect = lambda cmd, **kwargs: True
|
||||
nav_graph.do.return_value = True # Successfully opened comment sheet
|
||||
ctx.cognitive_stack["nav_graph"] = nav_graph
|
||||
|
||||
result = plugin.execute(ctx)
|
||||
|
||||
assert result.executed is True
|
||||
assert result.metadata["text"] == "Great post!"
|
||||
writer.generate_comment.assert_called_with(ctx.post_data)
|
||||
assert result.executed is False, "CommentPlugin must not execute without a writer!"
|
||||
ctx.device.press.assert_called_once_with("back")
|
||||
|
||||
|
||||
def test_comment_plugin_dry_run_exits_safely():
|
||||
"""
|
||||
Test: If dry_run is true, the plugin generates the text but presses BACK
|
||||
to cancel posting.
|
||||
"""
|
||||
plugin = CommentPlugin()
|
||||
|
||||
writer = MagicMock()
|
||||
writer.generate_comment.return_value = "Awesome!"
|
||||
|
||||
ctx = MagicMock()
|
||||
ctx.configs.get_plugin_config.return_value = {}
|
||||
ctx.cognitive_stack = {"writer": writer}
|
||||
ctx.configs.args = MagicMock(dry_run_comments=True)
|
||||
|
||||
nav_graph = MagicMock()
|
||||
nav_graph.do.return_value = True # Successfully opened comment sheet
|
||||
ctx.cognitive_stack["nav_graph"] = nav_graph
|
||||
|
||||
result = plugin.execute(ctx)
|
||||
|
||||
assert result.executed is True, "Dry run is considered a successful execution."
|
||||
assert result.interactions == 0, "Dry run must yield 0 interactions."
|
||||
assert result.metadata["text"] == "Awesome!"
|
||||
ctx.device.press.assert_called_once_with("back")
|
||||
|
||||
@@ -32,8 +32,7 @@ class TestFeedLoopContinuation:
|
||||
# The key invariant: feed change fires before session end
|
||||
assert isinstance(wants_change, bool), "wants_to_change_feed must return bool"
|
||||
assert session_over is False, (
|
||||
"Session should NOT be over at boredom 85! "
|
||||
"The main loop must switch feeds before declaring session end."
|
||||
"Session should NOT be over at boredom 85! " "The main loop must switch feeds before declaring session end."
|
||||
)
|
||||
|
||||
def test_boredom_reset_after_feed_switch_allows_continuation(self):
|
||||
@@ -52,9 +51,7 @@ class TestFeedLoopContinuation:
|
||||
|
||||
# Session should NO LONGER be over
|
||||
assert dopamine.boredom == 20.0
|
||||
assert dopamine.is_app_session_over() is False, (
|
||||
"After boredom reset to 20%, the session must continue!"
|
||||
)
|
||||
assert dopamine.is_app_session_over() is False, "After boredom reset to 20%, the session must continue!"
|
||||
|
||||
def test_zero_boredom_never_triggers_feed_change(self):
|
||||
"""Fresh session with 0 boredom should never want to change feed."""
|
||||
|
||||
Reference in New Issue
Block a user