def test_csv_export(now, client, answer_type, answer): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() editor = UserFactory() rs.add_editor(editor) reader = UserFactory() rs.add_reader(reader) q = QuestionFactory( question_text="foo", reader_study=rs, answer_type=answer_type ) a = AnswerFactory(question=q, answer=answer) a.images.add(im) a.save() response = get_view_for_user( viewname="api:reader-study-export-answers", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.get, content_type="application/json", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers assert ( f'filename="{rs.slug}-answers-2020-01-01T00:00:00+00:00.csv"' in headers ) assert a.question.question_text in content assert a.question.get_answer_type_display() in content assert str(a.question.required) in content assert a.question.get_image_port_display() in content if isinstance(answer, dict): for key in answer: assert key in content else: assert re.sub(r"[\n\r\t]", " ", str(a.answer)) in content assert im.name in content assert a.creator.username in content response = get_view_for_user( viewname="api:reader-study-export-answers", reverse_kwargs={"pk": rs.pk}, user=reader, client=client, method=client.get, content_type="application/json", ) assert response.status_code == 404
def test_assign_score(settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory(use_display_sets=False) im = ImageFactory() q1 = QuestionFactory(reader_study=rs) q2 = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.MULTIPLE_CHOICE) e, r1, r2 = UserFactory(), UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(e) rs.add_reader(r1) rs.add_reader(r2) with capture_on_commit_callbacks(execute=True): a1 = AnswerFactory(question=q1, creator=r1, answer="foo") a1.images.add(im) assert a1.score is None with capture_on_commit_callbacks(execute=True): gt = AnswerFactory(question=q1, creator=e, answer="foo", is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 with capture_on_commit_callbacks(execute=True): a2 = AnswerFactory(question=q1, creator=r2, answer="foo") a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0 with capture_on_commit_callbacks(execute=True): a1 = AnswerFactory(question=q2, creator=r1, answer=[]) a1.images.add(im) a1.refresh_from_db() assert a1.score is None with capture_on_commit_callbacks(execute=True): gt = AnswerFactory(question=q2, creator=e, answer=[], is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 with capture_on_commit_callbacks(execute=True): a2 = AnswerFactory(question=q2, creator=r2, answer=[]) a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0
def test_mine(client): im1, im2 = ImageFactory(), ImageFactory() rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory() rs1.images.add(im1) rs2.images.add(im2) reader = UserFactory() rs1.add_reader(reader) rs2.add_reader(reader) q1 = QuestionFactory( reader_study=rs1, answer_type=Question.ANSWER_TYPE_BOOL ) q2 = QuestionFactory( reader_study=rs2, answer_type=Question.ANSWER_TYPE_BOOL ) a1 = AnswerFactory(question=q1, creator=reader, answer=True) a1.images.add(im1) a2 = AnswerFactory(question=q2, creator=reader, answer=True) a2.images.add(im2) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, content_type="application/json", ) response = response.json() assert response["count"] == 2 response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, data={"question__reader_study": rs1.pk}, content_type="application/json", ) response = response.json() assert response["count"] == 1 assert response["results"][0]["pk"] == str(a1.pk) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, data={"question__reader_study": rs2.pk}, content_type="application/json", ) response = response.json() assert response["count"] == 1 assert response["results"][0]["pk"] == str(a2.pk)
def test_help_markdown_is_scrubbed(client): rs = ReaderStudyFactory( help_text_markdown="<b>My Help Text</b><script>naughty</script>") u = UserFactory() rs.add_reader(u) response = get_view_for_user(client=client, url=rs.api_url, user=u) assert response.status_code == 200 assert response.json()["help_text"] == "<p><b>My Help Text</b>naughty</p>"
def test_question_delete_disabled_for_questions_with_answers(client): rs = ReaderStudyFactory(use_display_sets=False) r1, editor = UserFactory(), UserFactory() rs.add_reader(r1) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.AnswerType.BOOL, ) AnswerFactory(creator=r1, question=q, answer=True) assert Answer.objects.count() == 1 assert Question.objects.count() == 1 assert not q.is_fully_editable response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 403 assert Question.objects.count() == 1 # if answer is deleted, deletion of the question is possible again get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=editor, ) assert Answer.objects.count() == 0 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 302 assert Question.objects.count() == 0
def test_assign_answer_image(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory() im = ImageFactory() editor, reader = UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(editor) rs.add_reader(reader) question = QuestionFactory( reader_study=rs, answer_type=Question.ANSWER_TYPE_POLYGON_IMAGE ) us = RawImageUploadSessionFactory(creator=reader) answer = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us.pk)}, ) f = StagedFileFactory( file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha" ) RawImageFileFactory(upload_session=us, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us.pk}, user=reader, client=client, method=client.patch, data={"answer": str(answer.pk)}, content_type="application/json", ) assert response.status_code == 200 answer.refresh_from_db() image = us.image_set.first() assert answer.answer_image == image assert reader.has_perm("view_image", image) assert editor.has_perm("view_image", image)
def test_rs_list_permissions(client): # Users should login response = get_view_for_user(viewname="reader-studies:list", client=client) assert response.status_code == 200 assert "Add a new reader study" not in response.rendered_content creator = get_rs_creator() # Creators should be able to see the create button response = get_view_for_user(viewname="reader-studies:list", client=client, user=creator) assert response.status_code == 200 assert "Add a new reader study" in response.rendered_content rs1, rs2 = ReaderStudyFactory(use_display_sets=False), ReaderStudyFactory( use_display_sets=False) reader1 = UserFactory() # Readers should only be able to see the studies they have access to response = get_view_for_user(viewname="reader-studies:list", client=client, user=reader1) assert response.status_code == 200 assert "Add a new reader study" not in response.rendered_content assert rs1.slug not in response.rendered_content assert rs2.slug not in response.rendered_content rs1.add_reader(user=reader1) response = get_view_for_user(viewname="reader-studies:list", client=client, user=reader1) assert response.status_code == 200 assert "Add a new reader study" not in response.rendered_content assert rs1.slug in response.rendered_content assert rs2.slug not in response.rendered_content editor2 = UserFactory() rs2.add_editor(user=editor2) # Editors should only be able to see the studies that they have access to response = get_view_for_user(viewname="reader-studies:list", client=client, user=editor2) assert response.status_code == 200 assert "Add a new reader study" not in response.rendered_content assert rs1.slug not in response.rendered_content assert rs2.slug in response.rendered_content
def test_validate_answer(): u = UserFactory() im1, im2, im3 = ImageFactory(), ImageFactory(), ImageFactory() rs = ReaderStudyFactory(hanging_list=[ { "main": im1.name, "main-overlay": im3.name }, { "main": im2.name, "main-overlay": im3.name }, ]) rs.images.set([im1, im2, im3]) rs.add_reader(u) q = QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q1", ) answer = AnswerFactory( creator=u, question=q, answer=True, ) answer.images.set([im1, im3]) with pytest.raises(ValidationError) as e: Answer.validate( creator=u, question=q, answer=True, images=[im1, im3], ) assert ( e.value.message == f"User {u} has already answered this question for this set of images." ) assert (Answer.validate( creator=u, question=q, answer=True, images=[im2, im3], ) is None)
def test_reader_study_delete(client): rs = ReaderStudyFactory(use_display_sets=False) editor = UserFactory() reader = UserFactory() rs.add_editor(editor) rs.add_reader(reader) assert ReaderStudy.objects.count() == 1 assert is_following(user=editor, obj=rs) response = get_view_for_user( viewname="reader-studies:delete", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=reader, ) assert response.status_code == 403 assert ReaderStudy.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:delete", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert "Confirm Deletion" in response.rendered_content response = get_view_for_user( viewname="reader-studies:delete", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 0 assert not is_following(user=editor, obj=rs)
def test_display_set_description(): rs = ReaderStudyFactory() reader = UserFactory() rs.add_reader(reader) images = [ImageFactory() for _ in range(6)] ci = ComponentInterface.objects.get(slug="generic-medical-image") result = {} for image in images: ds = DisplaySetFactory(reader_study=rs) result[ds.pk] = f"<p>{str(image.pk)}</p>" civ = ComponentInterfaceValueFactory(interface=ci, image=image) ds.values.add(civ) rs.case_text = {im.name: str(im.pk) for im in images} rs.case_text["no_image"] = "not an image" rs.save() for ds in rs.display_sets.all(): assert ds.description == result[ds.pk]
def test_case_text_is_scrubbed(client): u = UserFactory() im, im1 = ImageFactory(), ImageFactory() rs = ReaderStudyFactory( case_text={ im.name: "<b>My Help Text</b><script>naughty</script>", "not an image name": "Shouldn't appear in result", im1.name: "Doesn't belong to this study so ignore", }) rs.images.add(im) rs.add_reader(u) response = get_view_for_user(client=client, url=rs.api_url, user=u) assert response.status_code == 200 # Case should be indexed with the api url assert response.json()["case_text"] == { im.api_url: "<p><b>My Help Text</b>naughty</p>" }
def test_question_delete(client): rs = ReaderStudyFactory(use_display_sets=False) r1, editor = UserFactory(), UserFactory() rs.add_reader(r1) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.AnswerType.BOOL, ) assert Question.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, follow=True, user=r1, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 302 assert Question.objects.count() == 0 assert str(rs) in response.url
def test_upload_session_owned_by_answer_creator(client, settings, answer_type): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory() im = ImageFactory() editor, reader = UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(editor) rs.add_reader(reader) question = QuestionFactory(reader_study=rs, answer_type=answer_type) us1 = RawImageUploadSessionFactory(creator=reader) us2 = RawImageUploadSessionFactory(creator=editor) answer1 = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us1.pk)}, ) f = StagedFileFactory(file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha") RawImageFileFactory(upload_session=us1, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us2.pk}, user=editor, client=client, method=client.patch, data={"answer": str(answer1.pk)}, content_type="application/json", ) assert response.status_code == 400 assert (b"User does not have permission to add an image to this answer" in response.rendered_content)
def test_reader_study_copy(client): rs = ReaderStudyFactory(title="copied") editor = UserFactory() editor2 = UserFactory() reader = UserFactory() rs.add_reader(reader) rs.add_editor(editor) rs.add_editor(editor2) QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q1", ), QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q2", ) im1, im2 = ImageFactory(), ImageFactory() rs.images.set([im1, im2]) rs.hanging_list = [{"main": im1.name}, {"main": im2.name}] rs.case_text = {im1.name: "test", im2.name: "test2"} rs.save() assert ReaderStudy.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "1"}, user=reader, follow=True, ) assert response.status_code == 403 assert ReaderStudy.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "1"}, user=editor, follow=True, ) assert response.status_code == 403 assert ReaderStudy.objects.count() == 1 add_perm = Permission.objects.get( codename=f"add_{ReaderStudy._meta.model_name}" ) editor.user_permissions.add(add_perm) response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "1"}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 2 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "1" assert _rs.images.count() == 0 assert _rs.questions.count() == 0 assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 assert _rs.hanging_list == [] assert _rs.case_text == {} response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "2", "copy_questions": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 3 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "2" assert _rs.questions.count() == 2 assert _rs.images.count() == 0 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "3", "copy_images": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 4 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "3" assert _rs.questions.count() == 0 assert _rs.images.count() == 2 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "4", "copy_hanging_list": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ( "Hanging list and case text can only be copied if the images are copied as well" in response.rendered_content ) assert ReaderStudy.objects.count() == 4 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "4", "copy_images": True, "copy_hanging_list": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 5 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "4" assert _rs.questions.count() == 0 assert _rs.images.count() == 2 assert _rs.hanging_list == rs.hanging_list assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "5", "copy_images": True, "copy_case_text": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 6 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "5" assert _rs.questions.count() == 0 assert _rs.images.count() == 2 assert _rs.hanging_list == [] assert _rs.case_text == rs.case_text assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "6", "copy_readers": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 7 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "6" assert _rs.questions.count() == 0 assert _rs.images.count() == 0 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 1 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "7", "copy_editors": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 8 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "7" assert _rs.questions.count() == 0 assert _rs.images.count() == 0 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 2