aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/tests/test_drill_deck_stats.py
blob: 3154d429d020aeec6defd63659c3d32f41edfddf (plain)
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
"""Tests for drill-deck-stats.py: prompt-form heuristic + CLI inventory/gate.

Plain python3 script (no third-party deps), so the pure helper imports directly;
the inventory/gate behavior is exercised through the CLI.
"""
from __future__ import annotations

import importlib.util
import subprocess
import sys
from pathlib import Path

import pytest

SCRIPT = Path(__file__).resolve().parents[1] / "drill-deck-stats.py"


@pytest.fixture(scope="module")
def stats():
    spec = importlib.util.spec_from_file_location("drill_deck_stats", SCRIPT)
    assert spec and spec.loader
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


# --- is_prompt_form (pure) ---

def test_is_prompt_form_question_mark(stats):
    assert stats.is_prompt_form("What is DeepSat?") is True


def test_is_prompt_form_imperative_verb(stats):
    assert stats.is_prompt_form("Spell out the orbital regimes") is True


def test_is_prompt_form_imperative_is_case_insensitive(stats):
    assert stats.is_prompt_form("introduce yourself") is True


def test_is_prompt_form_topic_heading_is_not_a_prompt(stats):
    assert stats.is_prompt_form("DeepSat") is False


def test_is_prompt_form_strips_trailing_punctuation_off_first_word(stats):
    assert stats.is_prompt_form("List: the founders") is True


# --- CLI inventory + gate (integration) ---

CLEAN_DECK = """#+TITLE: DeepSat Flashcards

* Section
** What is DeepSat? :drill:
:PROPERTIES:
:ID: card-1
:END:
A satellite company.
"""

DIRTY_DECK = """#+TITLE: DeepSat Org-Drill Flashcards

* Section
** DeepSat :drill:
*** Answer
A satellite company.
"""


def _run(path):
    return subprocess.run(
        [sys.executable, str(SCRIPT), str(path)],
        capture_output=True, text=True,
    )


def test_cli_clean_deck_exits_zero(tmp_path):
    f = tmp_path / "clean.org"
    f.write_text(CLEAN_DECK)
    r = _run(f)
    assert r.returncode == 0
    assert "clean" in r.stdout


def test_cli_dirty_deck_warns_and_exits_one(tmp_path):
    f = tmp_path / "dirty.org"
    f.write_text(DIRTY_DECK)
    r = _run(f)
    assert r.returncode == 1
    assert "WARN" in r.stdout
    assert "org-drill" in r.stdout.lower()  # title-jargon audit fired


def test_cli_missing_file_exits_two(tmp_path):
    r = _run(tmp_path / "nope.org")
    assert r.returncode == 2


NO_TITLE_DECK = """* Section
** What is DeepSat? :drill:
:PROPERTIES:
:ID: card-1
:END:
A satellite company.
"""

# Two cards, only one PROPERTIES drawer.
PROP_MISMATCH_DECK = """#+TITLE: DeepSat Flashcards

* Section
** What is DeepSat? :drill:
A satellite company.
** Who founded it? :drill:
:PROPERTIES:
:ID: card-2
:END:
The team.
"""


def test_cli_missing_title_warns_and_exits_one(tmp_path):
    f = tmp_path / "notitle.org"
    f.write_text(NO_TITLE_DECK)
    r = _run(f)
    assert r.returncode == 1
    assert "no #+TITLE" in r.stdout


def test_cli_properties_count_mismatch_warns_and_exits_one(tmp_path):
    f = tmp_path / "mismatch.org"
    f.write_text(PROP_MISMATCH_DECK)
    r = _run(f)
    assert r.returncode == 1
    assert "does not match card count" in r.stdout