"""The answer side of a question, and who is handed it. Run: DATABASE_URL=sqlite:///:memory: PYTHONPATH=backend python -m unittest discover -s backend/tests """ import unittest import test_quiz_builder as fixtures class QuestionDetailAccessTests(unittest.TestCase): """Being able to reach a question is not the same as having earned its answer. The bank listing has always nulled the answer side for anybody who does not write the question. /questions/detail/{id} did not: anybody signed in could ask for a question by id and be handed the correct option, the explanation, the per-option reasoning and the key points for a question they had never sat. Same rule, both routes. """ def setUp(self): self.bank = fixtures.BuilderTests() self.bank.setUp() self.client = self.bank.client def tearDown(self): self.bank.tearDown() def detail(self, question_id=4): response = self.client.get(f"/questions/detail/{question_id}") self.assertEqual(response.status_code, 200, response.text) return response.json() def test_a_reader_gets_the_stem_and_none_of_the_answer(self): # Question 4 belongs to the peer; the owner may read it in the bank and # has no business writing it. self.bank.user = self.bank.owner body = self.detail(4) self.assertEqual(body["question_text"], "Question 4") self.assertEqual(body["options"], ["yes", "no"]) self.assertEqual(body["image_path"], "q.png") for field in ("correct_answer", "explanation", "option_explanations", "key_points", "attending_tip", "explanation_image_path"): self.assertIsNone(body[field], field) # And no explanation figure, which is answer-side by definition. self.assertTrue(all(f["role"] == "stem" for f in body["figures"]), body["figures"]) def test_whoever_writes_it_gets_all_of_it(self): self.bank.user = self.bank.mod body = self.detail(4) self.assertEqual(body["correct_answer"], "yes") self.assertEqual(body["explanation"], "Full explanation") self.assertEqual(body["explanation_image_path"], "answer.png") def test_the_figures_route_withholds_the_answer_side_too(self): """It had no check at all, next to a route that has one. Any signed-in account could ask for any question's figures by id and be handed its explanation images — the paths, and the library record that rides on them, whose titles run to "Neonatal Herpes Simplex · Q874". No attempt, and the answer in the title. """ from app.models.media import MediaAsset from app.models.question_media import QuestionMedia self.bank.db.add(MediaAsset(id=41, path="stem.png", title="A stem picture")) self.bank.db.add(MediaAsset(id=42, path="answer.png", title="The answer, named")) self.bank.db.flush() self.bank.db.add(QuestionMedia(question_id=4, media_id=41, role="stem", position=0)) self.bank.db.add(QuestionMedia(question_id=4, media_id=42, role="explanation", position=0)) self.bank.db.commit() self.bank.user = self.bank.owner rows = self.client.get("/questions/detail/4/figures").json() self.assertEqual([row["role"] for row in rows], ["stem"]) self.assertNotIn("answer.png", str(rows)) self.bank.user = self.bank.mod roles = sorted(row["role"] for row in self.client.get("/questions/detail/4/figures").json()) self.assertEqual(roles, ["explanation", "stem"]) def test_writing_a_question_does_not_make_it_yours(self): """Authorship is not a claim the bank honours. Question 3 carries the owner's id, from before the bank stopped belonging to people. Reading its answer takes the admin role, a grant, or an attempt — the same as anybody else's. """ self.bank.user = self.bank.owner self.assertIsNone(self.detail(3)["correct_answer"]) if __name__ == "__main__": unittest.main()