diff --git a/add_cdsa_example_data.py b/add_cdsa_example_data.py
index 16152baf..61e28226 100644
--- a/add_cdsa_example_data.py
+++ b/add_cdsa_example_data.py
@@ -9,6 +9,7 @@
ManagedGroupFactory,
WorkspaceGroupSharingFactory,
)
+from django.conf import settings
from primed.cdsa.tests import factories
from primed.duo.tests.factories import DataUseModifierFactory, DataUsePermissionFactory
@@ -17,16 +18,23 @@
from primed.users.models import User
from primed.users.tests.factories import UserFactory
+# Create major versions
+major_version = factories.AgreementMajorVersionFactory.create(version=1)
+
# Create some agreement versions
-v10 = factories.AgreementVersionFactory.create(major_version=1, minor_version=0)
-v11 = factories.AgreementVersionFactory.create(major_version=1, minor_version=1)
+v10 = factories.AgreementVersionFactory.create(
+ major_version=major_version, minor_version=0
+)
+v11 = factories.AgreementVersionFactory.create(
+ major_version=major_version, minor_version=1
+)
# Create a couple signed CDSAs.
dup = DataUsePermissionFactory.create(abbreviation="GRU")
dum = DataUseModifierFactory.create(abbreviation="NPU")
# create the CDSA auth group
-cdsa_group = ManagedGroupFactory.create(name="PRIMED_CDSA")
+cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME)
# Create some study sites.
StudySiteFactory.create(short_name="CARDINAL", full_name="CARDINAL")
diff --git a/config/settings/local.py b/config/settings/local.py
index 252382b3..b6b17daa 100644
--- a/config/settings/local.py
+++ b/config/settings/local.py
@@ -76,3 +76,4 @@
ANVIL_DATA_ACCESS_GROUP_PREFIX = env(
"ANVIL_DATA_ACCESS_GROUP_PREFIX", default="DEV_PRIMED"
)
+ANVIL_CDSA_GROUP_NAME = env("ANVIL_CDSA_GROUP_NAME", default="DEV_PRIMED_CDSA")
diff --git a/primed/cdsa/admin.py b/primed/cdsa/admin.py
index 6f5d373c..cdc71f4d 100644
--- a/primed/cdsa/admin.py
+++ b/primed/cdsa/admin.py
@@ -4,17 +4,33 @@
from . import models
+@admin.register(models.AgreementMajorVersion)
+class AgreementMajorVersion(SimpleHistoryAdmin):
+ """Admin class for the `AgreementMajorVersion` model."""
+
+ list_display = (
+ "version",
+ "is_valid",
+ )
+ list_filter = (
+ "version",
+ "is_valid",
+ )
+ sortable_by = ("version",)
+
+
@admin.register(models.AgreementVersion)
class AgreementVersion(SimpleHistoryAdmin):
"""Admin class for the `AgreementVersion` model."""
list_display = (
"full_version",
- "major_version",
- "minor_version",
"date_approved",
)
- list_filter = ("major_version",)
+ list_filter = (
+ "major_version",
+ "major_version__is_valid",
+ )
sortable_by = (
"major_version",
"minor_version",
@@ -38,6 +54,7 @@ class SignedAgreement(SimpleHistoryAdmin):
"type",
"is_primary",
"version",
+ "status",
)
search_fields = (
"representative",
@@ -63,6 +80,7 @@ class MemberAgreementAdmin(SimpleHistoryAdmin):
list_filter = (
"study_site",
"signed_agreement__is_primary",
+ "signed_agreement__status",
)
@@ -77,6 +95,7 @@ class DataAffiliateAgreementAdmin(SimpleHistoryAdmin):
list_filter = (
"study",
"signed_agreement__is_primary",
+ "signed_agreement__status",
)
@@ -88,7 +107,10 @@ class NonDataAffiliateAgreementAdmin(SimpleHistoryAdmin):
"signed_agreement",
"affiliation",
)
- list_filter = ("signed_agreement__is_primary",)
+ list_filter = (
+ "signed_agreement__is_primary",
+ "signed_agreement__status",
+ )
@admin.register(models.CDSAWorkspace)
diff --git a/primed/cdsa/audit/signed_agreement_audit.py b/primed/cdsa/audit/signed_agreement_audit.py
index 19002e6a..c264271f 100644
--- a/primed/cdsa/audit/signed_agreement_audit.py
+++ b/primed/cdsa/audit/signed_agreement_audit.py
@@ -2,7 +2,7 @@
from dataclasses import dataclass
import django_tables2 as tables
-from anvil_consortium_manager.models import GroupGroupMembership, ManagedGroup
+from anvil_consortium_manager.models import ManagedGroup
from django.conf import settings
from django.urls import reverse
from django.utils.safestring import mark_safe
@@ -39,8 +39,6 @@ def get_table_dictionary(self):
"""Return a dictionary that can be used to populate an instance of `SignedAgreementAccessAuditTable`."""
row = {
"signed_agreement": self.signed_agreement,
- "agreement_group": self.signed_agreement.agreement_group,
- "agreement_type": self.signed_agreement.combined_type,
"note": self.note,
"action": self.get_action(),
"action_url": self.get_action_url(),
@@ -107,8 +105,9 @@ class SignedAgreementAccessAuditTable(tables.Table):
"""A table to show results from a SignedAgreementAccessAudit instance."""
signed_agreement = tables.Column(linkify=True)
- agreement_group = tables.Column()
- agreement_type = tables.Column()
+ agreement_group = tables.Column(accessor="signed_agreement__agreement_group")
+ agreement_type = tables.Column(accessor="signed_agreement__combined_type")
+ agreement_version = tables.Column(accessor="signed_agreement__version")
note = tables.Column()
action = tables.Column()
@@ -127,118 +126,215 @@ class SignedAgreementAccessAudit:
"""Audit for Signed Agreements."""
# Access verified.
- VALID_PRIMARY_CDSA = "Valid primary CDSA."
- VALID_COMPONENT_CDSA = "Valid component CDSA."
+ ACTIVE_PRIMARY_AGREEMENT = "Active primary CDSA."
+ ACTIVE_COMPONENT_AGREEMENT = "Active component CDSA with active primary."
# Allowed reasons for no access.
- NO_PRIMARY_CDSA = "No primary CDSA for this group exists."
+ INACTIVE_AGREEMENT = "CDSA is inactive."
+ # INVALID_AGREEMENT_VERSION = "CDSA version is not valid."
+ NO_PRIMARY_AGREEMENT = "No primary CDSA for this group exists."
+ PRIMARY_NOT_ACTIVE = "Primary agreement for this CDSA is not active."
# Other errors
+ ERROR_NON_DATA_AFFILIATE_COMPONENT = (
+ "Non-data affiliate agreements must be primary."
+ )
ERROR_OTHER_CASE = "Signed Agreement did not match any expected situations."
results_table_class = SignedAgreementAccessAuditTable
def __init__(self):
# Store the CDSA group for auditing membership.
- self.anvil_cdsa_group = ManagedGroup.objects.get(
- name=settings.ANVIL_CDSA_GROUP_NAME
- )
self.completed = False
# Set up lists to hold audit results.
self.verified = []
self.needs_action = []
self.errors = []
- # Audit a single signed agreement.
- def _audit_signed_agreement(self, signed_agreement):
- # Check if the access group is in the overall CDSA group.
- in_cdsa_group = GroupGroupMembership.objects.filter(
- parent_group=self.anvil_cdsa_group,
- child_group=signed_agreement.anvil_access_group,
- ).exists()
- # Primary agreements don't need to check components.
- if signed_agreement.is_primary and in_cdsa_group:
- self.verified.append(
- VerifiedAccess(
- signed_agreement=signed_agreement,
- note=self.VALID_PRIMARY_CDSA,
- )
- )
- return
- elif signed_agreement.is_primary and not in_cdsa_group:
- self.needs_action.append(
- GrantAccess(
- signed_agreement=signed_agreement,
- note=self.VALID_PRIMARY_CDSA,
- )
- )
- return
- elif not signed_agreement.is_primary:
- # component agreements need to check for a primary.
- if hasattr(signed_agreement, "memberagreement"):
- # Member
- primary_exists = models.MemberAgreement.objects.filter(
- signed_agreement__is_primary=True,
- study_site=signed_agreement.memberagreement.study_site,
- ).exists()
- elif hasattr(signed_agreement, "dataaffiliateagreement"):
- # Data affiliate
- primary_exists = models.DataAffiliateAgreement.objects.filter(
- signed_agreement__is_primary=True,
- study=signed_agreement.dataaffiliateagreement.study,
- ).exists()
- elif hasattr(signed_agreement, "nondataaffiliateagreement"):
- # Non-data affiliate should not have components so this is an error.
- raise RuntimeError(
- "Non data affiliates should always be a primary CDSA."
- )
- else:
- # Some other case happened - log it as an error.
- self.errors.append(
- OtherError(
- signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE
- )
- )
- return
+ def _audit_primary_agreement(self, signed_agreement):
+ """Audit a single component signed agreement.
+
+ The following items are checked:
+ * if the primary agreement is active.
+ * if the primary agreement is in the CDSA group.
+
+ This audit does *not* check if the AgreementMajorVersion associated with the SignedAgreement is valid.
+ """
+ in_cdsa_group = signed_agreement.is_in_cdsa_group()
+ is_active = (
+ signed_agreement.status == models.SignedAgreement.StatusChoices.ACTIVE
+ )
- # Now check access for the component given the primary agreement.
- if primary_exists and in_cdsa_group:
+ if is_active:
+ if in_cdsa_group:
self.verified.append(
VerifiedAccess(
signed_agreement=signed_agreement,
- note=self.VALID_COMPONENT_CDSA,
+ note=self.ACTIVE_PRIMARY_AGREEMENT,
)
)
return
- elif primary_exists and not in_cdsa_group:
+ else:
self.needs_action.append(
GrantAccess(
signed_agreement=signed_agreement,
- note=self.VALID_COMPONENT_CDSA,
+ note=self.ACTIVE_PRIMARY_AGREEMENT,
)
)
return
- elif not primary_exists and not in_cdsa_group:
+ else:
+ if in_cdsa_group:
+ self.needs_action.append(
+ RemoveAccess(
+ signed_agreement=signed_agreement,
+ note=self.INACTIVE_AGREEMENT,
+ )
+ )
+ return
+ else:
self.verified.append(
VerifiedNoAccess(
signed_agreement=signed_agreement,
- note=self.NO_PRIMARY_CDSA,
+ note=self.INACTIVE_AGREEMENT,
)
)
return
- elif not primary_exists and in_cdsa_group:
+
+ # If we made it this far in audit, some other case happened - log it as an error.
+ # Haven't figured out a test for this because it is unexpected.
+ self.errors.append( # pragma: no cover
+ OtherError(
+ signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE
+ ) # pragma: no cover
+ ) # pragma: no cover
+
+ def _audit_component_agreement(self, signed_agreement):
+ """Audit a single component signed agreement.
+
+ The following items are checked:
+ * If a primary agreement exists
+ # If the primary agreement is active
+ * if the component agreement is active
+ * if the component agreement is in the CDSA group
+
+ This audit does *not* check if the AgreementMajorVersion associated with either the
+ SignedAgreement or its component is valid.
+ """
+ in_cdsa_group = signed_agreement.is_in_cdsa_group()
+ is_active = (
+ signed_agreement.status == models.SignedAgreement.StatusChoices.ACTIVE
+ )
+
+ # Get the set of potential primary agreements for this component.
+ if hasattr(signed_agreement, "memberagreement"):
+ # Member
+ primary_qs = models.SignedAgreement.objects.filter(
+ is_primary=True,
+ memberagreement__study_site=signed_agreement.memberagreement.study_site,
+ )
+ elif hasattr(signed_agreement, "dataaffiliateagreement"):
+ # Data affiliate
+ primary_qs = models.SignedAgreement.objects.filter(
+ is_primary=True,
+ dataaffiliateagreement__study=signed_agreement.dataaffiliateagreement.study,
+ )
+ elif hasattr(signed_agreement, "nondataaffiliateagreement"):
+ self.errors.append(
+ OtherError(
+ signed_agreement=signed_agreement,
+ note=self.ERROR_NON_DATA_AFFILIATE_COMPONENT,
+ )
+ )
+ return
+ primary_exists = primary_qs.exists()
+ primary_active = primary_qs.filter(
+ status=models.SignedAgreement.StatusChoices.ACTIVE,
+ ).exists()
+
+ if primary_exists:
+ if primary_active:
+ if is_active:
+ if in_cdsa_group:
+ self.verified.append(
+ VerifiedAccess(
+ signed_agreement=signed_agreement,
+ note=self.ACTIVE_COMPONENT_AGREEMENT,
+ )
+ )
+ return
+ else:
+ self.needs_action.append(
+ GrantAccess(
+ signed_agreement=signed_agreement,
+ note=self.ACTIVE_COMPONENT_AGREEMENT,
+ )
+ )
+ return
+ else:
+ if in_cdsa_group:
+ self.needs_action.append(
+ RemoveAccess(
+ signed_agreement=signed_agreement,
+ note=self.INACTIVE_AGREEMENT,
+ )
+ )
+ return
+ else:
+ self.verified.append(
+ VerifiedNoAccess(
+ signed_agreement=signed_agreement,
+ note=self.INACTIVE_AGREEMENT,
+ )
+ )
+ return
+ else:
+ if in_cdsa_group:
+ self.needs_action.append(
+ RemoveAccess(
+ signed_agreement=signed_agreement,
+ note=self.PRIMARY_NOT_ACTIVE,
+ )
+ )
+ return
+ else:
+ self.verified.append(
+ VerifiedNoAccess(
+ signed_agreement=signed_agreement,
+ note=self.PRIMARY_NOT_ACTIVE,
+ )
+ )
+ return
+ else:
+ if in_cdsa_group:
self.errors.append(
RemoveAccess(
signed_agreement=signed_agreement,
- note=self.NO_PRIMARY_CDSA,
+ note=self.NO_PRIMARY_AGREEMENT,
+ )
+ )
+ return
+ else:
+ self.verified.append(
+ VerifiedNoAccess(
+ signed_agreement=signed_agreement,
+ note=self.NO_PRIMARY_AGREEMENT,
)
)
return
# If we made it this far in audit, some other case happened - log it as an error.
- self.errors.append(
- OtherError(signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE)
- )
+ # Haven't figured out a test for this because it is unexpected.
+ self.errors.append( # pragma: no cover
+ OtherError(
+ signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE
+ ) # pragma: no cover
+ ) # pragma: no cover
+
+ def _audit_signed_agreement(self, signed_agreement):
+ if signed_agreement.is_primary:
+ self._audit_primary_agreement(signed_agreement)
+ else:
+ self._audit_component_agreement(signed_agreement)
def run_audit(self):
"""Run an audit on all SignedAgreements."""
diff --git a/primed/cdsa/audit/workspace_audit.py b/primed/cdsa/audit/workspace_audit.py
index 2d7d9267..e6ac2fc4 100644
--- a/primed/cdsa/audit/workspace_audit.py
+++ b/primed/cdsa/audit/workspace_audit.py
@@ -107,6 +107,9 @@ class WorkspaceAccessAuditTable(tables.Table):
workspace = tables.Column(linkify=True)
data_affiliate_agreement = tables.Column(linkify=True)
+ agreement_version = tables.Column(
+ accessor="data_affiliate_agreement__signed_agreement__version"
+ )
note = tables.Column()
action = tables.Column()
@@ -125,10 +128,11 @@ class WorkspaceAccessAudit:
"""Audit for CDSA Workspaces."""
# Access verified.
- VALID_PRIMARY_CDSA = "Valid primary CDSA."
+ ACTIVE_PRIMARY_AGREEMENT = "Active primary CDSA."
# Allowed reasons for no access.
- NO_PRIMARY_CDSA = "No primary CDSA for this study exists."
+ NO_PRIMARY_AGREEMENT = "No primary CDSA for this study."
+ INACTIVE_PRIMARY_AGREEMENT = "Primary CDSA for this study is inactive."
# Other errors
ERROR_OTHER_CASE = "Workspace did not match any expected situations."
@@ -146,7 +150,6 @@ def __init__(self):
self.needs_action = []
self.errors = []
- # Audit a single signed agreement.
def _audit_workspace(self, workspace):
# Check if the access group is in the overall CDSA group.
auth_domain = workspace.workspace.authorization_domains.first()
@@ -154,47 +157,76 @@ def _audit_workspace(self, workspace):
parent_group=auth_domain,
child_group=self.anvil_cdsa_group,
).exists()
- # WRITE ME!
- # See if there is a primary data affiliate agreement for this study.
- try:
- primary_agreement = models.DataAffiliateAgreement.objects.get(
- study=workspace.study,
- signed_agreement__is_primary=True,
+ primary_qs = models.DataAffiliateAgreement.objects.filter(
+ study=workspace.study, signed_agreement__is_primary=True
+ )
+ primary_exists = primary_qs.exists()
+
+ if primary_exists:
+ primary_agreement = (
+ primary_qs.filter(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+ .order_by(
+ "-signed_agreement__version__major_version__version",
+ "-signed_agreement__version__minor_version",
+ )
+ .first()
)
- if has_cdsa_group_in_auth_domain:
- self.verified.append(
- VerifiedAccess(
- workspace=workspace,
- data_affiliate_agreement=primary_agreement,
- note=self.VALID_PRIMARY_CDSA,
+ if primary_agreement:
+ if has_cdsa_group_in_auth_domain:
+ self.verified.append(
+ VerifiedAccess(
+ workspace=workspace,
+ data_affiliate_agreement=primary_agreement,
+ note=self.ACTIVE_PRIMARY_AGREEMENT,
+ )
)
- )
- return
+ return
+ else:
+ self.needs_action.append(
+ GrantAccess(
+ workspace=workspace,
+ data_affiliate_agreement=primary_agreement,
+ note=self.ACTIVE_PRIMARY_AGREEMENT,
+ )
+ )
+ return
else:
- self.needs_action.append(
- GrantAccess(
- workspace=workspace,
- data_affiliate_agreement=primary_agreement,
- note=self.VALID_PRIMARY_CDSA,
+ if has_cdsa_group_in_auth_domain:
+ self.needs_action.append(
+ RemoveAccess(
+ workspace=workspace,
+ data_affiliate_agreement=primary_agreement,
+ note=self.INACTIVE_PRIMARY_AGREEMENT,
+ )
)
- )
- return
- except models.DataAffiliateAgreement.DoesNotExist:
- if not has_cdsa_group_in_auth_domain:
- self.verified.append(
- VerifiedNoAccess(
+ return
+ else:
+ self.verified.append(
+ VerifiedNoAccess(
+ workspace=workspace,
+ data_affiliate_agreement=primary_agreement,
+ note=self.INACTIVE_PRIMARY_AGREEMENT,
+ )
+ )
+ return
+ else:
+ if has_cdsa_group_in_auth_domain:
+ self.errors.append(
+ RemoveAccess(
workspace=workspace,
data_affiliate_agreement=None,
- note=self.NO_PRIMARY_CDSA,
+ note=self.NO_PRIMARY_AGREEMENT,
)
)
return
else:
- self.errors.append(
- RemoveAccess(
+ self.verified.append(
+ VerifiedNoAccess(
workspace=workspace,
data_affiliate_agreement=None,
- note=self.NO_PRIMARY_CDSA,
+ note=self.NO_PRIMARY_AGREEMENT,
)
)
return
diff --git a/primed/cdsa/forms.py b/primed/cdsa/forms.py
index dde759cd..678991df 100644
--- a/primed/cdsa/forms.py
+++ b/primed/cdsa/forms.py
@@ -10,6 +10,15 @@
from . import models
+class AgreementMajorVersionIsValidForm(forms.ModelForm):
+ class Meta:
+ model = models.AgreementMajorVersion
+ fields = ("is_valid",)
+ widgets = {
+ "is_valid": forms.HiddenInput,
+ }
+
+
class SignedAgreementForm(Bootstrap5MediaFormMixin, forms.ModelForm):
"""Form for a SignedAgreement object."""
@@ -19,6 +28,9 @@ class SignedAgreementForm(Bootstrap5MediaFormMixin, forms.ModelForm):
widget=forms.RadioSelect,
label="Agreement type",
)
+ version = forms.ModelChoiceField(
+ queryset=models.AgreementVersion.objects.filter(major_version__is_valid=True)
+ )
class Meta:
model = models.SignedAgreement
@@ -40,6 +52,16 @@ class Meta:
}
+class SignedAgreementStatusForm(forms.ModelForm):
+ """Form to update the status of a SignedAgreement."""
+
+ class Meta:
+ model = models.SignedAgreement
+ fields = ("status",)
+ help_texts = {"status": """The status of this Signed Agreement."""}
+ widgets = {"status": forms.RadioSelect}
+
+
class MemberAgreementForm(forms.ModelForm):
class Meta:
model = models.MemberAgreement
diff --git a/primed/cdsa/helpers.py b/primed/cdsa/helpers.py
index 945185fc..5f613969 100644
--- a/primed/cdsa/helpers.py
+++ b/primed/cdsa/helpers.py
@@ -5,23 +5,34 @@
def get_representative_records_table():
"""Return the queryset for representative records."""
- qs = models.SignedAgreement.objects.all()
+ qs = models.SignedAgreement.active.all()
return tables.RepresentativeRecordsTable(qs)
def get_study_records_table():
"""Return the queryset for study records."""
- qs = models.DataAffiliateAgreement.objects.filter(signed_agreement__is_primary=True)
+ qs = models.DataAffiliateAgreement.objects.filter(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ signed_agreement__is_primary=True,
+ )
return tables.StudyRecordsTable(qs)
def get_user_access_records_table():
"""Return the queryset for user access records."""
- qs = GroupAccountMembership.objects.filter(group__signedagreement__isnull=False)
+ qs = GroupAccountMembership.objects.filter(
+ group__signedagreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ group__signedagreement__isnull=False,
+ )
return tables.UserAccessRecordsTable(qs)
def get_cdsa_workspace_records_table():
"""Return the queryset for workspace records."""
- qs = models.CDSAWorkspace.objects.all()
+ active_data_affiliates = models.DataAffiliateAgreement.objects.filter(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+ qs = models.CDSAWorkspace.objects.filter(
+ study__dataaffiliateagreement__in=active_data_affiliates,
+ )
return tables.CDSAWorkspaceRecordsTable(qs)
diff --git a/primed/cdsa/migrations/0002_agreementmajorversion_historicalagreementmajorversion.py b/primed/cdsa/migrations/0002_agreementmajorversion_historicalagreementmajorversion.py
new file mode 100644
index 00000000..a23e069b
--- /dev/null
+++ b/primed/cdsa/migrations/0002_agreementmajorversion_historicalagreementmajorversion.py
@@ -0,0 +1,53 @@
+# Generated by Django 3.2.19 on 2023-08-31 18:41
+
+from django.conf import settings
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django_extensions.db.fields
+import simple_history.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('cdsa', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AgreementMajorVersion',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
+ ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
+ ('version', models.IntegerField(help_text='Major version of the CDSA. Changes to the major version require resigning.', unique=True, validators=[django.core.validators.MinValueValidator(1)])),
+ ],
+ options={
+ 'get_latest_by': 'modified',
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='HistoricalAgreementMajorVersion',
+ fields=[
+ ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
+ ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
+ ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
+ ('version', models.IntegerField(db_index=True, help_text='Major version of the CDSA. Changes to the major version require resigning.', validators=[django.core.validators.MinValueValidator(1)])),
+ ('history_id', models.AutoField(primary_key=True, serialize=False)),
+ ('history_date', models.DateTimeField(db_index=True)),
+ ('history_change_reason', models.CharField(max_length=100, null=True)),
+ ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
+ ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'historical agreement major version',
+ 'verbose_name_plural': 'historical agreement major versions',
+ 'ordering': ('-history_date', '-history_id'),
+ 'get_latest_by': ('history_date', 'history_id'),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ ]
diff --git a/primed/cdsa/migrations/0003_agreementversion_add_major_version_fk.py b/primed/cdsa/migrations/0003_agreementversion_add_major_version_fk.py
new file mode 100644
index 00000000..e96af1f0
--- /dev/null
+++ b/primed/cdsa/migrations/0003_agreementversion_add_major_version_fk.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.2.19 on 2023-08-31 18:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0002_agreementmajorversion_historicalagreementmajorversion'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='agreementversion',
+ name='major_version_fk',
+ field=models.ForeignKey(blank=True, help_text='Major version of the CDSA. Changes to the major version require resigning.', null=True, on_delete=django.db.models.deletion.CASCADE, to='cdsa.agreementmajorversion'),
+ ),
+ migrations.AddField(
+ model_name='historicalagreementversion',
+ name='major_version_fk',
+ field=models.ForeignKey(blank=True, db_constraint=False, help_text='Major version of the CDSA. Changes to the major version require resigning.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cdsa.agreementmajorversion'),
+ ),
+ ]
diff --git a/primed/cdsa/migrations/0004_populate_agreementmajorversion.py b/primed/cdsa/migrations/0004_populate_agreementmajorversion.py
new file mode 100644
index 00000000..9c88b576
--- /dev/null
+++ b/primed/cdsa/migrations/0004_populate_agreementmajorversion.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.2.19 on 2023-08-31 18:58
+
+from django.db import migrations
+
+
+def populate_agreementversion_major_version_fk(apps, schema_editor):
+ """Populate the AgreementMajorVersion model using major_versions in AgreementVersion,
+ and set AgreementVersion.major_version_fk."""
+ AgreementVersion = apps.get_model("cdsa", "AgreementVersion")
+ AgreementMajorVersion = apps.get_model("cdsa", "AgreementMajorVersion")
+ for row in AgreementVersion.objects.all():
+ # Get or create the AgreementMajorVersion object.
+ try:
+ major_version = AgreementMajorVersion.objects.get(version=row.major_version)
+ except AgreementMajorVersion.DoesNotExist:
+ major_version = AgreementMajorVersion(
+ version=row.major_version,
+ )
+ major_version.full_clean()
+ major_version.save()
+ # Set major_version_fk for the agreement_version object.
+ row.major_version_fk = major_version
+ row.save(update_fields=["major_version_fk"])
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0003_agreementversion_add_major_version_fk'),
+ ]
+
+ operations = [
+ migrations.RunPython(populate_agreementversion_major_version_fk, reverse_code=migrations.RunPython.noop),
+ ]
diff --git a/primed/cdsa/migrations/0005_agreementversion_remove_field_major_version.py b/primed/cdsa/migrations/0005_agreementversion_remove_field_major_version.py
new file mode 100644
index 00000000..352c471e
--- /dev/null
+++ b/primed/cdsa/migrations/0005_agreementversion_remove_field_major_version.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.19 on 2023-08-31 22:55
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0004_populate_agreementmajorversion'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='agreementversion',
+ options={'get_latest_by': 'modified'},
+ ),
+ migrations.RemoveConstraint(
+ model_name='agreementversion',
+ name='unique_agreement_version',
+ ),
+ migrations.RemoveField(
+ model_name='agreementversion',
+ name='major_version',
+ ),
+ migrations.RemoveField(
+ model_name='historicalagreementversion',
+ name='major_version',
+ ),
+ ]
diff --git a/primed/cdsa/migrations/0006_agreementversion_rename_major_version_fk_to_major_version.py b/primed/cdsa/migrations/0006_agreementversion_rename_major_version_fk_to_major_version.py
new file mode 100644
index 00000000..712457c3
--- /dev/null
+++ b/primed/cdsa/migrations/0006_agreementversion_rename_major_version_fk_to_major_version.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.19 on 2023-08-31 22:57
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0005_agreementversion_remove_field_major_version'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='agreementversion',
+ options={'ordering': ['major_version', 'minor_version']},
+ ),
+ migrations.RenameField(
+ model_name='agreementversion',
+ old_name='major_version_fk',
+ new_name='major_version',
+ ),
+ migrations.RenameField(
+ model_name='historicalagreementversion',
+ old_name='major_version_fk',
+ new_name='major_version',
+ ),
+ migrations.AddConstraint(
+ model_name='agreementversion',
+ constraint=models.UniqueConstraint(fields=('major_version', 'minor_version'), name='unique_agreement_version_2'),
+ ),
+ ]
diff --git a/primed/cdsa/migrations/0007_alter_agreementversion_major_version.py b/primed/cdsa/migrations/0007_alter_agreementversion_major_version.py
new file mode 100644
index 00000000..ced250a9
--- /dev/null
+++ b/primed/cdsa/migrations/0007_alter_agreementversion_major_version.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.19 on 2023-08-31 23:14
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0006_agreementversion_rename_major_version_fk_to_major_version'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='agreementversion',
+ name='major_version',
+ field=models.ForeignKey(default=None, help_text='Major version of the CDSA. Changes to the major version require resigning.', on_delete=django.db.models.deletion.CASCADE, to='cdsa.agreementmajorversion'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/primed/cdsa/migrations/0008_agreementmajorversion_add_is_valid.py b/primed/cdsa/migrations/0008_agreementmajorversion_add_is_valid.py
new file mode 100644
index 00000000..c315857c
--- /dev/null
+++ b/primed/cdsa/migrations/0008_agreementmajorversion_add_is_valid.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.19 on 2023-09-13 23:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0007_alter_agreementversion_major_version'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='agreementmajorversion',
+ name='is_valid',
+ field=models.BooleanField(default=True, help_text='Boolean indicator of whether this version is valid.', verbose_name='Valid?'),
+ ),
+ migrations.AddField(
+ model_name='historicalagreementmajorversion',
+ name='is_valid',
+ field=models.BooleanField(default=True, help_text='Boolean indicator of whether this version is valid.', verbose_name='Valid?'),
+ ),
+ ]
diff --git a/primed/cdsa/migrations/0009_signedagreement_add_field_status.py b/primed/cdsa/migrations/0009_signedagreement_add_field_status.py
new file mode 100644
index 00000000..aa0a845c
--- /dev/null
+++ b/primed/cdsa/migrations/0009_signedagreement_add_field_status.py
@@ -0,0 +1,45 @@
+# Generated by Django 3.2.19 on 2023-09-19 17:33
+
+from django.db import migrations, models
+import django.utils.timezone
+import model_utils.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cdsa', '0008_agreementmajorversion_add_is_valid'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='historicalsignedagreement',
+ name='status',
+ field=model_utils.fields.StatusField(choices=[(0, 'dummy')], default='active', max_length=100, no_check_for_status=True, verbose_name='status'),
+ ),
+ migrations.AddField(
+ model_name='historicalsignedagreement',
+ name='status_changed',
+ field=model_utils.fields.MonitorField(default=django.utils.timezone.now, monitor='status', verbose_name='status changed'),
+ ),
+ migrations.AddField(
+ model_name='signedagreement',
+ name='status',
+ field=model_utils.fields.StatusField(choices=[(0, 'dummy')], default='active', max_length=100, no_check_for_status=True, verbose_name='status'),
+ ),
+ migrations.AddField(
+ model_name='signedagreement',
+ name='status_changed',
+ field=model_utils.fields.MonitorField(default=django.utils.timezone.now, monitor='status', verbose_name='status changed'),
+ ),
+ migrations.AlterField(
+ model_name='agreementmajorversion',
+ name='is_valid',
+ field=models.BooleanField(default=True, help_text='Boolean indicator of whether this version is valid.'),
+ ),
+ migrations.AlterField(
+ model_name='historicalagreementmajorversion',
+ name='is_valid',
+ field=models.BooleanField(default=True, help_text='Boolean indicator of whether this version is valid.'),
+ ),
+ ]
diff --git a/primed/cdsa/models.py b/primed/cdsa/models.py
index 4605f305..5dd8a053 100644
--- a/primed/cdsa/models.py
+++ b/primed/cdsa/models.py
@@ -2,25 +2,57 @@
from datetime import date
-from anvil_consortium_manager.models import BaseWorkspaceData, ManagedGroup
+from anvil_consortium_manager.models import (
+ BaseWorkspaceData,
+ GroupGroupMembership,
+ ManagedGroup,
+)
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.urls import reverse
from django_extensions.db.models import TimeStampedModel
+from model_utils.models import StatusModel
from simple_history.models import HistoricalRecords
from primed.duo.models import DataUseOntologyModel
from primed.primed_anvil.models import AvailableData, RequesterModel, Study, StudySite
+class AgreementMajorVersion(TimeStampedModel, models.Model):
+ """A model for a major agreement version."""
+
+ version = models.IntegerField(
+ help_text="Major version of the CDSA. Changes to the major version require resigning.",
+ validators=[MinValueValidator(1)],
+ unique=True,
+ )
+ is_valid = models.BooleanField(
+ default=True, help_text="Boolean indicator of whether this version is valid."
+ )
+
+ history = HistoricalRecords()
+
+ def __str__(self):
+ return "v{}".format(self.version)
+
+ def get_absolute_url(self):
+ return reverse(
+ "cdsa:agreement_versions:major_version_detail",
+ kwargs={
+ "major_version": self.version,
+ },
+ )
+
+
class AgreementVersion(TimeStampedModel, models.Model):
"""Model to track approved CDSA versions."""
- major_version = models.IntegerField(
+ major_version = models.ForeignKey(
+ AgreementMajorVersion,
help_text="Major version of the CDSA. Changes to the major version require resigning.",
- validators=[MinValueValidator(1)],
+ on_delete=models.CASCADE,
)
minor_version = models.IntegerField(
help_text="Minor version of the CDSA. Changes to the minor version do not require resigning.",
@@ -37,7 +69,7 @@ class Meta:
constraints = [
models.UniqueConstraint(
fields=["major_version", "minor_version"],
- name="unique_agreement_version",
+ name="unique_agreement_version_2",
)
]
ordering = ["major_version", "minor_version"]
@@ -45,12 +77,43 @@ class Meta:
def __str__(self):
return self.full_version
+ def get_absolute_url(self):
+ return reverse(
+ "cdsa:agreement_versions:detail",
+ kwargs={
+ "major_version": self.major_version.version,
+ "minor_version": self.minor_version,
+ },
+ )
+
@property
def full_version(self):
- return "v{}.{}".format(self.major_version, self.minor_version)
+ return "{}.{}".format(str(self.major_version), self.minor_version)
+
+
+class SignedAgreementStatusMixin:
+ """Mixin to define status choices for SignedAgreements."""
+ # This is required because we are using django-model-util's StatusModel with django-simple-history:
+ # See GitHub issue: https://github.com/jazzband/django-simple-history/issues/190
-class SignedAgreement(TimeStampedModel, models.Model):
+ class StatusChoices(models.TextChoices):
+ ACTIVE = "active", "Active"
+ """SignedAgreements that are currently active."""
+
+ WITHDRAWN = "withdrawn", "Withdrawn"
+ """SignedAgreements that have been withdrawn for some reason (e.g., PI changed institution,
+ study no longer wanted to participate.)"""
+
+ LAPSED = "lapsed", "Lapsed"
+ """SignedAgreements from a AgreementMajorVersion that is no longer valid."""
+
+ STATUS = StatusChoices.choices
+
+
+class SignedAgreement(
+ TimeStampedModel, SignedAgreementStatusMixin, StatusModel, models.Model
+):
"""Model to track verified, signed consortium data sharing agreements."""
MEMBER = "member"
@@ -107,7 +170,7 @@ class SignedAgreement(TimeStampedModel, models.Model):
on_delete=models.PROTECT,
)
- history = HistoricalRecords()
+ history = HistoricalRecords(bases=[SignedAgreementStatusMixin, models.Model])
def __str__(self):
return "{}".format(self.cc_id)
@@ -140,6 +203,13 @@ def get_agreement_type(self):
def agreement_group(self):
return self.get_agreement_type().get_agreement_group()
+ def is_in_cdsa_group(self):
+ anvil_cdsa_group = ManagedGroup.objects.get(name=settings.ANVIL_CDSA_GROUP_NAME)
+ return GroupGroupMembership.objects.filter(
+ parent_group=anvil_cdsa_group,
+ child_group=self.anvil_access_group,
+ ).exists()
+
class AgreementTypeModel(models.Model):
"""An abstract model that can be used to provide required fields for agreement type models."""
@@ -185,7 +255,7 @@ class MemberAgreement(TimeStampedModel, AgreementTypeModel, models.Model):
def get_absolute_url(self):
return reverse(
- "cdsa:agreements:members:detail",
+ "cdsa:signed_agreements:members:detail",
kwargs={"cc_id": self.signed_agreement.cc_id},
)
@@ -207,7 +277,7 @@ class DataAffiliateAgreement(TimeStampedModel, AgreementTypeModel, models.Model)
def get_absolute_url(self):
return reverse(
- "cdsa:agreements:data_affiliates:detail",
+ "cdsa:signed_agreements:data_affiliates:detail",
kwargs={"cc_id": self.signed_agreement.cc_id},
)
@@ -226,7 +296,7 @@ class NonDataAffiliateAgreement(TimeStampedModel, AgreementTypeModel, models.Mod
def get_absolute_url(self):
return reverse(
- "cdsa:agreements:non_data_affiliates:detail",
+ "cdsa:signed_agreements:non_data_affiliates:detail",
kwargs={"cc_id": self.signed_agreement.cc_id},
)
diff --git a/primed/cdsa/tables.py b/primed/cdsa/tables.py
index 2571fb39..856fb643 100644
--- a/primed/cdsa/tables.py
+++ b/primed/cdsa/tables.py
@@ -8,13 +8,33 @@
)
from primed.primed_anvil.tables import (
- BooleanCheckColumn,
+ BooleanIconColumn,
WorkspaceSharedWithConsortiumTable,
)
from . import models
+class AgreementVersionTable(tables.Table):
+
+ major_version = tables.Column(linkify=True)
+ full_version = tables.Column(
+ linkify=True, order_by=("major_version", "minor_version")
+ )
+ major_version__is_valid = BooleanIconColumn(
+ verbose_name="Valid?", show_false_icon=True
+ )
+
+ class Meta:
+ model = models.AgreementVersion
+ fields = (
+ "major_version",
+ "full_version",
+ "major_version__is_valid",
+ "date_approved",
+ )
+
+
class SignedAgreementTable(tables.Table):
cc_id = tables.Column(linkify=True)
@@ -35,6 +55,7 @@ class SignedAgreementTable(tables.Table):
if hasattr(record.agreement_group, "get_absolute_url")
else None
)
+ version = tables.Column(linkify=True)
class Meta:
model = models.SignedAgreement
@@ -46,6 +67,7 @@ class Meta:
"agreement_group",
"agreement_type",
"version",
+ "status",
"date_signed",
"number_accessors",
)
@@ -57,7 +79,7 @@ class MemberAgreementTable(tables.Table):
signed_agreement__cc_id = tables.Column(linkify=True)
study_site = tables.Column(linkify=True)
- signed_agreement__is_primary = BooleanCheckColumn(verbose_name="Primary?")
+ signed_agreement__is_primary = BooleanIconColumn(verbose_name="Primary?")
signed_agreement__representative__name = tables.Column(
linkify=lambda record: record.signed_agreement.representative.get_absolute_url(),
verbose_name="Representative",
@@ -67,6 +89,7 @@ class MemberAgreementTable(tables.Table):
verbose_name="Number of accessors",
accessor="signed_agreement__anvil_access_group__groupaccountmembership_set__count",
)
+ signed_agreement__version = tables.Column(linkify=True)
class Meta:
model = models.MemberAgreement
@@ -78,6 +101,7 @@ class Meta:
"signed_agreement__representative_role",
"signed_agreement__signing_institution",
"signed_agreement__version",
+ "signed_agreement__status",
"signed_agreement__date_signed",
"number_accessors",
)
@@ -89,7 +113,7 @@ class DataAffiliateAgreementTable(tables.Table):
signed_agreement__cc_id = tables.Column(linkify=True)
study = tables.Column(linkify=True)
- signed_agreement__is_primary = BooleanCheckColumn(verbose_name="Primary?")
+ signed_agreement__is_primary = BooleanIconColumn(verbose_name="Primary?")
signed_agreement__representative__name = tables.Column(
linkify=lambda record: record.signed_agreement.representative.get_absolute_url(),
verbose_name="Representative",
@@ -99,6 +123,7 @@ class DataAffiliateAgreementTable(tables.Table):
verbose_name="Number of accessors",
accessor="signed_agreement__anvil_access_group__groupaccountmembership_set__count",
)
+ signed_agreement__version = tables.Column(linkify=True)
class Meta:
model = models.DataAffiliateAgreement
@@ -110,6 +135,7 @@ class Meta:
"signed_agreement__representative_role",
"signed_agreement__signing_institution",
"signed_agreement__version",
+ "signed_agreement__status",
"signed_agreement__date_signed",
"number_accessors",
)
@@ -120,7 +146,6 @@ class NonDataAffiliateAgreementTable(tables.Table):
"""Table to display `DataAffiliateAgreement` objects."""
signed_agreement__cc_id = tables.Column(linkify=True)
- signed_agreement__is_primary = BooleanCheckColumn()
signed_agreement__representative__name = tables.Column(
linkify=lambda record: record.signed_agreement.representative.get_absolute_url(),
verbose_name="Representative",
@@ -130,6 +155,7 @@ class NonDataAffiliateAgreementTable(tables.Table):
verbose_name="Number of accessors",
accessor="signed_agreement__anvil_access_group__groupaccountmembership_set__count",
)
+ signed_agreement__version = tables.Column(linkify=True)
class Meta:
model = models.NonDataAffiliateAgreement
@@ -140,6 +166,7 @@ class Meta:
"signed_agreement__representative_role",
"signed_agreement__signing_institution",
"signed_agreement__version",
+ "signed_agreement__status",
"signed_agreement__date_signed",
"number_accessors",
)
@@ -162,6 +189,8 @@ class Meta:
"representative_role",
"signing_institution",
"signing_group",
+ "agreement_type",
+ "version",
)
order_by = ("representative__name",)
diff --git a/primed/cdsa/tests/factories.py b/primed/cdsa/tests/factories.py
index e84ac6d5..bf0459b8 100644
--- a/primed/cdsa/tests/factories.py
+++ b/primed/cdsa/tests/factories.py
@@ -14,13 +14,23 @@
from .. import models
+class AgreementMajorVersionFactory(DjangoModelFactory):
+ """A factory for the AgreementMajorVersion model."""
+
+ class Meta:
+ model = models.AgreementMajorVersion
+ django_get_or_create = ("version",)
+
+ version = Faker("random_int", min=1)
+
+
class AgreementVersionFactory(DjangoModelFactory):
"""A factory for the AgreementVersion model."""
class Meta:
model = models.AgreementVersion
- major_version = Faker("random_int", min=1)
+ major_version = SubFactory(AgreementMajorVersionFactory)
minor_version = Faker("random_int")
date_approved = Faker("date")
diff --git a/primed/cdsa/tests/test_audit.py b/primed/cdsa/tests/test_audit.py
index 641e91af..9a85e2e5 100644
--- a/primed/cdsa/tests/test_audit.py
+++ b/primed/cdsa/tests/test_audit.py
@@ -10,6 +10,7 @@
from primed.primed_anvil.tests.factories import StudyFactory, StudySiteFactory
+from .. import models
from ..audit import signed_agreement_audit, workspace_audit
from . import factories
@@ -117,238 +118,1171 @@ def test_no_signed_agreements(self):
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
- def test_one_signed_agreement_primary_verified_access(self):
- signed_agreement = factories.SignedAgreementFactory.create()
+ def test_loops_over_signed_agreements(self):
+ """run_audit loops over all signed agreements."""
+ # Create two signed agreements that need to be added to the SAG group.
+ factories.MemberAgreementFactory.create_batch(2)
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit.run_audit()
+ self.assertEqual(len(cdsa_audit.needs_action), 2)
+
+ def test_member_primary_in_group(self):
+ """Member primary agreement with valid version in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create()
# Add the signed agreement access group to the CDSA group.
GroupGroupMembershipFactory.create(
parent_group=self.cdsa_group,
- child_group=signed_agreement.anvil_access_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
)
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.verified[0]
self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
- self.assertEqual(record.signed_agreement, signed_agreement)
- self.assertEqual(record.note, cdsa_audit.VALID_PRIMARY_CDSA)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_member_primary_not_in_group(self):
+ """Member primary agreement with valid version not in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create()
+ # Do not add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_member_primary_invalid_version_in_group(self):
+ """Member primary agreement, active, with invalid version in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
- def test_one_signed_agreement_primary_needs_access(self):
- signed_agreement = factories.SignedAgreementFactory.create()
+ def test_member_primary_invalid_version_not_in_group(self):
+ """Member primary agreement, with invalid version, not in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False
+ )
# Do not add the signed agreement access group to the CDSA group.
# GroupGroupMembershipFactory.create(
- # parent_group=self.cdsa_group, child_group=signed_agreement.anvil_access_group
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
# )
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.needs_action[0]
self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
- self.assertEqual(record.signed_agreement, signed_agreement)
- self.assertEqual(record.note, cdsa_audit.VALID_PRIMARY_CDSA)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_member_primary_valid_not_active_in_group(self):
+ """Member primary agreement with valid version but isn't active, in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_member_primary_valid_not_active_not_in_group(self):
+ """Member primary agreement with valid version but isn't active, not in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
- def test_one_signed_agreement_member_component_verified_access(self):
+ def test_member_component_has_primary_in_group(self):
+ """Member component agreement, with valid version, with primary with valid version, in CDSA group."""
study_site = StudySiteFactory.create()
factories.MemberAgreementFactory.create(study_site=study_site)
- component_agreement = factories.MemberAgreementFactory.create(
+ this_agreement = factories.MemberAgreementFactory.create(
signed_agreement__is_primary=False, study_site=study_site
)
- # Add the component agreement access group to the CDSA group.
+ # Add the signed agreement access group to the CDSA group.
GroupGroupMembershipFactory.create(
parent_group=self.cdsa_group,
- child_group=component_agreement.signed_agreement.anvil_access_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
)
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.verified[0]
self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.VALID_COMPONENT_CDSA)
- self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_member_component_has_primary_not_in_group(self):
+ """Member component agreement, with valid version, with primary with valid version, not in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False, study_site=study_site
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
- def test_one_signed_agreement_member_component_no_primary_no_access(self):
- component_agreement = factories.MemberAgreementFactory.create(
- signed_agreement__is_primary=False
+ def test_member_component_inactive_has_primary_in_group(self):
+ """Member component agreement, inactive, with valid version, with primary with valid version, in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study_site=study_site,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_member_component_inactive_has_primary_not_in_group(self):
+ """Member component agreement, inactive, with valid version, with valid active primary, not in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study_site=study_site,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
)
- # Do not add the component agreement access group to the CDSA group.
+ # # Add the signed agreement access group to the CDSA group.
# GroupGroupMembershipFactory.create(
# parent_group=self.cdsa_group,
- # child_group=component_agreement.signed_agreement.anvil_access_group
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
# )
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.verified[0]
self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_member_component_has_primary_with_invalid_version_in_group(self):
+ """Member component agreement, with valid version, with active primary with invalid version, in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(
+ study_site=study_site,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False, study_site=study_site
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
- def test_one_signed_agreement_member_component_needs_access(self):
+ def test_member_component_has_primary_with_invalid_version_not_in_group(self):
+ """Member component agreement, with valid version, with active primary with invalid version, not in group."""
study_site = StudySiteFactory.create()
- factories.MemberAgreementFactory.create(study_site=study_site)
- component_agreement = factories.MemberAgreementFactory.create(
+ factories.MemberAgreementFactory.create(
+ study_site=study_site,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ this_agreement = factories.MemberAgreementFactory.create(
signed_agreement__is_primary=False, study_site=study_site
)
- # Do not add the component agreement access group to the CDSA group.
+ # # Add the signed agreement access group to the CDSA group.
# GroupGroupMembershipFactory.create(
# parent_group=self.cdsa_group,
- # child_group=component_agreement.signed_agreement.anvil_access_group
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
# )
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.needs_action[0]
self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.VALID_COMPONENT_CDSA)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_member_component_has_inactive_primary_in_group(self):
+ """Member component agreement, with valid version, with inactive primary, in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(
+ study_site=study_site,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False, study_site=study_site
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.PRIMARY_NOT_ACTIVE)
- def test_one_signed_agreement_member_component_needs_error(self):
- # No primary, but the agreement has access (incorrectly).
- component_agreement = factories.MemberAgreementFactory.create(
- signed_agreement__is_primary=False
+ def test_member_component_has_inactive_primary_not_in_group(self):
+ """Member component agreement, with valid version, with inactive primary, not in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(
+ study_site=study_site,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False, study_site=study_site
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.PRIMARY_NOT_ACTIVE)
+
+ def test_member_component_no_primary_in_group(self):
+ """Member component agreement, with valid version, with no primary, in CDSA group."""
+ study_site = StudySiteFactory.create()
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False, study_site=study_site
)
- # Add the component agreement access group to the CDSA group (to trip the error).
+ # Add the signed agreement access group to the CDSA group.
GroupGroupMembershipFactory.create(
parent_group=self.cdsa_group,
- child_group=component_agreement.signed_agreement.anvil_access_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
)
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 1)
record = cdsa_audit.errors[0]
self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
- def test_one_signed_agreement_data_affiliate_component_verified_access(self):
- study = StudyFactory.create()
- factories.DataAffiliateAgreementFactory.create(study=study)
- component_agreement = factories.DataAffiliateAgreementFactory.create(
- signed_agreement__is_primary=False, study=study
+ def test_member_component_no_primary_not_in_group(self):
+ """Member component agreement, with valid version, with no primary, not in CDSA group."""
+ study_site = StudySiteFactory.create()
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False, study_site=study_site
)
- # Add the component agreement access group to the CDSA group.
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_member_component_invalid_version_has_primary_in_group(self):
+ """Member component agreement, with invalid version, with a valid primary, in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study_site=study_site,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # Add the signed agreement access group to the CDSA group.
GroupGroupMembershipFactory.create(
parent_group=self.cdsa_group,
- child_group=component_agreement.signed_agreement.anvil_access_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
)
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 1)
- record = cdsa_audit.verified[0]
- self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.VALID_COMPONENT_CDSA)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
- def test_one_signed_agreement_data_affiliate_component_no_primary_no_access(self):
- component_agreement = factories.DataAffiliateAgreementFactory.create(
- signed_agreement__is_primary=False
+ def test_member_component_invalid_version_has_primary_not_in_group(self):
+ """Member component agreement, with invalid version, with a valid primary, not in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study_site=study_site,
+ signed_agreement__version__major_version__is_valid=False,
)
- # Do not add the component agreement access group to the CDSA group.
+ # # Add the signed agreement access group to the CDSA group.
# GroupGroupMembershipFactory.create(
# parent_group=self.cdsa_group,
- # child_group=component_agreement.signed_agreement.anvil_access_group
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
# )
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_member_component_invalid_version_has_primary_with_invalid_version_in_group(
+ self,
+ ):
+ """Member component agreement, with invalid version, with an invalid primary, in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study_site=study_site,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 1)
- record = cdsa_audit.verified[0]
- self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
- def test_one_signed_agreement_data_affiliate_component_needs_access(self):
- study = StudyFactory.create()
- factories.DataAffiliateAgreementFactory.create(study=study)
- component_agreement = factories.DataAffiliateAgreementFactory.create(
- signed_agreement__is_primary=False, study=study
+ def test_member_component_invalid_version_has_primary_with_invalid_version_not_in_group(
+ self,
+ ):
+ """Member component agreement, with invalid version, with an invalid primary, not in CDSA group."""
+ study_site = StudySiteFactory.create()
+ factories.MemberAgreementFactory.create(study_site=study_site)
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study_site=study_site,
+ signed_agreement__version__major_version__is_valid=False,
)
- # Do not add the component agreement access group to the CDSA group.
+ # # Add the signed agreement access group to the CDSA group.
# GroupGroupMembershipFactory.create(
# parent_group=self.cdsa_group,
- # child_group=component_agreement.signed_agreement.anvil_access_group
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
# )
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.needs_action[0]
self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.VALID_COMPONENT_CDSA)
- self.assertEqual(len(cdsa_audit.errors), 0)
-
- def test_one_signed_agreement_data_affiliate_component_needs_error(self):
- # No primary, but the agreement has access (incorrectly).
- component_agreement = factories.DataAffiliateAgreementFactory.create(
- signed_agreement__is_primary=False
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_member_component_invalid_version_no_primary_in_group(self):
+ """Member component agreement, with invalid version, with no primary, in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ signed_agreement__version__major_version__is_valid=False,
)
- # Add the component agreement access group to the CDSA group (to trip the error).
+ # Add the signed agreement access group to the CDSA group.
GroupGroupMembershipFactory.create(
parent_group=self.cdsa_group,
- child_group=component_agreement.signed_agreement.anvil_access_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
)
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(component_agreement.signed_agreement)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 1)
record = cdsa_audit.errors[0]
self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
- self.assertEqual(record.signed_agreement, component_agreement.signed_agreement)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
-
- def test_one_signed_agreement_nondataaffiliate_component(self):
- factories.NonDataAffiliateAgreementFactory.create(
- signed_agreement__is_primary=False
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_member_component_invalid_version_no_primary_not_in_group(self):
+ """Member component agreement, with invalid version, with no primary, not in CDSA group."""
+ this_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ signed_agreement__version__major_version__is_valid=False,
)
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- with self.assertRaises(RuntimeError):
- cdsa_audit.run_audit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
- def test_other_error(self):
- signed_agreement = factories.SignedAgreementFactory.create(
- is_primary=False, type="MEMBER"
+ def test_data_affiliate_primary_in_group(self):
+ """Member primary agreement with valid version in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create()
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
)
cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
- cdsa_audit._audit_signed_agreement(signed_agreement)
- self.assertEqual(len(cdsa_audit.verified), 0)
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
self.assertEqual(len(cdsa_audit.needs_action), 0)
- self.assertEqual(len(cdsa_audit.errors), 1)
- record = cdsa_audit.errors[0]
- self.assertIsInstance(record, signed_agreement_audit.OtherError)
- self.assertEqual(record.signed_agreement, signed_agreement)
- self.assertEqual(record.note, cdsa_audit.ERROR_OTHER_CASE)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+ def test_data_affiliate_primary_not_in_group(self):
+ """Member primary agreement with valid version not in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create()
+ # Do not add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
-class SignedAgreementAccessAuditTableTest(TestCase):
- """Tests for the `SignedAgreementAccessAuditTable` table."""
+ def test_data_affiliate_primary_invalid_version_in_group(self):
+ """Member primary agreement, active, with invalid version in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
- def test_no_rows(self):
- """Table works with no rows."""
- table = signed_agreement_audit.SignedAgreementAccessAuditTable([])
- self.assertIsInstance(
- table, signed_agreement_audit.SignedAgreementAccessAuditTable
+ def test_data_affiliate_primary_invalid_version_not_in_group(self):
+ """Member primary agreement, with invalid version, not in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False
)
- self.assertEqual(len(table.rows), 0)
+ # Do not add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
- def test_one_row(self):
- """Table works with one row."""
- signed_agreement = factories.SignedAgreementFactory.create()
+ def test_data_affiliate_primary_valid_not_active_in_group(self):
+ """Member primary agreement with valid version but isn't active, in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_data_affiliate_primary_valid_not_active_not_in_group(self):
+ """Member primary agreement with valid version but isn't active, not in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_data_affiliate_component_has_primary_in_group(self):
+ """Member component agreement, with valid version, with primary with valid version, in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_has_primary_not_in_group(self):
+ """Member component agreement, with valid version, with primary with valid version, not in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_inactive_has_primary_in_group(self):
+ """Member component agreement, inactive, with valid version, with primary with valid version, in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_data_affiliate_component_inactive_has_primary_not_in_group(self):
+ """Member component agreement, inactive, with valid version, with valid active primary, not in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_data_affiliate_component_has_primary_with_invalid_version_in_group(self):
+ """Member component agreement, with valid version, with active primary with invalid version, in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_has_primary_with_invalid_version_not_in_group(
+ self,
+ ):
+ """Member component agreement, with valid version, with active primary with invalid version, not in group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_has_inactive_primary_in_group(self):
+ """Member component agreement, with valid version, with inactive primary, in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.PRIMARY_NOT_ACTIVE)
+
+ def test_data_affiliate_component_has_inactive_primary_not_in_group(self):
+ """Member component agreement, with valid version, with inactive primary, not in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.PRIMARY_NOT_ACTIVE)
+
+ def test_data_affiliate_component_no_primary_in_group(self):
+ """Member component agreement, with valid version, with no primary, in CDSA group."""
+ study = StudyFactory.create()
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 1)
+ record = cdsa_audit.errors[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_data_affiliate_component_no_primary_not_in_group(self):
+ """Member component agreement, with valid version, with no primary, not in CDSA group."""
+ study = StudyFactory.create()
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False, study=study
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_data_affiliate_component_invalid_version_has_primary_in_group(self):
+ """Member component agreement, with invalid version, with a valid primary, in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_invalid_version_has_primary_not_in_group(self):
+ """Member component agreement, with invalid version, with a valid primary, not in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_invalid_version_has_primary_with_invalid_version_in_group(
+ self,
+ ):
+ """Member component agreement, with invalid version, with an invalid primary, in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_invalid_version_has_primary_with_invalid_version_not_in_group(
+ self,
+ ):
+ """Member component agreement, with invalid version, with an invalid primary, not in CDSA group."""
+ study = StudyFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=study)
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_COMPONENT_AGREEMENT)
+
+ def test_data_affiliate_component_invalid_version_no_primary_in_group(self):
+ """Member component agreement, with invalid version, with no primary, in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 1)
+ record = cdsa_audit.errors[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_data_affiliate_component_invalid_version_no_primary_not_in_group(self):
+ """Member component agreement, with invalid version, with no primary, not in CDSA group."""
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_non_data_affiliate_primary_in_group(self):
+ """Non data affiliate primary agreement with valid version in CDSA group."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create()
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_non_data_affiliate_primary_not_in_group(self):
+ """Non data affiliate primary agreement with valid version not in CDSA group."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create()
+ # Do not add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_non_data_affiliate_primary_invalid_version_in_group(self):
+ """Non data affiliate primary agreement with invalid version in CDSA group."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_non_data_affiliate_primary_invalid_version_not_in_group(self):
+ """Non data affiliate primary agreement, with invalid version, not in CDSA group."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False
+ )
+ # Do not add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.GrantAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_non_data_affiliate_primary_valid_not_active_in_group(self):
+ """Non Data affiliate primary agreement with valid version but isn't active, in CDSA group."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, signed_agreement_audit.RemoveAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_non_data_affiliate_primary_valid_not_active_not_in_group(self):
+ """Non data affiliate primary agreement with valid version but isn't active, not in CDSA group."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ # # Add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, signed_agreement_audit.VerifiedNoAccess)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_AGREEMENT)
+
+ def test_non_data_affiliate_component_in_cdsa_group(self):
+ """Non data affiliate component agreement."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False
+ )
+ # Add the signed agreement access group to the CDSA group.
+ GroupGroupMembershipFactory.create(
+ parent_group=self.cdsa_group,
+ child_group=this_agreement.signed_agreement.anvil_access_group,
+ )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 1)
+ record = cdsa_audit.errors[0]
+ self.assertIsInstance(record, signed_agreement_audit.OtherError)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ERROR_NON_DATA_AFFILIATE_COMPONENT)
+
+ def test_non_data_affiliate_component_not_in_cdsa_group(self):
+ """Non data affiliate component agreement."""
+ this_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__is_primary=False
+ )
+ # Do not add the signed agreement access group to the CDSA group.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=self.cdsa_group,
+ # child_group=this_agreement.signed_agreement.anvil_access_group,
+ # )
+ cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit()
+ cdsa_audit._audit_signed_agreement(this_agreement.signed_agreement)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 1)
+ record = cdsa_audit.errors[0]
+ self.assertIsInstance(record, signed_agreement_audit.OtherError)
+ self.assertEqual(record.signed_agreement, this_agreement.signed_agreement)
+ self.assertEqual(record.note, cdsa_audit.ERROR_NON_DATA_AFFILIATE_COMPONENT)
+
+
+class SignedAgreementAccessAuditTableTest(TestCase):
+ """Tests for the `SignedAgreementAccessAuditTable` table."""
+
+ def test_no_rows(self):
+ """Table works with no rows."""
+ table = signed_agreement_audit.SignedAgreementAccessAuditTable([])
+ self.assertIsInstance(
+ table, signed_agreement_audit.SignedAgreementAccessAuditTable
+ )
+ self.assertEqual(len(table.rows), 0)
+
+ def test_one_row(self):
+ """Table works with one row."""
+ signed_agreement = factories.SignedAgreementFactory.create()
data = [
{
"signed_agreement": signed_agreement,
@@ -531,33 +1465,126 @@ def test_anvil_group_name_setting(self):
class WorkspaceAccessAuditTest(TestCase):
"""Tests for the WorkspaceAccessAudit class."""
- def setUp(self):
- super().setUp()
- self.cdsa_group = ManagedGroupFactory.create(
- name=settings.ANVIL_CDSA_GROUP_NAME
+ def setUp(self):
+ super().setUp()
+ self.cdsa_group = ManagedGroupFactory.create(
+ name=settings.ANVIL_CDSA_GROUP_NAME
+ )
+
+ def test_completed(self):
+ """completed is updated properly."""
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ self.assertFalse(cdsa_audit.completed)
+ cdsa_audit.run_audit()
+ self.assertTrue(cdsa_audit.completed)
+
+ def test_primary_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_primary_not_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study
+ )
+ # Do not add the CDSA group to the auth domain.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=workspace.workspace.authorization_domains.first(),
+ # child_group=self.cdsa_group,
+ # )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, workspace_audit.GrantAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_no_primary_not_in_auth_domain(self):
+ workspace = factories.CDSAWorkspaceFactory.create()
+ # Do not CDSA group to the auth domain.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=workspace.workspace.authorization_domains.first(),
+ # child_group=self.cdsa_group,
+ # )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedNoAccess)
+ self.assertIsNone(record.data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
+
+ def test_no_primary_in_auth_domain(self):
+ workspace = factories.CDSAWorkspaceFactory.create()
+ # Add the CDSA group to the auth domain - this is an error.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
)
-
- def test_completed(self):
- """completed is updated properly."""
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- self.assertFalse(cdsa_audit.completed)
- cdsa_audit.run_audit()
- self.assertTrue(cdsa_audit.completed)
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 1)
+ record = cdsa_audit.errors[0]
+ self.assertIsInstance(record, workspace_audit.RemoveAccess)
+ self.assertIsNone(record.data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
- def test_no_workspaces(self):
- """Audit works when there are no workspaces."""
+ def test_primary_invalid_version_not_in_auth_domain(self):
+ workspace = factories.CDSAWorkspaceFactory.create()
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False,
+ study=workspace.study,
+ )
+ # Do not add the CDSA group to the auth domain.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=workspace.workspace.authorization_domains.first(),
+ # child_group=self.cdsa_group,
+ # )
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- self.assertFalse(cdsa_audit.completed)
- cdsa_audit.run_audit()
+ cdsa_audit._audit_workspace(workspace)
self.assertEqual(len(cdsa_audit.verified), 0)
- self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, workspace_audit.GrantAccess)
+ self.assertEqual(record.data_affiliate_agreement, this_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
- def test_one_workspace_with_primary_verified_access(self):
- study = StudyFactory.create()
- workspace = factories.CDSAWorkspaceFactory.create(study=study)
- data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
- study=study
+ def test_primary_invalid_version_in_auth_domain(self):
+ workspace = factories.CDSAWorkspaceFactory.create()
+ this_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__is_valid=False,
+ study=workspace.study,
)
# Add the CDSA group to the auth domain.
GroupGroupMembershipFactory.create(
@@ -565,43 +1592,95 @@ def test_one_workspace_with_primary_verified_access(self):
child_group=self.cdsa_group,
)
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_workspace(workspace)
self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.verified[0]
self.assertIsInstance(record, workspace_audit.VerifiedAccess)
- self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.data_affiliate_agreement, this_agreement)
self.assertEqual(record.workspace, workspace)
- self.assertEqual(record.note, cdsa_audit.VALID_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_primary_inactive_not_in_auth_domain(self):
+ workspace = factories.CDSAWorkspaceFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ study=workspace.study,
+ )
+ # Do not add the CDSA group to the auth domain.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=workspace.workspace.authorization_domains.first(),
+ # child_group=self.cdsa_group,
+ # )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedNoAccess)
+ self.assertIsNone(record.data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_PRIMARY_AGREEMENT)
- def test_one_workspace_no_primary_no_verified_access(self):
+ def test_primary_inactive_in_auth_domain(self):
workspace = factories.CDSAWorkspaceFactory.create()
- # Do not CDSA group to the auth domain.
+ factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ study=workspace.study,
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, workspace_audit.RemoveAccess)
+ self.assertIsNone(record.data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.INACTIVE_PRIMARY_AGREEMENT)
+
+ def test_component_agreement_not_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study, signed_agreement__is_primary=False
+ )
+ # Do not add the CDSA group to the auth domain.
# GroupGroupMembershipFactory.create(
# parent_group=workspace.workspace.authorization_domains.first(),
# child_group=self.cdsa_group,
# )
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_workspace(workspace)
self.assertEqual(len(cdsa_audit.verified), 1)
record = cdsa_audit.verified[0]
self.assertIsInstance(record, workspace_audit.VerifiedNoAccess)
self.assertIsNone(record.data_affiliate_agreement)
self.assertEqual(record.workspace, workspace)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
- def test_one_workspace_no_primary_error_has_access(self):
- workspace = factories.CDSAWorkspaceFactory.create()
- # Add the CDSA group to the auth domain - this is an error.
+ def test_component_agreement_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study, signed_agreement__is_primary=False
+ )
+ # Add the CDSA group to the auth domain.
GroupGroupMembershipFactory.create(
parent_group=workspace.workspace.authorization_domains.first(),
child_group=self.cdsa_group,
)
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_workspace(workspace)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 1)
@@ -609,13 +1688,71 @@ def test_one_workspace_no_primary_error_has_access(self):
self.assertIsInstance(record, workspace_audit.RemoveAccess)
self.assertIsNone(record.data_affiliate_agreement)
self.assertEqual(record.workspace, workspace)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_AGREEMENT)
- def test_one_workspace_with_primary_no_access(self):
+ def test_two_valid_primary_agreements_in_auth_domain(self):
study = StudyFactory.create()
workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study, signed_agreement__version__major_version__version=1
+ )
data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
- study=study
+ study=study, signed_agreement__version__major_version__version=2
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_valid_primary_agreements_same_major_version_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ major_version = factories.AgreementMajorVersionFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version=major_version,
+ signed_agreement__version__minor_version=1,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version=major_version,
+ signed_agreement__version__minor_version=2,
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_valid_primary_agreements_not_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study, signed_agreement__version__major_version__version=1
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study, signed_agreement__version__major_version__version=2
)
# Do not add the CDSA group to the auth domain.
# GroupGroupMembershipFactory.create(
@@ -623,21 +1760,57 @@ def test_one_workspace_with_primary_no_access(self):
# child_group=self.cdsa_group,
# )
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_workspace(workspace)
self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.needs_action[0]
self.assertIsInstance(record, workspace_audit.GrantAccess)
self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
self.assertEqual(record.workspace, workspace)
- self.assertEqual(record.note, cdsa_audit.VALID_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_primary_one_valid_one_invalid_both_active_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version__is_valid=True,
+ signed_agreement__version__major_version__version=1,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ signed_agreement__version__major_version__version=2,
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
- def test_ignores_component_agreement(self):
+ def test_two_primary_one_valid_one_invalid_both_active_not_in_auth_domain(self):
study = StudyFactory.create()
workspace = factories.CDSAWorkspaceFactory.create(study=study)
factories.DataAffiliateAgreementFactory.create(
- study=study, signed_agreement__is_primary=False
+ study=study,
+ signed_agreement__version__major_version__is_valid=True,
+ signed_agreement__version__major_version__version=1,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__version__major_version__is_valid=False,
+ signed_agreement__version__major_version__version=2,
)
# Do not add the CDSA group to the auth domain.
# GroupGroupMembershipFactory.create(
@@ -645,13 +1818,134 @@ def test_ignores_component_agreement(self):
# child_group=self.cdsa_group,
# )
cdsa_audit = workspace_audit.WorkspaceAccessAudit()
- cdsa_audit.run_audit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, workspace_audit.GrantAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_primary_one_active_one_inactive_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
record = cdsa_audit.verified[0]
- self.assertIsInstance(record, workspace_audit.VerifiedNoAccess)
- self.assertIsNone(record.data_affiliate_agreement)
+ self.assertIsInstance(record, workspace_audit.VerifiedAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_primary_one_active_one_inactive_not_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+ # Do not add the CDSA group to the auth domain.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=workspace.workspace.authorization_domains.first(),
+ # child_group=self.cdsa_group,
+ # )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, workspace_audit.GrantAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_primary_one_active_invalid_one_inactive_valid_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ signed_agreement__version__major_version__is_valid=True,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # Add the CDSA group to the auth domain.
+ GroupGroupMembershipFactory.create(
+ parent_group=workspace.workspace.authorization_domains.first(),
+ child_group=self.cdsa_group,
+ )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 1)
+ self.assertEqual(len(cdsa_audit.needs_action), 0)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.verified[0]
+ self.assertIsInstance(record, workspace_audit.VerifiedAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
+ self.assertEqual(record.workspace, workspace)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_two_primary_one_active_invalid_one_inactive_valid_not_in_auth_domain(self):
+ study = StudyFactory.create()
+ workspace = factories.CDSAWorkspaceFactory.create(study=study)
+ factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ signed_agreement__version__major_version__is_valid=True,
+ )
+ data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(
+ study=study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE,
+ signed_agreement__version__major_version__is_valid=False,
+ )
+ # # Add the CDSA group to the auth domain.
+ # GroupGroupMembershipFactory.create(
+ # parent_group=workspace.workspace.authorization_domains.first(),
+ # child_group=self.cdsa_group,
+ # )
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ cdsa_audit._audit_workspace(workspace)
+ self.assertEqual(len(cdsa_audit.verified), 0)
+ self.assertEqual(len(cdsa_audit.needs_action), 1)
+ self.assertEqual(len(cdsa_audit.errors), 0)
+ record = cdsa_audit.needs_action[0]
+ self.assertIsInstance(record, workspace_audit.GrantAccess)
+ self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
self.assertEqual(record.workspace, workspace)
- self.assertEqual(record.note, cdsa_audit.NO_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
+
+ def test_no_workspaces(self):
+ """Audit works when there are no workspaces."""
+ cdsa_audit = workspace_audit.WorkspaceAccessAudit()
+ self.assertFalse(cdsa_audit.completed)
+ cdsa_audit.run_audit()
+ self.assertEqual(len(cdsa_audit.verified), 0)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
@@ -678,12 +1972,12 @@ def test_two_workspaces(self):
self.assertIsInstance(record, workspace_audit.VerifiedAccess)
self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
self.assertEqual(record.workspace, workspace_1)
- self.assertEqual(record.note, cdsa_audit.VALID_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
record = cdsa_audit.verified[1]
self.assertIsInstance(record, workspace_audit.VerifiedAccess)
self.assertEqual(record.data_affiliate_agreement, data_affiliate_agreement)
self.assertEqual(record.workspace, workspace_2)
- self.assertEqual(record.note, cdsa_audit.VALID_PRIMARY_CDSA)
+ self.assertEqual(record.note, cdsa_audit.ACTIVE_PRIMARY_AGREEMENT)
self.assertEqual(len(cdsa_audit.needs_action), 0)
self.assertEqual(len(cdsa_audit.errors), 0)
diff --git a/primed/cdsa/tests/test_commands.py b/primed/cdsa/tests/test_commands.py
index b8ac8c76..e5ad8ebe 100644
--- a/primed/cdsa/tests/test_commands.py
+++ b/primed/cdsa/tests/test_commands.py
@@ -74,7 +74,8 @@ def test_cdsa_workspace_records_zero(self):
self.assertEqual(len(lines), 1)
def test_cdsa_workspace_records_one(self):
- factories.CDSAWorkspaceFactory.create()
+ agreement = factories.DataAffiliateAgreementFactory.create()
+ factories.CDSAWorkspaceFactory.create(study=agreement.study)
out = StringIO()
call_command("cdsa_records", self.outdir, "--no-color", stdout=out)
with open(os.path.join(self.outdir, "workspace_records.tsv")) as f:
diff --git a/primed/cdsa/tests/test_forms.py b/primed/cdsa/tests/test_forms.py
index 1366efc2..09aac23e 100644
--- a/primed/cdsa/tests/test_forms.py
+++ b/primed/cdsa/tests/test_forms.py
@@ -202,6 +202,68 @@ def test_invalid_duplicate_object(self):
self.assertEqual(len(form.errors["cc_id"]), 1)
self.assertIn("already exists", form.errors["cc_id"][0])
+ def test_invalid_version(self):
+ """Cannot select an AgreementVersion that is not valid."""
+ self.agreement_version.major_version.is_valid = False
+ self.agreement_version.major_version.save()
+ form_data = {
+ "cc_id": 1234,
+ "representative": self.representative,
+ "representative_role": "Test role",
+ "signing_institution": "Test insitution",
+ "version": self.agreement_version,
+ "date_signed": "2023-01-01",
+ "is_primary": True,
+ }
+ form = self.form_class(data=form_data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(len(form.errors), 1)
+ self.assertIn("version", form.errors)
+ self.assertEqual(len(form.errors["version"]), 1)
+ self.assertIn("valid choice", form.errors["version"][0])
+
+
+class SignedAgreementStatusFormTest(TestCase):
+ """Tests for the SignedAgreementStatusForm class."""
+
+ form_class = forms.SignedAgreementStatusForm
+
+ def setUp(self):
+ """Create related objects for use in the form."""
+ self.representative = UserFactory.create()
+ self.agreement_version = factories.AgreementVersionFactory.create()
+ self.signed_agreement = factories.SignedAgreementFactory.create()
+
+ def test_valid(self):
+ """Form is valid with necessary input."""
+ form_data = {
+ "status": models.SignedAgreement.StatusChoices.ACTIVE,
+ }
+ form = self.form_class(data=form_data)
+ self.assertTrue(form.is_valid())
+
+ def test_missing_status(self):
+ """Form is invalid when missing status."""
+ form_data = {}
+ form = self.form_class(data=form_data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(len(form.errors), 1)
+ self.assertIn("status", form.errors)
+ self.assertEqual(len(form.errors["status"]), 1)
+ self.assertIn("required", form.errors["status"][0])
+
+ def test_invalid_status(self):
+ """Cannot select a status that doesn't exist."""
+ self.agreement_version.major_version.is_valid = False
+ self.agreement_version.major_version.save()
+ form_data = {"status": "foo"}
+ form = self.form_class(data=form_data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(len(form.errors), 1)
+ self.assertIn("status", form.errors)
+ self.assertEqual(len(form.errors["status"]), 1)
+ self.assertIn("valid choice", form.errors["status"][0])
+
class MemberAgreementFormTest(TestCase):
"""Tests for the MemberAgreementForm class."""
diff --git a/primed/cdsa/tests/test_migrations.py b/primed/cdsa/tests/test_migrations.py
new file mode 100644
index 00000000..9eb16740
--- /dev/null
+++ b/primed/cdsa/tests/test_migrations.py
@@ -0,0 +1,102 @@
+"""Tests for data migrations in the app."""
+from datetime import date
+
+from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory
+from django_test_migrations.contrib.unittest_case import MigratorTestCase
+import factory
+
+from . import factories
+
+class AgreementMajorVersionMigrationsTest(MigratorTestCase):
+ """Tests for the migrations associated with creating the new AgreementMajorVersion model."""
+
+ migrate_from = ("cdsa", "0001_initial")
+ migrate_to = ("cdsa", "0007_alter_agreementversion_major_version")
+
+ def prepare(self):
+ """Prepare some data before the migration."""
+ # Get model definition for the old state.
+ AgreementVersion = self.old_state.apps.get_model("cdsa", "AgreementVersion")
+ # Populate with multiple major versions and minor versions.
+ self.agreement_version_1_0 = AgreementVersion.objects.create(
+ major_version=1,
+ minor_version=0,
+ )
+ self.agreement_version_1_1 = AgreementVersion.objects.create(
+ major_version=1,
+ minor_version=1,
+ )
+ self.agreement_version_1_2 = AgreementVersion.objects.create(
+ major_version=1,
+ minor_version=2,
+ )
+ self.agreement_version_2_1 = AgreementVersion.objects.create(
+ major_version=2,
+ minor_version=1,
+ )
+ self.agreement_version_2_3 = AgreementVersion.objects.create(
+ major_version=2,
+ minor_version=3,
+ )
+ self.agreement_version_3_0 = AgreementVersion.objects.create(
+ major_version=3,
+ minor_version=0,
+ )
+ self.agreement_version_5_6 = AgreementVersion.objects.create(
+ major_version=5,
+ minor_version=6,
+ )
+
+ def test_agreementmajorversion_model_correctly_populated(self):
+ """AgreementMajorVersion is correctly populated."""
+ AgreementMajorVersion = self.new_state.apps.get_model("cdsa", "AgreementMajorVersion")
+ self.assertEqual(AgreementMajorVersion.objects.count(), 4)
+ major_version = AgreementMajorVersion.objects.get(version=1)
+ major_version.full_clean()
+ major_version = AgreementMajorVersion.objects.get(version=2)
+ major_version.full_clean()
+ major_version = AgreementMajorVersion.objects.get(version=3)
+ major_version.full_clean()
+ major_version = AgreementMajorVersion.objects.get(version=5)
+ major_version.full_clean()
+
+ def test_agreement_version_major_version_correctly_populated(self):
+ """AgreementVersion.major_version is correctly populated."""
+ AgreementMajorVersion = self.new_state.apps.get_model("cdsa", "AgreementMajorVersion")
+ AgreementVersion = self.new_state.apps.get_model("cdsa", "AgreementVersion")
+ # Major version 1.
+ major_version = AgreementMajorVersion.objects.get(version=1)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_1_0.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 0)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_1_1.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 1)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_1_2.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 2)
+ # Major version 2.
+ major_version = AgreementMajorVersion.objects.get(version=2)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_2_1.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 1)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_2_3.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 3)
+ # Major version 3.
+ major_version = AgreementMajorVersion.objects.get(version=3)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_3_0.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 0)
+ # Major version 5.
+ major_version = AgreementMajorVersion.objects.get(version=5)
+ agreement_version = AgreementVersion.objects.get(pk=self.agreement_version_5_6.pk)
+ agreement_version.full_clean()
+ self.assertEqual(agreement_version.major_version, major_version)
+ self.assertEqual(agreement_version.minor_version, 6)
diff --git a/primed/cdsa/tests/test_models.py b/primed/cdsa/tests/test_models.py
index dcd4a36d..32b35cd2 100644
--- a/primed/cdsa/tests/test_models.py
+++ b/primed/cdsa/tests/test_models.py
@@ -2,14 +2,16 @@
from datetime import datetime
+from anvil_consortium_manager.models import ManagedGroup
from anvil_consortium_manager.tests.factories import (
+ GroupGroupMembershipFactory,
ManagedGroupFactory,
WorkspaceFactory,
)
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.db.models import ProtectedError
from django.db.utils import IntegrityError
-from django.test import TestCase
+from django.test import TestCase, override_settings
from primed.duo.tests.factories import DataUseModifierFactory, DataUsePermissionFactory
from primed.primed_anvil.tests.factories import (
@@ -23,58 +25,99 @@
from . import factories
-class AgreementVersionTest(TestCase):
- """Tests for the AgreementVersion model."""
+class AgreementMajorVersionTest(TestCase):
+ """Tests for the AgreementMajorVersion model."""
def test_model_saving(self):
- instance = models.AgreementVersion(
- major_version=1, minor_version=0, date_approved=datetime.today()
- )
+ instance = models.AgreementMajorVersion(version=1)
instance.save()
- self.assertIsInstance(instance, models.AgreementVersion)
+ self.assertIsInstance(instance, models.AgreementMajorVersion)
+
+ def test_is_valid_default(self):
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.assertTrue(instance.is_valid)
def test_unique(self):
- factories.AgreementVersionFactory.create(major_version=1, minor_version=0)
- instance = factories.AgreementVersionFactory.build(
- major_version=1, minor_version=0
- )
+ factories.AgreementMajorVersionFactory.create(version=1)
+ instance = factories.AgreementMajorVersionFactory.build(version=1)
with self.assertRaisesMessage(ValidationError, "already exists"):
instance.full_clean()
with self.assertRaises(IntegrityError):
instance.save()
- def test_major_version_zero(self):
- """ValidationError raised when major_version is zero."""
- instance = factories.AgreementVersionFactory.build(major_version=0)
+ def test_version_zero(self):
+ """ValidationError raised when version is zero."""
+ instance = factories.AgreementMajorVersionFactory.build(version=0)
with self.assertRaises(ValidationError) as e:
instance.full_clean()
self.assertEqual(len(e.exception.message_dict), 1)
- self.assertIn("major_version", e.exception.message_dict)
- self.assertEqual(len(e.exception.message_dict["major_version"]), 1)
+ self.assertIn("version", e.exception.message_dict)
+ self.assertEqual(len(e.exception.message_dict["version"]), 1)
self.assertIn(
- "greater than or equal to", e.exception.message_dict["major_version"][0]
+ "greater than or equal to", e.exception.message_dict["version"][0]
)
- def test_major_version_negative(self):
- """ValidationError raised when major_version is negative."""
- instance = factories.AgreementVersionFactory.build(major_version=-1)
+ def test_version_negative(self):
+ """ValidationError raised when version is negative."""
+ instance = factories.AgreementMajorVersionFactory.build(version=-1)
with self.assertRaises(ValidationError) as e:
instance.full_clean()
self.assertEqual(len(e.exception.message_dict), 1)
- self.assertIn("major_version", e.exception.message_dict)
- self.assertEqual(len(e.exception.message_dict["major_version"]), 1)
+ self.assertIn("version", e.exception.message_dict)
+ self.assertEqual(len(e.exception.message_dict["version"]), 1)
self.assertIn(
- "greater than or equal to", e.exception.message_dict["major_version"][0]
+ "greater than or equal to", e.exception.message_dict["version"][0]
+ )
+
+ def test_str(self):
+ """__str__ method works as expected."""
+ instance = factories.AgreementMajorVersionFactory.build()
+ self.assertIsInstance(str(instance), str)
+
+ def test_get_absolute_url(self):
+ """get_absolute_url method works correctly."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.assertIsInstance(instance.get_absolute_url(), str)
+
+
+class AgreementVersionTest(TestCase):
+ """Tests for the AgreementVersion model."""
+
+ def test_model_saving(self):
+ major_version = factories.AgreementMajorVersionFactory.create()
+ instance = models.AgreementVersion(
+ major_version=major_version, minor_version=0, date_approved=datetime.today()
+ )
+ instance.save()
+ self.assertIsInstance(instance, models.AgreementVersion)
+
+ def test_unique(self):
+ major_version = factories.AgreementMajorVersionFactory.create()
+ factories.AgreementVersionFactory.create(
+ major_version=major_version, minor_version=0
+ )
+ instance = factories.AgreementVersionFactory.build(
+ major_version=major_version, minor_version=0
)
+ with self.assertRaisesMessage(ValidationError, "already exists"):
+ instance.full_clean()
+ with self.assertRaises(IntegrityError):
+ instance.save()
def test_minor_version_zero(self):
"""full_clean raises no exception when minor_version is zero."""
- instance = factories.AgreementVersionFactory.build(minor_version=0)
+ major_version = factories.AgreementMajorVersionFactory.create()
+ instance = factories.AgreementVersionFactory.build(
+ major_version=major_version, minor_version=0
+ )
instance.full_clean()
def test_minor_version_negative(self):
"""ValidationError raised when minor_version is negative."""
- instance = factories.AgreementVersionFactory.build(minor_version=-1)
+ major_version = factories.AgreementMajorVersionFactory.create()
+ instance = factories.AgreementVersionFactory.build(
+ major_version=major_version, minor_version=-1
+ )
with self.assertRaises(ValidationError) as e:
instance.full_clean()
self.assertEqual(len(e.exception.message_dict), 1)
@@ -88,25 +131,25 @@ def test_full_version(self):
"""full_version property works as expected."""
self.assertEqual(
factories.AgreementVersionFactory(
- major_version=1, minor_version=0
+ major_version__version=1, minor_version=0
).full_version,
"v1.0",
)
self.assertEqual(
factories.AgreementVersionFactory(
- major_version=1, minor_version=5
+ major_version__version=1, minor_version=5
).full_version,
"v1.5",
)
self.assertEqual(
factories.AgreementVersionFactory(
- major_version=1, minor_version=10
+ major_version__version=1, minor_version=10
).full_version,
"v1.10",
)
self.assertEqual(
factories.AgreementVersionFactory(
- major_version=2, minor_version=3
+ major_version__version=2, minor_version=3
).full_version,
"v2.3",
)
@@ -116,6 +159,11 @@ def test_str(self):
instance = factories.AgreementVersionFactory.build()
self.assertIsInstance(str(instance), str)
+ def test_get_absolute_url(self):
+ """get_absolute_url method works correctly."""
+ instance = factories.AgreementVersionFactory.create()
+ self.assertIsInstance(instance.get_absolute_url(), str)
+
class SignedAgreementTest(TestCase):
"""Tests for the SignedAgreement model."""
@@ -202,7 +250,6 @@ def test_cc_id_zero(self):
user = UserFactory.create()
group = ManagedGroupFactory.create()
agreement_version = factories.AgreementVersionFactory.create()
- instance = factories.AgreementVersionFactory.build(major_version=0)
instance = factories.SignedAgreementFactory.build(
cc_id=0,
representative=user,
@@ -221,7 +268,6 @@ def test_cc_id_negative(self):
user = UserFactory.create()
group = ManagedGroupFactory.create()
agreement_version = factories.AgreementVersionFactory.create()
- instance = factories.AgreementVersionFactory.build(major_version=0)
instance = factories.SignedAgreementFactory.build(
cc_id=-1,
representative=user,
@@ -242,6 +288,28 @@ def test_agreement_version_protect(self):
with self.assertRaises(ProtectedError):
agreement_version.delete()
+ def test_status_field(self):
+ # default
+ instance = factories.SignedAgreementFactory.create()
+ self.assertEqual(instance.status, instance.StatusChoices.ACTIVE)
+ instance.full_clean()
+ # other choices
+ instance = factories.SignedAgreementFactory.create(
+ status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ self.assertEqual(instance.status, instance.StatusChoices.WITHDRAWN)
+ instance.full_clean()
+ instance = factories.SignedAgreementFactory.create(
+ status=models.SignedAgreement.StatusChoices.LAPSED
+ )
+ self.assertEqual(instance.status, instance.StatusChoices.LAPSED)
+ instance.full_clean()
+
+ # not allowed
+ instance = factories.SignedAgreementFactory.create(status="foo")
+ with self.assertRaises(ValidationError):
+ instance.full_clean()
+
def test_get_combined_type(self):
obj = factories.MemberAgreementFactory()
self.assertEqual(obj.signed_agreement.combined_type, "Member")
@@ -283,7 +351,6 @@ def test_clean_non_data_affiliate_is_primary_false(self):
user = UserFactory.create()
group = ManagedGroupFactory.create()
agreement_version = factories.AgreementVersionFactory.create()
- instance = factories.AgreementVersionFactory.build(major_version=0)
instance = factories.SignedAgreementFactory.build(
representative=user,
anvil_access_group=group,
@@ -298,6 +365,37 @@ def test_clean_non_data_affiliate_is_primary_false(self):
self.assertEqual(len(e.exception.message_dict[NON_FIELD_ERRORS]), 1)
self.assertIn("primary", e.exception.message_dict[NON_FIELD_ERRORS][0])
+ def test_is_in_cdsa_group(self):
+ """is_in_cdsa_group works as expected."""
+ obj = factories.SignedAgreementFactory.create()
+ # When group does not exist
+ with self.assertRaises(ManagedGroup.DoesNotExist):
+ obj.is_in_cdsa_group()
+ # Create group, without adding agreement
+ cdsa_group = ManagedGroupFactory.create(name="TEST_PRIMED_CDSA")
+ self.assertFalse(obj.is_in_cdsa_group())
+ # Add agreement and check again,
+ GroupGroupMembershipFactory.create(
+ parent_group=cdsa_group, child_group=obj.anvil_access_group
+ )
+ self.assertTrue(obj.is_in_cdsa_group())
+
+ @override_settings(ANVIL_CDSA_GROUP_NAME="FOO")
+ def test_is_in_cdsa_group_different_group_name(self):
+ """is_in_cdsa_group works as expected."""
+ obj = factories.SignedAgreementFactory.create()
+ # When group does not exist
+ with self.assertRaises(ManagedGroup.DoesNotExist):
+ obj.is_in_cdsa_group()
+ # Create group, without adding agreement
+ cdsa_group = ManagedGroupFactory.create(name="FOO")
+ self.assertFalse(obj.is_in_cdsa_group())
+ # Add agreement and check again,
+ GroupGroupMembershipFactory.create(
+ parent_group=cdsa_group, child_group=obj.anvil_access_group
+ )
+ self.assertTrue(obj.is_in_cdsa_group())
+
class MemberAgreementTest(TestCase):
"""Tests for the MemberAgremeent model."""
diff --git a/primed/cdsa/tests/test_tables.py b/primed/cdsa/tests/test_tables.py
index 6bf1fa68..aaae72d0 100644
--- a/primed/cdsa/tests/test_tables.py
+++ b/primed/cdsa/tests/test_tables.py
@@ -14,6 +14,26 @@
from . import factories
+class AgreementVersionTableTest(TestCase):
+ model = models.AgreementVersion
+ model_factory = factories.AgreementVersionFactory
+ table_class = tables.AgreementVersionTable
+
+ def test_row_count_with_no_objects(self):
+ table = self.table_class(self.model.objects.all())
+ self.assertEqual(len(table.rows), 0)
+
+ def test_row_count_with_one_object(self):
+ self.model_factory.create()
+ table = self.table_class(self.model.objects.all())
+ self.assertEqual(len(table.rows), 1)
+
+ def test_row_count_with_three_objects(self):
+ self.model_factory.create_batch(3)
+ table = self.table_class(self.model.objects.all())
+ self.assertEqual(len(table.rows), 3)
+
+
class SignedAgreementTableTest(TestCase):
model = models.SignedAgreement
table_class = tables.SignedAgreementTable
diff --git a/primed/cdsa/tests/test_views.py b/primed/cdsa/tests/test_views.py
index 6d928f06..1689582b 100644
--- a/primed/cdsa/tests/test_views.py
+++ b/primed/cdsa/tests/test_views.py
@@ -40,78 +40,1264 @@
User = get_user_model()
+class AgreementVersionListTest(TestCase):
+ """Tests for the AgreementVersionList view."""
+
+ def setUp(self):
+ """Set up test class."""
+ 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:agreement_versions:list", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.AgreementVersionList.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(self):
+ """Returns successful response code."""
+ 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_table_class(self):
+ """The table is the correct class."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ self.assertIsInstance(
+ response.context_data["table"], tables.AgreementVersionTable
+ )
+
+ def test_workspace_table_none(self):
+ """No rows are shown if there are no AgreementVersion objects."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 0)
+
+ def test_workspace_table_three(self):
+ """Two rows are shown if there are three AgreementVersion objects."""
+ factories.AgreementVersionFactory.create_batch(3)
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 3)
+
+
+class AgreementMajorVersionDetailTest(TestCase):
+ """Tests for the AgreementVersionDetail view."""
+
+ def setUp(self):
+ """Set up test class."""
+ 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+ # Create an object test this with.
+ self.obj = factories.AgreementMajorVersionFactory.create()
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:agreement_versions:major_version_detail", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.AgreementMajorVersionDetail.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(2))
+ self.assertRedirects(
+ response,
+ resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(2),
+ )
+
+ def test_status_code_with_user_permission(self):
+ """Returns successful response code."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ 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(2))
+ request.user = user_no_perms
+ with self.assertRaises(PermissionDenied):
+ self.get_view()(request, major_version=2)
+
+ def test_view_status_code_with_existing_object(self):
+ """Returns a successful status code for an existing object pk."""
+ # Only clients load the template.
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+
+ def test_view_status_code_with_invalid_version(self):
+ """Raises a 404 error with an invalid major and minor version."""
+ request = self.factory.get(self.get_url(self.obj.version + 1))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(
+ request,
+ major_version=self.obj.version + 1,
+ )
+
+ def test_context_table_classes(self):
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("tables", response.context_data)
+ self.assertEqual(len(response.context_data["tables"]), 2)
+ self.assertIsInstance(
+ response.context_data["tables"][0], tables.AgreementVersionTable
+ )
+ self.assertIsInstance(
+ response.context_data["tables"][1], tables.SignedAgreementTable
+ )
+
+ def test_response_includes_agreement_version_table(self):
+ """agreement_version_table includes agreement_versions with this major version."""
+ factories.AgreementVersionFactory.create(major_version=self.obj)
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(len(response.context_data["tables"][0].rows), 1)
+
+ def test_response_includes_agreement_version_table_other_major_version(self):
+ """agreement_version_table includes only agreement_versions with this major version."""
+ other_agreement = factories.AgreementVersionFactory.create(
+ major_version__version=self.obj.version + 1
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(len(response.context_data["tables"][0].rows), 0)
+ self.assertNotIn(other_agreement, response.context_data["tables"][0].data)
+
+ def test_response_signed_agreement_table_three_agreements(self):
+ """signed_agreement_table includes all types of agreements."""
+ factories.MemberAgreementFactory.create(
+ signed_agreement__version__major_version__version=self.obj.version
+ )
+ factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__version=self.obj.version
+ )
+ factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__version__major_version__version=self.obj.version
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context_data["tables"][1].rows), 3)
+
+ def test_response_signed_agreement_table_other_major_version(self):
+ """signed_agreement_table does not include agreements from other versions."""
+ factories.MemberAgreementFactory.create()
+ factories.DataAffiliateAgreementFactory.create()
+ factories.NonDataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context_data["tables"][1].rows), 0)
+
+ def test_response_show_deprecation_message_valid(self):
+ """response context does not show a deprecation warning when AgreementMajorVersion is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertFalse(response.context_data["show_deprecation_message"])
+ self.assertNotIn(b"Deprecated", response.content)
+
+ def test_response_show_deprecation_message_not_valid(self):
+ """response context does show a deprecation warning when AgreementMajorVersion is is not valid."""
+ self.obj.is_valid = False
+ self.obj.save()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertTrue(response.context_data["show_deprecation_message"])
+ self.assertIn(b"Deprecated", response.content)
+
+ def test_invalidate_button_valid_user_has_edit_perm(self):
+ """Invalidate button appears when the user has edit permission and the instance is valid."""
+ user = User.objects.create_user(username="test_edit", password="test_edit")
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME
+ )
+ )
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+ self.client.force_login(user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_invalidate_button", response.context_data)
+ self.assertTrue(response.context_data["show_invalidate_button"])
+ self.assertContains(
+ response,
+ reverse("cdsa:agreement_versions:invalidate", args=[self.obj.version]),
+ )
+
+ def test_invalidate_button_valid_user_has_view_perm(self):
+ """Invalidate button does not appear when the user has view permission and the instance is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_invalidate_button", response.context_data)
+ self.assertFalse(response.context_data["show_invalidate_button"])
+ self.assertNotContains(
+ response,
+ reverse("cdsa:agreement_versions:invalidate", args=[self.obj.version]),
+ )
+
+ def test_invalidate_button_invalid_user_has_edit_perm(self):
+ """Invalidate button does not appear when the user has edit permission and the instance is invalid."""
+ self.obj.is_valid = False
+ self.obj.save()
+ user = User.objects.create_user(username="test_edit", password="test_edit")
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME
+ )
+ )
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+ self.client.force_login(user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_invalidate_button", response.context_data)
+ self.assertFalse(response.context_data["show_invalidate_button"])
+ self.assertNotContains(
+ response,
+ reverse("cdsa:agreement_versions:invalidate", args=[self.obj.version]),
+ )
+
+ def test_invalidate_button_invalid_user_has_view_perm(self):
+ """Invalidate button does not appear when the user has view permission and the instance is invalid."""
+ self.obj.is_valid = False
+ self.obj.save()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.version))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_invalidate_button", response.context_data)
+ self.assertFalse(response.context_data["show_invalidate_button"])
+ self.assertNotContains(
+ response,
+ reverse("cdsa:agreement_versions:invalidate", args=[self.obj.version]),
+ )
+
+
+class AgreementMajorVersionInvalidateTest(TestCase):
+ """Tests for the AgreementMajorVersionInvalidate 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+ self.user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:agreement_versions:invalidate", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.AgreementMajorVersionInvalidate.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_edit(self):
+ """Returns successful response code."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.version))
+ 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(1))
+ request.user = user_no_perms
+ with self.assertRaises(PermissionDenied):
+ self.get_view()(request, major_version=1)
+
+ def test_access_without_user_permission_view(self):
+ """Raises permission denied if user has only view permission."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ user_view_perm = User.objects.create_user(
+ username="test-none", password="test-none"
+ )
+ user_view_perm.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME
+ )
+ )
+ request = self.factory.get(self.get_url(instance.version))
+ request.user = user_view_perm
+ with self.assertRaises(PermissionDenied):
+ self.get_view()(request, major_version=instance.version)
+
+ def test_object_does_not_exist(self):
+ request = self.factory.get(self.get_url(1))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, major_version=1)
+
+ def test_has_object_in_context(self):
+ """Response includes a form."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.version))
+ self.assertTrue("object" in response.context_data)
+ self.assertEqual(response.context_data["object"], instance)
+
+ def test_has_form_in_context(self):
+ """Response includes a form."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.version))
+ self.assertTrue("form" in response.context_data)
+
+ def test_form_class(self):
+ """Form is the expected class."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.version))
+ self.assertIsInstance(
+ response.context_data["form"], forms.AgreementMajorVersionIsValidForm
+ )
+
+ def test_invalidates_instance(self):
+ """Can invalidate the instance."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ self.assertEqual(response.status_code, 302)
+ instance.refresh_from_db()
+ self.assertFalse(instance.is_valid)
+
+ def test_sets_one_signed_agreement_to_lapsed(self):
+ """Sets SignedAgreements associated with this major version to LAPSED."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ signed_agreement = factories.SignedAgreementFactory.create(
+ version__major_version=instance
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ self.assertEqual(response.status_code, 302)
+ signed_agreement.refresh_from_db()
+ self.assertEqual(
+ signed_agreement.status, models.SignedAgreement.StatusChoices.LAPSED
+ )
+
+ def test_sets_two_signed_agreements_to_lapsed(self):
+ """Sets SignedAgreements associated with this major version to LAPSED."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ signed_agreement_1 = factories.SignedAgreementFactory.create(
+ version__major_version=instance
+ )
+ signed_agreement_2 = factories.SignedAgreementFactory.create(
+ version__major_version=instance
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ self.assertEqual(response.status_code, 302)
+ signed_agreement_1.refresh_from_db()
+ self.assertEqual(
+ signed_agreement_1.status, models.SignedAgreement.StatusChoices.LAPSED
+ )
+ signed_agreement_2.refresh_from_db()
+ self.assertEqual(
+ signed_agreement_2.status, models.SignedAgreement.StatusChoices.LAPSED
+ )
+
+ def test_only_sets_active_signed_agreements_to_lapsed(self):
+ """Does not set SignedAgreements with a different status to LAPSED."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ withdrawn_agreement = factories.SignedAgreementFactory.create(
+ version__major_version=instance,
+ status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ lapsed_agreement = factories.SignedAgreementFactory.create(
+ version__major_version=instance,
+ status=models.SignedAgreement.StatusChoices.LAPSED,
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ self.assertEqual(response.status_code, 302)
+ lapsed_agreement.refresh_from_db()
+ self.assertEqual(
+ lapsed_agreement.status, models.SignedAgreement.StatusChoices.LAPSED
+ )
+ withdrawn_agreement.refresh_from_db()
+ self.assertEqual(
+ withdrawn_agreement.status, models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+
+ def test_only_sets_associated_signed_agreements_to_lapsed(self):
+ """Does not set SignedAgreements associated with a different version to LAPSED."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ signed_agreement = factories.SignedAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ self.assertEqual(response.status_code, 302)
+ signed_agreement.refresh_from_db()
+ self.assertEqual(
+ signed_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE
+ )
+
+ def test_redirect_url(self):
+ """Redirects to successful url."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ self.assertRedirects(response, instance.get_absolute_url())
+
+ def test_success_message(self):
+ """Redirects to successful url."""
+ instance = factories.AgreementMajorVersionFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(self.get_url(instance.version), {})
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ views.AgreementMajorVersionInvalidate.success_message, str(messages[0])
+ )
+
+ def test_version_already_invalid_get(self):
+ instance = factories.AgreementMajorVersionFactory.create(is_valid=False)
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.version))
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ views.AgreementMajorVersionInvalidate.ERROR_ALREADY_INVALID,
+ str(messages[0]),
+ )
+
+ def test_version_already_invalid_post(self):
+ instance = factories.AgreementMajorVersionFactory.create(is_valid=False)
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.version), {})
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ views.AgreementMajorVersionInvalidate.ERROR_ALREADY_INVALID,
+ str(messages[0]),
+ )
+
+
+class AgreementVersionDetailTest(TestCase):
+ """Tests for the AgreementVersionDetail view."""
+
+ def setUp(self):
+ """Set up test class."""
+ 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+ # Create an object test this with.
+ self.obj = factories.AgreementVersionFactory.create()
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:agreement_versions:detail", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.AgreementVersionDetail.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(2, 5))
+ self.assertRedirects(
+ response,
+ resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(2, 5),
+ )
+
+ def test_status_code_with_user_permission(self):
+ """Returns successful response code."""
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ 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(2, 5))
+ request.user = user_no_perms
+ with self.assertRaises(PermissionDenied):
+ self.get_view()(request, major_version=2, minor_version=5)
+
+ def test_view_status_code_with_existing_object(self):
+ """Returns a successful status code for an existing object pk."""
+ # Only clients load the template.
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_view_status_code_with_invalid_version(self):
+ """Raises a 404 error with an invalid major and minor version."""
+ request = self.factory.get(
+ self.get_url(self.obj.major_version.version + 1, self.obj.minor_version + 1)
+ )
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(
+ request,
+ major_version=self.obj.major_version.version + 1,
+ minor_version=self.obj.minor_version + 1,
+ )
+
+ def test_view_status_code_with_other_major_version(self):
+ """Raises a 404 error with an invalid object major version."""
+ request = self.factory.get(
+ self.get_url(self.obj.major_version.version + 1, self.obj.minor_version)
+ )
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(
+ request,
+ major_version__version=self.obj.major_version.version + 1,
+ minor_version=self.obj.minor_version,
+ )
+
+ def test_view_status_code_with_other_minor_version(self):
+ """Raises a 404 error with an invalid object minor version."""
+ request = self.factory.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version + 1)
+ )
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(
+ request,
+ major_version=self.obj.major_version.version,
+ minor_version=self.obj.minor_version + 1,
+ )
+
+ # def test_response_includes_link_to_major_agreement(self):
+ # """Response includes a link to the user profile page."""
+ # self.client.force_login(self.user)
+ # response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ # self.assertContains(
+ # response, self.obj.signed_agreement.representative.get_absolute_url()
+ # )
+
+ def test_response_includes_signed_agreement_table(self):
+ """Response includes a table of SignedAgreements."""
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("signed_agreement_table", response.context_data)
+ self.assertIsInstance(
+ response.context_data["signed_agreement_table"], tables.SignedAgreementTable
+ )
+
+ def test_response_signed_agreement_table_three_agreements(self):
+ """signed_agreement_table includes all types of agreements."""
+ member_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__version=self.obj
+ )
+ da_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__version=self.obj
+ )
+ nda_agreement = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__version=self.obj
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context_data["signed_agreement_table"].rows), 3)
+ self.assertIn(
+ member_agreement.signed_agreement,
+ response.context_data["signed_agreement_table"].data,
+ )
+ self.assertIn(
+ da_agreement.signed_agreement,
+ response.context_data["signed_agreement_table"].data,
+ )
+ self.assertIn(
+ nda_agreement.signed_agreement,
+ response.context_data["signed_agreement_table"].data,
+ )
+
+ def test_response_signed_agreement_table_other_version(self):
+ """signed_agreement_table does not include agreements from other versions."""
+ member_agreement = factories.MemberAgreementFactory.create()
+ da_agreement = factories.DataAffiliateAgreementFactory.create()
+ nda_agreement = factories.NonDataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context_data["signed_agreement_table"].rows), 0)
+ self.assertNotIn(
+ member_agreement.signed_agreement,
+ response.context_data["signed_agreement_table"].data,
+ )
+ self.assertNotIn(
+ da_agreement.signed_agreement,
+ response.context_data["signed_agreement_table"].data,
+ )
+ self.assertNotIn(
+ nda_agreement.signed_agreement,
+ response.context_data["signed_agreement_table"].data,
+ )
+
+ def test_response_show_deprecation_message_valid(self):
+ """response context does not show a deprecation warning when AgreementMajorVersion is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertFalse(response.context_data["show_deprecation_message"])
+ self.assertNotIn(b"Deprecated", response.content)
+
+ def test_response_show_deprecation_message_not_valid(self):
+ """response context does show a deprecation warning when AgreementMajorVersion is not valid."""
+ self.obj.major_version.is_valid = False
+ self.obj.major_version.save()
+ self.client.force_login(self.user)
+ response = self.client.get(
+ self.get_url(self.obj.major_version.version, self.obj.minor_version)
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertTrue(response.context_data["show_deprecation_message"])
+ self.assertIn(b"Deprecated", response.content)
+
+
class SignedAgreementListTest(TestCase):
"""Tests for the SignedAgreementList view."""
def setUp(self):
"""Set up test class."""
self.factory = RequestFactory()
- # Create a user with both view and edit permission.
+ # 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:signed_agreements:list", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.SignedAgreementList.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(self):
+ """Returns successful response code."""
+ 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_table_class(self):
+ """The table is the correct class."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ self.assertIsInstance(
+ response.context_data["table"], tables.SignedAgreementTable
+ )
+
+ def test_workspace_table_none(self):
+ """No rows are shown if there are no SignedAgreement objects."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 0)
+
+ def test_workspace_table_three(self):
+ """Two rows are shown if there are three SignedAgreement objects."""
+ factories.MemberAgreementFactory.create()
+ factories.DataAffiliateAgreementFactory.create()
+ factories.NonDataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 3)
+
+
+class SignedAgreementStatusUpdateMemberTest(TestCase):
+ """Tests for the SignedAgreementStatusUpdate view with a MemberAgreement."""
+
+ def setUp(self):
+ """Set up test class."""
+ super().setUp()
+ 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+ self.user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:signed_agreements:members:update", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.SignedAgreementStatusUpdate.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."""
+ instance = factories.MemberAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.signed_agreement.cc_id))
+ 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.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, cc_id=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, cc_id=1)
+
+ def test_object_does_not_exist(self):
+ """Raises Http404 if object does not exist."""
+ request = self.factory.get(self.get_url(1))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, cc_id=1)
+
+ def test_object_different_agreement_type(self):
+ """Raises Http404 if object has a different agreement type."""
+ instance = factories.DataAffiliateAgreementFactory.create()
+ request = self.factory.get(self.get_url(instance.signed_agreement.cc_id))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, cc_id=instance.signed_agreement.cc_id)
+
+ def test_has_form_in_context(self):
+ """Response includes a form."""
+ instance = factories.MemberAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.signed_agreement.cc_id))
+ self.assertTrue("form" in response.context_data)
+ self.assertIsInstance(
+ response.context_data["form"], forms.SignedAgreementStatusForm
+ )
+
+ def test_can_modify_status(self):
+ """Can change the status."""
+ instance = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ )
+ self.assertEqual(response.status_code, 302)
+ instance.refresh_from_db()
+ self.assertEqual(
+ instance.signed_agreement.status,
+ models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+
+ def test_invalid_status(self):
+ """Can change the status."""
+ instance = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id), {"status": "foo"}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("form", response.context)
+ form = response.context_data["form"]
+ self.assertFalse(form.is_valid())
+ self.assertEqual(len(form.errors), 1)
+ self.assertIn("status", form.errors)
+ self.assertEqual(len(form.errors["status"]), 1)
+ self.assertIn("valid choice", form.errors["status"][0])
+ instance.refresh_from_db()
+ self.assertEqual(
+ instance.signed_agreement.status,
+ models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+
+ def test_success_message(self):
+ """Response includes a success message if successful."""
+ instance = factories.MemberAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ follow=True,
+ )
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ views.SignedAgreementStatusUpdate.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."""
+ # This needs to use the client because the RequestFactory doesn't handle redirects.
+ instance = factories.MemberAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ )
+ self.assertRedirects(response, instance.get_absolute_url())
+
+
+class SignedAgreementStatusUpdateDataAffiliateTest(TestCase):
+ """Tests for the SignedAgreementStatusUpdate view with a DataAffiliateAgreement."""
+
+ def setUp(self):
+ """Set up test class."""
+ super().setUp()
+ 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.VIEW_PERMISSION_CODENAME
+ )
+ )
+ self.user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+
+ def get_url(self, *args):
+ """Get the url for the view being tested."""
+ return reverse("cdsa:signed_agreements:data_affiliates:update", args=args)
+
+ def get_view(self):
+ """Return the view being tested."""
+ return views.SignedAgreementStatusUpdate.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."""
+ instance = factories.DataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.signed_agreement.cc_id))
+ 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.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, cc_id=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, cc_id=1)
+
+ def test_object_does_not_exist(self):
+ """Raises Http404 if object does not exist."""
+ request = self.factory.get(self.get_url(1))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, cc_id=1)
+
+ def test_object_different_agreement_type(self):
+ """Raises Http404 if object has a different agreement type."""
+ instance = factories.MemberAgreementFactory.create()
+ request = self.factory.get(self.get_url(instance.signed_agreement.cc_id))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, cc_id=instance.signed_agreement.cc_id)
+
+ def test_has_form_in_context(self):
+ """Response includes a form."""
+ instance = factories.DataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(instance.signed_agreement.cc_id))
+ self.assertTrue("form" in response.context_data)
+ self.assertIsInstance(
+ response.context_data["form"], forms.SignedAgreementStatusForm
+ )
+
+ def test_can_modify_status(self):
+ """Can change the status."""
+ instance = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ )
+ self.assertEqual(response.status_code, 302)
+ instance.refresh_from_db()
+ self.assertEqual(
+ instance.signed_agreement.status,
+ models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+
+ def test_invalid_status(self):
+ """Can change the status."""
+ instance = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE
+ )
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id), {"status": "foo"}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("form", response.context)
+ form = response.context_data["form"]
+ self.assertFalse(form.is_valid())
+ self.assertEqual(len(form.errors), 1)
+ self.assertIn("status", form.errors)
+ self.assertEqual(len(form.errors["status"]), 1)
+ self.assertIn("valid choice", form.errors["status"][0])
+ instance.refresh_from_db()
+ self.assertEqual(
+ instance.signed_agreement.status,
+ models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+
+ def test_success_message(self):
+ """Response includes a success message if successful."""
+ instance = factories.DataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ follow=True,
+ )
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ views.SignedAgreementStatusUpdate.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."""
+ # This needs to use the client because the RequestFactory doesn't handle redirects.
+ instance = factories.DataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ )
+ self.assertRedirects(response, instance.get_absolute_url())
+
+
+class SignedAgreementStatusUpdateNonDataAffiliateTest(TestCase):
+ """Tests for the SignedAgreementStatusUpdate view with a NonDataAffiliateAgreement."""
+
+ def setUp(self):
+ """Set up test class."""
+ super().setUp()
+ 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.VIEW_PERMISSION_CODENAME
)
)
+ self.user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:list", args=args)
+ return reverse("cdsa:signed_agreements:non_data_affiliates:update", args=args)
def get_view(self):
"""Return the view being tested."""
- return views.SignedAgreementList.as_view()
+ return views.SignedAgreementStatusUpdate.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())
+ response = self.client.get(self.get_url(1))
self.assertRedirects(
- response,
- resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(),
+ response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)
)
def test_status_code_with_user_permission(self):
"""Returns successful response code."""
+ instance = factories.NonDataAffiliateAgreementFactory.create()
self.client.force_login(self.user)
- response = self.client.get(self.get_url())
+ response = self.client.get(self.get_url(instance.signed_agreement.cc_id))
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.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, cc_id=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())
+ request = self.factory.get(self.get_url(1))
request.user = user_no_perms
with self.assertRaises(PermissionDenied):
- self.get_view()(request)
+ self.get_view()(request, cc_id=1)
- def test_table_class(self):
- """The table is the correct class."""
+ def test_object_does_not_exist(self):
+ """Raises Http404 if object does not exist."""
+ request = self.factory.get(self.get_url(1))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, cc_id=1)
+
+ def test_object_different_agreement_type(self):
+ """Raises Http404 if object has a different agreement type."""
+ instance = factories.MemberAgreementFactory.create()
+ request = self.factory.get(self.get_url(instance.signed_agreement.cc_id))
+ request.user = self.user
+ with self.assertRaises(Http404):
+ self.get_view()(request, cc_id=instance.signed_agreement.cc_id)
+
+ def test_has_form_in_context(self):
+ """Response includes a form."""
+ instance = factories.NonDataAffiliateAgreementFactory.create()
self.client.force_login(self.user)
- response = self.client.get(self.get_url())
- self.assertIn("table", response.context_data)
+ response = self.client.get(self.get_url(instance.signed_agreement.cc_id))
+ self.assertTrue("form" in response.context_data)
self.assertIsInstance(
- response.context_data["table"], tables.SignedAgreementTable
+ response.context_data["form"], forms.SignedAgreementStatusForm
)
- def test_workspace_table_none(self):
- """No rows are shown if there are no SignedAgreement objects."""
+ def test_can_modify_status(self):
+ """Can change the status."""
+ instance = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE
+ )
self.client.force_login(self.user)
- response = self.client.get(self.get_url())
- self.assertIn("table", response.context_data)
- self.assertEqual(len(response.context_data["table"].rows), 0)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ )
+ self.assertEqual(response.status_code, 302)
+ instance.refresh_from_db()
+ self.assertEqual(
+ instance.signed_agreement.status,
+ models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
- def test_workspace_table_three(self):
- """Two rows are shown if there are three SignedAgreement objects."""
- factories.MemberAgreementFactory.create()
- factories.DataAffiliateAgreementFactory.create()
- factories.NonDataAffiliateAgreementFactory.create()
+ def test_invalid_status(self):
+ """Can change the status."""
+ instance = factories.NonDataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE
+ )
self.client.force_login(self.user)
- response = self.client.get(self.get_url())
- self.assertIn("table", response.context_data)
- self.assertEqual(len(response.context_data["table"].rows), 3)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id), {"status": "foo"}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("form", response.context)
+ form = response.context_data["form"]
+ self.assertFalse(form.is_valid())
+ self.assertEqual(len(form.errors), 1)
+ self.assertIn("status", form.errors)
+ self.assertEqual(len(form.errors["status"]), 1)
+ self.assertIn("valid choice", form.errors["status"][0])
+ instance.refresh_from_db()
+ self.assertEqual(
+ instance.signed_agreement.status,
+ models.SignedAgreement.StatusChoices.ACTIVE,
+ )
+
+ def test_success_message(self):
+ """Response includes a success message if successful."""
+ instance = factories.NonDataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ follow=True,
+ )
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ views.SignedAgreementStatusUpdate.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."""
+ # This needs to use the client because the RequestFactory doesn't handle redirects.
+ instance = factories.NonDataAffiliateAgreementFactory.create()
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.get_url(instance.signed_agreement.cc_id),
+ {"status": models.SignedAgreement.StatusChoices.WITHDRAWN},
+ )
+ self.assertRedirects(response, instance.get_absolute_url())
class MemberAgreementCreateTest(AnVILAPIMockTestMixin, TestCase):
@@ -136,7 +1322,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:members:new", args=args)
+ return reverse("cdsa:signed_agreements:members:new", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -246,6 +1432,9 @@ def test_can_create_object(self):
self.assertEqual(
new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234"
)
+ self.assertEqual(
+ new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE
+ )
# Check the agreement type.
self.assertEqual(models.MemberAgreement.objects.count(), 1)
new_agreement_type = models.MemberAgreement.objects.latest("pk")
@@ -977,7 +2166,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:members:detail", args=args)
+ return reverse("cdsa:signed_agreements:members:detail", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -1044,6 +2233,67 @@ def test_response_includes_link_to_anvil_access_group(self):
response, self.obj.signed_agreement.anvil_access_group.get_absolute_url()
)
+ def test_response_show_deprecation_message_valid(self):
+ """response context does not show a deprecation warning when AgreementMajorVersion is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertFalse(response.context_data["show_deprecation_message"])
+ self.assertNotIn(b"Deprecated CDSA version", response.content)
+
+ def test_response_show_deprecation_message_not_valid(self):
+ """response context does show a deprecation warning when AgreementMajorVersion is not valid."""
+ self.obj.signed_agreement.version.major_version.is_valid = False
+ self.obj.signed_agreement.version.major_version.save()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertTrue(response.context_data["show_deprecation_message"])
+ self.assertIn(b"Deprecated CDSA version", response.content)
+
+ def test_change_status_button_user_has_edit_perm(self):
+ """Invalidate button appears when the user has edit permission and the instance is valid."""
+ user = User.objects.create_user(username="test_edit", password="test_edit")
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME
+ )
+ )
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+ self.client.force_login(user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_update_button", response.context_data)
+ self.assertTrue(response.context_data["show_update_button"])
+ self.assertContains(
+ response,
+ reverse(
+ "cdsa:signed_agreements:members:update",
+ args=[self.obj.signed_agreement.cc_id],
+ ),
+ )
+
+ def test_change_status_button_user_has_view_perm(self):
+ """Invalidate button does not appear when the user has view permission and the instance is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_update_button", response.context_data)
+ self.assertFalse(response.context_data["show_update_button"])
+ self.assertNotContains(
+ response,
+ reverse(
+ "cdsa:signed_agreements:members:update",
+ args=[self.obj.signed_agreement.cc_id],
+ ),
+ )
+
class MemberAgreementListTest(TestCase):
"""Tests for the MemberAgreementList view."""
@@ -1061,7 +2311,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:members:list", args=args)
+ return reverse("cdsa:signed_agreements:members:list", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -1139,7 +2389,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:data_affiliates:new", args=args)
+ return reverse("cdsa:signed_agreements:data_affiliates:new", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -1256,6 +2506,9 @@ def test_can_create_object(self):
self.assertEqual(
new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234"
)
+ self.assertEqual(
+ new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE
+ )
# Check the agreement type.
self.assertEqual(models.DataAffiliateAgreement.objects.count(), 1)
new_agreement_type = models.DataAffiliateAgreement.objects.latest("pk")
@@ -2125,7 +3378,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:data_affiliates:detail", args=args)
+ return reverse("cdsa:signed_agreements:data_affiliates:detail", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -2198,6 +3451,67 @@ def test_response_includes_link_to_anvil_upload_group(self):
response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
self.assertContains(response, self.obj.anvil_upload_group.get_absolute_url())
+ def test_response_show_deprecation_message_valid(self):
+ """response context does not show a deprecation warning when AgreementMajorVersion is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertFalse(response.context_data["show_deprecation_message"])
+ self.assertNotIn(b"Deprecated CDSA version", response.content)
+
+ def test_response_show_deprecation_message_not_valid(self):
+ """response context does show a deprecation warning when AgreementMajorVersion is not valid."""
+ self.obj.signed_agreement.version.major_version.is_valid = False
+ self.obj.signed_agreement.version.major_version.save()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertTrue(response.context_data["show_deprecation_message"])
+ self.assertIn(b"Deprecated CDSA version", response.content)
+
+ def test_change_status_button_user_has_edit_perm(self):
+ """Invalidate button appears when the user has edit permission and the instance is valid."""
+ user = User.objects.create_user(username="test_edit", password="test_edit")
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME
+ )
+ )
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+ self.client.force_login(user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_update_button", response.context_data)
+ self.assertTrue(response.context_data["show_update_button"])
+ self.assertContains(
+ response,
+ reverse(
+ "cdsa:signed_agreements:data_affiliates:update",
+ args=[self.obj.signed_agreement.cc_id],
+ ),
+ )
+
+ def test_change_status_button_user_has_view_perm(self):
+ """Invalidate button does not appear when the user has view permission and the instance is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_update_button", response.context_data)
+ self.assertFalse(response.context_data["show_update_button"])
+ self.assertNotContains(
+ response,
+ reverse(
+ "cdsa:signed_agreements:data_affiliates:update",
+ args=[self.obj.signed_agreement.cc_id],
+ ),
+ )
+
class DataAffiliateAgreementListTest(TestCase):
"""Tests for the DataAffiliateAgreement view."""
@@ -2215,7 +3529,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:data_affiliates:list", args=args)
+ return reverse("cdsa:signed_agreements:data_affiliates:list", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -2293,7 +3607,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:non_data_affiliates:new", args=args)
+ return reverse("cdsa:signed_agreements:non_data_affiliates:new", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -2403,6 +3717,9 @@ def test_can_create_object(self):
self.assertEqual(
new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234"
)
+ self.assertEqual(
+ new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE
+ )
# Check the agreement type.
self.assertEqual(models.NonDataAffiliateAgreement.objects.count(), 1)
new_agreement_type = models.NonDataAffiliateAgreement.objects.latest("pk")
@@ -3118,7 +4435,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:non_data_affiliates:detail", args=args)
+ return reverse("cdsa:signed_agreements:non_data_affiliates:detail", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -3179,6 +4496,67 @@ def test_response_includes_link_to_anvil_access_group(self):
response, self.obj.signed_agreement.anvil_access_group.get_absolute_url()
)
+ def test_response_show_deprecation_message_valid(self):
+ """response context does not show a deprecation warning when AgreementMajorVersion is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertFalse(response.context_data["show_deprecation_message"])
+ self.assertNotIn(b"Deprecated CDSA version", response.content)
+
+ def test_response_show_deprecation_message_is_not_valid(self):
+ """response context does show a deprecation warning when AgreementMajorVersion is not valid."""
+ self.obj.signed_agreement.version.major_version.is_valid = False
+ self.obj.signed_agreement.version.major_version.save()
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_deprecation_message", response.context_data)
+ self.assertTrue(response.context_data["show_deprecation_message"])
+ self.assertIn(b"Deprecated CDSA version", response.content)
+
+ def test_change_status_button_user_has_edit_perm(self):
+ """Invalidate button appears when the user has edit permission and the instance is valid."""
+ user = User.objects.create_user(username="test_edit", password="test_edit")
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME
+ )
+ )
+ user.user_permissions.add(
+ Permission.objects.get(
+ codename=AnVILProjectManagerAccess.EDIT_PERMISSION_CODENAME
+ )
+ )
+ self.client.force_login(user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_update_button", response.context_data)
+ self.assertTrue(response.context_data["show_update_button"])
+ self.assertContains(
+ response,
+ reverse(
+ "cdsa:signed_agreements:non_data_affiliates:update",
+ args=[self.obj.signed_agreement.cc_id],
+ ),
+ )
+
+ def test_change_status_button_user_has_view_perm(self):
+ """Invalidate button does not appear when the user has view permission and the instance is valid."""
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id))
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("show_update_button", response.context_data)
+ self.assertFalse(response.context_data["show_update_button"])
+ self.assertNotContains(
+ response,
+ reverse(
+ "cdsa:signed_agreements:non_data_affiliates:update",
+ args=[self.obj.signed_agreement.cc_id],
+ ),
+ )
+
class NonDataAffiliateAgreementListTest(TestCase):
"""Tests for the NonDataAffiliateAgreement view."""
@@ -3196,7 +4574,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
- return reverse("cdsa:agreements:non_data_affiliates:list", args=args)
+ return reverse("cdsa:signed_agreements:non_data_affiliates:list", args=args)
def get_view(self):
"""Return the view being tested."""
@@ -3338,6 +4716,23 @@ def test_table_three_rows(self):
self.assertIn("table", response.context_data)
self.assertEqual(len(response.context_data["table"].rows), 3)
+ def test_only_includes_active_agreements(self):
+ active_agreement = factories.MemberAgreementFactory.create()
+ lapsed_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.LAPSED
+ )
+ withdrawn_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ table = response.context_data["table"]
+ self.assertEqual(len(table.rows), 1)
+ self.assertIn(active_agreement.signed_agreement, table.data)
+ self.assertNotIn(lapsed_agreement.signed_agreement, table.data)
+ self.assertNotIn(withdrawn_agreement.signed_agreement, table.data)
+
class SignedAgreementAuditTest(TestCase):
"""Tests for the SignedAgreementAudit view."""
@@ -3358,7 +4753,7 @@ def setUp(self):
def get_url(self, *args):
"""Get the url for the view being tested."""
return reverse(
- "cdsa:audit:agreements",
+ "cdsa:audit:signed_agreements",
args=args,
)
@@ -3425,14 +4820,14 @@ def test_context_verified_table_access(self):
)
self.assertEqual(
table.rows[0].get_cell_value("note"),
- signed_agreement_audit.SignedAgreementAccessAudit.VALID_PRIMARY_CDSA,
+ signed_agreement_audit.SignedAgreementAccessAudit.ACTIVE_PRIMARY_AGREEMENT,
)
self.assertIsNone(table.rows[0].get_cell_value("action"))
def test_context_verified_table_no_access(self):
"""verified_table shows a record when audit has verified no access."""
member_agreement = factories.MemberAgreementFactory.create(
- signed_agreement__is_primary=False
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
)
# Check the table in the context.
self.client.force_login(self.user)
@@ -3450,7 +4845,7 @@ def test_context_verified_table_no_access(self):
)
self.assertEqual(
table.rows[0].get_cell_value("note"),
- signed_agreement_audit.SignedAgreementAccessAudit.NO_PRIMARY_CDSA,
+ signed_agreement_audit.SignedAgreementAccessAudit.INACTIVE_AGREEMENT,
)
self.assertIsNone(table.rows[0].get_cell_value("action"))
@@ -3473,7 +4868,7 @@ def test_context_needs_action_table_grant(self):
)
self.assertEqual(
table.rows[0].get_cell_value("note"),
- signed_agreement_audit.SignedAgreementAccessAudit.VALID_PRIMARY_CDSA,
+ signed_agreement_audit.SignedAgreementAccessAudit.ACTIVE_PRIMARY_AGREEMENT,
)
self.assertIsNotNone(table.rows[0].get_cell_value("action"))
@@ -3501,7 +4896,7 @@ def test_context_error_table_has_access(self):
)
self.assertEqual(
table.rows[0].get_cell_value("note"),
- signed_agreement_audit.SignedAgreementAccessAudit.NO_PRIMARY_CDSA,
+ signed_agreement_audit.SignedAgreementAccessAudit.NO_PRIMARY_AGREEMENT,
)
self.assertIsNotNone(table.rows[0].get_cell_value("action"))
@@ -3609,7 +5004,7 @@ def test_context_verified_table_access(self):
)
self.assertEqual(
table.rows[0].get_cell_value("note"),
- workspace_audit.WorkspaceAccessAudit.VALID_PRIMARY_CDSA,
+ workspace_audit.WorkspaceAccessAudit.ACTIVE_PRIMARY_AGREEMENT,
)
self.assertIsNone(table.rows[0].get_cell_value("action"))
@@ -3633,7 +5028,7 @@ def test_context_verified_table_no_access(self):
self.assertIsNone(table.rows[0].get_cell_value("data_affiliate_agreement"))
self.assertEqual(
table.rows[0].get_cell_value("note"),
- workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_CDSA,
+ workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_AGREEMENT,
)
self.assertIsNone(table.rows[0].get_cell_value("action"))
@@ -3662,7 +5057,7 @@ def test_context_needs_action_table_grant(self):
)
self.assertEqual(
table.rows[0].get_cell_value("note"),
- workspace_audit.WorkspaceAccessAudit.VALID_PRIMARY_CDSA,
+ workspace_audit.WorkspaceAccessAudit.ACTIVE_PRIMARY_AGREEMENT,
)
self.assertIsNotNone(table.rows[0].get_cell_value("action"))
@@ -3686,7 +5081,7 @@ def test_context_error_table_has_access(self):
self.assertIsNone(table.rows[0].get_cell_value("data_affiliate_agreement"))
self.assertEqual(
table.rows[0].get_cell_value("note"),
- workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_CDSA,
+ workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_AGREEMENT,
)
self.assertIsNotNone(table.rows[0].get_cell_value("action"))
@@ -3786,6 +5181,23 @@ def test_only_shows_primary_data_affiliate_records(self):
self.assertIn(primary_agreement, table.data)
self.assertNotIn(component_agreement, table.data)
+ def test_only_includes_active_agreements(self):
+ active_agreement = factories.DataAffiliateAgreementFactory.create()
+ lapsed_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.LAPSED
+ )
+ withdrawn_agreement = factories.DataAffiliateAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ table = response.context_data["table"]
+ self.assertEqual(len(table.rows), 1)
+ self.assertIn(active_agreement, table.data)
+ self.assertNotIn(lapsed_agreement, table.data)
+ self.assertNotIn(withdrawn_agreement, table.data)
+
class UserAccessRecordsList(TestCase):
"""Tests for the StudyRecordsList view."""
@@ -3945,6 +5357,32 @@ def test_does_not_show_other_group_members(self):
table = response.context_data["table"]
self.assertEqual(len(table.rows), 0)
+ def test_only_includes_active_agreements(self):
+ active_agreement = factories.MemberAgreementFactory.create()
+ active_member = GroupAccountMembershipFactory.create(
+ group=active_agreement.signed_agreement.anvil_access_group
+ )
+ lapsed_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.LAPSED
+ )
+ lapsed_member = GroupAccountMembershipFactory.create(
+ group=lapsed_agreement.signed_agreement.anvil_access_group
+ )
+ withdrawn_agreement = factories.MemberAgreementFactory.create(
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN
+ )
+ withdrawn_member = GroupAccountMembershipFactory.create(
+ group=withdrawn_agreement.signed_agreement.anvil_access_group
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ table = response.context_data["table"]
+ self.assertEqual(len(table.rows), 1)
+ self.assertIn(active_member, table.data)
+ self.assertNotIn(lapsed_member, table.data)
+ self.assertNotIn(withdrawn_member, table.data)
+
class CDSAWorkspaceRecordsList(TestCase):
"""Tests for the CDSAWorkspaceRecords view."""
@@ -3987,13 +5425,41 @@ def test_table_no_rows(self):
self.assertIn("table", response.context_data)
self.assertEqual(len(response.context_data["table"].rows), 0)
- def test_table_three_rows(self):
+ def test_table_two_rows(self):
"""Three rows are shown if there are three CDSAWorkspaces objects."""
- factories.CDSAWorkspaceFactory.create_batch(3)
+ active_workspace_1 = factories.CDSAWorkspaceFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=active_workspace_1.study)
+ active_workspace_2 = factories.CDSAWorkspaceFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=active_workspace_2.study)
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertIn("table", response.context_data)
- self.assertEqual(len(response.context_data["table"].rows), 3)
+ table = response.context_data["table"]
+ self.assertEqual(len(table.rows), 2)
+ self.assertIn(active_workspace_1, table.data)
+ self.assertIn(active_workspace_2, table.data)
+
+ def test_only_includes_workspaces_with_active_agreements(self):
+ active_workspace = factories.CDSAWorkspaceFactory.create()
+ factories.DataAffiliateAgreementFactory.create(study=active_workspace.study)
+ lapsed_workspace = factories.CDSAWorkspaceFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=lapsed_workspace.study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.LAPSED,
+ )
+ withdrawn_workspace = factories.CDSAWorkspaceFactory.create()
+ factories.DataAffiliateAgreementFactory.create(
+ study=withdrawn_workspace.study,
+ signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN,
+ )
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url())
+ self.assertIn("table", response.context_data)
+ table = response.context_data["table"]
+ self.assertEqual(len(table.rows), 1)
+ self.assertIn(active_workspace, table.data)
+ self.assertNotIn(lapsed_workspace, table.data)
+ self.assertNotIn(withdrawn_workspace, table.data)
class CDSAWorkspaceDetailTest(TestCase):
diff --git a/primed/cdsa/urls.py b/primed/cdsa/urls.py
index a96e7715..742fffd5 100644
--- a/primed/cdsa/urls.py
+++ b/primed/cdsa/urls.py
@@ -1,14 +1,45 @@
from django.urls import include, path
-from . import views
+from . import models, views
app_name = "cdsa"
+agreement_version_patterns = (
+ [
+ path(
+ "",
+ views.AgreementVersionList.as_view(),
+ name="list",
+ ),
+ path(
+ "v Are you sure you want to invalidate Agreement {{ object }}? This will change the status of all "Active" agreements associated with this version to "Lapsed". Invalidate Agreement {{ object }}
+
+
+
+{% endblock panel %}
+
+{% block after_panel %}
+
+Agreement versions
+
+
+
+
+
+ Signed agreements
+
+
+
+
+