"""Per-option explanation validation and serialization.""" import unittest import test_quiz_builder as fixtures from app.models.question import Question from app.routers import questions, quizzes class OptionExplanationTests(unittest.TestCase): def setUp(self): self.bank = fixtures.BuilderTests() self.bank.setUp() self.client = self.bank.client def tearDown(self): self.bank.tearDown() def create_payload(self, **overrides): payload = {"question_text": "Explained", "question_type": "mcq", "options": ["yes", "no"], "correct_answer": "yes", "option_explanations": {"yes": "Right because", "no": "Wrong because"}} payload.update(overrides) return payload def test_create_validates_and_serializes_explanations(self): self.bank.user = self.bank.mod created = self.client.post('/questions/create', json=self.create_payload()) self.assertIn(created.status_code, (200, 201), created.text) question_id = created.json()['id'] bank = self.client.get('/questions/bank', params={'q': 'Explained'}).json() row = next(r for r in bank['questions'] if r['id'] == question_id) self.assertEqual(row['option_explanations'], {"yes": "Right because", "no": "Wrong because"}) for bad in ( {"option_explanations": {"nope": "x"}}, {"option_explanations": "not-a-dict"}, {"option_explanations": {"yes": "x" * 2001}}, ): response = self.client.post('/questions/create', json=self.create_payload(**bad)) self.assertIn(response.status_code, (400, 422), bad) def test_edit_requires_keys_to_match_current_options(self): self.bank.user = self.bank.mod response = self.client.patch('/questions/3', json={"option_explanations": {"yes": "Correct path", "no": "Incorrect path"}}) self.assertEqual(response.status_code, 200, response.text) self.assertEqual(self.bank.db.get(Question, 3).option_explanations, {"yes": "Correct path", "no": "Incorrect path"}) # Changing options without updating keys is rejected atomically. response = self.client.patch('/questions/3', json={"options": ["a", "b"], "correct_answer": "a", "option_explanations": {"yes": "stale"}}) self.assertEqual(response.status_code, 400, response.text) self.assertEqual(self.bank.db.get(Question, 3).options, ["yes", "no"]) def test_key_points_validate_serialize_and_smart_link(self): from app.models.article import Article article = Article(slug='kp-target', title='Key point target', content='Intro', sections=[{'id': 'e' * 32, 'slug': 'kp', 'title': 'KP section', 'content': 'Body'}], user_id=3, status='published') self.bank.db.add(article) self.bank.db.commit() self.bank.user = self.bank.mod good = [{'text': 'Brief take-away', 'article_id': article.id, 'article_section_id': 'e' * 32}] created = self.client.post('/questions/create', json=self.create_payload(key_points=good)) self.assertIn(created.status_code, (200, 201), created.text) question_id = created.json()['id'] bank = self.client.get('/questions/bank', params={'q': 'Explained'}).json() row = next(r for r in bank['questions'] if r['id'] == question_id) self.assertEqual(row['key_points'][0]['article_id'], article.id) for bad in ( [{'text': 'x', 'article_id': 999}], [{'text': 'x', 'article_id': article.id, 'article_section_id': 'bad'}], [{'text': ''}], 'not-a-list', [{'text': 'x'} for _ in range(13)], ): response = self.client.patch(f'/questions/{question_id}', json={'key_points': bad}) self.assertIn(response.status_code, (400, 422), bad) response = self.client.patch(f'/questions/{question_id}', json={'key_points': []}) self.assertEqual(response.status_code, 200, response.text) self.assertIsNone(self.bank.db.get(fixtures.Question, question_id).key_points or None) # Study reveal carries key points. self.bank.user = self.bank.mod self.client.patch('/questions/1', json={'key_points': good}) self.bank.user = self.bank.owner attempt = self.client.post('/attempts/start?quiz_id=1&mode=study').json()['id'] detail = self.client.get(f'/quizzes/1?attempt_id={attempt}').json() self.assertEqual(detail['questions'][0]['key_points'][0]['article_section_id'], 'e' * 32) def test_quiz_reveal_and_editor_route_carry_explanations(self): self.bank.user = self.bank.mod self.client.patch('/questions/1', json={"option_explanations": {"yes": "Preferred response", "no": "Distractor"}}) self.bank.user = self.bank.owner attempt = self.client.post('/attempts/start?quiz_id=1&mode=study').json()['id'] detail = self.client.get(f'/quizzes/1?attempt_id={attempt}').json() self.assertEqual(detail['questions'][0]['option_explanations'], {"yes": "Preferred response", "no": "Distractor"}) self.bank.user = self.bank.mod response = self.client.patch('/quizzes/1/questions/1', json={"option_explanations": {"nope": "bad"}}) self.assertEqual(response.status_code, 400, response.text) response = self.client.patch('/quizzes/1/questions/1', json={"option_explanations": {"no": "Updated distractor"}}) self.assertEqual(response.status_code, 200, response.text) self.assertEqual(self.bank.db.get(Question, 1).option_explanations, {"no": "Updated distractor"}) if __name__ == '__main__': unittest.main()