diff --git a/anvil_consortium_manager/tests/test_app/adapters.py b/anvil_consortium_manager/tests/test_app/adapters.py index 50ad0591..1b2e10db 100644 --- a/anvil_consortium_manager/tests/test_app/adapters.py +++ b/anvil_consortium_manager/tests/test_app/adapters.py @@ -1,5 +1,7 @@ from anvil_consortium_manager.adapters.account import BaseAccountAdapter from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter +from anvil_consortium_manager.forms import WorkspaceForm +from anvil_consortium_manager.tables import WorkspaceTable from . import filters, forms, models, tables @@ -39,3 +41,16 @@ def get_autocomplete_queryset(self, queryset, q): def get_autocomplete_label(self, account): return "TEST {}".format(account.email) + + +class TestForeignKeyWorkspaceAdapter(BaseWorkspaceAdapter): + """Adapter for TestForeignKeyWorkspace.""" + + name = "Test foreign key workspace" + type = "test_fk" + description = "Workspace type for testing" + list_table_class = WorkspaceTable + workspace_form_class = WorkspaceForm + workspace_data_model = models.TestForeignKeyWorkspaceData + workspace_data_form_class = forms.TestForeignKeyWorkspaceDataForm + workspace_detail_template_name = "workspace_detail.html" diff --git a/anvil_consortium_manager/tests/test_app/forms.py b/anvil_consortium_manager/tests/test_app/forms.py index 01451ee7..fefff797 100644 --- a/anvil_consortium_manager/tests/test_app/forms.py +++ b/anvil_consortium_manager/tests/test_app/forms.py @@ -28,3 +28,11 @@ def clean_name(self): if name and name == "test-fail": raise ValidationError("Workspace name cannot be 'test-fail'!") return name + + +class TestForeignKeyWorkspaceDataForm(forms.ModelForm): + """Form for a TestForeignKeyWorkspace object.""" + + class Meta: + model = models.TestForeignKeyWorkspaceData + fields = ("other_workspace", "workspace") diff --git a/anvil_consortium_manager/tests/test_app/migrations/0004_testforeignkeyworkspacedata_and_more.py b/anvil_consortium_manager/tests/test_app/migrations/0004_testforeignkeyworkspacedata_and_more.py new file mode 100644 index 00000000..2c5d8add --- /dev/null +++ b/anvil_consortium_manager/tests/test_app/migrations/0004_testforeignkeyworkspacedata_and_more.py @@ -0,0 +1,49 @@ +# Generated by Django 4.2.7 on 2023-11-07 20:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('anvil_consortium_manager', '0013_alter_anvilprojectmanageraccess_options'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('test_app', '0003_testworkspacedata_alter_study_name'), + ] + + operations = [ + migrations.CreateModel( + name='TestForeignKeyWorkspaceData', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('other_workspace', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='test_foreign_key_workspaces', to='anvil_consortium_manager.workspace')), + ('workspace', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='anvil_consortium_manager.workspace')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='HistoricalTestForeignKeyWorkspaceData', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('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)), + ('other_workspace', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.workspace')), + ('workspace', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.workspace')), + ], + options={ + 'verbose_name': 'historical test foreign key workspace data', + 'verbose_name_plural': 'historical test foreign key workspace datas', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/anvil_consortium_manager/tests/test_app/models.py b/anvil_consortium_manager/tests/test_app/models.py index 091c4014..40286c70 100644 --- a/anvil_consortium_manager/tests/test_app/models.py +++ b/anvil_consortium_manager/tests/test_app/models.py @@ -26,3 +26,11 @@ class ProtectedWorkspaceData(models.Model): """Model to test having a protected foreign key to DefaultWorkspaceData.""" workspace_data = models.ForeignKey(DefaultWorkspaceData, on_delete=models.PROTECT) + + +class TestForeignKeyWorkspaceData(BaseWorkspaceData): + """Custom model with a second fk to Workspace.""" + + other_workspace = models.ForeignKey( + Workspace, related_name="test_foreign_key_workspaces", on_delete=models.PROTECT + ) diff --git a/anvil_consortium_manager/tests/test_views.py b/anvil_consortium_manager/tests/test_views.py index 2a795f20..fc45d4be 100644 --- a/anvil_consortium_manager/tests/test_views.py +++ b/anvil_consortium_manager/tests/test_views.py @@ -29,7 +29,7 @@ from .test_app import forms as app_forms from .test_app import models as app_models from .test_app import tables as app_tables -from .test_app.adapters import TestWorkspaceAdapter +from .test_app.adapters import TestForeignKeyWorkspaceAdapter, TestWorkspaceAdapter from .test_app.factories import TestWorkspaceDataFactory from .test_app.filters import TestAccountListFilter from .utils import AnVILAPIMockTestMixin, TestCase # Redefined to work with Django < 4.2 and Django=4.2. @@ -1473,6 +1473,7 @@ def test_accessible_workspace_only_groups_for_this_account(self): def test_render_with_user_get_absolute_url(self): """HTML includes a link to the user profile when the linked user has a get_absolute_url method.""" + # Dynamically set the get_absolute_url method. This is hacky... def foo(self): return "test_profile_{}".format(self.username) @@ -4645,7 +4646,6 @@ def test_group_visualization(self): class ManagedGroupCreateTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 201 def setUp(self): @@ -5095,7 +5095,6 @@ def test_view_with_filter_returns_mutiple_objects(self): class ManagedGroupDeleteTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -6785,7 +6784,6 @@ def test_clone_links_with_two_registered_workspace_adapters(self): class WorkspaceCreateTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 201 def setUp(self): @@ -7644,6 +7642,64 @@ def test_adapter_does_not_create_object_if_workspace_form_invalid(self): self.assertEqual(models.Workspace.objects.count(), 0) self.assertEqual(len(responses.calls), 0) + def test_get_workspace_data_with_second_foreign_key_to_workspace(self): + # Overriding settings doesn't work, because appconfig.ready has already run and + # registered the default adapter. Instead, unregister the default and register the + # new adapter here. + workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter) + self.workspace_type = "test_fk" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertEqual(response.status_code, 200) + + def test_post_workspace_data_with_second_foreign_key_to_workspace(self): + """Posting valid data to the form creates a workspace data object when using a custom adapter.""" + # Overriding settings doesn't work, because appconfig.ready has already run and + # registered the default adapter. Instead, unregister the default and register the + # new adapter here. + workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter) + self.workspace_type = TestForeignKeyWorkspaceAdapter().get_type() + other_workspace = factories.WorkspaceFactory.create() + billing_project = factories.BillingProjectFactory.create(name="test-billing-project") + json_data = { + "namespace": "test-billing-project", + "name": "test-workspace", + "attributes": {}, + } + self.anvil_response_mock.add( + responses.POST, + self.api_url, + status=self.api_success_code, + match=[responses.matchers.json_params_matcher(json_data)], + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(self.workspace_type), + { + "billing_project": billing_project.pk, + "name": "test-workspace", + # Default workspace data for formset. + "workspacedata-TOTAL_FORMS": 1, + "workspacedata-INITIAL_FORMS": 0, + "workspacedata-MIN_NUM_FORMS": 1, + "workspacedata-MAX_NUM_FORMS": 1, + "workspacedata-0-other_workspace": other_workspace.pk, + }, + ) + self.assertEqual(response.status_code, 302) + # The workspace is created. + new_workspace = models.Workspace.objects.latest("pk") + # workspace_type is set properly. + self.assertEqual( + new_workspace.workspace_type, + TestForeignKeyWorkspaceAdapter().get_type(), + ) + # Workspace data is added. + self.assertEqual(app_models.TestForeignKeyWorkspaceData.objects.count(), 1) + new_workspace_data = app_models.TestForeignKeyWorkspaceData.objects.latest("pk") + self.assertEqual(new_workspace_data.workspace, new_workspace) + self.assertEqual(new_workspace_data.other_workspace, other_workspace) + class WorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): """Tests for the WorkspaceImport view.""" @@ -9003,6 +9059,87 @@ def test_api_error_acl_call(self): self.assertEqual(models.Workspace.objects.count(), 0) self.assertEqual(models.WorkspaceGroupSharing.objects.count(), 0) + def test_get_workspace_data_with_second_foreign_key_to_workspace(self): + # Overriding settings doesn't work, because appconfig.ready has already run and + # registered the default adapter. Instead, unregister the default and register the + # new adapter here. + workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter) + self.workspace_type = TestForeignKeyWorkspaceAdapter().get_type() + billing_project = factories.BillingProjectFactory.create(name="billing-project") + workspace_name = "workspace" + # Available workspaces API call. + self.anvil_response_mock.add( + responses.GET, + self.workspace_list_url, + match=[ + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) + ], + status=200, + json=[self.get_api_json_response(billing_project.name, workspace_name)], + ) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertEqual(response.status_code, 200) + + def test_post_workspace_data_with_second_foreign_key_to_workspace(self): + # Overriding settings doesn't work, because appconfig.ready has already run and + # registered the default adapter. Instead, unregister the default and register the + # new adapter here. + workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter) + self.workspace_type = TestForeignKeyWorkspaceAdapter().get_type() + billing_project = factories.BillingProjectFactory.create(name="billing-project") + workspace_name = "workspace" + other_workspace = factories.WorkspaceFactory.create() + # Available workspaces API call. + self.anvil_response_mock.add( + responses.GET, + self.workspace_list_url, + match=[ + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) + ], + status=200, + json=[self.get_api_json_response(billing_project.name, workspace_name)], + ) + # Response for ACL query. + self.anvil_response_mock.add( + responses.GET, + self.get_api_url_acl(billing_project.name, workspace_name), + status=200, # successful response code. + json=self.api_json_response_acl, + ) + url = self.get_api_url(billing_project.name, workspace_name) + self.anvil_response_mock.add( + responses.GET, + url, + status=self.api_success_code, + json=self.get_api_json_response(billing_project.name, workspace_name), + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(self.workspace_type), + { + "workspace": billing_project.name + "/" + workspace_name, + # Default workspace data for formset. + "workspacedata-TOTAL_FORMS": 1, + "workspacedata-INITIAL_FORMS": 0, + "workspacedata-MIN_NUM_FORMS": 1, + "workspacedata-MAX_NUM_FORMS": 1, + "workspacedata-0-other_workspace": other_workspace.pk, + }, + ) + self.assertEqual(response.status_code, 302) + # The workspace is created. + new_workspace = models.Workspace.objects.latest("pk") + self.assertEqual( + new_workspace.workspace_type, + TestForeignKeyWorkspaceAdapter().get_type(), + ) + # Workspace data is added. + self.assertEqual(app_models.TestForeignKeyWorkspaceData.objects.count(), 1) + new_workspace_data = app_models.TestForeignKeyWorkspaceData.objects.latest("pk") + self.assertEqual(new_workspace_data.workspace, new_workspace) + self.assertEqual(new_workspace_data.other_workspace, other_workspace) + class WorkspaceCloneTest(AnVILAPIMockTestMixin, TestCase): """Tests for the WorkspaceClone view.""" @@ -10681,7 +10818,6 @@ def test_view_with_filter_returns_mutiple_objects(self): class WorkspaceDeleteTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 202 def setUp(self): @@ -11864,7 +12000,6 @@ def test_detail_page_links(self): class GroupGroupMembershipCreateTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -12454,7 +12589,6 @@ def test_api_child_group_does_not_exist_parent_group_exists(self): class GroupGroupMembershipCreateByParentTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -12962,7 +13096,6 @@ def test_api_error_404(self): class GroupGroupMembershipCreateByChildTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -13431,7 +13564,6 @@ def test_api_error_404(self): class GroupGroupMembershipCreateByParentChildTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -14489,7 +14621,6 @@ def test_detail_page_links(self): class GroupAccountMembershipCreateTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -15051,7 +15182,6 @@ def test_api_user_does_not_exist_group_exists(self): class GroupAccountMembershipCreateByGroupTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -15552,7 +15682,6 @@ def test_cannot_add_inactive_account_to_group(self): class GroupAccountMembershipCreateByAccountTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -16037,7 +16166,6 @@ def test_cannot_add_inactive_account_to_group(self): class GroupAccountMembershipCreateByGroupAccountTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -16819,7 +16947,6 @@ def test_does_not_show_active_accounts(self): class GroupAccountMembershipDeleteTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 204 def setUp(self): @@ -17210,7 +17337,6 @@ def test_view_permission(self): class WorkspaceGroupSharingCreateTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 200 def setUp(self): @@ -17920,7 +18046,6 @@ def test_api_sharing_workspace_that_doesnt_exist_with_group_that_doesnt_exist( class WorkspaceGroupSharingCreateByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 200 def setUp(self): @@ -18654,7 +18779,6 @@ def test_api_sharing_workspace_that_doesnt_exist_with_group_that_doesnt_exist( class WorkspaceGroupSharingCreateByGroupTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 200 def setUp(self): @@ -19325,7 +19449,6 @@ def test_api_sharing_workspace_that_doesnt_exist_with_group_that_doesnt_exist( class WorkspaceGroupSharingCreateByWorkspaceGroupTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 200 def setUp(self): @@ -20651,7 +20774,6 @@ def test_view_with_two_objects(self): class WorkspaceGroupSharingDeleteTest(AnVILAPIMockTestMixin, TestCase): - api_success_code = 200 def setUp(self):