summaryrefslogtreecommitdiff
path: root/tests/test_models.py
blob: d19b5369b9bb1e1afe151255f358ea9354f6e43c (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
from __future__ import annotations

import pytest
from pydantic import ValidationError

from gap_pipeline.models import (
    JudgeVerdict,
    KernelCandidate,
    KernelPlan,
    ProofPlanDAG,
)


def test_valid_plan_and_candidate(plan_dict: dict, candidate_dict: dict) -> None:
    plan = KernelPlan.model_validate(plan_dict)
    candidate = KernelCandidate.model_validate(candidate_dict)
    assert len(plan.core_steps) == 2
    assert candidate.question.startswith("Let x")


def test_plan_requires_slots(plan_dict: dict) -> None:
    plan_dict["mutable_slots"] = {}
    with pytest.raises(ValidationError, match="mutable slot"):
        KernelPlan.model_validate(plan_dict)


def test_plan_rejects_more_than_five_core_steps(plan_dict: dict) -> None:
    plan_dict["core_steps"] = [f"step {index}" for index in range(6)]
    with pytest.raises(ValidationError):
        KernelPlan.model_validate(plan_dict)


def test_candidate_requires_question_and_solution(candidate_dict: dict) -> None:
    candidate_dict["solution"] = ""
    with pytest.raises(ValidationError, match="non-empty"):
        KernelCandidate.model_validate(candidate_dict)


def test_accept_verdict_cannot_report_blocking_issue() -> None:
    with pytest.raises(ValidationError, match="cannot contain"):
        JudgeVerdict(
            verdict="accept",
            step_by_step_check="checked",
            blocking_issues="an error",
        )


def test_path_dag_is_derived_from_ordered_plan(plan_dict: dict) -> None:
    dag = ProofPlanDAG.from_plan(KernelPlan.model_validate(plan_dict))
    assert [node.node_id for node in dag.nodes] == ["n1", "n2"]
    assert dag.nodes[0].dependencies == []
    assert dag.nodes[1].dependencies == ["n1"]
    assert dag.terminal_node_id == "n2"
    assert dag.method_labels_payload()[0].startswith("n1: ")


def test_dag_rejects_cycle() -> None:
    with pytest.raises(ValidationError, match="cycle"):
        ProofPlanDAG.model_validate(
            {
                "nodes": [
                    {
                        "node_id": "n1",
                        "method_label": "first",
                        "dependencies": ["n2"],
                    },
                    {
                        "node_id": "n2",
                        "method_label": "second",
                        "dependencies": ["n1"],
                    },
                ],
                "terminal_node_id": "n2",
            }
        )


def test_path_dag_rejects_skipped_dependency() -> None:
    with pytest.raises(ValidationError, match="lead to the terminal"):
        ProofPlanDAG.model_validate(
            {
                "nodes": [
                    {
                        "node_id": "n1",
                        "method_label": "first",
                        "dependencies": [],
                    },
                    {
                        "node_id": "n2",
                        "method_label": "second",
                        "dependencies": [],
                    },
                ],
                "terminal_node_id": "n2",
            }
        )


def test_judge_verdict_requires_every_dag_node(plan_dict: dict) -> None:
    dag = ProofPlanDAG.from_plan(KernelPlan.model_validate(plan_dict))
    verdict = JudgeVerdict(
        verdict="accept",
        step_by_step_check="n1 is valid; n2 is valid",
    )
    assert verdict.validate_coverage(dag) is verdict

    with pytest.raises(ValueError, match="does not cover DAG nodes.*n2"):
        JudgeVerdict(
            verdict="accept",
            step_by_step_check="n1 is valid",
        ).validate_coverage(dag)


def test_dag_labels_must_match_prompt_a_plan(plan_dict: dict) -> None:
    plan = KernelPlan.model_validate(plan_dict)
    dag = ProofPlanDAG.from_plan(plan)
    changed_plan = plan.model_copy(
        update={"core_steps": ["a different method", plan.core_steps[1]]}
    )
    with pytest.raises(ValueError, match="must equal Prompt-A core_steps"):
        dag.validate_plan(changed_plan)