elemes/services/tests/test_prerequisite_logic.py

150 lines
4.9 KiB
Python

"""Unit test logika prasyarat: status skor & ambang minimum (`min: N%`).
Kontrak yang diuji:
- Status `scored` (mis. "3/4") memenuhi prasyarat untuk skor berapa pun.
- `completed` selalu memenuhi.
- `not_started` / progress kosong / status tidak dikenal → belum terpenuhi.
- Bila ada ambang `min_percent`, skor wajib earned/total >= ambang.
"""
import pytest
pytestmark = pytest.mark.unit
from services.lesson_service import (
_parse_prerequisite_bullet,
get_missing_prerequisites,
is_prerequisite_met,
)
# ---------------------------------------------------------------------------
# _parse_prerequisite_bullet
# ---------------------------------------------------------------------------
def test_parse_bullet_link_without_threshold():
assert _parse_prerequisite_bullet("- [Kuis](lesson/quiz.md)") == ("quiz", None)
def test_parse_bullet_link_with_percent_threshold():
assert _parse_prerequisite_bullet("- [Kuis](lesson/quiz.md) min: 75%") == ("quiz", 75.0)
def test_parse_bullet_link_without_colon():
assert _parse_prerequisite_bullet("- [Kuis](lesson/quiz.md) min 50%") == ("quiz", 50.0)
def test_parse_bullet_link_with_minimum_word():
assert _parse_prerequisite_bullet("- [Kuis](lesson/quiz.md) minimum: 80%") == ("quiz", 80.0)
def test_parse_bullet_link_with_comma_decimal():
assert _parse_prerequisite_bullet("- [Kuis](lesson/quiz.md) min: 62,5%") == ("quiz", 62.5)
def test_parse_bullet_plain_text_with_threshold():
assert _parse_prerequisite_bullet("- Kuis min 75%") == ("Kuis", 75.0)
def test_parse_bullet_plain_text_without_threshold():
assert _parse_prerequisite_bullet("- Dasar") == ("Dasar", None)
def test_parse_bullet_trailing_comma_before_threshold():
assert _parse_prerequisite_bullet("- [Kuis](lesson/quiz.md), min: 75%") == ("quiz", 75.0)
@pytest.mark.parametrize("bullet", ["- Tidak ada", "- None", "-", ""])
def test_parse_bullet_skip_values(bullet):
assert _parse_prerequisite_bullet(bullet) is None
def test_parse_bullet_path_with_folder():
assert _parse_prerequisite_bullet("- [Dasar](lesson/dasar.md)") == ("dasar", None)
# ---------------------------------------------------------------------------
# is_prerequisite_met
# ---------------------------------------------------------------------------
def test_met_no_progress():
assert is_prerequisite_met(None, "dasar") is False
assert is_prerequisite_met({}, "dasar") is False
def test_met_not_started():
assert is_prerequisite_met({"dasar": ""}, "dasar") is False
assert is_prerequisite_met({"dasar": "not_started"}, "dasar") is False
def test_met_completed():
assert is_prerequisite_met({"dasar": "completed"}, "dasar") is True
def test_met_any_score_satisfies():
assert is_prerequisite_met({"quiz": "3/4"}, "quiz") is True
assert is_prerequisite_met({"quiz": "0/4"}, "quiz") is True
assert is_prerequisite_met({"quiz": "4/4"}, "quiz") is True
def test_met_unknown_status():
assert is_prerequisite_met({"dasar": "xyz"}, "dasar") is False
def test_met_missing_slug():
assert is_prerequisite_met({"lain": "completed"}, "dasar") is False
def test_met_spec_dict_without_threshold():
assert is_prerequisite_met({"quiz": "2/4"}, {"slug": "quiz", "min_percent": None}) is True
def test_met_threshold_below():
spec = {"slug": "quiz", "min_percent": 75.0}
assert is_prerequisite_met({"quiz": "2/4"}, spec) is False
assert is_prerequisite_met({"quiz": "3/4"}, spec) is True
def test_met_threshold_exact_boundary():
spec = {"slug": "quiz", "min_percent": 62.5}
assert is_prerequisite_met({"quiz": "4/8"}, spec) is False
assert is_prerequisite_met({"quiz": "5/8"}, spec) is True
def test_met_completed_ignores_threshold():
spec = {"slug": "quiz", "min_percent": 90.0}
assert is_prerequisite_met({"quiz": "completed"}, spec) is True
def test_met_threshold_with_zero_total():
spec = {"slug": "quiz", "min_percent": 75.0}
# total 0 tidak valid → tidak terpenuhi
assert is_prerequisite_met({"quiz": "0/0"}, spec) is False
# ---------------------------------------------------------------------------
# get_missing_prerequisites
# ---------------------------------------------------------------------------
def test_missing_returns_only_unmet_in_order():
progress = {"a": "completed", "b": "3/4", "c": ""}
specs = [{"slug": "a"}, {"slug": "b"}, {"slug": "c"}]
assert get_missing_prerequisites(progress, specs) == ["c"]
def test_missing_accepts_plain_slugs():
progress = {"b": "0/4"}
assert get_missing_prerequisites(progress, ["a", "b"]) == ["a"]
def test_missing_threshold():
progress = {"q": "2/4"}
assert get_missing_prerequisites(progress, [{"slug": "q", "min_percent": 75.0}]) == ["q"]
progress2 = {"q": "3/4"}
assert get_missing_prerequisites(progress2, [{"slug": "q", "min_percent": 75.0}]) == []
def test_missing_empty_and_none():
assert get_missing_prerequisites({}, []) == []
assert get_missing_prerequisites(None, [{"slug": "a"}]) == ["a"]