"""P7 记分板新增纯函数测试(M3 计划 T8): 成功率/坏源占比/错误分类。"""

import pytest

from tools.soak.scoreboard import (
    inv_errors_classified,
    inv_fault_share,
    inv_success_rate,
)

_KNOWN = ("TransientError", "AllSourcesExhausted", "cancelled")


def _row(source="monkey_1", error=None, cache_hit=0):
    return {"source_name": source, "error": error, "cache_hit": cache_hit}


class TestSuccessRate:
    def test_pass_at_threshold(self):
        inv_success_rate(98, 100, min_rate=0.98)

    def test_fail_below(self):
        with pytest.raises(AssertionError, match="成功率"):
            inv_success_rate(97, 100, min_rate=0.98)

    def test_zero_total_rejected(self):
        with pytest.raises(AssertionError):
            inv_success_rate(0, 0, min_rate=0.98)


class TestFaultShare:
    def test_share_within_threshold(self):
        rows = [_row()] * 9 + [_row("monkey_3")]
        inv_fault_share(rows, ["monkey_3", "monkey_4"], max_share=0.15)

    def test_share_exceeds(self):
        rows = [_row()] * 4 + [_row("monkey_3")] * 2
        with pytest.raises(AssertionError, match="坏源尝试占比"):
            inv_fault_share(rows, ["monkey_3"], max_share=0.15)

    def test_cache_rows_excluded(self):
        rows = [_row(cache_hit=1)] * 100 + [_row("monkey_3")] + [_row()] * 9
        inv_fault_share(rows, ["monkey_3"], max_share=0.15)  # 缓存行不入分母


class TestErrorsClassified:
    def test_known_prefixes_pass(self):
        rows = [_row(error="TransientError: m1 超时"), _row(error="cancelled"), _row()]
        inv_errors_classified(rows, _KNOWN)

    def test_unknown_prefix_fails(self):
        rows = [_row(error="KeyError: 'oops'")]
        with pytest.raises(AssertionError, match="未分类"):
            inv_errors_classified(rows, _KNOWN)


class TestRssAbsolute:
    def test_within_bound(self):
        from tools.soak.scoreboard import inv_rss_absolute

        inv_rss_absolute([120.0, 180.0, 150.0], max_mb=500.0)

    def test_peak_exceeds(self):
        from tools.soak.scoreboard import inv_rss_absolute

        with pytest.raises(AssertionError, match="峰值"):
            inv_rss_absolute([120.0, 690.0, 200.0], max_mb=500.0)  # verifier I1 实测形态

    def test_empty_rejected(self):
        from tools.soak.scoreboard import inv_rss_absolute

        with pytest.raises(AssertionError):
            inv_rss_absolute([], max_mb=500.0)
