diff --git a/add_upload_workspace_audit_test_data.py b/add_upload_workspace_audit_test_data.py new file mode 100644 index 00000000..704c19de --- /dev/null +++ b/add_upload_workspace_audit_test_data.py @@ -0,0 +1,385 @@ +from anvil_consortium_manager.models import GroupGroupMembership, WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) +from django.conf import settings +from django.utils import timezone + +from gregor_django.gregor_anvil.tests import factories + +# Create groups involved in the audit. +dcc_admin_group = ManagedGroupFactory(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) +dcc_writer_group = ManagedGroupFactory(name="GREGOR_DCC_WRITERS") +dcc_member_group = ManagedGroupFactory(name="GREGOR_DCC_MEMBERS") +rc_1_member_group = ManagedGroupFactory(name="DEMO_RC1_MEMBERS") +rc_1_uploader_group = ManagedGroupFactory(name="DEMO_RC1_UPLOADERS") +rc_1_nonmember_group = ManagedGroupFactory(name="DEMO_RC1_NONMEMBERS") +gregor_all_group = ManagedGroupFactory(name="GREGOR_ALL") + +# Create an RC +rc = factories.ResearchCenterFactory.create( + full_name="Research Center 1", + short_name="RC1", + member_group=rc_1_member_group, + uploader_group=rc_1_uploader_group, + non_member_group=rc_1_nonmember_group, +) + + +# Create a future upload cycle. +upload_cycle = factories.UploadCycleFactory.create( + cycle=1, + is_future=True, +) +workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U01_RC1", +) + +# Create a current upload cycle before compute. +upload_cycle = factories.UploadCycleFactory.create( + cycle=2, + is_current=True, +) +workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U02_RC1", +) +# Create records as appropriate for the previous point in the cycle - future cycle. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# RC uploaders. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=rc_1_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=False, +) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) + + +# Create a current upload cycle after compute. +upload_cycle = factories.UploadCycleFactory.create( + cycle=3, + is_current=True, +) +upload_cycle.date_ready_for_compute = upload_cycle.start_date +upload_cycle.save() +workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U03_RC1", +) +# Create records as appropriate for the previous point in the cycle - current cycle before compute. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# RC uploaders. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=rc_1_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=False, +) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) + +# Create a past upload cycle before qc is completed. +upload_cycle = factories.UploadCycleFactory.create( + cycle=4, + is_past=True, +) +workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U04_RC1", + date_qc_completed=None, +) +# Create records as appropriate for the previous point in the cycle - current cycle after compute. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# RC uploaders. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=rc_1_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) + +# Create a past upload cycle after QC is completed. +upload_cycle = factories.UploadCycleFactory.create( + cycle=5, + is_past=True, +) +workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U05_RC1", + date_qc_completed=timezone.now(), +) +# Create records as appropriate for the previous point in the cycle - past cycle before QC complete. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) + +# Create a past upload cycle with a combined workspace. +upload_cycle = factories.UploadCycleFactory.create( + cycle=6, + is_past=True, +) +workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U06_RC1", + date_qc_completed=timezone.now(), +) +factories.CombinedConsortiumDataWorkspaceFactory.create( + upload_cycle=upload_cycle, + date_completed=timezone.now(), + workspace__name="TEST_U06_COMBINED", +) +# Create records as appropriate for the previous point in the cycle - past cycle before QC complete. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) diff --git a/config/settings/base.py b/config/settings/base.py index 55cccf80..5539f254 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -86,6 +86,7 @@ "fontawesomefree", "simple_history", "constance", + "django_htmx", ] LOCAL_APPS = [ @@ -153,6 +154,7 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "maintenance_mode.middleware.MaintenanceModeMiddleware", "simple_history.middleware.HistoryRequestMiddleware", + "django_htmx.middleware.HtmxMiddleware", ] # STATIC diff --git a/config/settings/test.py b/config/settings/test.py index a4b2a4dc..b356314c 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -36,3 +36,6 @@ # get the last templates entry and set debug option TEMPLATES[-1]["OPTIONS"]["debug"] = True # noqa ANVIL_DCC_ADMINS_GROUP_NAME = "TEST_GREGOR_DCC_ADMINS" + +# Suppress DEBUG logging in tests without changing base.py file. +LOGGING["root"]["level"] = "INFO" # noqa: F405 diff --git a/gregor_django/gregor_anvil/audit.py b/gregor_django/gregor_anvil/audit/base.py similarity index 91% rename from gregor_django/gregor_anvil/audit.py rename to gregor_django/gregor_anvil/audit/base.py index d8387e01..3602a6ef 100644 --- a/gregor_django/gregor_anvil/audit.py +++ b/gregor_django/gregor_anvil/audit/base.py @@ -1,17 +1,17 @@ from abc import ABC, abstractmethod, abstractproperty -class GREGORAuditResult(ABC): +class GREGoRAuditResult(ABC): """Abstract base class to hold an audit result for a single check. Subclasses of this class are typically also dataclasses. They can define any number of fields that track information about an audit and its result. The companion RPIMEDAudit class `verified`, `needs_action`, and `errors` attributes should store lists of - GREGORAuditResult instances. + GREGoRAuditResult instances. Typical usage: @dataclass - class MyAuditResult(GREGORAuditResult): + class MyAuditResult(GREGoRAuditResult): some_value: str @@ -27,7 +27,7 @@ def get_table_dictionary(self): ... # pragma: no cover -class GREGORAudit(ABC): +class GREGoRAudit(ABC): """Abstract base class for GREGOR audit classes. This class is intended to be subclassed in order to store all results for a GREGOR audit. @@ -38,14 +38,14 @@ class GREGORAudit(ABC): attributes. Attributes: - verified: A list of GREGORAuditResult subclasses instances that have been verified. - needs_action: A list of GREGORAuditResult subclasses instances that some sort of need action. - errors: A list of GREGORAuditResult subclasses instances where an error has been detected. + verified: A list of GREGoRAuditResult subclasses instances that have been verified. + needs_action: A list of GREGoRAuditResult subclasses instances that some sort of need action. + errors: A list of GREGoRAuditResult subclasses instances where an error has been detected. completed: A boolean indicator of whether the audit has been run. """ # TODO: Add add_verified_result, add_needs_action_result, add_error_result methods. They should - # verify that the result is an instance of GREGORAuditResult (subclass). + # verify that the result is an instance of GREGoRAuditResult (subclass). @abstractproperty def results_table_class(self): @@ -65,7 +65,7 @@ def _run_audit(self): This method should typically loop over a set of instances or checks, and store the results in the `verified`, `needs_action`, and `errors` attributes. The results should - be instances of GREGORAuditResult subclasses. This method should not be called directly. + be instances of GREGoRAuditResult subclasses. This method should not be called directly. When deciding which list to store a result in, consider the following: - verified: The result is as expected and no action is needed. diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py new file mode 100644 index 00000000..ebb98e34 --- /dev/null +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -0,0 +1,449 @@ +from dataclasses import dataclass + +import django_tables2 as tables +from anvil_consortium_manager.models import GroupGroupMembership, ManagedGroup +from django.conf import settings +from django.db.models import Q, QuerySet + +from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace +from .base import GREGoRAudit, GREGoRAuditResult + + +@dataclass +class UploadWorkspaceAuthDomainAuditResult(GREGoRAuditResult): + """Base class to hold results for auditing upload workspace sharing.""" + + workspace: UploadWorkspace + note: str + managed_group: ManagedGroup + action: str = None + current_membership_instance: GroupGroupMembership = None + + def get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `dbGaPDataSharingSnapshotAuditTable`.""" + row = { + "workspace": self.workspace, + "managed_group": self.managed_group, + "role": self.current_membership_instance.role if self.current_membership_instance else None, + "note": self.note, + "action": self.action, + } + return row + + +@dataclass +class VerifiedMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when member membership has been verified.""" + + def __str__(self): + return f"Verified member: {self.note}" + + +@dataclass +class VerifiedAdmin(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when membership with an admin role has been verified.""" + + is_shared: bool = False + + def __str__(self): + return f"Verified admin: {self.note}" + + +@dataclass +class VerifiedNotMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when member membership has been verified.""" + + def __str__(self): + return f"Verified member: {self.note}" + + +@dataclass +class AddMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when a member role should be added.""" + + action: str = "Add member" + + def __str__(self): + return f"Add member: {self.note}" + + +@dataclass +class AddAdmin(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when an admin role should be added.""" + + action: str = "Add admin" + + def __str__(self): + return f"Add admin: {self.note}" + + +@dataclass +class ChangeToMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when an admin role should be changed to a member role.""" + + action: str = "Change to member" + + def __str__(self): + return f"Change to member: {self.note}" + + +@dataclass +class ChangeToAdmin(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when a member role should be changed to an admin role.""" + + action: str = "Change to admin" + + def __str__(self): + return f"Change to admin: {self.note}" + + +@dataclass +class Remove(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when group membership should be removed.""" + + action: str = "Remove" + + def __str__(self): + return f"Share as owner: {self.note}" + + +class UploadWorkspaceAuthDomainAuditTable(tables.Table): + """A table to show results from a UploadWorkspaceAuthDomainAudit subclass.""" + + workspace = tables.Column(linkify=True) + managed_group = tables.Column(linkify=True) + # is_shared = tables.Column() + role = tables.Column(verbose_name="Current role") + note = tables.Column() + # action = tables.Column() + action = tables.TemplateColumn( + template_name="gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html" + ) + + class Meta: + attrs = {"class": "table align-middle"} + + +class UploadWorkspaceAuthDomainAudit(GREGoRAudit): + """A class to hold audit results for the GREGoR UploadWorkspace auth domain audit.""" + + # RC notes. + RC_FUTURE_CYCLE = "RC groups should not be in the auth domain until the upload cycle starts." + RC_UPLOADERS_BEFORE_QC = "RC uploader group should be a member of the auth domain before QC is complete." + RC_UPLOADERS_AFTER_QC = "RC uploader group should not be a member of the auth domain after QC is complete." + RC_MEMBERS_BEFORE_COMBINED = ( + "RC member group should be a member of the auth domain before the combined workspace is complete." + ) + RC_MEMBERS_AFTER_COMBINED = ( + "RC member group should not be a member of the auth domain after the combined workspace is complete." + ) + RC_NON_MEMBERS_AFTER_START = ( + "RC non-member group should be a member of the auth domain for current and past upload cycles." + ) + + # DCC notes. + DCC_ADMINS = "DCC admin group should always be an admin of the auth domain." + DCC_BEFORE_COMBINED = "DCC groups should be a member of the auth domain before the combined workspace is complete." + DCC_AFTER_COMBINED = ( + "DCC groups should not be direct members of the auth domain after the combined workspace is complete." + ) + + # GREGOR_ALL notes. + GREGOR_ALL_BEFORE_COMBINED = "GREGOR_ALL should not have access before the combined workspace is complete." + GREGOR_ALL_AFTER_COMBINED = ( + "GREGOR_ALL should be a member of the auth domain after the combined workspace is complete." + ) + + # Other group notes. + OTHER_GROUP = "This group should not have access to the auth domain." + UNEXPECTED_ADMIN = "Only the DCC admins group should be an admin of the auth domain." + + results_table_class = UploadWorkspaceAuthDomainAuditTable + + def __init__(self, queryset=None): + super().__init__() + if queryset is None: + queryset = UploadWorkspace.objects.all() + if not (isinstance(queryset, QuerySet) and queryset.model is UploadWorkspace): + raise ValueError("queryset must be a queryset of UploadWorkspace objects.") + self.queryset = queryset + + def _run_audit(self): + for workspace in self.queryset: + self.audit_upload_workspace(workspace) + + def _get_current_membership(self, upload_workspace, managed_group): + try: + current_membership = GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=managed_group + ) + except GroupGroupMembership.DoesNotExist: + current_membership = None + return current_membership + + def _get_combined_workspace(self, upload_cycle): + """Returns the combined workspace, but only if it is ready for sharing.""" + try: + combined_workspace = CombinedConsortiumDataWorkspace.objects.get( + upload_cycle=upload_cycle, date_completed__isnull=False + ) + except CombinedConsortiumDataWorkspace.DoesNotExist: + combined_workspace = None + return combined_workspace + + def audit_upload_workspace(self, upload_workspace): + """Audit the auth domain membership of a single UploadWorkspace.""" + research_center = upload_workspace.research_center + group_names = [ + "GREGOR_ALL", + "GREGOR_DCC_MEMBERS", + "GREGOR_DCC_WRITERS", + settings.ANVIL_DCC_ADMINS_GROUP_NAME, + ] + groups_to_audit = ManagedGroup.objects.filter( + # RC uploader group. + Q(research_center_of_uploaders=research_center) + | + # RC member group. + Q(research_center_of_members=research_center) + | + # RC non-member group. + Q(research_center_of_non_members=research_center) + | + # Other sepcific groups to include. + Q(name__in=group_names) + | + # Any other groups that are members. + Q(parent_memberships__parent_group=upload_workspace.workspace.authorization_domains.first()) + ).distinct() + + for group in groups_to_audit: + self.audit_workspace_and_group(upload_workspace, group) + + def audit_workspace_and_group(self, upload_workspace, managed_group): + if managed_group == upload_workspace.research_center.uploader_group: + self._audit_workspace_and_group_for_rc_uploaders(upload_workspace, managed_group) + elif managed_group == upload_workspace.research_center.member_group: + self._audit_workspace_and_group_for_rc_members(upload_workspace, managed_group) + elif managed_group == upload_workspace.research_center.non_member_group: + self._audit_workspace_and_group_for_rc_non_members(upload_workspace, managed_group) + elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: + self._audit_workspace_and_group_for_dcc_admin(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_DCC_WRITERS": + self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_DCC_MEMBERS": + self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_ALL": + self._audit_workspace_and_group_for_gregor_all(upload_workspace, managed_group) + elif managed_group.name == "anvil-admins": + self._audit_workspace_and_anvil_group(upload_workspace, managed_group) + elif managed_group.name == "anvil_devs": + self._audit_workspace_and_anvil_group(upload_workspace, managed_group) + else: + self._audit_workspace_and_other_group(upload_workspace, managed_group) + + def _audit_workspace_and_group_for_rc_uploaders(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + } + + # Otherwise, proceed with other checks. + if upload_workspace.upload_cycle.is_future: + note = self.RC_FUTURE_CYCLE + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + elif upload_workspace.upload_cycle.is_current: + note = self.RC_UPLOADERS_BEFORE_QC + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) + elif upload_workspace.upload_cycle.is_past and not upload_workspace.date_qc_completed: + note = self.RC_UPLOADERS_BEFORE_QC + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) + else: + note = self.RC_UPLOADERS_AFTER_QC + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + + def _audit_workspace_and_group_for_rc_members(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + } + + if upload_workspace.upload_cycle.is_future: + note = self.RC_FUTURE_CYCLE + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + elif not combined_workspace: + note = self.RC_MEMBERS_BEFORE_COMBINED + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) + else: + note = self.RC_MEMBERS_AFTER_COMBINED + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + + def _audit_workspace_and_group_for_rc_non_members(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + } + + if upload_workspace.upload_cycle.is_future: + note = self.RC_FUTURE_CYCLE + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + else: + note = self.RC_NON_MEMBERS_AFTER_START + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) + + def _audit_workspace_and_group_for_dcc_admin(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + if not membership: + self.needs_action.append( + AddAdmin( + workspace=upload_workspace, + managed_group=managed_group, + note=self.DCC_ADMINS, + current_membership_instance=membership, + ) + ) + elif membership.role == GroupGroupMembership.ADMIN: + self.verified.append( + VerifiedAdmin( + workspace=upload_workspace, + managed_group=managed_group, + note=self.DCC_ADMINS, + current_membership_instance=membership, + ) + ) + else: + self.needs_action.append( + ChangeToAdmin( + workspace=upload_workspace, + managed_group=managed_group, + note=self.DCC_ADMINS, + current_membership_instance=membership, + ) + ) + + def _audit_workspace_and_group_for_dcc(self, upload_workspace, managed_group): + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) + membership = self._get_current_membership(upload_workspace, managed_group) + if combined_workspace: + note = self.DCC_AFTER_COMBINED + else: + note = self.DCC_BEFORE_COMBINED + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": note, + } + + if not combined_workspace and not membership: + self.needs_action.append(AddMember(**result_kwargs)) + elif not combined_workspace and membership: + if membership.role == GroupGroupMembership.MEMBER: + self.verified.append(VerifiedMember(**result_kwargs)) + else: + self.errors.append(ChangeToMember(**result_kwargs)) + elif combined_workspace and not membership: + self.verified.append(VerifiedNotMember(**result_kwargs)) + elif combined_workspace and membership: + if membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(**result_kwargs)) + else: + self.needs_action.append(Remove(**result_kwargs)) + + def _audit_workspace_and_group_for_gregor_all(self, upload_workspace, managed_group): + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) + membership = self._get_current_membership(upload_workspace, managed_group) + if combined_workspace: + note = self.GREGOR_ALL_AFTER_COMBINED + else: + note = self.GREGOR_ALL_BEFORE_COMBINED + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": note, + } + + if not combined_workspace and not membership: + self.verified.append(VerifiedNotMember(**result_kwargs)) + elif not combined_workspace and membership: + self.errors.append(Remove(**result_kwargs)) + elif combined_workspace and not membership: + self.needs_action.append(AddMember(**result_kwargs)) + elif combined_workspace and membership: + if membership.role == GroupGroupMembership.MEMBER: + self.verified.append(VerifiedMember(**result_kwargs)) + else: + self.errors.append(ChangeToMember(**result_kwargs)) + + def _audit_workspace_and_anvil_group(self, upload_workspace, managed_group): + """Ignore the AnVIL groups in this audit. + + We don't want to make assumptions about what access level AnVIL has.""" + pass + + def _audit_workspace_and_other_group(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": self.OTHER_GROUP, + } + + if not membership: + self.verified.append(VerifiedNotMember(**result_kwargs)) + elif membership: + self.errors.append(Remove(**result_kwargs)) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py new file mode 100644 index 00000000..249c7a24 --- /dev/null +++ b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py @@ -0,0 +1,559 @@ +from dataclasses import dataclass + +import django_tables2 as tables +from anvil_consortium_manager.models import ManagedGroup, WorkspaceGroupSharing +from django.conf import settings +from django.db.models import Q, QuerySet + +from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace +from ..tables import BooleanIconColumn +from .base import GREGoRAudit, GREGoRAuditResult + + +@dataclass +class UploadWorkspaceSharingAuditResult(GREGoRAuditResult): + """Base class to hold results for auditing upload workspace sharing.""" + + workspace: UploadWorkspace + note: str + managed_group: ManagedGroup + action: str = None + current_sharing_instance: WorkspaceGroupSharing = None + + def get_action_url(self): + """The URL that handles the action needed.""" + # return reverse( + # "gregor_anvil:audit:upload_workspaces:sharing:resolve", + # args=[ + # self.dbgap_application.dbgap_project_id, + # self.workspace.workspace.billing_project.name, + # self.workspace.workspace.name, + # ], + # ) + return "" + + def get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `dbGaPDataSharingSnapshotAuditTable`.""" + row = { + "workspace": self.workspace, + "managed_group": self.managed_group, + "access": self.current_sharing_instance.access if self.current_sharing_instance else None, + "can_compute": self.current_sharing_instance.can_compute if self.current_sharing_instance else None, + "note": self.note, + "action": self.action, + "action_url": self.get_action_url(), + } + return row + + +@dataclass +class VerifiedShared(UploadWorkspaceSharingAuditResult): + """Audit results class for when Sharing has been verified.""" + + is_shared: bool = True + + def __str__(self): + return f"Verified sharing: {self.note}" + + +@dataclass +class VerifiedNotShared(UploadWorkspaceSharingAuditResult): + """Audit results class for when no Sharing has been verified.""" + + is_shared: bool = False + + def __str__(self): + return f"Verified not shared: {self.note}" + + +@dataclass +class ShareAsReader(UploadWorkspaceSharingAuditResult): + """Audit results class for when Sharing should be granted as a reader.""" + + is_shared: bool = False + action: str = "Share as reader" + + def __str__(self): + return f"Share as reader: {self.note}" + + +@dataclass +class ShareAsWriter(UploadWorkspaceSharingAuditResult): + """Audit results class for when Sharing should be granted as a writer.""" + + is_shared: bool = False + action: str = "Share as writer" + + def __str__(self): + return f"Share as writer: {self.note}" + + +@dataclass +class ShareAsOwner(UploadWorkspaceSharingAuditResult): + """Audit results class for when Sharing should be granted as an owner.""" + + is_shared: bool = False + action: str = "Share as owner" + + def __str__(self): + return f"Share as owner: {self.note}" + + +@dataclass +class ShareWithCompute(UploadWorkspaceSharingAuditResult): + """Audit results class for when Sharing should be granted with compute access.""" + + is_shared: bool = False + action: str = "Share with compute" + + def __str__(self): + return f"Share with compute: {self.note}" + + +@dataclass +class StopSharing(UploadWorkspaceSharingAuditResult): + """Audit results class for when Sharing should be removed for a known reason.""" + + is_shared: bool = True + action: str = "Stop sharing" + + def __str__(self): + return f"Stop sharing: {self.note}" + + +@dataclass +class Error(UploadWorkspaceSharingAuditResult): + """Audit results class for when an error has been detected (e.g., shared and never should have been).""" + + pass + + +class UploadWorkspaceSharingAuditTable(tables.Table): + """A table to show results from a UploadWorkspaceSharingAudit subclass.""" + + workspace = tables.Column(linkify=True) + managed_group = tables.Column(linkify=True) + # is_shared = tables.Column() + access = tables.Column(verbose_name="Current access") + can_compute = BooleanIconColumn(show_false_icon=True, null=True) + note = tables.Column() + # action = tables.Column() + action = tables.TemplateColumn( + template_name="gregor_anvil/snippets/upload_workspace_sharing_audit_action_button.html" + ) + + class Meta: + attrs = {"class": "table align-middle"} + + +class UploadWorkspaceSharingAudit(GREGoRAudit): + """A class to hold audit results for the GREGoR UploadWorkspace audit.""" + + # RC uploader statues. + RC_UPLOADERS_FUTURE_CYCLE = "Uploaders should not have access to future cycles." + RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE = ( + "Uploaders should have write access before compute is enabled for this upload cycle." + ) + RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE = "Uploaders should have write access with compute for this upload cycle." + RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE = "Uploaders should not have direct access before QC is complete." + RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE = "Uploader group should not have direct access after QC is complete." + RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY = ( + "Uploader group should not have direct access when the combined workspace is ready to share or shared." + ) + + # DCC writer group status. + DCC_WRITERS_FUTURE_CYCLE = "DCC writers should have write and compute access for future cycles." + DCC_WRITERS_CURRENT_CYCLE = "DCC writers should have write and compute access for the current upload cycle." + DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE = ( + "DCC writers should have write and compute access before QC is complete." + ) + DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE = "DCC writers should not have direct access after QC is complete." + DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY = ( + "DCC writers should not have direct access when the combined workspace is ready to share or shared." + ) + + # DCC admin group status. + DCC_ADMIN_AS_OWNER = "The DCC admin group should always be an owner." + + # Auth domain status. + AUTH_DOMAIN_AS_READER = "The auth domain should always be a reader." + + # Other group. + OTHER_GROUP_NO_ACCESS = "Other groups should not have direct access." + + results_table_class = UploadWorkspaceSharingAuditTable + + def __init__(self, queryset=None): + super().__init__() + if queryset is None: + queryset = UploadWorkspace.objects.all() + if not (isinstance(queryset, QuerySet) and queryset.model is UploadWorkspace): + raise ValueError("queryset must be a queryset of UploadWorkspace objects.") + self.queryset = queryset + + def _run_audit(self): + for workspace in self.queryset: + self.audit_upload_workspace(workspace) + + def _get_current_sharing(self, upload_workspace, managed_group): + try: + current_sharing = WorkspaceGroupSharing.objects.get( + workspace=upload_workspace.workspace, group=managed_group + ) + except WorkspaceGroupSharing.DoesNotExist: + current_sharing = None + return current_sharing + + def _get_combined_workspace(self, upload_cycle): + """Returns the combined workspace, but only if it is ready for sharing.""" + try: + combined_workspace = CombinedConsortiumDataWorkspace.objects.get( + upload_cycle=upload_cycle, date_completed__isnull=False + ) + except CombinedConsortiumDataWorkspace.DoesNotExist: + combined_workspace = None + return combined_workspace + + def audit_upload_workspace(self, upload_workspace): + """Audit access for a specific UploadWorkspace.""" + # Get a list of managed groups that should be included in this audit. + # This includes the members group for the RC, the DCC groups, GREGOR_ALL, and the auth domain. + research_center = upload_workspace.research_center + group_names_to_include = [ + "GREGOR_DCC_WRITERS", # DCC writers + settings.ANVIL_DCC_ADMINS_GROUP_NAME, # DCC admins + "anvil-admins", # AnVIL admins + "anvil_devs", # AnVIL devs + ] + groups_to_audit = ManagedGroup.objects.filter( + # RC uploader group. + Q(research_center_of_uploaders=research_center) + | + # Specific groups from above. + Q(name__in=group_names_to_include) + | + # Auth domain. + Q(workspaceauthorizationdomain__workspace=upload_workspace.workspace) + | + # Groups that the workspace is shared with. + Q(workspacegroupsharing__workspace=upload_workspace.workspace) + ).distinct() + + for group in groups_to_audit: + self.audit_workspace_and_group(upload_workspace, group) + + def audit_workspace_and_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and ManagedGroup.""" + # Check the group type, and then call the appropriate audit method. + if upload_workspace.research_center.uploader_group == managed_group: + self._audit_workspace_and_rc_uploader_group(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_DCC_WRITERS": + self._audit_workspace_and_dcc_writer_group(upload_workspace, managed_group) + elif managed_group in upload_workspace.workspace.authorization_domains.all(): + self._audit_workspace_and_auth_domain(upload_workspace, managed_group) + elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: + self._audit_workspace_and_dcc_admin_group(upload_workspace, managed_group) + elif managed_group.name in ["anvil-admins", "anvil_devs"]: + self._audit_workspace_and_anvil_group(upload_workspace, managed_group) + else: + self._audit_workspace_and_other_group(upload_workspace, managed_group) + + def _audit_workspace_and_anvil_group(self, upload_workspace, managed_group): + """Ignore the AnVIL groups in this audit. + + We don't want to make assumptions about what access level AnVIL has.""" + pass + + def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and RC uploader group. + + Sharing expectations: + - No access to future upload cycle. + - Write access before compute is enabled for current upload cycle. + - Write+compute access after compute is enabled for current upload cycle. + - Read access to past upload cycle workspaces before QC is completed. + - No access to past upload cycle workspaces after QC is completed (read access via auth domain). + """ + upload_cycle = upload_workspace.upload_cycle + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + combined_workspace = self._get_combined_workspace(upload_cycle) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + if upload_cycle.is_future: + note = self.RC_UPLOADERS_FUTURE_CYCLE + if not current_sharing: + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + else: + self.needs_action.append(StopSharing(note=note, **audit_result_args)) + elif upload_cycle.is_current and not upload_cycle.date_ready_for_compute: + note = self.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareAsWriter(note=note, **audit_result_args)) + elif ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and not current_sharing.can_compute + ): + self.verified.append(VerifiedShared(note=note, **audit_result_args)) + else: + self.needs_action.append(ShareAsWriter(note=note, **audit_result_args)) + elif upload_cycle.is_current and upload_cycle.date_ready_for_compute: + note = self.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append(VerifiedShared(note=note, **audit_result_args)) + else: + self.needs_action.append(ShareWithCompute(note=note, **audit_result_args)) + elif upload_cycle.is_past and not upload_workspace.date_qc_completed: + note = self.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + if not current_sharing: + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + else: + self.needs_action.append(StopSharing(note=note, **audit_result_args)) + elif upload_cycle.is_past and upload_workspace.date_qc_completed and not combined_workspace: + note = self.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + if not current_sharing: + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + else: + self.needs_action.append(StopSharing(note=note, **audit_result_args)) + elif upload_cycle.is_past and combined_workspace: + note = self.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + if not current_sharing: + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + else: + self.needs_action.append(StopSharing(note=note, **audit_result_args)) + else: + raise ValueError("No case matched for RC uploader group.") + + def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and the DCC writer group. + + Sharing expectations: + - Write+compute access to future and current upload cycles. + - Write+compute access to past upload cycles before QC is complete. + - No direct access to past upload cycle workspaces after QC is completed (read access via auth domain). + """ + upload_cycle = upload_workspace.upload_cycle + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + combined_workspace = self._get_combined_workspace(upload_cycle) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + if upload_cycle.is_future: + note = self.DCC_WRITERS_FUTURE_CYCLE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_current: + note = self.DCC_WRITERS_CURRENT_CYCLE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and not upload_workspace.date_qc_completed: + note = self.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and upload_workspace.date_qc_completed and not combined_workspace: + note = self.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + elif not current_sharing: + self.verified.append( + VerifiedNotShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + StopSharing( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and combined_workspace: + note = self.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + elif not current_sharing: + self.verified.append( + VerifiedNotShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + StopSharing( + note=note, + **audit_result_args, + ) + ) + + else: + raise ValueError("No case matched for DCC writer group.") + + def _audit_workspace_and_auth_domain(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and its auth domain. + + Sharing expectations: + - Read access at all times. + """ + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + note = self.AUTH_DOMAIN_AS_READER + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareAsReader(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.READER: + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareAsReader( + note=note, + **audit_result_args, + ) + ) + + def _audit_workspace_and_dcc_admin_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and the DCC admin group. + + Sharing expectations: + - Owner access at all times. + """ + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + note = self.DCC_ADMIN_AS_OWNER + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareAsOwner( + note=note, + **audit_result_args, + ) + ) + + def _audit_workspace_and_other_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and other groups. + + Sharing expectations: + - No access. + """ + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + if not current_sharing: + self.verified.append( + VerifiedNotShared( + note=self.OTHER_GROUP_NO_ACCESS, + **audit_result_args, + ) + ) + else: + self.errors.append( + StopSharing( + note=self.OTHER_GROUP_NO_ACCESS, + **audit_result_args, + ) + ) diff --git a/gregor_django/gregor_anvil/forms.py b/gregor_django/gregor_anvil/forms.py index 76990a07..e71531a3 100644 --- a/gregor_django/gregor_anvil/forms.py +++ b/gregor_django/gregor_anvil/forms.py @@ -19,15 +19,41 @@ class Meta: "cycle", "start_date", "end_date", + "date_ready_for_compute", "note", ) widgets = { "start_date": CustomDateInput(), "end_date": CustomDateInput(), + "date_ready_for_compute": CustomDateInput(), } +class UploadCycleCreateForm(UploadCycleForm): + """Form to create an UploadCycle object.""" + + class Meta(UploadCycleForm.Meta): + fields = ( + "cycle", + "start_date", + "end_date", + "note", + ) + + +class UploadCycleUpdateForm(UploadCycleForm): + """Form to update an UploadCycle object.""" + + class Meta(UploadCycleForm.Meta): + fields = ( + "start_date", + "end_date", + "date_ready_for_compute", + "note", + ) + + class UploadWorkspaceForm(forms.ModelForm): """Form for a UploadWorkspace object.""" @@ -37,8 +63,12 @@ class Meta: "research_center", "consent_group", "upload_cycle", + "date_qc_completed", "workspace", ) + widgets = { + "date_qc_completed": CustomDateInput(), + } class PartnerUploadWorkspaceForm(forms.ModelForm): @@ -88,7 +118,11 @@ class Meta: fields = ( "workspace", "upload_cycle", + "date_completed", ) + widgets = { + "date_completed": CustomDateInput(), + } class ReleaseWorkspaceForm(Bootstrap5MediaFormMixin, forms.ModelForm): diff --git a/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py new file mode 100644 index 00000000..fb7db4e9 --- /dev/null +++ b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py @@ -0,0 +1,72 @@ +from django.contrib.sites.models import Site +from django.core.mail import send_mail +from django.core.management.base import BaseCommand +from django.template.loader import render_to_string +from django.urls import reverse + +from ...audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit + + +class Command(BaseCommand): + help = "Run access audits on UploadWorkspace." + + def add_arguments(self, parser): + email_group = parser.add_argument_group(title="Email reports") + email_group.add_argument( + "--email", + help="""Email to which to send audit reports that need action or have errors.""", + ) + + def run_sharing_audit(self, *args, **options): + self.stdout.write("Running UploadWorkspace sharing audit... ", ending="") + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self._handle_audit_results(audit, reverse("gregor_anvil:audit:upload_workspaces:sharing:all"), **options) + + def run_auth_domain_audit(self, *args, **options): + self.stdout.write("Running UploadWorkspace auth domain audit... ", ending="") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self._handle_audit_results(audit, reverse("gregor_anvil:audit:upload_workspaces:auth_domains:all"), **options) + + def _handle_audit_results(self, audit, url, **options): + # Report errors and needs access. + audit_ok = audit.ok() + # Construct the url for handling errors. + url = "https://" + Site.objects.get_current().domain + url + if audit_ok: + self.stdout.write(self.style.SUCCESS("ok!")) + else: + self.stdout.write(self.style.ERROR("problems found.")) + + # Print results + self.stdout.write("* Verified: {}".format(len(audit.verified))) + self.stdout.write("* Needs action: {}".format(len(audit.needs_action))) + self.stdout.write("* Errors: {}".format(len(audit.errors))) + + if not audit_ok: + self.stdout.write(self.style.ERROR(f"Please visit {url} to resolve these issues.")) + + # Send email if requested and there are problems. + email = options["email"] + subject = "{} - problems found".format(audit.__class__.__name__) + html_body = render_to_string( + "gregor_anvil/email_audit_report.html", + context={ + "title": "Upload workspace audit", + "audit_results": audit, + "url": url, + }, + ) + send_mail( + subject, + "Audit problems found. Please see attached report.", + None, + [email], + fail_silently=False, + html_message=html_body, + ) + + def handle(self, *args, **options): + self.run_sharing_audit(*args, **options) + self.run_auth_domain_audit(*args, **options) diff --git a/gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py b/gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py new file mode 100644 index 00000000..98879a4d --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py @@ -0,0 +1,100 @@ +# Generated by Django 4.2.15 on 2024-08-27 21:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("anvil_consortium_manager", "0019_accountuserarchive"), + ("gregor_anvil", "0026_historicalpartnergroup_drupal_node_id_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="combinedconsortiumdataworkspace", + name="date_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that data preparation in this workspace was completed.", + null=True, + ), + ), + migrations.AddField( + model_name="historicalcombinedconsortiumdataworkspace", + name="date_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that data preparation in this workspace was completed.", + null=True, + ), + ), + migrations.AddField( + model_name="historicalresearchcenter", + name="non_member_group", + field=models.ForeignKey( + blank=True, + db_constraint=False, + help_text="The AnVIL group containing non-members from this Research Center.", + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="anvil_consortium_manager.managedgroup", + ), + ), + migrations.AddField( + model_name="historicaluploadcycle", + name="date_ready_for_compute", + field=models.DateField( + blank=True, + default=None, + help_text="Date that this workspace was ready for RC uploaders to run compute.", + null=True, + ), + ), + migrations.AddField( + model_name="historicaluploadworkspace", + name="date_qc_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that QC was completed for this workspace. If null, QC is not complete.", + null=True, + ), + ), + migrations.AddField( + model_name="researchcenter", + name="non_member_group", + field=models.OneToOneField( + blank=True, + help_text="The AnVIL group containing non-members from this Research Center.", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="research_center_of_non_members", + to="anvil_consortium_manager.managedgroup", + ), + ), + migrations.AddField( + model_name="uploadcycle", + name="date_ready_for_compute", + field=models.DateField( + blank=True, + default=None, + help_text="Date that this workspace was ready for RC uploaders to run compute.", + null=True, + ), + ), + migrations.AddField( + model_name="uploadworkspace", + name="date_qc_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that QC was completed for this workspace. If null, QC is not complete.", + null=True, + ), + ), + ] diff --git a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py new file mode 100644 index 00000000..db6819f0 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:04 + +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + + +def populate_uploadcycle_date_ready_for_compute(apps, schema_editor): + UploadCycle = apps.get_model("gregor_anvil", "UploadCycle") + # Create one UploadCycle for each unique version. + for row in UploadCycle.objects.all(): + assumed_date_ready_for_compute = row.start_date + timedelta(weeks=4) + if row.end_date <= timezone.localdate(): + row.date_ready_for_compute = assumed_date_ready_for_compute + row.full_clean() + row.save(update_fields=["date_ready_for_compute"]) + + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0027_tracking_fields_for_custom_audits"), + ] + + operations = [ + migrations.RunPython(populate_uploadcycle_date_ready_for_compute, reverse_code=migrations.RunPython.noop), + ] diff --git a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py new file mode 100644 index 00000000..aa06ff42 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:04 + +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + +def populate_uploadworkspace_date_qc_complete(apps, schema_editor): + UploadWorkspace = apps.get_model("gregor_anvil", "UploadWorkspace") + # Create one UploadWorkspace for each unique version. + for row in UploadWorkspace.objects.all(): + assumed_date_qc_completed = row.upload_cycle.end_date + timedelta(weeks=2) + if assumed_date_qc_completed <= timezone.localdate(): + row.date_qc_completed = assumed_date_qc_completed + row.full_clean() + row.save(update_fields=["date_qc_completed"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0028_populate_uploadcycle_date_ready_for_compute"), + ] + + operations = [ + migrations.RunPython(populate_uploadworkspace_date_qc_complete, reverse_code=migrations.RunPython.noop), + ] diff --git a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py new file mode 100644 index 00000000..a32852a4 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:05 + +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + +def populate_combinedworkspace_date_completed(apps, schema_editor): + CombinedConsortiumDataWorkspace = apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + WorkspaceGroupSharing = apps.get_model("anvil_consortium_manager", "WorkspaceGroupSharing") + # Create one UploadWorkspace for each unique version. + for row in CombinedConsortiumDataWorkspace.objects.all(): + try: + workspace_group_sharing = WorkspaceGroupSharing.objects.get( + workspace=row.workspace, + group=row.workspace.authorization_domains.first(), + ) + except WorkspaceGroupSharing.DoesNotExist: + continue + else: + row.date_completed = workspace_group_sharing.created.date() + row.full_clean() + row.save(update_fields=["date_completed"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_completed"), + ] + + operations = [ + migrations.RunPython(populate_combinedworkspace_date_completed, reverse_code=migrations.RunPython.noop), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 752db5ca..ec0a4f85 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -3,6 +3,7 @@ from django.core.validators import MinValueValidator from django.db import models from django.urls import reverse +from django.utils import timezone from django_extensions.db.models import TimeStampedModel from simple_history.models import HistoricalRecords @@ -54,6 +55,15 @@ class ResearchCenter(TimeStampedModel, models.Model): null=True, ) + non_member_group = models.OneToOneField( + ManagedGroup, + on_delete=models.PROTECT, + help_text="The AnVIL group containing non-members from this Research Center.", + related_name="research_center_of_non_members", + blank=True, + null=True, + ) + uploader_group = models.OneToOneField( ManagedGroup, on_delete=models.PROTECT, @@ -82,6 +92,10 @@ def clean(self): # Members group and uploaders group must be different. if self.member_group and self.uploader_group and self.member_group == self.uploader_group: raise ValidationError("member_group and uploader_group must be different!") + if self.non_member_group and self.uploader_group and self.non_member_group == self.uploader_group: + raise ValidationError("non_member_group and uploader_group must be different!") + if self.member_group and self.non_member_group and self.member_group == self.non_member_group: + raise ValidationError("member_group and non_member_group must be different!") class PartnerGroup(TimeStampedModel, models.Model): @@ -145,7 +159,14 @@ class UploadCycle(TimeStampedModel, models.Model): ) start_date = models.DateField(help_text="The start date of this upload cycle.") end_date = models.DateField(help_text="The end date of this upload cycle.") + date_ready_for_compute = models.DateField( + help_text="Date that this workspace was ready for RC uploaders to run compute.", + blank=True, + null=True, + default=None, + ) note = models.TextField(blank=True, help_text="Additional notes.") + # Django simple history. history = HistoricalRecords() @@ -166,6 +187,12 @@ def clean(self): # End date must be after start date. if self.start_date and self.end_date and self.start_date >= self.end_date: raise ValidationError("end_date must be after start_date!") + # date_ready_for_compute must be after start_date + if self.start_date and self.date_ready_for_compute and self.start_date > self.date_ready_for_compute: + raise ValidationError("date_ready_for_compute must be after start_date!") + # date_ready_for_compute must be before end_date + if self.end_date and self.date_ready_for_compute and self.end_date < self.date_ready_for_compute: + raise ValidationError("date_ready_for_compute must be before end_date!") def get_partner_upload_workspaces(self): """Return a queryset of PartnerUploadWorkspace objects that are included in this upload cycle. @@ -183,6 +210,21 @@ def get_partner_upload_workspaces(self): pks_to_keep.append(instance.pk) return qs.filter(pk__in=pks_to_keep) + @property + def is_current(self): + """Return a boolean indicating whether this upload cycle is the current one.""" + return self.start_date <= timezone.localdate() and self.end_date >= timezone.localdate() + + @property + def is_past(self): + """Return a boolean indicating whether this upload cycle is a past cycle.""" + return self.end_date < timezone.localdate() + + @property + def is_future(self): + """Return a boolean indicating whether this upload cycle is a future cycle.""" + return self.start_date > timezone.localdate() + class UploadWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track additional data about an upload workspace.""" @@ -197,6 +239,13 @@ class UploadWorkspace(TimeStampedModel, BaseWorkspaceData): upload_cycle = models.ForeignKey(UploadCycle, on_delete=models.PROTECT) """The UploadCycle associated with this workspace.""" + date_qc_completed = models.DateField( + help_text="Date that QC was completed for this workspace. If null, QC is not complete.", + blank=True, + null=True, + default=None, + ) + class Meta: constraints = [ # Model uniqueness. @@ -206,6 +255,15 @@ class Meta: ), ] + def __str__(self): + return self.workspace.name + + def clean(self): + """Custom cleaning methods.""" + # Check that date_qc_completed is after the upload cycle end date. + if self.date_qc_completed and self.upload_cycle.end_date > self.date_qc_completed: + raise ValidationError("date_qc_completed must after end_date of associated upload_cycle.") + class PartnerUploadWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track additional data about a partner workspace.""" @@ -252,6 +310,18 @@ class CombinedConsortiumDataWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track a workspace that has data combined from multiple upload workspaces.""" upload_cycle = models.ForeignKey(UploadCycle, on_delete=models.PROTECT) + date_completed = models.DateField( + help_text="Date that data preparation in this workspace was completed.", + blank=True, + null=True, + default=None, + ) + + def clean(self): + """Custom cleaning methods.""" + # Check that date_qc_completed is after the upload cycle end date. + if self.date_completed and self.upload_cycle.end_date > self.date_completed: + raise ValidationError("date_completed must after end_date of associated upload_cycle.") class ReleaseWorkspace(TimeStampedModel, BaseWorkspaceData): diff --git a/gregor_django/gregor_anvil/tables.py b/gregor_django/gregor_anvil/tables.py index 236c98c2..23ac7857 100644 --- a/gregor_django/gregor_anvil/tables.py +++ b/gregor_django/gregor_anvil/tables.py @@ -6,6 +6,52 @@ from . import models +class BooleanIconColumn(tables.BooleanColumn): + """A column that renders a boolean value as an icon. + + This column renders a boolean value as a bootstrap icon. By default, the icon is a green checkmark for True, + and nothing for False, but this can be customized by keyword arguments. + + Args: + show_false_icon (bool): Whether to show an icon for False values. + true_color (str): The color of the icon for True values. (Default: green) + false_color (str): The color of the icon for False values. (Default: red) + true_icon (str): The icon to use for True values. (Default: check-circle-fill) + false_icon (str): The icon to use for False values. (Default: x-circle-fill) + """ + + def __init__( + self, + show_false_icon=False, + true_color="green", + false_color="red", + true_icon="check-circle-fill", + false_icon="x-circle-fill", + **kwargs, + ): + super().__init__(**kwargs) + self.show_false_icon = show_false_icon + self.true_color = true_color + self.false_color = false_color + self.true_icon = true_icon + self.false_icon = false_icon + + def render(self, value, record, bound_column): + value = self._get_bool_value(record, value, bound_column) + if value: + rendered_value = format_html( + f"""""" + ) + else: + if self.show_false_icon: + rendered_value = format_html( + f"""""" # noqa: E501 + ) + else: + rendered_value = "" + return rendered_value + + class AccountTable(tables.Table): """A custom table for `Accounts`.""" @@ -74,6 +120,7 @@ class Meta: "cycle", "start_date", "end_date", + "date_ready_for_compute", ) def render_cycle(self, record): @@ -142,6 +189,7 @@ class Meta: "uploadworkspace__upload_cycle", "uploadworkspace__research_center", "uploadworkspace__consent_group", + "uploadworkspace__date_qc_completed", "consortium_access", ) @@ -205,6 +253,7 @@ class Meta: fields = ( "name", "combinedconsortiumdataworkspace__upload_cycle", + "combinedconsortiumdataworkspace__date_completed", ) diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 5b77b97b..2a1f64a0 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -1,7 +1,8 @@ from datetime import timedelta -from anvil_consortium_manager.tests.factories import WorkspaceFactory -from factory import Faker, LazyAttribute, Sequence, SubFactory +from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceFactory +from django.utils import timezone +from factory import Faker, LazyAttribute, Sequence, SubFactory, Trait, post_generation from factory.django import DjangoModelFactory from .. import models @@ -39,6 +40,19 @@ class UploadCycleFactory(DjangoModelFactory): class Params: duration = 90 + is_past = Trait( + start_date=timezone.localdate() - timedelta(days=100), + end_date=timezone.localdate() - timedelta(days=10), + date_ready_for_compute=timezone.localdate() - timedelta(days=90), + ) + is_current = Trait( + start_date=timezone.localdate() - timedelta(days=45), + end_date=timezone.localdate() + timedelta(days=45), + ) + is_future = Trait( + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=100), + ) class Meta: model = models.UploadCycle @@ -66,6 +80,18 @@ class UploadWorkspaceFactory(DjangoModelFactory): class Meta: model = models.UploadWorkspace + skip_postgeneration_save = True + + @post_generation + def authorization_domains(self, create, extracted, **kwargs): + # Add an authorization domain. + if not create: + # Simple build, do nothing. + return + + # Create an authorization domain. + auth_domain = ManagedGroupFactory.create(name="auth_{}".format(self.workspace.name)) + self.workspace.authorization_domains.add(auth_domain) class PartnerUploadWorkspaceFactory(DjangoModelFactory): diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py new file mode 100644 index 00000000..83dc8ff5 --- /dev/null +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -0,0 +1,7357 @@ +"""Tests for the `py` module.""" + +from dataclasses import dataclass + +import django_tables2 as tables +from anvil_consortium_manager.models import GroupGroupMembership, WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) +from django.conf import settings +from django.test import TestCase, override_settings +from faker import Faker + +from .. import models +from ..audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit +from ..audit.base import GREGoRAudit, GREGoRAuditResult +from ..tests import factories + +fake = Faker() + + +@dataclass +class TempAuditResult(GREGoRAuditResult): + value: str + + def get_table_dictionary(self): + return {"value": self.value} + + +class TempResultsTable(tables.Table): + """A dummy class to use as the results_table_class attribute of GREGoR""" + + # Columns. + value = tables.Column() + + +class TempAudit(GREGoRAudit): + """A dummy class to use for testing the GREGoRAudit class.""" + + # Required abstract properties. + results_table_class = TempResultsTable + + def _run_audit(self): + # For this test, do nothing. + pass + + +class GREGoRAuditResultTest(TestCase): + """Tests for the `GREGoRAuditResult` class.""" + + def test_abstract_base_class(self): + """The abstract base class cannot be instantiated.""" + with self.assertRaises(TypeError): + GREGoRAuditResult() + + def test_instantiation(self): + """Subclass of abstract base class can be instantiated.""" + TempAuditResult(value="foo") + + def test_get_table_dictionary(self): + audit_result = TempAuditResult(value="foo") + self.assertEqual(audit_result.get_table_dictionary(), {"value": "foo"}) + + +class GREGoRAuditTest(TestCase): + """Tests for the `GREGoRAudit` class.""" + + def test_abstract_base_class(self): + """The abstract base class cannot be instantiated.""" + with self.assertRaises(TypeError): + GREGoRAudit() + + def test_instantiation(self): + """Subclass of abstract base class can be instantiated.""" + TempAudit() + + def test_results_lists(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit_results = TempAudit() + self.assertEqual(audit_results.verified, []) + self.assertEqual(audit_results.needs_action, []) + self.assertEqual(audit_results.errors, []) + + def test_completed(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit_results = TempAudit() + self.assertFalse(audit_results.completed) + audit_results.run_audit() + self.assertTrue(audit_results.completed) + + def test_get_all_results(self): + audit_results = TempAudit() + audit_results.run_audit() + # Manually set some audit results to get the output we want. + audit_results.verified = ["a"] + audit_results.needs_action = ["b"] + audit_results.errors = ["c"] + self.assertEqual(audit_results.get_all_results(), ["a", "b", "c"]) + + def test_get_all_results_incomplete(self): + audit_results = TempAudit() + with self.assertRaises(ValueError) as e: + audit_results.get_all_results() + self.assertEqual( + str(e.exception), + "Audit has not been completed. Use run_audit() to run the audit.", + ) + + def test_get_verified_table(self): + audit_results = TempAudit() + audit_results.run_audit() + audit_results.verified = [ + TempAuditResult(value="a"), + ] + audit_results.needs_action = [ + TempAuditResult(value="b"), + ] + audit_results.errors = [ + TempAuditResult(value="c"), + ] + table = audit_results.get_verified_table() + self.assertIsInstance(table, TempResultsTable) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell("value"), "a") + + def test_get_needs_action_table(self): + audit_results = TempAudit() + audit_results.run_audit() + audit_results.verified = [ + TempAuditResult(value="a"), + ] + audit_results.needs_action = [ + TempAuditResult(value="b"), + ] + audit_results.errors = [ + TempAuditResult(value="c"), + ] + table = audit_results.get_needs_action_table() + self.assertIsInstance(table, TempResultsTable) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell("value"), "b") + + def test_get_errors_table(self): + audit_results = TempAudit() + audit_results.run_audit() + audit_results.verified = [ + TempAuditResult(value="a"), + ] + audit_results.needs_action = [ + TempAuditResult(value="b"), + ] + audit_results.errors = [ + TempAuditResult(value="c"), + ] + table = audit_results.get_errors_table() + self.assertIsInstance(table, TempResultsTable) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell("value"), "c") + + +class UploadWorkspaceSharingAuditTest(TestCase): + """General tests of the `UploadWorkspaceSharingAudit` class.""" + + def test_completed(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + self.assertFalse(audit.completed) + audit.run_audit() + self.assertTrue(audit.completed) + + def test_no_upload_workspaces(self): + """The audit works if there are no UploadWorkspaces.""" + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_no_groups(self): + upload_workspace = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, upload_workspace.workspace.authorization_domains.first()) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_one_upload_workspace_rc_upload_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__uploader_group=group, upload_cycle__is_future=True + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_writer_group(self): + group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.WRITER, can_compute=True + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_auth_domain(self): + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = upload_workspace.workspace.authorization_domains.first() + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) # auth domain is shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_admin_group(self): + group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_one_upload_workspace_dcc_admin_group_different_name(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_anvil_admin_group(self): + group = ManagedGroupFactory.create(name="anvil-admins") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create(workspace=upload_workspace.workspace, group=group) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_anvil_dev_group(self): + group = ManagedGroupFactory.create(name="anvil_devs") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create(workspace=upload_workspace.workspace, group=group) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_other_group_shared(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_other_group_not_shared(self): + ManagedGroupFactory.create(name="foo") + factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + + def test_two_upload_workspaces(self): + """Audit works with two UploadWorkspaces.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace_1.workspace, + group=upload_workspace_1.workspace.authorization_domains.first(), + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, upload_workspace_1.workspace.authorization_domains.first()) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, upload_workspace_2.workspace.authorization_domains.first()) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_queryset(self): + """Audit only runs on the specified queryset of dbGaPApplications.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace_1.workspace, + group=upload_workspace_1.workspace.authorization_domains.first(), + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + # First application + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_1.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, upload_workspace_1.workspace.authorization_domains.first()) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + # Second application + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_2.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, upload_workspace_2.workspace.authorization_domains.first()) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_queryset_wrong_class(self): + """Raises ValueError if queryset is not a QuerySet.""" + with self.assertRaises(ValueError): + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit(queryset="foo") + with self.assertRaises(ValueError): + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.CombinedConsortiumDataWorkspace.objects.all() + ) + + +class UploadWorkspaceSharingAuditFutureCycleTest(TestCase): + """Tests for the `UploadWorkspaceSharingAudit` class for future cycle UploadWorkspaces. + + Expectations at this point in the upload cycle: + - RC uploader group should be writers without compute. + - DCC writer group should be writers with compute. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE + ) + + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE + ) + + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceSharingAuditCurrentCycleBeforeComputeTest(TestCase): + """Tests for the `UploadWorkspaceSharingAudit` class for current cycle UploadWorkspaces before compute is enabled. + + Expectations at this point in the upload cycle: + - RC uploader group should be writers without compute. + - DCC writer group should be writers with compute. + """ + + def setUp(self): + super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + upload_cycle__date_ready_for_compute=None, + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceSharingAuditCurrentCycleAfterComputeTest(TestCase): + """Tests for the `UploadWorkspaceSharingAudit` class for current cycle UploadWorkspaces after compute is enabled. + + Expectations at this point in the upload cycle: + - RC uploader group should be writers with compute. + - DCC writer group should be writers with compute. + """ + + def setUp(self): + super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + ) + # Set date ready for compute to a non-null value. + self.upload_workspace.upload_cycle.date_ready_for_compute = self.upload_workspace.upload_cycle.start_date + self.upload_workspace.upload_cycle.save() + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE, + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE, + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE, + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE, + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE, + ) + + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_CURRENT_CYCLE + ) + + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceSharingAuditPastCycleBeforeQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceSharingAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE, + ) + + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceSharingAuditPastCycleAfterQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceSharingAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceSharingAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): + """Tests for the `UploadWorkspaceSharingAudit` class for past cycles after the combined workspace is ready to share. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + # Create a corresponding combined workspace. + self.combined_workspace = factories.CombinedConsortiumDataWorkspaceFactory.create( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=fake.date_this_year( + before_today=True, + after_today=False, + ), + ) + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY, + ) + + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER) + + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceAuthDomainAuditTest(TestCase): + """General tests of the `UploadWorkspaceAuthDomainAudit` class.""" + + def test_completed(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + self.assertFalse(audit.completed) + audit.run_audit() + self.assertTrue(audit.completed) + + def test_no_upload_workspaces(self): + """The audit works if there are no UploadWorkspaces.""" + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_no_groups(self): + upload_workspace = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + self.assertEqual(len(audit.queryset), 1) + self.assertIn(upload_workspace, audit.queryset) + + def test_two_upload_workspace_no_groups(self): + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + self.assertEqual(len(audit.queryset), 2) + self.assertIn(upload_workspace_1, audit.queryset) + self.assertIn(upload_workspace_2, audit.queryset) + + def test_one_upload_workspace_rc_member_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__member_group=group, upload_cycle__is_future=True + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_rc_upload_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__uploader_group=group, upload_cycle__is_future=True + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_rc_nonmember_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__non_member_group=group, upload_cycle__is_future=True + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_member_group(self): + group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_writer_group(self): + group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_admin_group(self): + group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_one_upload_workspace_dcc_admin_group_different_name(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_anvil_admin_group(self): + group = ManagedGroupFactory.create(name="anvil-admins") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_anvil_dev_group(self): + group = ManagedGroupFactory.create(name="anvil_devs") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_gregor_all_group(self): + group = ManagedGroupFactory.create(name="GREGOR_ALL") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_other_group_member(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_other_group_not_member(self): + ManagedGroupFactory.create(name="foo") + factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_two_upload_workspaces(self): + """Audit works with two UploadWorkspaces.""" + admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + membership = GroupGroupMembershipFactory.create( + parent_group=upload_workspace_1.workspace.authorization_domains.first(), + child_group=admin_group, + role=GroupGroupMembership.ADMIN, + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_queryset(self): + """Audit only runs on the specified queryset of UploadWorkspaces.""" + admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + membership = GroupGroupMembershipFactory.create( + parent_group=upload_workspace_1.workspace.authorization_domains.first(), + child_group=admin_group, + role=GroupGroupMembership.ADMIN, + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + # First application + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_1.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + # Second application + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_2.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_queryset_wrong_class(self): + """Raises ValueError if queryset is not a QuerySet.""" + with self.assertRaises(ValueError): + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit(queryset="foo") + with self.assertRaises(ValueError): + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.CombinedConsortiumDataWorkspace.objects.all() + ) + + +class UploadWorkspaceAuthDomainAuditFutureCycleTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for future cycle UploadWorkspaces.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + +class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + upload_cycle__date_ready_for_compute=None, + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + +class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + ) + # Set date ready for compute to a non-null value. + self.upload_workspace.upload_cycle.date_ready_for_compute = self.upload_workspace.upload_cycle.start_date + self.upload_workspace.upload_cycle.save() + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + +class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC + ) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + +class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC + ) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED + ) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + +class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + # Create a corresponding combined workspace. + self.combined_workspace = factories.CombinedConsortiumDataWorkspaceFactory.create( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=fake.date_this_year( + before_today=True, + after_today=False, + ), + ) + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC + ) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_AFTER_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_AFTER_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_AFTER_COMBINED + ) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED + ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) diff --git a/gregor_django/gregor_anvil/tests/test_commands.py b/gregor_django/gregor_anvil/tests/test_commands.py new file mode 100644 index 00000000..b39cbfbe --- /dev/null +++ b/gregor_django/gregor_anvil/tests/test_commands.py @@ -0,0 +1,366 @@ +"""Tests for management commands in the `gregor_anvil` app.""" + +from io import StringIO + +from anvil_consortium_manager.models import GroupGroupMembership, WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) +from django.conf import settings +from django.contrib.sites.models import Site +from django.core import mail +from django.core.management import call_command +from django.test import TestCase +from django.urls import reverse + +from . import factories + + +class RunUploadWorkspaceAuditTest(TestCase): + """Tests for the run_upload_workspace_audit command""" + + def test_no_upload_workspaces(self): + """Test command output.""" + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... ok!", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... ok!", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_one_instance_verified(self): + """Test command output with one verified instance.""" + # Create a workspace and matching DAR. + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... ok!", + "* Verified: 1", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_one_instance_needs_action(self): + """Test command output with one needs_action instance.""" + # Create a workspace and matching DAR. + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 1", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_one_instance_error(self): + """Test command output with one error instance.""" + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.OWNER, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_one_instance_verified_email(self): + """No email is sent when there are no errors.""" + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + self.assertIn("Running UploadWorkspace sharing audit... ok!", out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_one_instance_needs_action_email(self): + """Email is sent for one needs_action instance.""" + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 1", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "UploadWorkspaceSharingAudit - problems found") + + def test_sharing_audit_one_instance_error_email(self): + """Test command output with one error instance.""" + # Create a workspace and matching DAR. + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.OWNER, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "UploadWorkspaceSharingAudit - problems found") + + def test_sharing_audit_one_instance_needs_action_link_in_output(self): + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + url = reverse("gregor_anvil:audit:upload_workspaces:sharing:all") + self.assertIn(url, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_different_domain(self): + """Test command output when a different domain is specified.""" + site = Site.objects.create(domain="foobar.com", name="test") + site.save() + with self.settings(SITE_ID=site.id): + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + self.assertIn("Running UploadWorkspace sharing audit... problems found.", out.getvalue()) + self.assertIn("https://foobar.com", out.getvalue()) + + def test_auth_domain_audit_one_instance_verified(self): + """Test command output with one verified instance.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group__name=settings.ANVIL_DCC_ADMINS_GROUP_NAME, + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... ok!", + "* Verified: 1", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_auth_domain_audit_one_instance_needs_action(self): + """Test command output with one needs_action instance.""" + factories.UploadWorkspaceFactory.create() + ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... problems found.", + "* Verified: 0", + "* Needs action: 1", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_auth_domain_audit_one_instance_error(self): + """Test command output with one error instance.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + # Share with the auth domain to prevent an error in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_auth_domain_audit_one_instance_verified_email(self): + """No email is sent when there are no errors.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + dcc_admins_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + ) + # Share with the DCC admin group to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=dcc_admins_group, + access=WorkspaceGroupSharing.OWNER, + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=dcc_admins_group, + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + self.assertIn("Running UploadWorkspace auth domain audit... ok!", out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_auth_domain_audit_one_instance_needs_action_email(self): + """Email is sent for one needs_action instance.""" + # Create a workspace and matching DAR. + upload_workspace = factories.UploadWorkspaceFactory.create() + dcc_admins_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + ) + # Share with the DCC admin group to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=dcc_admins_group, + access=WorkspaceGroupSharing.OWNER, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "UploadWorkspaceAuthDomainAudit - problems found") + + def test_auth_domain_audit_one_instance_error_email(self): + """Test command output with one error instance.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + # Share with the auth domain to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "UploadWorkspaceAuthDomainAudit - problems found") + + def test_auth_domain_audit_different_domain(self): + """Test command output when a different domain is specified.""" + site = Site.objects.create(domain="foobar.com", name="test") + site.save() + with self.settings(SITE_ID=site.id): + factories.UploadWorkspaceFactory.create() + ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + self.assertIn("Running UploadWorkspace auth domain audit... problems found.", out.getvalue()) + self.assertIn("https://foobar.com", out.getvalue()) + + def test_auth_domain_audit_one_instance_needs_action_link_in_output(self): + upload_workspace = factories.UploadWorkspaceFactory.create() + # Share with the auth domain to prevent an error in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + url = reverse("gregor_anvil:audit:upload_workspaces:auth_domains:all") + self.assertIn(url, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) diff --git a/gregor_django/gregor_anvil/tests/test_forms.py b/gregor_django/gregor_anvil/tests/test_forms.py index 61ea420c..163fab15 100644 --- a/gregor_django/gregor_anvil/tests/test_forms.py +++ b/gregor_django/gregor_anvil/tests/test_forms.py @@ -9,10 +9,10 @@ from . import factories -class UploadCycleForm(TestCase): - """Tests for the UploadCycleForm class.""" +class UploadCycleCreateFormTest(TestCase): + """Tests for the UploadCycleCreateForm class.""" - form_class = forms.UploadCycleForm + form_class = forms.UploadCycleCreateForm def test_valid(self): """Form is valid with necessary input.""" @@ -78,6 +78,67 @@ def test_valid_note(self): self.assertTrue(form.is_valid()) +class UploadCycleUpdateFormTest(TestCase): + """Tests for the UploadCycleUpdateForm class.""" + + form_class = forms.UploadCycleUpdateForm + + def test_valid(self): + """Form is valid with necessary input.""" + form_data = { + "start_date": date.today(), + "end_date": date.today() + timedelta(days=1), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + + def test_invalid_missing_start_date(self): + """Form is invalid when missing start_date.""" + form_data = { + # "start_date": date.today(), + "end_date": date.today() + timedelta(days=1), + } + form = self.form_class(data=form_data) + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 1) + self.assertIn("start_date", form.errors) + self.assertEqual(len(form.errors["start_date"]), 1) + self.assertIn("required", form.errors["start_date"][0]) + + def test_invalid_missing_end_date(self): + """Form is invalid when missing cycle.""" + form_data = { + "start_date": date.today(), + # "end_date": date.today() + timedelta(days=1), + } + form = self.form_class(data=form_data) + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 1) + self.assertIn("end_date", form.errors) + self.assertEqual(len(form.errors["end_date"]), 1) + self.assertIn("required", form.errors["end_date"][0]) + + def test_valid_note(self): + """Form is valid with a note.""" + form_data = { + "start_date": date.today(), + "end_date": date.today() + timedelta(days=1), + "note": "my test note", + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + + def test_valid_date_ready_for_compute(self): + """Form is valid with date_ready_for_compute.""" + form_data = { + "start_date": date.today(), + "end_date": date.today() + timedelta(days=60), + "date_ready_for_compute": date.today() + timedelta(days=10), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + + class UploadWorkspaceFormTest(TestCase): """Tests for the UploadWorkspace class.""" @@ -181,6 +242,21 @@ def test_invalid_duplicate_object(self): self.assertEqual(len(non_field_errors), 1) self.assertIn("already exists", non_field_errors[0]) + def test_valid_date_qc_completed(self): + """Form is invalid with a duplicated object.""" + research_center = factories.ResearchCenterFactory() + consent_group = factories.ConsentGroupFactory() + upload_cycle = factories.UploadCycleFactory(is_past=True) + form_data = { + "research_center": research_center, + "consent_group": consent_group, + "upload_cycle": upload_cycle, + "workspace": self.workspace, + "date_qc_completed": date.today(), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + class PartnerUploadWorkspaceFormTest(TestCase): """Tests for the PartnerUploadWorkspace class.""" @@ -374,10 +450,9 @@ def setUp(self): def test_invalid_missing_workspace(self): """Form is invalid when missing workspace.""" upload_cycle = factories.UploadCycleFactory.create() - upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=upload_cycle) form_data = { "upload_cycle": upload_cycle, - "upload_workspaces": [upload_workspace], + # "workspace": workspace, } form = self.form_class(data=form_data) self.assertFalse(form.is_valid()) @@ -398,6 +473,18 @@ def test_invalid_blank_upload_cycle(self): self.assertEqual(len(form.errors["upload_cycle"]), 1) self.assertIn("required", form.errors["upload_cycle"][0]) + def test_valid_date_qc_completed(self): + """Form is invalid with a duplicated object.""" + upload_cycle = factories.UploadCycleFactory(is_past=True) + workspace = WorkspaceFactory.create() + form_data = { + "upload_cycle": upload_cycle, + "workspace": workspace, + "date_completed": date.today(), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + class ReleaseWorkspaceFormTest(TestCase): """Tests for the ReleaseWorkspace class.""" diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 148617de..fb377f31 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -1,10 +1,16 @@ """Tests for data migrations in the app.""" -from datetime import date +from datetime import date, timedelta import factory -from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory +from anvil_consortium_manager.tests.factories import ( + BillingProjectFactory, + WorkspaceAuthorizationDomainFactory, + WorkspaceFactory, +) +from django.utils import timezone from django_test_migrations.contrib.unittest_case import MigratorTestCase +from freezegun import freeze_time from . import factories @@ -312,3 +318,207 @@ def test_relationships(self): self.assertTrue(hasattr(workspace, "exampleworkspace")) self.assertIsInstance(workspace.exampleworkspace, ExampleWorkspace) self.assertEqual(workspace.exampleworkspace, example_workspace) + + +class PopulateUploadCycleIsReadyForComputeForwardMigrationTest(MigratorTestCase): + """Tests for the 0028_populate_uploadcycle_is_ready_for_compute migration.""" + + migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") + migrate_to = ("gregor_anvil", "0028_populate_uploadcycle_date_ready_for_compute") + + def prepare(self): + """Prepare some data before the migration.""" + # Get model definition for the old state. + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + # Create a past upload cycle. + self.upload_cycle_past = UploadCycle.objects.create( + cycle=1, + start_date=timezone.localdate() - timedelta(days=30), + end_date=timezone.localdate() - timedelta(days=20), + ) + # Create a current upload cycle - nothing should change for this one. + self.upload_cycle_current = UploadCycle.objects.create( + cycle=2, + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=10), + ) + # Create a future upload cycle - nothing should change for this one. + self.upload_cycle_future = UploadCycle.objects.create( + cycle=3, + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=20), + ) + + def test_date_completed(self): + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_past.pk) + self.assertEqual(upload_cycle.date_ready_for_compute, upload_cycle.start_date + timedelta(weeks=4)) + upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_current.pk) + self.assertIsNone(upload_cycle.date_ready_for_compute) + upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_future.pk) + self.assertIsNone(upload_cycle.date_ready_for_compute) + + +class PopulateUploadWorkspaceDateQCComplete(MigratorTestCase): + """Tests for the 0029_populate_uploadworkspace_date_qc_complete migration.""" + + migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") + migrate_to = ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_completed") + + def prepare(self): + """Prepare some data before the migration.""" + # Get model definition for the old state. + BillingProject = self.old_state.apps.get_model("anvil_consortium_manager", "BillingProject") + Workspace = self.old_state.apps.get_model("anvil_consortium_manager", "Workspace") + ResearchCenter = self.old_state.apps.get_model("gregor_anvil", "ResearchCenter") + ConsentGroup = self.old_state.apps.get_model("gregor_anvil", "ConsentGroup") + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + UploadWorkspace = self.old_state.apps.get_model("gregor_anvil", "UploadWorkspace") + # Make FKs. + consent_group = factory.create(ConsentGroup, FACTORY_CLASS=factories.ConsentGroupFactory) + research_center = ResearchCenter.objects.create(short_name="rc", full_name="Research Center") + # Create an upload workspace from a past upload cycle. + upload_cycle = UploadCycle.objects.create( + cycle=1, + start_date=timezone.localdate() - timedelta(days=30), + end_date=timezone.localdate() - timedelta(days=20), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.upload_workspace_past = UploadWorkspace.objects.create( + upload_cycle=upload_cycle, + research_center=research_center, + consent_group=consent_group, + workspace=workspace, + ) + # Create a current upload cycle - nothing should change for this one. + upload_cycle = UploadCycle.objects.create( + cycle=2, + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=10), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.upload_workspace_current = UploadWorkspace.objects.create( + upload_cycle=upload_cycle, + research_center=research_center, + consent_group=consent_group, + workspace=workspace, + ) + # Create a future upload cycle - nothing should change for this one. + upload_cycle = UploadCycle.objects.create( + cycle=3, + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=20), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.upload_workspace_future = UploadWorkspace.objects.create( + upload_cycle=upload_cycle, + research_center=research_center, + consent_group=consent_group, + workspace=workspace, + ) + + def test_date_qc_completed(self): + UploadWorkspace = self.old_state.apps.get_model("gregor_anvil", "UploadWorkspace") + upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_past.pk) + self.assertEqual( + upload_workspace.date_qc_completed, upload_workspace.upload_cycle.end_date + timedelta(weeks=2) + ) + upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_current.pk) + self.assertIsNone(upload_workspace.date_qc_completed) + upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_future.pk) + self.assertIsNone(upload_workspace.date_qc_completed) + + +class PopulateConsortiumCombinedDataWorkspaceIsComplete(MigratorTestCase): + """Tests for the 0030_populate_consortiumcombineddataworkspace_date_complete migration.""" + + migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") + migrate_to = ("gregor_anvil", "0030_populate_consortiumcombineddataworkspace_date_completed") + + def prepare(self): + """Prepare some data before the migration.""" + # Get model definition for the old state. + ManagedGroup = self.old_state.apps.get_model("anvil_consortium_manager", "ManagedGroup") + BillingProject = self.old_state.apps.get_model("anvil_consortium_manager", "BillingProject") + Workspace = self.old_state.apps.get_model("anvil_consortium_manager", "Workspace") + WorkspaceAuthorizationDomain = self.old_state.apps.get_model( + "anvil_consortium_manager", "WorkspaceAuthorizationDomain" + ) + WorkspaceGroupSharing = self.old_state.apps.get_model("anvil_consortium_manager", "WorkspaceGroupSharing") + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + CombinedConsortiumDataWorkspace = self.old_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + # Create an auth domain for the combined workspaces. + auth_domain_group = ManagedGroup.objects.create( + name="auth_domain", + email="auth_domain@firecloud.org", + ) + # Create a shared combined workspace. + upload_cycle = UploadCycle.objects.create( + cycle=1, + start_date=timezone.localdate() - timedelta(days=50), + end_date=timezone.localdate() - timedelta(days=40), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + factory.create( + WorkspaceAuthorizationDomain, + FACTORY_CLASS=WorkspaceAuthorizationDomainFactory, + workspace=workspace, + group=auth_domain_group, + ) + self.combined_workspace_shared = CombinedConsortiumDataWorkspace.objects.create( + upload_cycle=upload_cycle, + workspace=workspace, + ) + # Shared with its auth domain. + self.date_shared = timezone.localdate() - timedelta(days=35) + with freeze_time(self.date_shared): + WorkspaceGroupSharing.objects.create( + workspace=workspace, + group=auth_domain_group, + access="READER", + can_compute=False, + ) + # Create a combined workspace that has not been shared. + upload_cycle = UploadCycle.objects.create( + cycle=2, + start_date=timezone.localdate() - timedelta(days=30), + end_date=timezone.localdate() - timedelta(days=20), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + factory.create( + WorkspaceAuthorizationDomain, + FACTORY_CLASS=WorkspaceAuthorizationDomainFactory, + workspace=workspace, + group=auth_domain_group, + ) + self.combined_workspace_not_shared = CombinedConsortiumDataWorkspace.objects.create( + upload_cycle=upload_cycle, + workspace=workspace, + ) + + def test_date_completed(self): + CombinedConsortiumDataWorkspace = self.new_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_shared.pk) + self.assertEqual(workspace.date_completed, self.date_shared) + workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_not_shared.pk) + self.assertIsNone(workspace.date_completed) diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 27f9833f..1c8b527e 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -4,10 +4,14 @@ from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.db.utils import IntegrityError from django.test import TestCase +from django.utils import timezone +from faker import Faker from .. import models from . import factories +fake = Faker() + class ConsentGroupTest(TestCase): """Tests for the ConsentGroup model.""" @@ -137,6 +141,38 @@ def test_member_group_uploader_group_must_be_different(self): self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + def test_member_group_non_member_group_must_be_different(self): + """The same group cannot be used as the members group and non-members group.""" + group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + member_group=group, + non_member_group=group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + + def test_non_member_group_uploader_group_must_be_different(self): + """The same group cannot be used as the members group and uploaders group.""" + group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + non_member_group=group, + uploader_group=group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + def test_error_two_rcs_same_member_group(self): """Cannot have the same member group for two RCs.""" member_group = ManagedGroupFactory.create() @@ -183,6 +219,7 @@ def test_model_saving(self): instance.full_clean() instance.save() self.assertIsInstance(instance, models.UploadCycle) + self.assertIsNone(instance.date_ready_for_compute) def test_model_saving_with_note(self): """Creation using the model constructor and .save() works.""" @@ -266,8 +303,39 @@ def test_start_date_equal_to_end_date(self): self.assertEqual(len(e.exception.message_dict), 1) self.assertIn(NON_FIELD_ERRORS, e.exception.message_dict) self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("end_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) self.assertIn("after start_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) + def test_start_date_after_date_ready_for_compute(self): + today = date.today() + instance = factories.UploadCycleFactory.build( + start_date=today + timedelta(days=1), + end_date=today + timedelta(days=10), + date_ready_for_compute=today, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.message_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.message_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("date_ready_for_compute", e.exception.message_dict[NON_FIELD_ERRORS][0]) + self.assertIn("after start_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) + + def test_date_ready_for_compute_after_end_date(self): + today = date.today() + instance = factories.UploadCycleFactory.build( + start_date=today, + end_date=today + timedelta(days=10), + date_ready_for_compute=today + timedelta(days=11), + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.message_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.message_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("date_ready_for_compute", e.exception.message_dict[NON_FIELD_ERRORS][0]) + self.assertIn("before end_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) + def test_get_partner_upload_workspaces_no_date_completed(self): """PartnerUploadWorkspace with no date_completed is not included.""" upload_cycle = factories.UploadCycleFactory.create() @@ -373,7 +441,6 @@ def test_get_partner_upload_workspaces_full_test(self): version=2, date_completed=None, ) - # import ipdb; ipdb.set_trace() included_workspaces = upload_cycle.get_partner_upload_workspaces() self.assertEqual(included_workspaces.count(), 2) self.assertNotIn(workspace_1, included_workspaces) @@ -381,6 +448,57 @@ def test_get_partner_upload_workspaces_full_test(self): self.assertIn(workspace_3, included_workspaces) self.assertNotIn(workspace_4, included_workspaces) + def test_date_ready_for_compute(self): + """UploadCycle is ready for compute if all PartnerUploadWorkspaces have date_completed.""" + upload_cycle = factories.UploadCycleFactory.create() + self.assertIsNone(upload_cycle.date_ready_for_compute) + date = timezone.localdate() + upload_cycle.date_ready_for_compute = date + upload_cycle.save() + self.assertEqual(upload_cycle.date_ready_for_compute, date) + + def test_is_current_is_past_is_future(self): + # Previous cycle. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() - timedelta(days=40), + end_date=timezone.localdate() - timedelta(days=10), + ) + self.assertTrue(instance.is_past) + self.assertFalse(instance.is_current) + self.assertFalse(instance.is_future) + # Current cycle, end date today. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate(), + ) + self.assertFalse(instance.is_past) + self.assertTrue(instance.is_current) + self.assertFalse(instance.is_future) + # Current cycle. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=10), + ) + self.assertFalse(instance.is_past) + self.assertTrue(instance.is_current) + self.assertFalse(instance.is_future) + # Current cycle, start date today. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate(), + end_date=timezone.localdate() + timedelta(days=10), + ) + self.assertFalse(instance.is_past) + self.assertTrue(instance.is_current) + self.assertFalse(instance.is_future) + # Future cycle. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=40), + ) + self.assertFalse(instance.is_past) + self.assertFalse(instance.is_current) + self.assertTrue(instance.is_future) + class UploadWorkspaceTest(TestCase): """Tests for the UploadWorkspace model.""" @@ -399,13 +517,14 @@ def test_model_saving(self): ) instance.save() self.assertIsInstance(instance, models.UploadWorkspace) + self.assertIsNone(instance.date_qc_completed) def test_str_method(self): """The custom __str__ method returns the correct string.""" instance = factories.UploadWorkspaceFactory.create() instance.save() self.assertIsInstance(instance.__str__(), str) - self.assertEqual(instance.__str__(), instance.workspace.__str__()) + self.assertEqual(instance.__str__(), instance.workspace.name) def test_unique_constraint(self): """Cannot save two instances with the same ResearchCenter, ConsentGroup, and version.""" @@ -487,6 +606,16 @@ def test_duplicated_workspace(self): with self.assertRaises(IntegrityError): instance_2.save() + def test_date_qc_completed_before_upload_cycle_end_date(self): + instance = factories.UploadWorkspaceFactory.create(upload_cycle__is_current=True) + instance.date_qc_completed = timezone.localdate() + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("after end_date", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + class PartnerGroupTest(TestCase): """Tests for the ResearchCenter model.""" @@ -748,6 +877,16 @@ def test_str_method(self): self.assertIsInstance(instance.__str__(), str) self.assertEqual(instance.__str__(), instance.workspace.__str__()) + def test_date_completed_before_upload_cycle_end_date(self): + instance = factories.CombinedConsortiumDataWorkspaceFactory.create(upload_cycle__is_current=True) + instance.date_completed = timezone.localdate() + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("after end_date", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + class ReleaseWorkspaceTest(TestCase): """Tests for the ReleaseWorkspace model.""" diff --git a/gregor_django/gregor_anvil/tests/test_tables.py b/gregor_django/gregor_anvil/tests/test_tables.py index bafc2386..fb5d90c4 100644 --- a/gregor_django/gregor_anvil/tests/test_tables.py +++ b/gregor_django/gregor_anvil/tests/test_tables.py @@ -15,6 +15,63 @@ from . import factories +class BooleanIconColumnTest(TestCase): + """Tests for the BooleanIconColumn class.""" + + def test_render_default(self): + """render method with defaults.""" + column = tables.BooleanIconColumn() + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + value = column.render(False, None, None) + self.assertEqual(value, "") + + def test_render_show_false_icon(self): + """render method with defaults.""" + column = tables.BooleanIconColumn(show_false_icon=True) + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + value = column.render(False, None, None) + self.assertIn("bi-x-circle-fill", value) + self.assertIn("red", value) + + def test_true_color(self): + column = tables.BooleanIconColumn(true_color="blue") + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("blue", value) + value = column.render(False, None, None) + self.assertEqual(value, "") + + def test_true_icon(self): + column = tables.BooleanIconColumn(true_icon="dash") + value = column.render(True, None, None) + self.assertIn("bi-dash", value) + self.assertIn("green", value) + value = column.render(False, None, None) + self.assertEqual(value, "") + + def test_false_color(self): + column = tables.BooleanIconColumn(show_false_icon=True, false_color="blue") + value = column.render(False, None, None) + self.assertIn("bi-x-circle-fill", value) + self.assertIn("blue", value) + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + + def test_false_icon(self): + column = tables.BooleanIconColumn(show_false_icon=True, false_icon="dash") + value = column.render(False, None, None) + self.assertIn("bi-dash", value) + self.assertIn("red", value) + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + + class AccountTableTest(TestCase): """Tests for the AccountTable in this app.""" diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 478b7549..fa752a8a 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -6,6 +6,7 @@ from anvil_consortium_manager import models as acm_models from anvil_consortium_manager.models import AnVILProjectManagerAccess from anvil_consortium_manager.tests import factories as acm_factories +from anvil_consortium_manager.tests.api_factories import ErrorResponseFactory from anvil_consortium_manager.tests.utils import AnVILAPIMockTestMixin from constance.test import override_config from django.conf import settings @@ -17,11 +18,14 @@ from django.shortcuts import resolve_url from django.test import RequestFactory, TestCase from django.urls import reverse +from django.utils import timezone +from freezegun import freeze_time from gregor_django.users.tables import UserTable from gregor_django.users.tests.factories import UserFactory from .. import forms, models, tables, views +from ..audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit from . import factories # from .utils import AnVILAPIMockTestMixin @@ -311,6 +315,14 @@ def test_link_to_member_group(self): response = self.client.get(self.get_url(obj.pk)) self.assertContains(response, member_group.get_absolute_url()) + def test_link_to_non_member_group(self): + """Response includes a link to the non-members group if it exists.""" + non_member_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(non_member_group=non_member_group) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertContains(response, non_member_group.get_absolute_url()) + def test_link_to_uploader_group(self): """Response includes a link to the uploader group if it exists.""" uploader_group = acm_factories.ManagedGroupFactory.create() @@ -719,7 +731,7 @@ def test_has_form_in_context(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertTrue("form" in response.context_data) - self.assertIsInstance(response.context_data["form"], forms.UploadCycleForm) + self.assertIsInstance(response.context_data["form"], forms.UploadCycleCreateForm) def test_can_create_an_object(self): """Posting valid data to the form creates an object.""" @@ -822,6 +834,168 @@ def test_post_blank_data(self): self.assertEqual(models.UploadCycle.objects.count(), 0) +class UploadCycleUpdateTest(TestCase): + def setUp(self): + """Set up test class.""" + self.factory = RequestFactory() + # Create a user with both view and edit permissions. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + # Data for forms + self.start_date = date.today() + self.end_date = self.start_date + timedelta(days=10) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("gregor_anvil:upload_cycles:update", args=args) + + def get_view(self): + """Return the view being tested.""" + return views.UploadCycleUpdate.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url(1)) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) + + def test_status_code_with_user_permission(self): + """Returns successful response code.""" + obj = factories.UploadCycleFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertEqual(response.status_code, 200) + + def test_access_with_view_permission(self): + """Raises permission denied if user has only view permission.""" + user_with_view_perm = User.objects.create_user(username="test-other", password="test-other") + user_with_view_perm.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + request = self.factory.get(self.get_url(1)) + request.user = user_with_view_perm + with self.assertRaises(PermissionDenied): + self.get_view()(request, pk=1) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(1)) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request, pk=1) + + def test_has_form_in_context(self): + """Response includes a form.""" + obj = factories.UploadCycleFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertTrue("form" in response.context_data) + self.assertIsInstance(response.context_data["form"], forms.UploadCycleUpdateForm) + + def test_can_update_an_object(self): + """Posting valid data to the form creates an object.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": obj.start_date, + "end_date": obj.end_date, + "date_ready_for_compute": timezone.localdate(), + "note": "", + }, + ) + self.assertEqual(response.status_code, 302) + self.assertEqual(models.UploadCycle.objects.count(), 1) + obj.refresh_from_db() + self.assertEqual(obj.date_ready_for_compute, timezone.localdate()) + # History is added. + self.assertEqual(obj.history.count(), 2) + self.assertEqual(obj.history.latest().history_type, "~") + + def test_success_message(self): + """Response includes a success message if successful.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": obj.start_date, + "end_date": obj.end_date, + "date_ready_for_compute": timezone.localdate(), + "note": "", + }, + ) + self.assertEqual(response.status_code, 302) + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertEqual(views.UploadCycleUpdate.success_message, str(messages[0])) + + def test_redirects_to_object_detail(self): + """After successfully creating an object, view redirects to the object's detail page.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": obj.start_date, + "end_date": obj.end_date, + "date_ready_for_compute": timezone.localdate(), + "note": "", + }, + ) + self.assertRedirects(response, obj.get_absolute_url()) + + def test_object_does_not_exist(self): + """Raises 404 when object doesn't exist.""" + request = self.factory.get(self.get_url(1)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request, pk=1) + + def test_invalid_input(self): + """Posting invalid data does not create an object.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + {"start_date": self.start_date, "end_date": self.end_date, "date_ready_for_compute": "foo"}, + ) + self.assertEqual(response.status_code, 200) + form = response.context_data["form"] + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors.keys()), 1) + self.assertIn("date_ready_for_compute", form.errors.keys()) + self.assertEqual(len(form.errors["date_ready_for_compute"]), 1) + obj.refresh_from_db() + self.assertIsNone(obj.date_ready_for_compute) + + def test_post_blank_data_ready_for_compute(self): + """Can successfully post blank data for date_ready_for_compute.""" + obj = factories.UploadCycleFactory.create(is_current=True) + start_date = obj.start_date + end_date = obj.end_date + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": start_date + timedelta(days=1), + "end_date": end_date + timedelta(days=1), + }, + ) + self.assertEqual(response.status_code, 302) + obj.refresh_from_db() + self.assertEqual(obj.start_date, start_date + timedelta(days=1)) + self.assertEqual(obj.end_date, end_date + timedelta(days=1)) + self.assertIsNone(obj.date_ready_for_compute) + + class UploadCycleDetailTest(TestCase): """Tests for the UploadCycle view.""" @@ -973,6 +1147,53 @@ def test_partner_upload_workspace_table(self): self.assertIn(workspace.workspace, table.data) self.assertNotIn(other_workspace.workspace, table.data) + def test_link_to_audit(self): + """Response includes a link to the audit page.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertContains( + response, reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) + ) + + def test_link_to_update_view_staff_edit(self): + """Response includes a link to the update view for staff edit users.""" + obj = self.model_factory.create() + self.user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + + def test_link_to_update_view_staff_view(self): + """Response includes a link to the update view for staff edit users.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertNotContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + + def test_contains_sharing_audit_button(self): + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + url = reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) + self.assertContains(response, url) + + def test_contains_auth_domain_audit_button(self): + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + url = reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) + self.assertContains(response, url) + + def test_includes_date_ready_for_compute(self): + obj = self.model_factory.create(is_past=True, date_ready_for_compute="2022-01-01") + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class UploadCycleListTest(TestCase): """Tests for the UploadCycleList view.""" @@ -1101,22 +1322,38 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) - def test_contains_share_with_auth_domain_button(self): - acm_factories.WorkspaceAuthorizationDomainFactory.create( - workspace=self.object.workspace, group__name="test_auth" + def test_contains_sharing_audit_button(self): + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + url = reverse( + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], ) + self.assertContains(response, url) + + def test_contains_auth_domain_audit_button(self): self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) url = reverse( - "anvil_consortium_manager:workspaces:sharing:new_by_group", + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", args=[ self.object.workspace.billing_project.name, self.object.workspace.name, - "test_auth", ], ) self.assertContains(response, url) + def test_includes_date_qc_completed(self): + self.object.date_qc_completed = "2022-01-01" + self.object.save() + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class UploadWorkspaceListTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" @@ -1685,6 +1922,14 @@ def test_contains_share_with_auth_domain_button(self): ) self.assertContains(response, url) + def test_includes_date_completed(self): + self.object.date_completed = "2022-01-01" + self.object.save() + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class ReleaseWorkspaceDetailTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceDetail view using the ReleaseWorkspaceAdapter.""" @@ -2140,3 +2385,4519 @@ def test_cc_admins_membership(self): self.assertEqual(membership.parent_group, new_group) self.assertEqual(membership.child_group, self.admins_group) self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + +class UploadWorkspaceSharingAuditTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAudit view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:sharing:all", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceSharingAudit.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url()) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url()) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=acm_models.WorkspaceGroupSharing.OWNER + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), acm_models.WorkspaceGroupSharing.OWNER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_reader(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_writer(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_with_compute(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_owner(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_stop_sharing(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + # Change upload workspace end dates so it's in the past. + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_past=True, + research_center__uploader_group=group, + date_qc_completed=timezone.localdate() - timedelta(days=1), + ) + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_stop_sharing(self): + """error_table shows a record when an audit error is detected.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceSharingAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAuditByWorkspace view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceSharingAuditByWorkspace.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar"), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_billing_project_name(self): + """Raises a 404 error with an invalid object billing project.""" + request = self.factory.get(self.get_url("foo", self.upload_workspace.workspace.name)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug="foo", + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_workspace_name(self): + """Raises a 404 error with an invalid workspace name.""" + request = self.factory.get(self.get_url(self.upload_workspace.workspace.billing_project.name, "foo")) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug="foo", + ) + + def test_context_audit_results(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(self.upload_workspace, audit_results.queryset) + + def test_context_audit_results_does_not_include_other_workspaces(self): + """The audit_results does not include other workspaces.""" + other_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + audit_results = response.context_data["audit_results"] + self.assertEqual(audit_results.queryset.count(), 1) + self.assertNotIn(other_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=acm_models.WorkspaceGroupSharing.OWNER + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), acm_models.WorkspaceGroupSharing.OWNER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_reader(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = self.upload_workspace.workspace.authorization_domains.first() + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_writer(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_with_compute(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_owner(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_stop_sharing(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + # Change upload workspace end dates so it's in the past. + self.upload_workspace.upload_cycle.start_date = timezone.now() - timedelta(days=20) + self.upload_workspace.upload_cycle.end_date = timezone.now() - timedelta(days=10) + self.upload_workspace.upload_cycle.save() + self.upload_workspace.date_qc_completed = timezone.localdate() - timedelta(days=1) + self.upload_workspace.save() + group = acm_factories.ManagedGroupFactory.create() + rc = self.upload_workspace.research_center + rc.uploader_group = group + rc.save() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_stop_sharing(self): + """error_table shows a record when an audit error is detected.""" + # Change upload workspace end dates so it's in the past. + group = acm_factories.ManagedGroupFactory.create() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceSharingAuditByUploadCycleTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAuditByUploadCycle view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.upload_cycle = factories.UploadCycleFactory.create(is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceSharingAuditByUploadCycle.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url(self.upload_cycle.cycle + 1)) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.upload_cycle.cycle + 1), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(self.upload_cycle.cycle)) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request, cycle=self.upload_cycle.cycle) + + def test_invalid_upload_cycle(self): + """Raises a 404 error with an invalid upload cycle.""" + request = self.factory.get(self.get_url(self.upload_cycle.cycle + 1)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request, cycle=self.upload_cycle.cycle + 1) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + upload_workspace_2 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_audit_results_ignores_other_upload_cycles(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__cycle=self.upload_cycle.cycle + 1) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + self.assertNotIn(upload_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=acm_models.WorkspaceGroupSharing.OWNER + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), acm_models.WorkspaceGroupSharing.OWNER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_reader(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = upload_workspace.workspace.authorization_domains.first() + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_writer(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_with_compute(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_owner(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_stop_sharing(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + # Change upload workspace end dates so it's in the past. + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=self.upload_cycle, + research_center__uploader_group=group, + date_qc_completed=timezone.localdate() - timedelta(days=1), + ) + self.upload_cycle.start_date = timezone.now() - timedelta(days=20) + self.upload_cycle.end_date = timezone.now() - timedelta(days=10) + self.upload_cycle.save() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_stop_sharing(self): + """error_table shows a record when an audit error is detected.""" + # Change upload workspace end dates so it's in the past. + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceSharingAuditResolveTest(AnVILAPIMockTestMixin, TestCase): + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:sharing:resolve", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceSharingAuditResolve.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar", "foobar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar", "foobar"), + ) + + def test_status_code_with_user_permission_staff_edit(self): + """Returns successful response code if the user has staff edit permission.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + + def test_status_code_with_user_permission_staff_view(self): + """Returns 403 response code if the user has staff view permission.""" + user_view = User.objects.create_user(username="test-view", password="test-view") + user_view.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(self.user) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_view + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_status_code_with_user_permission_view(self): + """Returns forbidden response code if the user has view permission.""" + user = User.objects.create_user(username="test-none", password="test-none") + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_get_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_group_does_not_exist(self): + """get request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_get_context_audit_result(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + self.assertIsInstance( + response.context_data["audit_result"], + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditResult, + ) + + def test_get_verified_shared(self): + """Get request with VerifiedShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER + ) + + def test_get_verified_not_shared(self): + """Get request with VerifiedNotShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.VerifiedNotShared) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS + ) + + def test_get_share_as_reader(self): + """Get request with ShareAsReader result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER + ) + + def test_get_share_as_writer(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareAsWriter) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, + ) + + def test_get_share_with_compute(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareWithCompute) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE + ) + + def test_get_share_as_owner(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareAsOwner) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER + ) + + def test_post_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_group_does_not_exist(self): + """post request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_post_verified_shared(self): + """Post request with VerifiedShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertEqual(sharing.modified, date_created) + + def test_post_verified_not_shared(self): + """Post request with VerifiedNotShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + + def test_post_new_share_as_reader(self): + """Post request with ShareAsReader result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "READER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.READER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_as_writer(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_with_compute(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertTrue(sharing.can_compute) + + def test_post_new_share_as_owner(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "OWNER", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_post_new_stop_sharing(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "NO ACCESS", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + + def test_post_update_share_as_reader(self): + """Post request with ShareAsReader result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.WRITER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "READER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.READER) + self.assertFalse(sharing.can_compute) + + def test_post_update_share_as_writer(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertFalse(sharing.can_compute) + + def test_post_update_share_with_compute(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertTrue(sharing.can_compute) + + def test_post_update_share_as_owner(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "OWNER", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_post_share_as_reader_htmx(self): + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "READER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.READER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_as_writer_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_with_compute_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertTrue(sharing.can_compute) + + def test_post_new_share_as_owner_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "OWNER", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_post_new_stop_sharing_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "NO ACCESS", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + + def test_post_share_as_reader_anvil_api_error(self): + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareAsReader) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_share_as_writer_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareAsWriter) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_share_with_compute_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareWithCompute) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_share_as_owner_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.ShareAsOwner) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_stop_sharing_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The sharing object was not deleted. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertEqual(sharing.modified, date_created) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_sharing_audit.StopSharing) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_share_as_reader_anvil_api_error_htmx(self): + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_as_writer_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_with_compute_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_as_owner_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_stop_sharing_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_error) + # The sharing object was not deleted. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertEqual(sharing.modified, date_created) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_as_writer_group_not_found_on_anvil_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + # Successful error code, but with usersNotFound + status=200, + json={"invitesSent": [], "usersNotFound": acls, "usersUpdated": []}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceSharingAuditResolve.htmx_error) + # The sharing object was not deleted. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # sharing.refresh_from_db() + # self.assertEqual(sharing.created, date_created) + # self.assertEqual(sharing.modified, date_created) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + +class UploadWorkspaceAuthDomainAuditTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAudit view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:all", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditByUploadCycle.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url()) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url()) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_invalid_upload_cycle(self): + """Raises a 404 error with an invalid upload cycle.""" + request = self.factory.get(self.get_url()) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_verified_table_no_access(self): + """verified_table shows a record when audit has verifiednotmember.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_past=True) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), None) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_member(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_admin(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_remove(self): + """needs_action_table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_past=True) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_remove(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_errors_table_change_to_member(self): + """errors table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_change_to_admin(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceAuthDomainAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceAuthDomainAuditByWorkspace view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditByWorkspace.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar"), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_billing_project_name(self): + """Raises a 404 error with an invalid object billing project.""" + request = self.factory.get(self.get_url("foo", self.upload_workspace.workspace.name)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug="foo", + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_workspace_name(self): + """Raises a 404 error with an invalid workspace name.""" + request = self.factory.get(self.get_url(self.upload_workspace.workspace.billing_project.name, "foo")) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug="foo", + ) + + def test_context_audit_results(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(self.upload_workspace, audit_results.queryset) + + def test_context_audit_results_does_not_include_other_workspaces(self): + """The audit_results does not include other workspaces.""" + other_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + audit_results = response.context_data["audit_results"] + self.assertEqual(audit_results.queryset.count(), 1) + self.assertNotIn(other_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_verified_table_no_access(self): + """verified_table shows a record when audit has verifiednotmember.""" + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=20) + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=10) + self.upload_workspace.upload_cycle.save() + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), None) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_member(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_admin(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_remove(self): + """needs_action_table shows a record when audit finds that access needs to be removed.""" + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=20) + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=10) + self.upload_workspace.upload_cycle.save() + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_remove(self): + """error table shows a record when audit finds that access needs to be removed.""" + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_errors_table_change_to_member(self): + """needs action table shows a record when audit finds that access needs to be removed.""" + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_change_to_admin(self): + """error table shows a record when audit finds that access needs to be removed.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceAuthDomainAuditByUploadCycleTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAuditByUploadCycle view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.upload_cycle = factories.UploadCycleFactory.create(is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditByUploadCycle.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url(self.upload_cycle.cycle + 1)) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.upload_cycle.cycle + 1), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(self.upload_cycle.cycle)) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request, cycle=self.upload_cycle.cycle) + + def test_invalid_upload_cycle(self): + """Raises a 404 error with an invalid upload cycle.""" + request = self.factory.get(self.get_url(self.upload_cycle.cycle + 1)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request, cycle=self.upload_cycle.cycle + 1) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + upload_workspace_2 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_audit_results_ignores_other_upload_cycles(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__cycle=self.upload_cycle.cycle + 1) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + self.assertNotIn(upload_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_verified_table_no_access(self): + """verified_table shows a record when audit has verifiednotmember.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__start_date=timezone.localdate() - timedelta(days=20), + upload_cycle__end_date=timezone.localdate() - timedelta(days=10), + ) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), None) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_member(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_admin(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_remove(self): + """needs_action_table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__start_date=timezone.localdate() - timedelta(days=20), + upload_cycle__end_date=timezone.localdate() - timedelta(days=10), + ) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_remove(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_errors_table_change_to_member(self): + """needs action table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_change_to_admin(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceAuthDomainAuditResolveTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceAuthDomainAuditResolve view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:resolve", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditResolve.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar", "foobar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar", "foobar"), + ) + + def test_status_code_with_user_permission_staff_edit(self): + """Returns successful response code if the user has staff edit permission.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + + def test_status_code_with_user_permission_staff_view(self): + """Returns 403 response code if the user has staff view permission.""" + user_view = User.objects.create_user(username="test-view", password="test-view") + user_view.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(self.user) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_view + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_status_code_with_user_permission_view(self): + """Returns forbidden response code if the user has view permission.""" + user = User.objects.create_user(username="test-none", password="test-none") + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_get_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_group_does_not_exist(self): + """get request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_get_context_audit_result(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + self.assertIsInstance( + response.context_data["audit_result"], + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditResult, + ) + + def test_get_verified_member(self): + """Get request with VerifiedMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_get_verified_admin(self): + """Get request with VerifiedAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS + ) + + def test_get_verified_not_member(self): + """Get request with VerifiedNotMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP + ) + + def test_get_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_get_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS + ) + + def test_get_change_to_member(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_get_change_to_admin(self): + """Get request with ChangeToAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS + ) + + def test_get_remove(self): + """Get request with ChangeToAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP + ) + + def test_post_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_group_does_not_exist(self): + """post request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_post_verified_member(self): + """Get request with VerifiedMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_verified_admin(self): + """Get request with VerifiedAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_verified_not_member(self): + """Get request with VerifiedNotMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_change_to_member(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_change_to_admin(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_htmx_verified_member(self): + """Get request with VerifiedMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_htmx_verified_admin(self): + """Get request with VerifiedAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_htmx_verified_not_member(self): + """Get request with VerifiedNotMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_htmx_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_htmx_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_htmx_change_to_member(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_htmx_change_to_admin(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_htmx_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_htmx_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_api_error_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_member_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_member_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_admin_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_admin_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_htmx_api_error_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_member_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_member_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_admin_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_admin_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 771957d9..c65a5893 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -33,6 +33,7 @@ [ path("/", views.UploadCycleDetail.as_view(), name="detail"), path("new/", views.UploadCycleCreate.as_view(), name="new"), + path("/update/", views.UploadCycleUpdate.as_view(), name="update"), path("", views.UploadCycleList.as_view(), name="list"), ], "upload_cycles", @@ -45,6 +46,65 @@ "reports", ) +upload_workspace_sharing_audit_patterns = ( + [ + path("all/", views.UploadWorkspaceSharingAudit.as_view(), name="all"), + path( + "resolve////", + views.UploadWorkspaceSharingAuditResolve.as_view(), + name="resolve", + ), + path( + "upload_cycle//", + views.UploadWorkspaceSharingAuditByUploadCycle.as_view(), + name="by_upload_cycle", + ), + path( + "//", + views.UploadWorkspaceSharingAuditByWorkspace.as_view(), + name="by_upload_workspace", + ), + ], + "sharing", +) + +upload_workspace_auth_domain_audit_patterns = ( + [ + path("all/", views.UploadWorkspaceAuthDomainAudit.as_view(), name="all"), + path( + "resolve////", + views.UploadWorkspaceAuthDomainAuditResolve.as_view(), + name="resolve", + ), + path( + "upload_cycle//", + views.UploadWorkspaceAuthDomainAuditByUploadCycle.as_view(), + name="by_upload_cycle", + ), + path( + "//", + views.UploadWorkspaceAuthDomainAuditByWorkspace.as_view(), + name="by_upload_workspace", + ), + ], + "auth_domains", +) + +upload_workspace_audit_patterns = ( + [ + path("sharing/", include(upload_workspace_sharing_audit_patterns)), + path("auth_domain/", include(upload_workspace_auth_domain_audit_patterns)), + ], + "upload_workspaces", +) + +audit_patterns = ( + [ + path("upload_workspaces/", include(upload_workspace_audit_patterns)), + ], + "audit", +) + urlpatterns = [ # path("", views.Index.as_view(), name="index"), path("research_centers/", include(research_center_patterns)), @@ -52,4 +112,5 @@ path("consent_groups/", include(consent_group_patterns)), path("upload_cycles/", include(upload_cycle_patterns)), path("reports/", include(workspace_report_patterns)), + path("audit/", include(audit_patterns)), ] diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 4f251af5..8b43b389 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -1,17 +1,31 @@ +from anvil_consortium_manager.anvil_api import AnVILAPIError from anvil_consortium_manager.auth import ( AnVILConsortiumManagerStaffEditRequired, AnVILConsortiumManagerStaffViewRequired, ) -from anvil_consortium_manager.models import Account, Workspace +from anvil_consortium_manager.exceptions import AnVILGroupNotFound +from anvil_consortium_manager.models import ( + Account, + GroupGroupMembership, + ManagedGroup, + Workspace, + WorkspaceGroupSharing, +) +from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.messages.views import SuccessMessageMixin +from django.db import transaction from django.db.models import Count, Q -from django.views.generic import CreateView, DetailView, TemplateView +from django.forms import Form +from django.http import Http404, HttpResponse +from django.utils.translation import gettext_lazy as _ +from django.views.generic import CreateView, DetailView, FormView, TemplateView, UpdateView from django_tables2 import MultiTableMixin, SingleTableView from gregor_django.users.tables import UserTable from . import forms, models, tables +from .audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit User = get_user_model() @@ -97,10 +111,19 @@ class UploadCycleCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageM """View to create a new UploadCycle object.""" model = models.UploadCycle - form_class = forms.UploadCycleForm + form_class = forms.UploadCycleCreateForm success_message = "Successfully created Upload Cycle." +class UploadCycleUpdate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView): + """View to create a new UploadCycle object.""" + + model = models.UploadCycle + slug_field = "cycle" + form_class = forms.UploadCycleUpdateForm + success_message = "Successfully updated Upload Cycle." + + class UploadCycleDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): """View to show details about an `UploadCycle`.""" @@ -163,3 +186,409 @@ def get_context_data(self, **kwargs): ) context["workspace_count_table"] = tables.WorkspaceReportTable(qs) return context + + +class UploadWorkspaceSharingAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView): + """View to audit UploadWorkspace sharing for all UploadWorkspaces.""" + + template_name = "gregor_anvil/upload_workspace_sharing_audit.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceSharingAuditByWorkspace(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_sharing_audit.html" + model = models.UploadWorkspace + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=self.model.objects.filter(pk=self.object.pk) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceSharingAuditByUploadCycle(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_sharing_audit.html" + model = models.UploadCycle + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + cycle = self.kwargs.get("cycle", None) + queryset = self.model.objects.filter(cycle=cycle) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.UploadWorkspace.objects.filter(upload_cycle=self.object) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceSharingAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): + """View to resolve UploadWorkspace audit results.""" + + form_class = Form + template_name = "gregor_anvil/upload_workspace_sharing_audit_resolve.html" + htmx_success = """ Handled!""" + htmx_error = """ Error!""" + + def get_upload_workspace(self): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_managed_group(self, queryset=None): + """Look up the ManagedGroup by name.""" + try: + obj = ManagedGroup.objects.get(name=self.kwargs.get("managed_group_slug", None)) + except ManagedGroup.DoesNotExist: + raise Http404("No ManagedGroups found matching the query") + return obj + + def get_audit_result(self): + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + # No way to set the group queryset, since it is dynamically determined by the workspace. + audit.audit_workspace_and_group(self.upload_workspace, self.managed_group) + # Set to completed, because we are just running this one specific check. + audit.completed = True + return audit.get_all_results()[0] + + def get(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().post(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["upload_workspace"] = self.upload_workspace + context["managed_group"] = self.managed_group + context["audit_result"] = self.audit_result + return context + + def get_success_url(self): + return self.upload_workspace.get_absolute_url() + + def form_valid(self, form): + # Handle the result. + try: + # Set up the sharing instance. + if self.audit_result.current_sharing_instance: + sharing = self.audit_result.current_sharing_instance + else: + sharing = WorkspaceGroupSharing( + workspace=self.upload_workspace.workspace, + group=self.managed_group, + ) + with transaction.atomic(): + if isinstance(self.audit_result, upload_workspace_sharing_audit.VerifiedShared): + # No changes needed. + pass + elif isinstance(self.audit_result, upload_workspace_sharing_audit.VerifiedNotShared): + # No changes needed. + pass + elif isinstance(self.audit_result, upload_workspace_sharing_audit.StopSharing): + sharing.anvil_delete() + sharing.delete() + else: + if isinstance(self.audit_result, upload_workspace_sharing_audit.ShareAsReader): + sharing.access = WorkspaceGroupSharing.READER + sharing.can_compute = False + elif isinstance(self.audit_result, upload_workspace_sharing_audit.ShareAsWriter): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = False + elif isinstance(self.audit_result, upload_workspace_sharing_audit.ShareWithCompute): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = True + elif isinstance(self.audit_result, upload_workspace_sharing_audit.ShareAsOwner): + sharing.access = WorkspaceGroupSharing.OWNER + sharing.can_compute = True + sharing.full_clean() + sharing.save() + sharing.anvil_create_or_update() + except (AnVILAPIError, AnVILGroupNotFound) as e: + if self.request.htmx: + return HttpResponse(self.htmx_error) + else: + messages.error(self.request, "AnVIL API Error: " + str(e)) + return super().form_invalid(form) + # Otherwise, the audit resolution succeeded. + if self.request.htmx: + return HttpResponse(self.htmx_success) + else: + return super().form_valid(form) + + +class UploadWorkspaceAuthDomainAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView): + """View to audit UploadWorkspace auth domain membership for all UploadWorkspaces.""" + + template_name = "gregor_anvil/upload_workspace_sharing_audit.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceAuthDomainAuditByWorkspace(AnVILConsortiumManagerStaffEditRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_auth_domain_audit.html" + model = models.UploadWorkspace + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=self.model.objects.filter(pk=self.object.pk) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceAuthDomainAuditByUploadCycle(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_auth_domain_audit.html" + model = models.UploadCycle + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + cycle = self.kwargs.get("cycle", None) + queryset = self.model.objects.filter(cycle=cycle) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.UploadWorkspace.objects.filter(upload_cycle=self.object) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceAuthDomainAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): + """View to resolve UploadWorkspace auth domain audit results.""" + + form_class = Form + template_name = "gregor_anvil/upload_workspace_auth_domain_audit_resolve.html" + htmx_success = """ Handled!""" + htmx_error = """ Error!""" + + def get_upload_workspace(self): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_managed_group(self, queryset=None): + """Look up the ManagedGroup by name.""" + try: + obj = ManagedGroup.objects.get(name=self.kwargs.get("managed_group_slug", None)) + except ManagedGroup.DoesNotExist: + raise Http404("No ManagedGroups found matching the query") + return obj + + def get_audit_result(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + # No way to set the group queryset, since it is dynamically determined by the workspace. + audit.audit_workspace_and_group(self.upload_workspace, self.managed_group) + # Set to completed, because we are just running this one specific check. + audit.completed = True + return audit.get_all_results()[0] + + def get(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().post(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["upload_workspace"] = self.upload_workspace + context["managed_group"] = self.managed_group + context["audit_result"] = self.audit_result + return context + + def get_success_url(self): + return self.upload_workspace.get_absolute_url() + + def form_valid(self, form): + # Handle the result. + try: + with transaction.atomic(): + # Set up the membership instance. + if self.audit_result.current_membership_instance: + membership = self.audit_result.current_membership_instance + else: + membership = GroupGroupMembership( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.managed_group, + ) + # Now process the result. + if isinstance(self.audit_result, upload_workspace_auth_domain_audit.VerifiedMember): + pass + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.VerifiedAdmin): + pass + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.VerifiedNotMember): + pass + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.Remove): + membership.anvil_delete() + membership.delete() + else: + if isinstance(self.audit_result, upload_workspace_auth_domain_audit.ChangeToMember): + membership.anvil_delete() + membership.role = GroupGroupMembership.MEMBER + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.ChangeToAdmin): + membership.anvil_delete() + membership.role = GroupGroupMembership.ADMIN + else: + if isinstance(self.audit_result, upload_workspace_auth_domain_audit.AddMember): + membership.role = GroupGroupMembership.MEMBER + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.AddAdmin): + membership.role = GroupGroupMembership.ADMIN + membership.full_clean() + membership.save() + membership.anvil_create() + except (AnVILAPIError, AnVILGroupNotFound) as e: + if self.request.htmx: + return HttpResponse(self.htmx_error) + else: + messages.error(self.request, "AnVIL API Error: " + str(e)) + return super().form_invalid(form) + # Otherwise, the audit resolution succeeded. + if self.request.htmx: + return HttpResponse(self.htmx_success) + else: + return super().form_valid(form) diff --git a/gregor_django/templates/__audit_tables.html b/gregor_django/templates/__audit_tables.html new file mode 100644 index 00000000..c7107964 --- /dev/null +++ b/gregor_django/templates/__audit_tables.html @@ -0,0 +1,69 @@ +{% load django_tables2 %} + + +
+
+
+

+ +

+
+
+ + {% render_table verified_table %} + +
+
+
+
+
+ + + +
+
+
+

+ +

+
+
+ + {% render_table needs_action_table %} + +
+
+
+
+
+ + + +
+
+
+

+ +

+
+
+ + {% render_table errors_table %} + +
+
+
+
+
diff --git a/gregor_django/templates/base.html b/gregor_django/templates/base.html index 94e4a3e5..4a6a5c08 100644 --- a/gregor_django/templates/base.html +++ b/gregor_django/templates/base.html @@ -30,6 +30,7 @@ + diff --git a/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html b/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html index bf43f32d..48832960 100644 --- a/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html @@ -3,7 +3,8 @@ {% block workspace_data %}
-
Upload cycle
{{ object.combinedconsortiumdataworkspace.upload_cycle }}
+
Upload cycle
{{ workspace_data_object.upload_cycle }}
+
Date completed
{{ workspace_data_object.date_completed }}
{% endblock workspace_data %} diff --git a/gregor_django/templates/gregor_anvil/email_audit_report.html b/gregor_django/templates/gregor_anvil/email_audit_report.html new file mode 100644 index 00000000..fe112c9e --- /dev/null +++ b/gregor_django/templates/gregor_anvil/email_audit_report.html @@ -0,0 +1,48 @@ + +{% load static i18n %} +{% load render_table from django_tables2 %} +{% get_current_language as LANGUAGE_CODE %} + + + Audit report + + + + +
+ +{% block content %} + +

{{ title }}

+ +

Please visit {{url}} to resolve.

+ +

Verified

+
+ {{ audit_results.verified|length }} record(s) verified. +
+ +

Needs action - {{audit_results.needs_action|length }} record(s)

+
+
    + {% for record in audit_results.needs_action %} +
  • {{ record|stringformat:'r' }}
  • + {% endfor %} +
+
+ +

Errors - {{audit_results.errors|length }} record(s)

+
+
    + {% for record in audit_results.errors %} +
  • {{ record|stringformat:'r' }}
  • + {% endfor %} +
+
+ + +{% endblock content %} + +
+ + diff --git a/gregor_django/templates/gregor_anvil/researchcenter_detail.html b/gregor_django/templates/gregor_anvil/researchcenter_detail.html index 4b8bb1ef..a149f17e 100644 --- a/gregor_django/templates/gregor_anvil/researchcenter_detail.html +++ b/gregor_django/templates/gregor_anvil/researchcenter_detail.html @@ -14,6 +14,13 @@ — {% endif %} +
  • Non-member group: + {% if object.non_member_group %} + {{ object.non_member_group }} + {% else %} + — + {% endif %} +
  • Uploader group: {% if object.uploader_group %} {{ object.uploader_group }} diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html new file mode 100644 index 00000000..1e0437e9 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html @@ -0,0 +1,22 @@ +
    + {% if record.action %} +
    + + {% csrf_token %} + +
    + + {% else %} + — + {% endif %} +
    diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html new file mode 100644 index 00000000..edd9af6c --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html @@ -0,0 +1,36 @@ +
    +
    +

    + +

    +
    +
    + +

    + This audit checks that auth domain membership is appropriate for the current point in the upload cycle. + Membership is expected to be as follows. +

      +
    • Before the combined workspace is completed:
    • +
        +
      • GREGOR_DCC_ADMINS (as admin)
      • +
      • GREGOR_DCC_WRITERS
      • +
      • GREGOR_DCC_MEMBERS
      • +
      • The upload group for the Research Center associated with this upload workspace
      • +
      • The member group for the Research Center associated with this upload workspace
      • +
      • The non-member group for the Research Center associated with this upload workspace
      • +
      +
    • After the combined workspace is completed:
    • +
        +
      • GREGOR_DCC_ADMINS (as admin)
      • +
      • GREGOR_ALL
      • +
      +
    + Note that groups associated with AnVIL (e.g., anvil-admins, anvil-devs) are ignored by the audit. +

    +

    Any errors should be reported!

    +
    +
    +
    +
    diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_action_button.html new file mode 100644 index 00000000..d0acfef9 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_action_button.html @@ -0,0 +1,22 @@ +
    + {% if record.action %} +
    + + {% csrf_token %} + +
    + + {% else %} + — + {% endif %} +
    diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html new file mode 100644 index 00000000..af07d58f --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html @@ -0,0 +1,47 @@ +
    +
    +

    + +

    +
    +
    + +

    + This audit checks that workspace sharing is appropriate for the current point in the upload cycle. + Sharing with the following groups are checked: +

      +
    • The authorization domain of the workspace
    • +
    • GREGOR_DCC_ADMINS
    • +
    • GREGOR_DCC_WRITERS
    • +
    • The upload group for the Research Center associated with this upload workspace
    • +
    • Any additional groups that the workspace is shared with
    • +
    + Note that groups associated with AnVIL (e.g., anvil-admins, anvil-devs) are ignored by the audit. +

    +

    The audit result categories are explained below. +

      + +
    • Verified includes the following:
    • +
        +
      • The workspace is shared with a group with appropriate permissions.
      • +
      • The workspace is not shared with a group that it should not be shared with.
      • +
      + +
    • Needs action includes the following:
    • +
        +
      • The access level for a specific group needs to be changed.
      • +
      + +
    • Errors
    • +
        +
      • The workspace has been shared with an unexpected group as an "OWNER".
      • +
      +
    +

    +

    Any errors should be reported!

    +
    +
    +
    +
    diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html new file mode 100644 index 00000000..1588d613 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html @@ -0,0 +1,25 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} + +{% block title %}Upload workspace auth domain audit{% endblock %} + + +{% block content %} + +

    Upload workspace auth domain audit: {{ object }}

    + +
    +

    + Auditing workspace auth domain membership for {{ object }}. + All records in the "Needs action" table should be handled by clicking on the action button. + Any records in the "Errors" table should be reported. +

    + {% include "gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html" %} + +
    + +

    Audit results

    + +{% include "__audit_tables.html" with verified_table=verified_table needs_action_table=needs_action_table errors_table=errors_table %} + +{% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html new file mode 100644 index 00000000..3f8270e3 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html @@ -0,0 +1,44 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} +{% load crispy_forms_tags %} + +{% block title %}Resolve upload workspace audit{% endblock %} + + +{% block content %} + +

    Resolve upload workspace auth domain audit

    + +
    + + + {% include "gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html" %} + +
    + +

    Audit results

    + + +
    +
    +
    Result
    +

    {{ audit_result }}

    + {% if audit_result.action %} +
    + + {% csrf_token %} + {{ form|crispy }} + + +
    + {% else %} + + {% endif %} +
    +
    + + +{% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit.html new file mode 100644 index 00000000..dc4b1a0c --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit.html @@ -0,0 +1,25 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} + +{% block title %}Upload workspace sharing audit{% endblock %} + + +{% block content %} + +

    Upload workspace sharing audit: {{ object }}

    + +
    +

    + Auditing workspace sharing for {{ object }}. + All records in the "Needs action" table should be handled by clicking on the action button. + Any records in the "Errors" table should be reported. +

    + {% include "gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html" %} + +
    + +

    Audit results

    + +{% include "__audit_tables.html" with verified_table=verified_table needs_action_table=needs_action_table errors_table=errors_table %} + +{% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html b/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html new file mode 100644 index 00000000..9f1b7a89 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html @@ -0,0 +1,44 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} +{% load crispy_forms_tags %} + +{% block title %}Resolve upload workspace audit{% endblock %} + + +{% block content %} + +

    Resolve upload workspace sharing audit

    + +
    + + + {% include "gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html" %} + +
    + +

    Audit results

    + + +
    +
    +
    Result
    +

    {{ audit_result }}

    + {% if audit_result.action %} +
    + + {% csrf_token %} + {{ form|crispy }} + + +
    + {% else %} + + {% endif %} +
    +
    + + +{% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html index f48d3a23..b8683f76 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html @@ -16,6 +16,14 @@
  • Cycle: {{ object.cycle }}
  • Start date: {{ object.start_date }}
  • End date: {{ object.end_date }}
  • +
  • + Date ready for compute: + {% if object.date_ready_for_compute %} + {{ object.date_ready_for_compute }} + {% else %} + — + {% endif %} +
  • {% endblock panel %} @@ -51,3 +59,21 @@

    Partner upload workspaces

    {% endblock after_panel %} + +{% block action_buttons %} +{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_edit %} +

    + + Update + +

    + {% endif %} +

    + + Audit consortium sharing + + + Audit auth domain membership + +

    +{% endblock action_buttons %} diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_form.html b/gregor_django/templates/gregor_anvil/uploadcycle_form.html index 59cfe860..5a2d3581 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_form.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_form.html @@ -5,7 +5,13 @@ {% block content %} -

    Add a new Upload Cycle

    +

    + {% if object %} + Update Upload Cycle {{ object }} + {% else %} + Add a new Upload Cycle + {% endif %} +

    {% csrf_token %} diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index e556e626..f4646b7b 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -3,10 +3,16 @@ {% block workspace_data %}
    -
    Research Center
    {{ object.uploadworkspace.research_center }}
    -
    Consent group
    {{ object.uploadworkspace.consent_group }}
    -
    Upload cycle
    {{ object.uploadworkspace.upload_cycle }}
    -
    Note
    {{ object.note }}
    +
    Research Center
    {{ workspace_data_object.research_center }}
    +
    Consent group
    {{ workspace_data_object.consent_group }}
    +
    Upload cycle
    {{ workspace_data_object.upload_cycle }}
    +
    Date QC completed
    + {% if workspace_data_object.date_qc_completed %} + {{ workspace_data_object.date_qc_completed }} + {% else %} + — + {% endif %} +
    {% endblock workspace_data %} @@ -22,7 +28,7 @@

    - {{ object.uploadworkspace.consent_group.data_use_limitations }} + {{ workspace_data_object.consent_group.data_use_limitations }}
    @@ -34,13 +40,14 @@

    {% block action_buttons %} {% if show_edit_links %} - {% if object.authorization_domains.first %}

    - Share with auth domain -

    - {% else %} -

    no auth domain

    - {% endif %} + + Audit consortium sharing + + + Audit auth domain membership + +

    {% endif %} {{ block.super }} diff --git a/gregor_django/users/audit.py b/gregor_django/users/audit.py index 2a71d97c..c2b82f2b 100644 --- a/gregor_django/users/audit.py +++ b/gregor_django/users/audit.py @@ -14,7 +14,7 @@ from requests_oauthlib import OAuth2, OAuth2Session from gregor_django.drupal_oauth_provider.provider import CustomProvider -from gregor_django.gregor_anvil.audit import GREGORAudit, GREGORAuditResult +from gregor_django.gregor_anvil.audit.base import GREGoRAudit, GREGoRAuditResult from gregor_django.gregor_anvil.models import PartnerGroup, ResearchCenter logger = logging.getLogger(__name__) @@ -43,7 +43,7 @@ class Meta: @dataclass -class UserAuditResult(GREGORAuditResult): +class UserAuditResult(GREGoRAuditResult): local_user: SocialAccount = None anvil_account: Account = None remote_user_data: jsonapi_requests.JsonApiObject = None @@ -118,7 +118,7 @@ class OverDeactivateThresholdUser(UserAuditResult): pass -class UserAudit(GREGORAudit): +class UserAudit(GREGoRAudit): ISSUE_TYPE_USER_INACTIVE = "User is inactive in drupal" ISSUE_TYPE_USER_REMOVED_FROM_SITE = "User removed from site" USER_DEACTIVATE_THRESHOLD = 3 @@ -329,7 +329,7 @@ class Meta: @dataclass -class SiteAuditResult(GREGORAuditResult): +class SiteAuditResult(GREGoRAuditResult): local_site: ResearchCenter remote_site_data: jsonapi_requests.JsonApiObject = None changes: dict = None @@ -377,7 +377,7 @@ class UpdateSite(SiteAuditResult): changes: dict -class SiteAudit(GREGORAudit): +class SiteAudit(GREGoRAudit): ISSUE_TYPE_LOCAL_SITE_INVALID = "Local site is invalid" results_table_class = SiteAuditResultsTable @@ -466,7 +466,7 @@ class Meta: @dataclass -class PartnerGroupAuditResult(GREGORAuditResult): +class PartnerGroupAuditResult(GREGoRAuditResult): local_partner_group: ResearchCenter remote_partner_group_data: jsonapi_requests.JsonApiObject = None changes: dict = None @@ -514,7 +514,7 @@ class UpdatePartnerGroup(PartnerGroupAuditResult): changes: dict -class PartnerGroupAudit(GREGORAudit): +class PartnerGroupAudit(GREGoRAudit): ISSUE_TYPE_LOCAL_PARTNER_GROUP_INVALID = "Local PartnerGroup is invalid" results_table_class = PartnerGroupAuditResultsTable diff --git a/requirements/requirements.in b/requirements/requirements.in index 4de5165b..313b7167 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -55,3 +55,6 @@ jsonapi-requests # for exporting django-tables2 tables to tsv. tablib + +# for htmx +django-htmx diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ed01a7a2..a54d6583 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -12,6 +12,7 @@ asgiref==3.8.1 # via # -r requirements/requirements.in # django + # django-htmx build==1.0.3 # via pip-tools cachetools==5.3.2 @@ -45,6 +46,7 @@ django==4.2.15 # django-dbbackup # django-extensions # django-filter + # django-htmx # django-model-utils # django-picklefield # django-simple-history @@ -72,6 +74,8 @@ django-extensions==3.2.1 # django-anvil-consortium-manager django-filter==23.5 # via django-anvil-consortium-manager +django-htmx==1.19.0 + # via -r requirements/requirements.in django-login-required-middleware==0.9.0 # via -r requirements/requirements.in django-maintenance-mode==0.21.1 diff --git a/requirements/test-requirements.in b/requirements/test-requirements.in index 79f587cf..9381429e 100644 --- a/requirements/test-requirements.in +++ b/requirements/test-requirements.in @@ -25,3 +25,5 @@ pytest-django # https://github.com/pytest-dev/pytest-django django_test_migrations # Mock json api data marshmallow-jsonapi +# Freeze time in tests +freezegun # https://github.com/spulec/freezegun diff --git a/requirements/test-requirements.txt b/requirements/test-requirements.txt index 25be142a..cc792a59 100644 --- a/requirements/test-requirements.txt +++ b/requirements/test-requirements.txt @@ -29,6 +29,8 @@ factory-boy==3.3.0 # via -r requirements/test-requirements.in faker==23.2.1 # via factory-boy +freezegun==1.5.1 + # via -r requirements/test-requirements.in idna==3.7 # via # -c requirements/requirements.txt @@ -63,7 +65,9 @@ pytest-sugar==1.0.0 pytest-xdist==3.6.1 # via -r requirements/test-requirements.in python-dateutil==2.8.2 - # via faker + # via + # faker + # freezegun pyyaml==6.0.1 # via responses requests==2.32.3