def test_algorithm_no_permission(self): alg = AlgorithmFactory() form = SubmissionForm( user=UserFactory(), algorithm_submission=True, data={"algorithm": alg.pk}, ) assert form.errors["algorithm"] == [ "Select a valid choice. That choice is not one of the available choices." ]
def test_algorithm_groups_permissions_are_assigned(): alg = AlgorithmFactory() editors_perms = get_group_perms(alg.editors_group, alg) assert "view_algorithm" in editors_perms assert "change_algorithm" in editors_perms assert "execute_algorithm" in editors_perms users_perms = get_group_perms(alg.users_group, alg) assert "view_algorithm" in users_perms assert "change_algorithm" not in users_perms assert "execute_algorithm" in users_perms
def test_algorithm_with_permission(self): user = UserFactory() alg = AlgorithmFactory() alg.add_editor(user=user) alg.inputs.clear() alg.outputs.clear() ai = AlgorithmImageFactory(ready=True, algorithm=alg) AlgorithmJobFactory(algorithm_image=ai, status=4) p = PhaseFactory(submission_kind=Phase.SubmissionKind.ALGORITHM) form = SubmissionForm( user=user, phase=p, data={"algorithm": alg.pk, "creator": user, "phase": p}, ) assert form.errors == {} assert "algorithm" not in form.errors assert form.is_valid()
def test_algorithm_no_permission(self): alg = AlgorithmFactory() form = SubmissionForm( user=UserFactory(), phase=PhaseFactory(submission_kind=Phase.SubmissionKind.ALGORITHM), data={"algorithm": alg.pk}, ) assert form.errors["algorithm"] == [ "Select a valid choice. That choice is not one of the available choices." ]
def test_algorithm_permission_request_notification_for_admins_only(client): base_object = AlgorithmFactory() editor = UserFactory() user = UserFactory() participant = UserFactory() base_object.add_editor(editor) base_object.add_user(participant) # create an algorithm job follow for participant follow(user=participant, obj=base_object, flag="job-active") permission_create_url = reverse( "algorithms:permission-request-create", kwargs={"slug": base_object.slug}, ) # Create the permission request _ = get_view_for_user( client=client, user=user, url=permission_create_url, method=client.post, ) assert Notification.objects.count() == 1 assert Notification.objects.get().user == editor
def test_algorithm_image_create_link_view(client): alg = AlgorithmFactory() expected_url = reverse( "algorithms:image-create", kwargs={"slug": alg.slug} ) user = UserFactory() alg.add_user(user) response = get_view_for_user( viewname="algorithms:detail", reverse_kwargs={"slug": alg.slug}, client=client, user=user, ) assert response.status_code == 200 assert expected_url not in response.rendered_content alg.add_editor(user) response = get_view_for_user( viewname="algorithms:detail", reverse_kwargs={"slug": alg.slug}, client=client, user=user, ) assert response.status_code == 200 assert expected_url in response.rendered_content
def test_algorithm_job_update_status(): alg = AlgorithmFactory() user = UserFactory() editor = UserFactory() alg.add_user(user) alg.add_editor(editor) ai = AlgorithmImageFactory(algorithm=alg) job = AlgorithmJobFactory(algorithm_image=ai, creator=user) for status, _ in Job.STATUS_CHOICES: job.update_status(status=status) job.refresh_from_db() assert job.status == status remaining_recipients = {user.email, editor.email} for email in mail.outbox: remaining_recipients -= set(email.to) assert ( email.subject == f"[{Site.objects.get_current().domain.lower()}] [{alg.title.lower()}] Job Failed" ) assert ( f"Unfortunately your job for algorithm '{alg.title}' failed with an error" in email.body) assert remaining_recipients == set()
def test_editor_update_form(client): alg, _ = AlgorithmFactory(), AlgorithmFactory() editor = UserFactory() alg.editors_group.user_set.add(editor) assert alg.editors_group.user_set.count() == 1 new_editor = UserFactory() assert not alg.is_editor(user=new_editor) response = get_view_for_user( viewname="algorithms:editors-update", client=client, method=client.post, data={"user": new_editor.pk, "action": "ADD"}, reverse_kwargs={"slug": alg.slug}, follow=True, user=editor, ) assert response.status_code == 200 alg.refresh_from_db() assert alg.editors_group.user_set.count() == 2 assert alg.is_editor(user=new_editor) response = get_view_for_user( viewname="algorithms:editors-update", client=client, method=client.post, data={"user": new_editor.pk, "action": "REMOVE"}, reverse_kwargs={"slug": alg.slug}, follow=True, user=editor, ) assert response.status_code == 200 alg.refresh_from_db() assert alg.editors_group.user_set.count() == 1 assert not alg.is_editor(user=new_editor)
def test_algorithm_image_create_detail(client): user = UserFactory() algorithm = AlgorithmFactory() algorithm.add_editor(user) algorithm_image = StagedFileFactory(file__filename="test_image.tar.gz") response = get_view_for_user( client=client, viewname="algorithms:image-create", reverse_kwargs={"slug": algorithm.slug}, user=user, ) assert response.status_code == 200 assert AlgorithmImage.objects.all().count() == 0 response = get_view_for_user( client=client, method=client.post, viewname="algorithms:image-create", reverse_kwargs={"slug": algorithm.slug}, user=user, data={ "chunked_upload": algorithm_image.file_id, "requires_memory_gb": 24, }, ) assert response.status_code == 302 images = AlgorithmImage.objects.all() assert len(images) == 1 assert images[0].algorithm == algorithm assert response.url == reverse( "algorithms:image-detail", kwargs={ "slug": algorithm.slug, "pk": images[0].pk }, )
def test_visible_to_public_group_permissions(): g_reg_anon = Group.objects.get( name=settings.REGISTERED_AND_ANON_USERS_GROUP_NAME) algorithm = AlgorithmFactory() assert "view_algorithm" not in get_perms(g_reg_anon, algorithm) algorithm.public = True algorithm.save() assert "view_algorithm" in get_perms(g_reg_anon, algorithm) algorithm.public = False algorithm.save() assert "view_algorithm" not in get_perms(g_reg_anon, algorithm)
def test_group_deletion(): algorithm = AlgorithmFactory() users_group = algorithm.users_group editors_group = algorithm.editors_group assert users_group assert editors_group Algorithm.objects.filter(pk__in=[algorithm.pk]).delete() with pytest.raises(ObjectDoesNotExist): users_group.refresh_from_db() with pytest.raises(ObjectDoesNotExist): editors_group.refresh_from_db()
def test_algorithm_list_view(client): alg1, alg2 = AlgorithmFactory(), AlgorithmFactory() user = UserFactory() alg1.add_user(user) alg2.add_user(user) response = get_view_for_user( viewname="algorithms:list", client=client, user=user ) assert alg1.get_absolute_url() in response.rendered_content assert alg2.get_absolute_url() in response.rendered_content alg1.remove_user(user) response = get_view_for_user( viewname="algorithms:list", client=client, user=user ) assert alg1.get_absolute_url() not in response.rendered_content assert alg2.get_absolute_url() in response.rendered_content
def test_algorithm_permission_request_create(client): user = UserFactory() alg = AlgorithmFactory(public=False) response = get_view_for_user( viewname="algorithms:detail", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=user, follow=True, ) assert response.status_code == 200 assert "Request access" in response.rendered_content response = get_view_for_user( viewname="algorithms:permission-request-create", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=user, method=client.post, follow=True, ) assert AlgorithmPermissionRequest.objects.count() == 1 assert response.status_code == 200 pr = AlgorithmPermissionRequest.objects.get(user=user) assert "Request access" in response.rendered_content assert pr.status_to_string() in response.rendered_content assert pr.status == AlgorithmPermissionRequest.PENDING # Calling create again should not create a new permission request object response = get_view_for_user( viewname="algorithms:permission-request-create", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=user, method=client.post, follow=True, ) assert AlgorithmPermissionRequest.objects.count() == 1
def test_notification_list_view_num_queries(client, django_assert_num_queries): user1 = UserFactory() _ = NotificationFactory( user=user1, message="requested access to", target=AlgorithmFactory(), type=Notification.Type.ACCESS_REQUEST, ) notifications = Notification.objects.select_related( "actor_content_type", "target_content_type", "action_object_content_type", "user", ).all() notifications_with_prefetched_fks = prefetch_generic_foreign_key_objects( Notification.objects.select_related( "actor_content_type", "target_content_type", "action_object_content_type", "user", ).all()) # double check that there is an action target for the test below to be meaningful assert notifications[0].target try: settings.DEBUG = True notifications[0].target # when the generic foreign keys have not been prefetched, accessing the # action target, result in two db calls assert len(connection.queries) == 2 reset_queries() notifications_with_prefetched_fks[0].target # when gfks have been prefetched, accessing the action target # no longer requires any db calls assert len(connection.queries) == 0 finally: settings.DEBUG = False reset_queries()
def test_algorithm_detail_flexible_inputs(client): editor = UserFactory() alg = AlgorithmFactory(use_flexible_inputs=False) alg.add_editor(editor) AlgorithmImageFactory(algorithm=alg, ready=True) flexi_input_url = reverse( viewname="algorithms:execution-session-create-new", kwargs={"slug": alg.slug}, ) response = get_view_for_user( viewname="algorithms:detail", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) assert response.status_code == 200 assert flexi_input_url not in response.rendered_content alg.use_flexible_inputs = True alg.save() response = get_view_for_user( viewname="algorithms:detail", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) assert response.status_code == 200 assert flexi_input_url in response.rendered_content
def test_follows_deleted_when_base_obj_deleted(client): base_object = AlgorithmFactory( access_request_handling=AccessRequestHandlingOptions.MANUAL_REVIEW) editor = UserFactory() base_object.add_editor(editor) permission_create_url = reverse( "algorithms:permission-request-create", kwargs={"slug": base_object.slug}, ) user = UserFactory() _ = get_view_for_user(client=client, user=user, url=permission_create_url, method=client.post) pr = AlgorithmPermissionRequest.objects.get() assert is_following(user, pr) base_object.delete() assert not Follow.objects.filter(object_id=base_object.pk)
def test_publication_object_visibilty(client, mocker): user1 = UserFactory() user2 = UserFactory() alg = AlgorithmFactory() alg.add_user(user1) assert user1.has_perm("view_algorithm", alg) assert not user2.has_perm("view_algorithm", alg) mocker.patch("grandchallenge.publications.utils.get_doi_csl", return_value=TEST_CSL) # create publication _ = get_view_for_user( viewname="publications:create", client=client, method=client.post, data={"identifier": TEST_DOI}, user=user1, ) # add publication to algorithm alg.publications.add(Publication.objects.get()) alg.save() response = get_view_for_user( viewname="publications:list", client=client, method=client.get, user=user1, ) assert response.status_code == 200 assert alg.title in response.rendered_content response = get_view_for_user( viewname="publications:list", client=client, method=client.get, user=user2, ) assert response.status_code == 200 assert alg.title not in response.rendered_content
def test_default_interfaces_created(): a = AlgorithmFactory() assert {i.kind for i in a.inputs.all()} == {InterfaceKindChoices.IMAGE} assert {o.kind for o in a.outputs.all()} == {InterfaceKindChoices.JSON}
def test_archive_item_form(client, settings): # Override the celery settings settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) archive = ArchiveFactory() editor = UserFactory() archive.editors_group.user_set.add(editor) ci = ComponentInterfaceFactory( kind=InterfaceKind.InterfaceKindChoices.BOOL ) civ = ComponentInterfaceValueFactory( interface=ci, value=True, file=None, image=None ) ai = ArchiveItemFactory(archive=archive) ai.values.add(civ) response = get_view_for_user( viewname="archives:item-edit", client=client, method=client.get, reverse_kwargs={"slug": archive.slug, "id": ai.pk}, follow=True, user=editor, ) assert response.status_code == 200 for _ci in ComponentInterface.objects.all(): assert _ci.slug in response.rendered_content assert f'id="id_{ci.slug}" checked' in response.rendered_content assert Job.objects.count() == 0 alg = AlgorithmFactory() AlgorithmImageFactory(algorithm=alg, ready=True) alg.inputs.set([ci]) with capture_on_commit_callbacks(execute=True): archive.algorithms.add(alg) assert Job.objects.count() == 1 civ_count = ComponentInterfaceValue.objects.count() with capture_on_commit_callbacks(execute=True): with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="archives:item-edit", client=client, method=client.post, reverse_kwargs={"slug": archive.slug, "id": ai.pk}, data={ci.slug: False}, follow=True, user=editor, ) assert ai.values.filter(pk=civ.pk).count() == 0 # This should created a new CIV as they are immutable assert ComponentInterfaceValue.objects.count() == civ_count + 1 # A new job should have been created, because the value for 'bool' # has changed assert Job.objects.count() == 2 with capture_on_commit_callbacks(execute=True): with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="archives:item-edit", client=client, method=client.post, reverse_kwargs={"slug": archive.slug, "id": ai.pk}, data={ci.slug: True}, follow=True, user=editor, ) # New jobs should be created as there is a new CIV assert Job.objects.count() == 3 assert ComponentInterfaceValue.objects.count() == civ_count + 2
def test_algorithm_permission_request_update(client): user = UserFactory() editor = UserFactory() alg = AlgorithmFactory(public=True) alg.add_editor(editor) pr = AlgorithmPermissionRequestFactory(algorithm=alg, user=user) assert pr.status == AlgorithmPermissionRequest.PENDING response = get_view_for_user( viewname="algorithms:permission-request-update", reverse_kwargs={ "slug": slugify(alg.slug), "pk": pr.pk }, client=client, user=editor, method=client.get, follow=True, ) assert "review access request for user" in response.rendered_content assert "Request access" not in response.rendered_content response = get_view_for_user( viewname="algorithms:permission-request-update", reverse_kwargs={ "slug": slugify(alg.slug), "pk": pr.pk }, client=client, user=editor, method=client.post, follow=True, data={"status": "NONEXISTENT"}, ) pr.refresh_from_db() assert response.status_code == 200 assert pr.status == AlgorithmPermissionRequest.PENDING response = get_view_for_user( viewname="algorithms:permission-request-update", reverse_kwargs={ "slug": slugify(alg.slug), "pk": pr.pk }, client=client, user=editor, method=client.post, follow=True, data={"status": AlgorithmPermissionRequest.REJECTED}, ) pr.refresh_from_db() assert response.status_code == 200 assert pr.status == AlgorithmPermissionRequest.REJECTED response = get_view_for_user( viewname="algorithms:permission-request-update", reverse_kwargs={ "slug": slugify(alg.slug), "pk": pr.pk }, client=client, user=user, method=client.get, follow=True, ) assert response.status_code == 403 # User should not be able to change the status response = get_view_for_user( viewname="algorithms:permission-request-update", reverse_kwargs={ "slug": slugify(alg.slug), "pk": pr.pk }, client=client, user=user, method=client.post, follow=True, data={"status": AlgorithmPermissionRequest.ACCEPTED}, ) pr.refresh_from_db() assert response.status_code == 403 assert pr.status == AlgorithmPermissionRequest.REJECTED response = get_view_for_user( viewname="algorithms:permission-request-update", reverse_kwargs={ "slug": slugify(alg.slug), "pk": pr.pk }, client=client, user=editor, method=client.post, follow=True, data={"status": AlgorithmPermissionRequest.ACCEPTED}, ) pr.refresh_from_db() assert response.status_code == 200 assert pr.status == AlgorithmPermissionRequest.ACCEPTED
def test_algorithm_jobs_list_view(client): editor = UserFactory() alg = AlgorithmFactory(public=True) alg.add_editor(editor) im = AlgorithmImageFactory(algorithm=alg) for x in range(50): created = timezone.now() - datetime.timedelta(days=x + 365) job = AlgorithmJobFactory(algorithm_image=im, status=Job.SUCCESS) job.created = created job.save() job.viewer_groups.add(alg.editors_group) response = get_view_for_user( viewname="algorithms:job-list", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, ) assert response.status_code == 200 response = get_view_for_user( viewname="algorithms:job-list", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, data={ "length": 10, "draw": 1, "order[0][dir]": "desc", "order[0][column]": 0, }, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) resp = response.json() assert resp["recordsTotal"] == 50 assert len(resp["data"]) == 10 response = get_view_for_user( viewname="algorithms:job-list", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, data={ "length": 50, "draw": 1, "order[0][dir]": "desc", "order[0][column]": 0, }, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) resp = response.json() assert resp["recordsTotal"] == 50 assert len(resp["data"]) == 50 response = get_view_for_user( viewname="algorithms:job-list", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, data={ "length": 50, "draw": 1, "order[0][dir]": "asc", "order[0][column]": 0, }, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) resp_new = response.json() assert resp_new["recordsTotal"] == 50 assert resp_new["data"] == resp["data"][::-1] response = get_view_for_user( viewname="algorithms:job-list", reverse_kwargs={"slug": slugify(alg.slug)}, client=client, user=editor, method=client.get, follow=True, data={ "length": 50, "draw": 1, "search[value]": job.creator.username, "order[0][column]": 0, }, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) resp = response.json() assert resp["recordsTotal"] == 50 assert resp["recordsFiltered"] == 1 assert len(resp["data"]) == 1
def test_adding_algorithms_triggers_task(reverse, mocker): mocker.patch( "grandchallenge.algorithms.tasks.create_algorithm_jobs_for_archive.apply_async" ) create_algorithm_jobs_for_archive.apply_async.assert_not_called() arch_set = TwoArchives() with capture_on_commit_callbacks(execute=True): arch_set.arch1.algorithms.add(AlgorithmFactory()) arch_set.arch2.algorithms.add(AlgorithmFactory()) create_algorithm_jobs_for_archive.apply_async.assert_has_calls( [ call( kwargs={ "archive_pks": [arch_set.arch1.pk], "algorithm_pks": [arch_set.arch1.algorithms.first().pk], } ), call( kwargs={ "archive_pks": [arch_set.arch2.pk], "algorithm_pks": [arch_set.arch2.algorithms.first().pk], } ), ] ) create_algorithm_jobs_for_archive.apply_async.reset_mock() algorithms = ( AlgorithmFactory(), AlgorithmFactory(), AlgorithmFactory(), AlgorithmFactory(), ) if not reverse: with capture_on_commit_callbacks(execute=True): arch_set.arch1.algorithms.add(*algorithms) kwargs = create_algorithm_jobs_for_archive.apply_async.call_args.kwargs[ "kwargs" ] create_algorithm_jobs_for_archive.apply_async.assert_called_once() assert {*kwargs["archive_pks"]} == {arch_set.arch1.pk} assert {*kwargs["algorithm_pks"]} == {a.pk for a in algorithms} create_algorithm_jobs_for_archive.apply_async.reset_mock() with capture_on_commit_callbacks(execute=True): arch_set.arch1.algorithms.remove(algorithms[0], algorithms[1]) arch_set.arch1.algorithms.clear() create_algorithm_jobs_for_archive.apply_async.assert_not_called() else: for alg in algorithms: with capture_on_commit_callbacks(execute=True): alg.archive_set.add(arch_set.arch1, arch_set.arch2) kwargs = create_algorithm_jobs_for_archive.apply_async.call_args.kwargs[ "kwargs" ] create_algorithm_jobs_for_archive.apply_async.assert_called_once() assert {*kwargs["archive_pks"]} == { arch_set.arch1.pk, arch_set.arch2.pk, } assert {*kwargs["algorithm_pks"]} == {alg.pk} create_algorithm_jobs_for_archive.apply_async.reset_mock() with capture_on_commit_callbacks(execute=True): for im in algorithms[-2:]: im.archive_set.remove(arch_set.arch1, arch_set.arch2) for im in algorithms[:2]: im.archive_set.remove(arch_set.arch2) algorithms[0].archive_set.clear() create_algorithm_jobs_for_archive.apply_async.assert_not_called()