def test_user_upload_to_display_set_without_interface(client, settings): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) user = UserFactory() rs = ReaderStudyFactory(use_display_sets=False) rs.add_editor(user=user) ci = ComponentInterface.objects.filter(slug="generic-overlay").get() civ = ComponentInterfaceValueFactory(interface=ci) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) assert ds.values.count() == 1 upload = create_upload_from_file( file_path=Path(__file__).parent / "resources" / "image10x10x10.mha", creator=user, ) with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="api:upload-session-list", user=user, client=client, method=client.post, content_type="application/json", data={ "uploads": [upload.api_url], "display_set": ds.pk }, HTTP_X_FORWARDED_PROTO="https", ) assert response.status_code == 400 assert ("An interface needs to be defined to upload to a display set." in response.json()["non_field_errors"])
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_workstation_changes(client): # Ensure that read permissions are kept up to date if the workstation # changes ws1, ws2 = WorkstationFactory(), WorkstationFactory() reader = UserFactory() rs = ReaderStudyFactory(workstation=ws1) assert "view_workstation" not in get_perms(reader, ws1) assert "view_workstation" not in get_perms(reader, ws2) rs.add_reader(user=reader) assert "view_workstation" in get_perms(reader, ws1) assert "view_workstation" not in get_perms(reader, ws2) rs.workstation = ws2 rs.save() assert "view_workstation" not in get_perms(reader, ws1) assert "view_workstation" in get_perms(reader, ws2) # Test permission cleanup assign_perm("view_workstation", rs.readers_group, ws1) assert "view_workstation" in get_perms(reader, ws1) assert "view_workstation" in get_perms(reader, ws2) rs.save() assert "view_workstation" not in get_perms(reader, ws1) assert "view_workstation" in get_perms(reader, ws2)
def test_hanging_list_validation(hanging_list, expected): assert JSONValidator(schema=HANGING_LIST_SCHEMA)(hanging_list) is None rs = ReaderStudyFactory(hanging_list=hanging_list) images = [ImageFactory(name=f"image_{n}") for n in range(5)] rs.images.set(images) rs.save() assert rs.images.all().count() == 5 assert rs.hanging_list_valid == expected
def test_session_with_user_upload_to_readerstudy(client, settings): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) user = UserFactory() rs = ReaderStudyFactory(use_display_sets=False) rs.add_editor(user=user) upload = create_upload_from_file( file_path=Path(__file__).parent / "resources" / "image10x10x10.mha", creator=user, ) # try upload with interface with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="api:upload-session-list", user=user, client=client, method=client.post, content_type="application/json", data={ "uploads": [upload.api_url], "reader_study": rs.slug, "interface": "generic-overlay", }, HTTP_X_FORWARDED_PROTO="https", ) assert response.status_code == 400 assert ( "An interface can only be defined for archive, archive item or display set uploads." in response.json()["non_field_errors"]) # try without interface with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="api:upload-session-list", user=user, client=client, method=client.post, content_type="application/json", data={ "uploads": [upload.api_url], "reader_study": rs.slug }, HTTP_X_FORWARDED_PROTO="https", ) assert response.status_code == 201 upload_session = response.json() assert upload_session["uploads"] == [upload.api_url]
def test_upload_some_images(client: Client, challenge_set, settings): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) user = UserFactory() # Use reader studies as this uses UploadRawImagesForm rs = ReaderStudyFactory(use_display_sets=False) rs.add_editor(user) response = get_view_for_user( client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, ) assert response.status_code == 200 assert rs.images.count() == 0 assert RawImageUploadSession.objects.count() == 0 user_upload = create_upload_from_file(file_path=RESOURCE_PATH / "image10x10x10.mha", creator=user) with capture_on_commit_callbacks(execute=True): response = get_view_for_user( data={"user_uploads": [user_upload.pk]}, client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, method=client.post, ) assert response.status_code == 302 assert rs.images.count() == 1 sessions = RawImageUploadSession.objects.all() assert len(sessions) == 1 response = get_view_for_user(url=sessions[0].get_absolute_url(), client=client, user=user) assert response.status_code == 200 response = get_view_for_user( url=sessions[0].get_absolute_url(), client=client, user=UserFactory(is_staff=True), ) assert response.status_code == 403
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_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 __init__(self): self.creator = get_rs_creator() self.rs1, self.rs2 = ReaderStudyFactory( use_display_sets=False), ReaderStudyFactory(use_display_sets=False) self.editor1, self.reader1, self.editor2, self.reader2 = ( UserFactory(), UserFactory(), UserFactory(), UserFactory(), ) self.rs1.add_editor(user=self.editor1) self.rs2.add_editor(user=self.editor2) self.rs1.add_reader(user=self.reader1) self.rs2.add_reader(user=self.reader2) self.u = UserFactory()
def test_reader_study_add_ground_truth_ds(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory(use_display_sets=True) QuestionFactory( reader_study=rs, question_text="bar", answer_type=Question.AnswerType.SINGLE_LINE_TEXT, ) civ = ComponentInterfaceValueFactory(image=ImageFactory()) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) editor = UserFactory() rs.editors_group.user_set.add(editor) gt = io.StringIO() fake_writer = csv.writer(gt) fake_writer.writerows([["images", "foo"], [str(ds.pk), "bar"]]) gt.seek(0) response = get_view_for_user( viewname="reader-studies:add-ground-truth", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"ground_truth": gt}, follow=True, user=editor, ) assert response.status_code == 200
def test_view_permission_when_reused(in_archive, in_rs, in_job): """When an image is reused it should have view_image set correctly""" im = ImageFactory() job = AlgorithmJobFactory() rs = ReaderStudyFactory(use_display_sets=False) archive = ArchiveFactory() if in_archive: civ = ComponentInterfaceValueFactory(image=im) ai = ArchiveItemFactory(archive=archive) with capture_on_commit_callbacks(execute=True): ai.values.add(civ) if in_rs: rs.images.add(im) if in_job: civ = ComponentInterfaceValueFactory(image=im) job.inputs.add(civ) assert ("view_image" in get_perms(archive.editors_group, im)) is in_archive assert ("view_image" in get_perms(archive.uploaders_group, im)) is in_archive assert ("view_image" in get_perms(archive.users_group, im)) is in_archive assert ("view_image" in get_perms(rs.editors_group, im)) is in_rs assert ("view_image" in get_perms(rs.readers_group, im)) is in_rs for g in job.viewer_groups.all(): assert ("view_image" in get_perms(g, im)) is in_job
def test_view_permission_when_reused(in_archive, in_rs, in_job): """When an image is reused it should have view_image set correctly""" im = ImageFactory() job = AlgorithmJobFactory() rs = ReaderStudyFactory() archive = ArchiveFactory() if in_archive: archive.images.add(im) if in_rs: rs.images.add(im) if in_job: civ = ComponentInterfaceValueFactory() civ.image = im civ.save() job.inputs.add(civ) assert ("view_image" in get_perms(archive.editors_group, im)) is in_archive assert ( "view_image" in get_perms(archive.uploaders_group, im) ) is in_archive assert ("view_image" in get_perms(archive.users_group, im)) is in_archive assert ("view_image" in get_perms(rs.editors_group, im)) is in_rs assert ("view_image" in get_perms(rs.readers_group, im)) is in_rs for g in job.viewer_groups.all(): assert ("view_image" in get_perms(g, im)) is in_job
def test_assert_modification_allowed(): rs = ReaderStudyFactory(use_display_sets=False) ci = ComponentInterfaceFactory( kind=InterfaceKind.InterfaceKindChoices.BOOL) civ = ComponentInterfaceValueFactory(interface=ci, value=True) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) del ds.is_editable civ2 = ComponentInterfaceValueFactory(interface=ci, value=True) ds.values.remove(civ) ds.values.add(civ2) assert ds.values.count() == 1 assert ds.values.first() == civ2 q = QuestionFactory(reader_study=rs) AnswerFactory(question=q, display_set=ds) del ds.is_editable with pytest.raises(ValidationError): with transaction.atomic(): ds.values.remove(civ2) assert ds.values.count() == 1 assert ds.values.first() == civ2
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_upload_some_images(client: Client, challenge_set, settings): # Override the celery settings settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) user = UserFactory() # Use reader studies as this uses UploadRawImagesForm rs = ReaderStudyFactory() rs.add_editor(user) response = get_view_for_user( client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, ) assert response.status_code == 200 assert rs.images.count() == 0 assert RawImageUploadSession.objects.count() == 0 file1 = create_file_from_filepath(RESOURCE_PATH / "image10x10x10.mha") response = get_view_for_user( data={"files": f"{file1.uuid}"}, client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, method=client.post, ) assert response.status_code == 302 assert rs.images.count() == 1 sessions = RawImageUploadSession.objects.all() assert len(sessions) == 1 response = get_view_for_user( url=sessions[0].get_absolute_url(), client=client, user=user ) assert response.status_code == 200 response = get_view_for_user( url=sessions[0].get_absolute_url(), client=client, user=UserFactory(is_staff=True), ) assert response.status_code == 403
def test_reader_study_list_view_filter(client): user = UserFactory() rs1, rs2, pubrs = ( ReaderStudyFactory(use_display_sets=False), ReaderStudyFactory(use_display_sets=False), ReaderStudyFactory(public=True, use_display_sets=False), ) rs1.add_reader(user) response = get_view_for_user(viewname="reader-studies:list", client=client, user=user) assert response.status_code == 200 assert rs1.get_absolute_url() in response.rendered_content assert rs2.get_absolute_url() not in response.rendered_content assert pubrs.get_absolute_url() in response.rendered_content
def test_generate_hanging_list_api_view(generate_hanging_list, client): rs = ReaderStudyFactory() editor = UserFactory() rs.add_editor(editor) response = get_view_for_user( viewname="api:reader-study-generate-hanging-list", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.patch, follow=True, ) assert response.status_code == 200 assert "Hanging list generated." in str(response.content) generate_hanging_list.assert_called_once()
def test_api_list_is_filtered(client): rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory() rs1_editor = UserFactory() rs1.add_editor(rs1_editor) q1, q2 = ( QuestionFactory(reader_study=rs1), QuestionFactory(reader_study=rs2), ) a1, _ = ( AnswerFactory(question=q1, answer=True), AnswerFactory(question=q2, answer=False), ) response = get_view_for_user(viewname="api:reader-study-list", user=rs1_editor, client=client) assert response.status_code == 200 assert response.json()["count"] == 1 response = get_view_for_user( viewname="api:reader-study-detail", reverse_kwargs={"pk": rs1.pk}, user=rs1_editor, client=client, ) assert response.status_code == 200 assert len(response.json()["questions"]) == 1 response = get_view_for_user( viewname="api:reader-studies-question-list", user=rs1_editor, client=client, ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(q1.pk) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=rs1_editor, client=client, ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(a1.pk)
def test_reader_update_form(client): rs, _ = ReaderStudyFactory(), ReaderStudyFactory() editor = UserFactory() rs.editors_group.user_set.add(editor) assert rs.readers_group.user_set.count() == 0 new_reader = UserFactory() assert not rs.is_reader(user=new_reader) response = get_view_for_user( viewname="reader-studies:readers-update", client=client, method=client.post, data={ "user": new_reader.pk, "action": "ADD" }, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 rs.refresh_from_db() assert rs.readers_group.user_set.count() == 1 assert rs.is_reader(user=new_reader) response = get_view_for_user( viewname="reader-studies:readers-update", client=client, method=client.post, data={ "user": new_reader.pk, "action": "REMOVE" }, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 rs.refresh_from_db() assert rs.readers_group.user_set.count() == 0 assert not rs.is_reader(user=new_reader)
def test_group_deletion_reverse(group): rs = ReaderStudyFactory() readers_group = rs.readers_group editors_group = rs.editors_group assert readers_group assert editors_group getattr(rs, group).delete() with pytest.raises(ObjectDoesNotExist): readers_group.refresh_from_db() with pytest.raises(ObjectDoesNotExist): editors_group.refresh_from_db() with pytest.raises(ObjectDoesNotExist): rs.refresh_from_db()
def test_validate_hanging_list(): 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]) assert rs.hanging_list_valid is False rs.validate_hanging_list = False assert rs.hanging_list_valid is True
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_group_deletion_reverse(group): rs = ReaderStudyFactory(use_display_sets=False) readers_group = rs.readers_group editors_group = rs.editors_group assert readers_group assert editors_group with pytest.raises(ProtectedError): getattr(rs, group).delete()
def test_ground_truth_is_excluded(client): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) editor = UserFactory() rs.add_editor(editor) rs.add_reader(editor) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) a1 = AnswerFactory(question=q, creator=editor, answer=True, is_ground_truth=True) a1.images.add(im) a2 = AnswerFactory(question=q, creator=editor, answer=True, is_ground_truth=False) a2.images.add(im) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=editor, client=client, method=client.get, content_type="application/json", ) results = response.json()["results"] assert len(results) == 1 assert results[0]["pk"] == str(a2.pk)
def test_answer_is_correct_type(client, answer_type, answer, expected): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=answer_type) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": answer, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == expected
def test_answer_create(client): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": True, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == 201 answer = Answer.objects.get(pk=response.data.get("pk")) assert answer.creator == reader assert answer.images.count() == 1 assert answer.images.all()[0] == im assert answer.question == q assert answer.answer is True
def test_question_accepts_image_type_answers(client, settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory() im = ImageFactory() reader = UserFactory() rs.images.add(im) rs.add_reader(reader) question = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) 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 == 400 assert (b"This question does not accept image type answers" in response.rendered_content)
def test_display_set_order(): rs = ReaderStudyFactory(use_display_sets=False) ds = DisplaySetFactory(reader_study=rs) assert ds.order == 10 ds = DisplaySetFactory(reader_study=rs) assert ds.order == 20 ds.order = 15 ds.save() ds = DisplaySetFactory(reader_study=rs) assert ds.order == 20
def test_visible_to_public_group_permissions(): g_reg_anon = Group.objects.get( name=settings.REGISTERED_AND_ANON_USERS_GROUP_NAME) rs = ReaderStudyFactory() assert "view_readerstudy" not in get_perms(g_reg_anon, rs) rs.public = True rs.save() assert "view_readerstudy" in get_perms(g_reg_anon, rs) rs.public = False rs.save() assert "view_readerstudy" not in get_perms(g_reg_anon, rs)