File size: 5,920 Bytes
66ee87e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Request validation for /api/decide questions (app/validation.py)."""
import unittest

from app.validation import validate_questions

CHOICE = {"type": "choice", "instructions": "Which team?", "criteria": {"a": "billing", "b": "shipping"}}


class ValidateQuestionsTest(unittest.TestCase):
    def assertRejects(self, questions, message, max_options=40):
        with self.assertRaises(ValueError) as ctx:
            validate_questions(questions, max_options=max_options)
        self.assertEqual(str(ctx.exception), message)

    def test_accepts_choice_score_and_noul(self):
        validate_questions({
            "team": CHOICE,
            "urgency": {"type": "score", "instructions": "How urgent?", "criteria": ["Low", "High"]},
            "angry": {"type": "noul", "instructions": "The customer sounds angry"},
        }, max_options=40)

    def test_type_defaults_to_choice(self):
        q = {k: v for k, v in CHOICE.items() if k != "type"}
        validate_questions({"team": q}, max_options=40)

    def test_accepts_question_key_instead_of_instructions(self):
        validate_questions({"angry": {"type": "noul", "question": "Angry?"}}, max_options=40)

    def test_accepts_options_key_instead_of_criteria(self):
        validate_questions({"t": {"type": "choice", "instructions": "x", "options": ["a", "b"]}}, max_options=40)

    def test_rejects_empty_question_set(self):
        self.assertRejects({}, "Add at least one question.")

    def test_rejects_unknown_type(self):
        self.assertRejects({"q": {"type": "rank", "instructions": "x"}},
                           "Question 'q': type must be choice, score or noul (got 'rank').")

    def test_rejects_missing_instructions(self):
        self.assertRejects({"q": {"type": "noul"}}, "Question 'q': add 'instructions' with the question text.")

    def test_rejects_fewer_than_two_options(self):
        self.assertRejects({"q": {"type": "choice", "instructions": "x", "criteria": {"a": "only"}}},
                           "Question 'q': give 'criteria' with at least 2 options.")

    def test_rejects_non_collection_criteria(self):
        self.assertRejects({"q": {"type": "score", "instructions": "x", "criteria": "low,high"}},
                           "Question 'q': give 'criteria' with at least 2 options.")

    def test_accepts_exactly_max_options(self):
        crit = {str(i): "o" for i in range(5)}
        validate_questions({"q": {"type": "choice", "instructions": "x", "criteria": crit}}, max_options=5)

    def test_rejects_more_than_max_options(self):
        crit = {str(i): "o" for i in range(6)}
        self.assertRejects({"q": {"type": "choice", "instructions": "x", "criteria": crit}},
                           "Question 'q': 6 options is over the lab's limit of 5.", max_options=5)

    def test_noul_needs_no_criteria(self):
        validate_questions({"q": {"type": "noul", "instructions": "x"}}, max_options=40)


if __name__ == "__main__":
    unittest.main()


from app.validation import LIMITS, validate_models


class SizeLimitTest(unittest.TestCase):
    def assertRejects(self, questions, message):
        with self.assertRaises(ValueError) as ctx:
            validate_questions(questions, max_options=40)
        self.assertEqual(str(ctx.exception), message)

    def test_too_many_questions(self):
        qs = {f"q{i}": {"type": "noul", "instructions": "x"} for i in range(LIMITS["questions"] + 1)}
        self.assertRejects(qs, f"At most {LIMITS['questions']} questions per request (got {LIMITS['questions'] + 1}).")

    def test_question_name_too_long(self):
        name = "n" * (LIMITS["name"] + 1)
        self.assertRejects({name: {"type": "noul", "instructions": "x"}},
                           f"Question names are limited to {LIMITS['name']} characters.")

    def test_instructions_too_long(self):
        self.assertRejects({"q": {"type": "noul", "instructions": "x" * (LIMITS["instructions"] + 1)}},
                           f"Question 'q': instructions are limited to {LIMITS['instructions']} characters.")

    def test_option_text_too_long(self):
        crit = {"a": "x" * (LIMITS["option"] + 1), "b": "y"}
        self.assertRejects({"q": {"type": "choice", "instructions": "x", "criteria": crit}},
                           f"Question 'q': each option (key and text) is limited to {LIMITS['option']} characters.")

    def test_option_key_too_long(self):
        crit = {"k" * (LIMITS["option"] + 1): "x", "b": "y"}
        self.assertRejects({"q": {"type": "choice", "instructions": "x", "criteria": crit}},
                           f"Question 'q': each option (key and text) is limited to {LIMITS['option']} characters.")

    def test_non_text_option_is_rejected(self):
        self.assertRejects({"q": {"type": "choice", "instructions": "x", "criteria": {"a": {"nested": 1}, "b": "y"}}},
                           "Question 'q': options must be text.")

    def test_limits_at_the_boundary_are_accepted(self):
        crit = {"a": "x" * LIMITS["option"], "b": "y"}
        validate_questions({"n" * LIMITS["name"]: {"type": "choice", "instructions": "i" * LIMITS["instructions"],
                                                    "criteria": crit}}, max_options=40)


class ValidateModelsTest(unittest.TestCase):
    KNOWN = ["a", "b", "laya"]

    def test_none_means_every_model(self):
        self.assertEqual(validate_models(None, self.KNOWN), self.KNOWN)

    def test_duplicates_are_dropped_keeping_order(self):
        self.assertEqual(validate_models(["b", "a", "b"], self.KNOWN), ["b", "a"])

    def test_unknown_key_is_rejected_by_name(self):
        with self.assertRaises(ValueError) as ctx:
            validate_models(["a", "zzz"], self.KNOWN)
        self.assertEqual(str(ctx.exception), "Unknown model 'zzz'. Known models: a, b, laya.")

    def test_empty_list_is_allowed(self):
        self.assertEqual(validate_models([], self.KNOWN), [])