File size: 13,004 Bytes
5c28dc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
"""Phase 6 tests: ModerationAgent, OriginalityAgent, R6/R7 rewards, aggregator, env.step()."""
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from viral_script_engine.agents.moderation_agent import ModerationAgent, ModerationOutput
from viral_script_engine.agents.originality_agent import OriginalityAgent, OriginalityOutput
from viral_script_engine.rewards.r6_safety import SafetyReward
from viral_script_engine.rewards.r7_originality import OriginalityReward
from viral_script_engine.rewards.reward_aggregator import RewardAggregator
from viral_script_engine.environment.observations import RewardComponents

BASE_DIR = Path(__file__).parent.parent


@pytest.fixture
def moderation_agent():
    return ModerationAgent(kb_path=str(BASE_DIR / "data" / "shadowban_triggers.json"))


@pytest.fixture
def originality_agent():
    return OriginalityAgent(templates_path=str(BASE_DIR / "data" / "viral_templates.json"))


@pytest.fixture
def r6():
    return SafetyReward()


@pytest.fixture
def r7():
    return OriginalityReward()


# ── ModerationAgent ────────────────────────────────────────────────────────────

class TestModerationAgent:
    def test_flags_high_severity_health_claim(self, moderation_agent):
        script = (
            "Doctors don't want you to know this miracle remedy. "
            "It is a guaranteed weight loss solution that cures everything. "
            "Get started today."
        )
        result = moderation_agent.check(script)
        assert result.total_flags > 0
        severities = [f.severity for f in result.flags]
        assert "high" in severities

    def test_flags_policy_violation(self, moderation_agent):
        script = (
            "Want to grow your audience fast? Buy followers today. "
            "Make $5000 in 30 days guaranteed β€” it's the easiest passive income. "
            "Get rich quick with our proven method."
        )
        result = moderation_agent.check(script)
        assert result.total_flags > 0
        categories = [f.category for f in result.flags]
        assert "policy_violation" in categories

    def test_flags_engagement_bait(self, moderation_agent):
        script = (
            "Here's something nobody talks about in fitness. "
            "These 3 exercises changed my life completely. "
            "Comment if you agree and tag 3 friends who need this."
        )
        result = moderation_agent.check(script)
        assert result.total_flags > 0

    def test_clean_script_is_safe(self, moderation_agent):
        script = (
            "I tested three different morning routines for 30 days each. "
            "The data surprised me β€” the simplest one produced the best results. "
            "Here's what I found and what I now do every day."
        )
        result = moderation_agent.check(script)
        assert result.is_safe is True
        assert result.overall_risk == "safe"

    def test_high_severity_flag_sets_is_safe_false(self, moderation_agent):
        script = (
            "This miracle remedy cures cancer permanently. "
            "Big pharma is hiding this from you. "
            "Share before it gets taken down."
        )
        result = moderation_agent.check(script)
        assert result.is_safe is False


# ── SafetyReward ───────────────────────────────────────────────────────────────

class TestSafetyReward:
    def test_zero_score_on_high_severity(self, moderation_agent, r6):
        script = (
            "This miracle remedy cures all diseases overnight. "
            "Buy followers now to grow faster. "
            "Make $10,000 in 30 days guaranteed."
        )
        mod_out = moderation_agent.check(script)
        result = r6.score(mod_out)
        assert result.score == 0.0

    def test_full_score_on_clean_script(self, moderation_agent, r6):
        script = (
            "I spent 6 months studying sleep science. "
            "These 3 evidence-based habits improved my sleep quality measurably. "
            "Start with the simplest one first."
        )
        mod_out = moderation_agent.check(script)
        result = r6.score(mod_out)
        assert result.score == 1.0

    def test_medium_score_on_medium_severity(self, moderation_agent, r6):
        script = (
            "This app is a must-have. "
            "Watch the full movie version for free on our site. "
            "Click the link below for the download."
        )
        mod_out = moderation_agent.check(script)
        result = r6.score(mod_out)
        # medium or high severity β€” score should be <= 0.5
        assert result.score <= 0.5


# ── OriginalityAgent ───────────────────────────────────────────────────────────

class TestOriginalityAgent:
    def test_detects_overused_hook(self, originality_agent):
        script = (
            "Nobody talks about this but your morning routine is wrong. "
            "Here are three things I wish I knew before starting. "
            "Follow for more tips."
        )
        result = originality_agent.check(script)
        assert len(result.flags) > 0
        template_types = [f.template_type for f in result.flags]
        assert any(t in ("overused_hook", "overused_cta") for t in template_types)

    def test_unique_script_scores_high(self, originality_agent):
        script = (
            "In 2019, the average Indian millennial checked their phone 94 times a day. "
            "I tracked my own usage for a month and found a pattern nobody warned me about. "
            "The solution had nothing to do with willpower."
        )
        result = originality_agent.check(script)
        assert result.originality_score >= 0.8

    def test_template_clone_is_generic(self, originality_agent):
        script = (
            "Nobody talks about this but you have been doing it wrong your whole life. "
            "Stop doing this immediately and save this for later. "
            "Follow for more and share with someone who needs this."
        )
        result = originality_agent.check(script)
        assert result.is_generic is True or result.originality_score < 0.6


# ── OriginalityReward ──────────────────────────────────────────────────────────

class TestOriginalityReward:
    def test_zero_score_on_template_clone(self, originality_agent, r7):
        script = (
            "Nobody talks about this but you have been doing it wrong your whole life. "
            "Stop doing this immediately and save this for later. "
            "Follow for more and share with someone who needs this."
        )
        orig_out = originality_agent.check(script)
        # Force a low originality_score scenario
        from viral_script_engine.agents.originality_agent import OriginalityOutput
        low_out = OriginalityOutput(
            flags=orig_out.flags,
            originality_score=0.2,
            is_generic=True,
            unique_elements=[],
        )
        result = r7.score(low_out)
        assert result.score == 0.0

    def test_full_score_on_high_originality(self, originality_agent, r7):
        from viral_script_engine.agents.originality_agent import OriginalityOutput
        high_out = OriginalityOutput(
            flags=[],
            originality_score=0.95,
            is_generic=False,
            unique_elements=["unique sentence 1", "unique sentence 2"],
        )
        result = r7.score(high_out)
        assert result.score == 1.0


# ── RewardAggregator with R6/R7 ────────────────────────────────────────────────

class TestRewardAggregatorPhase6:
    def test_r6_r7_included_in_total(self):
        agg = RewardAggregator()
        components = RewardComponents(
            r1_hook_strength=0.8,
            r2_coherence=0.7,
            r3_cultural_alignment=0.75,
            r4_debate_resolution=0.6,
            r5_defender_preservation=0.7,
            r6_safety=1.0,
            r7_originality=0.9,
        )
        start = RewardComponents(
            r1_hook_strength=0.5,
            r2_coherence=0.5,
            r3_cultural_alignment=0.5,
            r4_debate_resolution=0.5,
            r5_defender_preservation=0.5,
            r6_safety=1.0,
            r7_originality=0.9,
        )
        result, log = agg.compute(components, start, [], episode_id="test", step_num=1)
        assert result.total > 0.0
        assert not log.triggered

    def test_catastrophic_drop_fires_on_r6_zero(self):
        agg = RewardAggregator()
        components = RewardComponents(
            r1_hook_strength=0.8,
            r2_coherence=0.7,
            r3_cultural_alignment=0.75,
            r4_debate_resolution=0.6,
            r5_defender_preservation=0.7,
            r6_safety=0.0,
            r7_originality=0.9,
        )
        start = RewardComponents(
            r1_hook_strength=0.8,
            r2_coherence=0.7,
            r3_cultural_alignment=0.75,
            r4_debate_resolution=0.6,
            r5_defender_preservation=0.7,
            r6_safety=1.0,
            r7_originality=0.9,
        )
        result, log = agg.compute(components, start, [], episode_id="test", step_num=1)
        assert result.total == 0.0
        assert log.triggered
        assert log.rule_triggered == "r6_safety_hard_zero"


# ── env.step() integration ─────────────────────────────────────────────────────

class TestEnvStepPhase6:
    def test_step_includes_moderation_and_originality(self, monkeypatch):
        from unittest.mock import MagicMock
        from viral_script_engine.environment.env import ViralScriptEnv
        from viral_script_engine.agents.critic import CritiqueOutput, CritiqueClaim
        from viral_script_engine.agents.defender import DefenderOutput
        from viral_script_engine.agents.rewriter import RewriteResult

        env = ViralScriptEnv(
            scripts_path=str(BASE_DIR / "data" / "test_scripts" / "scripts.json"),
            max_steps=1,
            difficulty="easy",
            use_escalation=False,
        )

        dummy_claim = CritiqueClaim(
            claim_id="C1",
            critique_class="hook_weakness",
            claim_text="Hook is weak",
            timestamp_range="0-3s",
            evidence="Opening is vague",
            is_falsifiable=True,
            severity="medium",
        )
        dummy_critique = CritiqueOutput(
            claims=[dummy_claim],
            overall_severity="medium",
            raw_response="Hook is weak",
        )
        dummy_defender = DefenderOutput(
            core_strength="The hook has genuine curiosity value.",
            core_strength_quote="First sentence",
            defense_argument="The structure is sound; only specificity needs improvement.",
            flagged_critic_claims=["C1"],
            regional_voice_elements=["regional phrase"],
        )
        dummy_rewrite = RewriteResult(
            rewritten_script="3 things nobody tells you about morning routines that actually work.",
            diff="- Old hook\n+ 3 things nobody tells you about morning routines that actually work.",
            word_count_delta=2,
        )

        from viral_script_engine.rewards.r4_debate_resolution import DebateResolutionResult
        dummy_r4 = DebateResolutionResult(
            score=0.7,
            resolution_status="resolved",
            original_claim_id="C1",
            original_claim_class="hook_weakness",
            new_claims_count=0,
        )

        monkeypatch.setattr(env.critic, "critique", lambda *a, **kw: dummy_critique)
        monkeypatch.setattr(env.defender, "defend", lambda **kw: dummy_defender)
        monkeypatch.setattr(env.rewriter, "rewrite", lambda script, action: dummy_rewrite)
        monkeypatch.setattr(env.r4, "score", lambda **kw: dummy_r4)

        env.reset()
        action = {
            "action_type": "hook_rewrite",
            "target_section": "hook",
            "instruction": "Make the hook more engaging.",
            "critique_claim_id": "C1",
            "reasoning": "test",
        }
        _, _, _, _, info = env.step(action)
        assert "moderation_output" in info
        assert "originality_output" in info
        rc = info["reward_components"]
        assert rc.get("r6_safety") is not None
        assert rc.get("r7_originality") is not None