Spaces:
Sleeping
Sleeping
File size: 12,758 Bytes
240cde9 7792db8 240cde9 7792db8 240cde9 7792db8 240cde9 7792db8 240cde9 7792db8 12e6d83 | 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 | """
test_grader.py β Unit tests for grader.py
==========================================
Run with: pytest test_grader.py -v
These tests cover every branch of grade_action() and the edge-cases of
calculate_overall_score(). They also act as a regression guard β any
change to a reward constant will immediately fail the assertion that was
relying on it, forcing an intentional review.
"""
from __future__ import annotations
import pytest
from grader import (
HEALTH_DRAIN_THRESHOLD,
PASS_THRESHOLD,
R_BREACH,
R_DISRUPTION,
R_MALWARE_QUARANTINE,
R_PARTIAL,
R_PERFECT,
R_PHISH_BEC_QUARANTINE,
R_SPAM_BLOCK,
R_SPAM_QUARANTINE,
R_WRONG_PROCEDURE,
calculate_overall_score,
grade_action,
)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# CONSTANT SANITY CHECKS
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestRewardConstants:
def test_all_rewards_in_open_interval(self):
for r in [R_PERFECT, R_MALWARE_QUARANTINE, R_PHISH_BEC_QUARANTINE,
R_SPAM_BLOCK, R_SPAM_QUARANTINE, R_WRONG_PROCEDURE,
R_DISRUPTION, R_BREACH]:
assert 0.0 < r < 1.0, f"Reward {r} violates open-interval contract"
def test_reward_ordering(self):
assert R_BREACH < R_DISRUPTION < R_WRONG_PROCEDURE
assert R_WRONG_PROCEDURE < HEALTH_DRAIN_THRESHOLD
assert HEALTH_DRAIN_THRESHOLD < R_SPAM_QUARANTINE
assert R_SPAM_QUARANTINE < R_SPAM_BLOCK
assert R_SPAM_BLOCK < R_PHISH_BEC_QUARANTINE
assert R_PHISH_BEC_QUARANTINE < R_MALWARE_QUARANTINE
assert R_MALWARE_QUARANTINE < R_PERFECT
def test_partial_alias(self):
assert R_PARTIAL == R_MALWARE_QUARANTINE
def test_health_drain_covers_breach_disruption_wrong(self):
assert R_BREACH < HEALTH_DRAIN_THRESHOLD
assert R_DISRUPTION < HEALTH_DRAIN_THRESHOLD
assert R_WRONG_PROCEDURE < HEALTH_DRAIN_THRESHOLD
def test_cautious_scores_never_drain_health(self):
assert R_SPAM_QUARANTINE >= HEALTH_DRAIN_THRESHOLD
assert R_SPAM_BLOCK >= HEALTH_DRAIN_THRESHOLD
assert R_PHISH_BEC_QUARANTINE >= HEALTH_DRAIN_THRESHOLD
assert R_MALWARE_QUARANTINE >= HEALTH_DRAIN_THRESHOLD
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# GRADE_ACTION β PERFECT MATCH
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestPerfectTriage:
@pytest.mark.parametrize("action,etype", [
("MOVE_TO_SPAM", "SPAM"),
("BLOCK_DOMAIN", "PHISH"),
("MARK_SAFE", "SAFE"),
("QUARANTINE", "MALWARE"),
("QUARANTINE", "BEC"),
("BLOCK_DOMAIN", "BEC"),
("QUARANTINE", "PHISH"),
])
def test_perfect_match(self, action, etype):
reward, msg = grade_action(action, action, etype)
assert reward == R_PERFECT
assert "PERFECT_TRIAGE" in msg
def test_case_insensitive_action(self):
reward, _ = grade_action("move_to_spam", "MOVE_TO_SPAM", "SPAM")
assert reward == R_PERFECT
def test_case_insensitive_type(self):
reward, _ = grade_action("MARK_SAFE", "MARK_SAFE", "safe")
assert reward == R_PERFECT
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# GRADE_ACTION β SECURITY BREACH
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestSecurityBreach:
@pytest.mark.parametrize("etype", ["PHISH", "BEC", "MALWARE", "SPAM"])
def test_mark_safe_on_any_threat_is_breach(self, etype):
"""BUG FIX coverage: SPAM + MARK_SAFE must return R_BREACH (not R_WRONG_PROCEDURE)."""
reward, msg = grade_action("MARK_SAFE", "BLOCK_DOMAIN", etype)
assert reward == R_BREACH
assert "SECURITY_BREACH" in msg
def test_spam_mark_safe_is_breach_not_wrong_procedure(self):
"""Regression: the original code excluded SPAM from _THREAT_TYPES."""
reward, _ = grade_action("MARK_SAFE", "MOVE_TO_SPAM", "SPAM")
assert reward == R_BREACH, (
"SPAM + MARK_SAFE must be R_BREACH β SPAM was excluded from "
"_THREAT_TYPES in the v1.0.2 bug"
)
assert reward < HEALTH_DRAIN_THRESHOLD # must drain health
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# GRADE_ACTION β BUSINESS DISRUPTION
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestBusinessDisruption:
@pytest.mark.parametrize("blocking_action", ["BLOCK_DOMAIN", "QUARANTINE", "MOVE_TO_SPAM"])
def test_blocking_safe_email_is_disruption(self, blocking_action):
reward, msg = grade_action(blocking_action, "MARK_SAFE", "SAFE")
assert reward == R_DISRUPTION
assert "BUSINESS_DISRUPTION" in msg
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# GRADE_ACTION β PARTIAL CREDIT
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestPartialCredit:
def test_malware_quarantine(self):
reward, msg = grade_action("QUARANTINE", "BLOCK_DOMAIN", "MALWARE")
assert reward == R_MALWARE_QUARANTINE
assert "CAUTIOUS" in msg
def test_phish_quarantine(self):
reward, msg = grade_action("QUARANTINE", "BLOCK_DOMAIN", "PHISH")
assert reward == R_PHISH_BEC_QUARANTINE
assert "UNDER_RESPONSE" in msg
def test_bec_quarantine(self):
reward, msg = grade_action("QUARANTINE", "BLOCK_DOMAIN", "BEC")
assert reward == R_PHISH_BEC_QUARANTINE
def test_spam_block_domain(self):
reward, msg = grade_action("BLOCK_DOMAIN", "MOVE_TO_SPAM", "SPAM")
assert reward == R_SPAM_BLOCK
assert "OVER_ESCALATION" in msg
def test_spam_quarantine(self):
reward, msg = grade_action("QUARANTINE", "MOVE_TO_SPAM", "SPAM")
assert reward == R_SPAM_QUARANTINE
assert "OVER_ESCALATION" in msg
@pytest.mark.parametrize("action", ["QUARANTINE", "BLOCK_DOMAIN", "MOVE_TO_SPAM"])
def test_partial_credits_never_drain_health(self, action):
reward, _ = grade_action(action, "MOVE_TO_SPAM", "SPAM")
assert reward >= HEALTH_DRAIN_THRESHOLD
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# GRADE_ACTION β WRONG PROCEDURE / INVALID
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestWrongProcedure:
def test_malware_block_domain_is_wrong_procedure(self):
"""lv4 / lv8: expected QUARANTINE, agent picks BLOCK_DOMAIN."""
reward, msg = grade_action("BLOCK_DOMAIN", "QUARANTINE", "MALWARE")
assert reward == R_WRONG_PROCEDURE
assert "INCORRECT_PROCEDURE" in msg
def test_invalid_action_token(self):
reward, msg = grade_action("DELETE", "QUARANTINE", "MALWARE")
assert reward == R_WRONG_PROCEDURE
assert "INVALID_ACTION" in msg
def test_invalid_action_drains_health(self):
reward, _ = grade_action("NONSENSE", "QUARANTINE", "PHISH")
assert reward < HEALTH_DRAIN_THRESHOLD
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# CALCULATE_OVERALL_SCORE
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestCalculateOverallScore:
def test_empty_list_returns_r_breach(self):
assert calculate_overall_score([]) == R_BREACH
def test_all_perfect_returns_r_perfect(self):
assert calculate_overall_score([R_PERFECT, R_PERFECT, R_PERFECT]) == R_PERFECT
def test_result_never_exceeds_r_perfect(self):
assert calculate_overall_score([1.0, 1.0]) == R_PERFECT
def test_result_never_below_r_breach(self):
assert calculate_overall_score([0.0, 0.0]) == R_BREACH
def test_easy_all_perfect(self):
scores = [R_PERFECT] * 3
assert calculate_overall_score(scores) == R_PERFECT
def test_medium_mixed(self):
scores = [R_PERFECT, R_BREACH, R_PERFECT, R_PHISH_BEC_QUARANTINE]
result = calculate_overall_score(scores)
expected = round((R_PERFECT + R_BREACH + R_PERFECT + R_PHISH_BEC_QUARANTINE) / 4, 4)
assert result == expected
def test_hard_mostly_bad(self):
scores = [R_BREACH, R_DISRUPTION, R_PERFECT]
result = calculate_overall_score(scores)
assert result < PASS_THRESHOLD
def test_returns_four_decimal_places(self):
result = calculate_overall_score([R_PERFECT, R_BREACH])
assert result == round(result, 4)
def test_single_perfect_step(self):
assert calculate_overall_score([R_PERFECT]) == R_PERFECT
def test_single_breach_step(self):
assert calculate_overall_score([R_BREACH]) == R_BREACH
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# EDGE CASES β INPUT NORMALIZATION
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class TestInputNormalization:
def test_whitespace_padded_action(self):
"""Actions with leading/trailing spaces should still match."""
reward, msg = grade_action(" MARK_SAFE ", "MARK_SAFE", "SAFE")
assert reward == R_PERFECT
assert "PERFECT_TRIAGE" in msg
def test_mixed_case_email_type_phish(self):
reward, _ = grade_action("MARK_SAFE", "BLOCK_DOMAIN", "Phish")
assert reward == R_BREACH
def test_mixed_case_email_type_bec(self):
reward, _ = grade_action("QUARANTINE", "BLOCK_DOMAIN", "bEc")
assert reward == R_PHISH_BEC_QUARANTINE
def test_tab_in_action(self):
reward, _ = grade_action("\tQUARANTINE\t", "QUARANTINE", "MALWARE")
assert reward == R_PERFECT
def test_empty_action_string(self):
reward, msg = grade_action("", "QUARANTINE", "MALWARE")
assert reward == R_WRONG_PROCEDURE
assert "INVALID_ACTION" in msg
def test_calculate_overall_score_single_breach(self):
assert calculate_overall_score([R_BREACH]) == R_BREACH
def test_calculate_overall_score_all_disruption(self):
result = calculate_overall_score([R_DISRUPTION] * 5)
assert result == R_DISRUPTION
|