Spaces:
Running on Zero
Running on Zero
Download tests/test_validation.py from RealFalconsAI/DecisionLab: direct link, hf CLI and curl.
- Browser
- Download file 5.92 kB
-
https://huggingface.co/spaces/RealFalconsAI/DecisionLab/resolve/main/tests/test_validation.py
- Command line
-
hf download hf://spaces/RealFalconsAI/DecisionLab/tests/test_validation.py
-
curl -L -o test_validation.py https://huggingface.co/spaces/RealFalconsAI/DecisionLab/resolve/main/tests/test_validation.py
5.92 kB
| """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), []) | |