diff --git a/.github/workflows/scripts/install.sh b/.github/workflows/scripts/install.sh index 58a2c90c..5f6da299 100755 --- a/.github/workflows/scripts/install.sh +++ b/.github/workflows/scripts/install.sh @@ -88,7 +88,7 @@ if [ "$TEST" = "s3" ]; then sed -i -e '$a s3_test: true\ minio_access_key: "'$MINIO_ACCESS_KEY'"\ minio_secret_key: "'$MINIO_SECRET_KEY'"\ -pulp_scenario_settings: {"allowed_content_checksums": ["md5", "sha224", "sha256", "sha384", "sha512"]}\ +pulp_scenario_settings: {"allowed_content_checksums": ["md5", "sha224", "sha256", "sha384", "sha512"], "domain_enabled": true}\ pulp_scenario_env: {}\ ' vars/main.yaml export PULP_API_ROOT="/rerouted/djnd/" diff --git a/CHANGES/154.feature b/CHANGES/154.feature new file mode 100644 index 00000000..384fe296 --- /dev/null +++ b/CHANGES/154.feature @@ -0,0 +1 @@ +Added support for RBAC and domains. diff --git a/pulp_gem/app/__init__.py b/pulp_gem/app/__init__.py index 40cff2c9..b59e915b 100644 --- a/pulp_gem/app/__init__.py +++ b/pulp_gem/app/__init__.py @@ -8,3 +8,4 @@ class PulpGemPluginAppConfig(PulpPluginAppConfig): label = "gem" version = "0.4.0.dev" python_package_name = "pulp-gem" + domain_compatible = True diff --git a/pulp_gem/app/migrations/0012_alter_gemdistribution_options_and_more.py b/pulp_gem/app/migrations/0012_alter_gemdistribution_options_and_more.py new file mode 100644 index 00000000..b8213b5d --- /dev/null +++ b/pulp_gem/app/migrations/0012_alter_gemdistribution_options_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 4.2.4 on 2023-10-04 10:25 + +from django.db import migrations, models +import django.db.models.deletion +import pulpcore.app.util + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0110_apiappstatus"), + ("gem", "0011_alter_gemcontent_platform"), + ] + + operations = [ + migrations.AlterModelOptions( + name="gemdistribution", + options={ + "default_related_name": "%(app_label)s_%(model_name)s", + "permissions": [ + ("manage_roles_gemdistribution", "Can manage roles on gem distributions") + ], + }, + ), + migrations.AlterModelOptions( + name="gempublication", + options={ + "default_related_name": "%(app_label)s_%(model_name)s", + "permissions": [ + ("manage_roles_gempublication", "Can manage roles on gem publications") + ], + }, + ), + migrations.AlterModelOptions( + name="gemremote", + options={ + "default_related_name": "%(app_label)s_%(model_name)s", + "permissions": [("manage_roles_gemremote", "Can manage roles on gem remotes")], + }, + ), + migrations.AlterModelOptions( + name="gemrepository", + options={ + "default_related_name": "%(app_label)s_%(model_name)s", + "permissions": [ + ("sync_gemrepository", "Can start a sync task"), + ("modify_gemrepository", "Can modify content of the repository"), + ("manage_roles_gemrepository", "Can manage roles on gem repositories"), + ("repair_gemrepository", "Can repair repository versions"), + ], + }, + ), + migrations.AlterUniqueTogether( + name="gemcontent", + unique_together=set(), + ), + migrations.AddField( + model_name="gemcontent", + name="_pulp_domain", + field=models.ForeignKey( + default=pulpcore.app.util.get_domain_pk, + on_delete=django.db.models.deletion.PROTECT, + to="core.domain", + ), + ), + migrations.AlterUniqueTogether( + name="gemcontent", + unique_together={("_pulp_domain", "checksum")}, + ), + ] diff --git a/pulp_gem/app/models.py b/pulp_gem/app/models.py index 60c11748..7adf29c5 100644 --- a/pulp_gem/app/models.py +++ b/pulp_gem/app/models.py @@ -6,12 +6,14 @@ from django.db import models from pulpcore.plugin.models import ( + AutoAddObjPermsMixin, Content, Publication, Distribution, Remote, Repository, ) +from pulpcore.plugin.util import get_domain_pk from pulp_gem.specs import analyse_gem @@ -30,6 +32,7 @@ class GemContent(Content): TYPE = "gem" repo_key_fields = ("name", "version", "platform") + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) name = models.TextField(blank=False, null=False) version = models.TextField(blank=False, null=False) platform = models.TextField(blank=False, null=False) @@ -81,21 +84,28 @@ def __str__(self): class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("checksum",) + unique_together = ( + "_pulp_domain", + "checksum", + ) -class GemDistribution(Distribution): +class GemDistribution(Distribution, AutoAddObjPermsMixin): """ A Distribution for GemContent. """ TYPE = "gem" + SERVE_FROM_PUBLICATION = True class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("manage_roles_gemdistribution", "Can manage roles on gem distributions"), + ] -class GemPublication(Publication): +class GemPublication(Publication, AutoAddObjPermsMixin): """ A Publication for GemContent. """ @@ -104,9 +114,12 @@ class GemPublication(Publication): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("manage_roles_gempublication", "Can manage roles on gem publications"), + ] -class GemRemote(Remote): +class GemRemote(Remote, AutoAddObjPermsMixin): """ A Remote for GemContent. """ @@ -133,9 +146,12 @@ def get_remote_artifact_content_type(self, relative_path=None): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("manage_roles_gemremote", "Can manage roles on gem remotes"), + ] -class GemRepository(Repository): +class GemRepository(Repository, AutoAddObjPermsMixin): """ A Repository for GemContent. """ @@ -146,3 +162,9 @@ class GemRepository(Repository): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("sync_gemrepository", "Can start a sync task"), + ("modify_gemrepository", "Can modify content of the repository"), + ("manage_roles_gemrepository", "Can manage roles on gem repositories"), + ("repair_gemrepository", "Can repair repository versions"), + ] diff --git a/pulp_gem/app/serializers.py b/pulp_gem/app/serializers.py index 380e410e..75076261 100644 --- a/pulp_gem/app/serializers.py +++ b/pulp_gem/app/serializers.py @@ -23,6 +23,7 @@ RepositorySerializer, SingleContentArtifactField, ) +from pulpcore.plugin.util import get_domain_pk from pulp_gem.app.models import ( GemContent, @@ -126,7 +127,9 @@ def deferred_validate(self, data): return data def retrieve(self, validated_data): - return GemContent.objects.filter(checksum=validated_data["checksum"]).first() + return GemContent.objects.filter( + _pulp_domain=get_domain_pk(), checksum=validated_data["checksum"] + ).first() class Meta: fields = MultipleArtifactContentSerializer.Meta.fields + ( diff --git a/pulp_gem/app/viewsets.py b/pulp_gem/app/viewsets.py index 555aded8..798fe5a9 100644 --- a/pulp_gem/app/viewsets.py +++ b/pulp_gem/app/viewsets.py @@ -11,6 +11,7 @@ RemoteViewSet, RepositoryViewSet, RepositoryVersionViewSet, + RolesMixin, ) from pulpcore.plugin.serializers import ( AsyncOperationResponseSerializer, @@ -55,8 +56,28 @@ class GemContentViewSet(SingleArtifactContentUploadViewSet): serializer_class = GemContentSerializer filterset_class = GemContentFilter + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_required_repo_perms_on_upload:gem.modify_gemrepository", + "has_upload_param_model_or_domain_or_obj_perms:core.change_upload", + ], + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + -class GemRemoteViewSet(RemoteViewSet): +class GemRemoteViewSet(RemoteViewSet, RolesMixin): """ A ViewSet for GemRemote. """ @@ -64,9 +85,71 @@ class GemRemoteViewSet(RemoteViewSet): endpoint_name = "gem" queryset = GemRemote.objects.all() serializer_class = GemRemoteSerializer + queryset_filtering_required_permission = "gem.view_gemremote" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_domain_perms:gem.add_gemremote", + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_domain_or_obj_perms:gem.view_gemremote", + }, + { + "action": ["update", "partial_update", "set_label", "unset_label"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.change_gemremote", + ], + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.delete_gemremote", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_domain_or_obj_perms:gem.manage_roles_gemremote"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "gem.gemremote_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "gem.gemremote_creator": ["gem.add_gemremote"], + "gem.gemremote_owner": [ + "gem.view_gemremote", + "gem.change_gemremote", + "gem.delete_gemremote", + "gem.manage_roles_gemremote", + ], + "gem.gemremote_viewer": ["gem.view_gemremote"], + } -class GemPublicationViewSet(PublicationViewSet): +class GemPublicationViewSet(PublicationViewSet, RolesMixin): """ A ViewSet for GemPublication. """ @@ -74,9 +157,64 @@ class GemPublicationViewSet(PublicationViewSet): endpoint_name = "gem" queryset = GemPublication.objects.exclude(complete=False) serializer_class = GemPublicationSerializer + queryset_filtering_required_permission = "gem.view_gempublication" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_perms:gem.add_gempublication", + "has_repo_or_repo_ver_param_model_or_domain_or_obj_perms:" + "gem.view_gemrepository", + ], + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_domain_or_obj_perms:gem.view_gempublication", + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.delete_gempublication", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_domain_or_obj_perms:gem.manage_roles_gempublication"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "gem.gempublication_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "gem.gempublication_creator": ["gem.add_gempublication"], + "gem.gempublication_owner": [ + "gem.view_gempublication", + "gem.delete_gempublication", + "gem.manage_roles_gempublication", + ], + "gem.gempublication_viewer": ["gem.view_gempublication"], + } - # This decorator is necessary since a publish operation is asyncrounous and returns - # the id and href of the publish task. @extend_schema( description="Trigger an asynchronous task to publish gem content", responses={202: AsyncOperationResponseSerializer}, @@ -100,7 +238,7 @@ def create(self, request): return OperationPostponedResponse(result, request) -class GemRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin): +class GemRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin, RolesMixin): """ A ViewSet for GemRepository. """ @@ -108,6 +246,92 @@ class GemRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin): endpoint_name = "gem" queryset = GemRepository.objects.all() serializer_class = GemRepositorySerializer + queryset_filtering_required_permission = "gem.view_gemrepository" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_perms:gem.add_gemrepository", + "has_remote_param_model_or_domain_or_obj_perms:gem.view_gemremote", + ], + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_domain_or_obj_perms:gem.view_gemrepository", + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.delete_gemrepository", + ], + }, + { + "action": ["update", "partial_update", "set_label", "unset_label"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.change_gemrepository", + "has_remote_param_model_or_domain_or_obj_perms:gem.view_gemremote", + ], + }, + { + "action": ["sync"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.sync_gemrepository", + "has_remote_param_model_or_domain_or_obj_perms:gem.view_gemremote", + ], + }, + { + "action": ["modify"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.modify_gemrepository", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_domain_or_obj_perms:gem.manage_roles_gemrepository"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "gem.gemrepository_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "gem.gemrepository_creator": ["gem.add_gemrepository"], + "gem.gemrepository_owner": [ + "gem.view_gemrepository", + "gem.change_gemrepository", + "gem.delete_gemrepository", + "gem.modify_gemrepository", + "gem.sync_gemrepository", + "gem.manage_roles_gemrepository", + "gem.repair_gemrepository", + ], + "gem.gemrepository_viewer": ["gem.view_gemrepository"], + } @extend_schema( description="Trigger an asynchronous task to sync gem content.", @@ -148,8 +372,35 @@ class GemRepositoryVersionViewSet(RepositoryVersionViewSet): parent_viewset = GemRepositoryViewSet + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_repository_model_or_domain_or_obj_perms:gem.view_gemrepository", + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_repository_model_or_domain_or_obj_perms:gem.delete_gemrepository", + ], + }, + { + "action": ["repair"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_repository_model_or_domain_or_obj_perms:gem.repair_gemrepository", + ], + }, + ], + } + -class GemDistributionViewSet(DistributionViewSet): +class GemDistributionViewSet(DistributionViewSet, RolesMixin): """ ViewSet for GemDistributions. """ @@ -157,3 +408,73 @@ class GemDistributionViewSet(DistributionViewSet): endpoint_name = "gem" queryset = GemDistribution.objects.all() serializer_class = GemDistributionSerializer + queryset_filtering_required_permission = "gem.view_gemdistribution" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_perms:gem.add_gemdistribution", + "has_repo_or_repo_ver_param_model_or_domain_or_obj_perms:" + "gem.view_gemrepository", + "has_publication_param_model_or_domain_or_obj_perms:gem.view_gempublication", + ], + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_domain_or_obj_perms:gem.view_gemdistribution", + }, + { + "action": ["update", "partial_update", "set_label", "unset_label"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.change_gemdistribution", + "has_repo_or_repo_ver_param_model_or_domain_or_obj_perms:" + "gem.view_gemrepository", + "has_publication_param_model_or_domain_or_obj_perms:gem.view_gempublication", + ], + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_domain_or_obj_perms:gem.delete_gemdistribution", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_domain_or_obj_perms:gem.manage_roles_gemdistribution"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "gem.gemdistribution_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "gem.gemdistribution_creator": ["gem.add_gemdistribution"], + "gem.gemdistribution_owner": [ + "gem.view_gemdistribution", + "gem.change_gemdistribution", + "gem.delete_gemdistribution", + "gem.manage_roles_gemdistribution", + ], + "gem.gemdistribution_viewer": ["gem.view_gemdistribution"], + } diff --git a/pulp_gem/tests/functional/api/test_domain.py b/pulp_gem/tests/functional/api/test_domain.py new file mode 100644 index 00000000..cc5b633b --- /dev/null +++ b/pulp_gem/tests/functional/api/test_domain.py @@ -0,0 +1,15 @@ +import pytest +from django.conf import settings + +if not settings.DOMAIN_ENABLED: + pytest.skip("Domains not enabled.", allow_module_level=True) + + +@pytest.mark.parallel +def test_domains(domain_factory, gem_repository_factory, gem_repository_api_client): + domain = domain_factory() + domain_name = domain.name + repo = gem_repository_factory(pulp_domain=domain_name) + assert domain_name in repo.pulp_href + result = gem_repository_api_client.list(pulp_domain=domain_name) + assert result.count == 1 diff --git a/pulp_gem/tests/functional/conftest.py b/pulp_gem/tests/functional/conftest.py index 7bfa0d2f..136df796 100644 --- a/pulp_gem/tests/functional/conftest.py +++ b/pulp_gem/tests/functional/conftest.py @@ -1,17 +1,11 @@ -import pytest import uuid -from pulpcore.client.pulp_gem import ( - ApiClient, - ContentGemApi, - DistributionsGemApi, - PublicationsGemApi, - RepositoriesGemApi, - RepositoriesGemVersionsApi, - RemotesGemApi, -) - +import pytest from pulp_gem.tests.functional.constants import GEM_FIXTURE_URL +from pulpcore.client.pulp_gem import (ApiClient, ContentGemApi, + DistributionsGemApi, PublicationsGemApi, + RemotesGemApi, RepositoriesGemApi, + RepositoriesGemVersionsApi) # Api Bindings fixtures @@ -62,9 +56,10 @@ def gem_repository_factory(gem_repository_api_client, gen_object_with_cleanup): """A factory to generate a Gem Repository with auto-deletion after the test run.""" def _gem_repository_factory(**kwargs): + pulp_domain = kwargs.pop("pulp_domain", None) data = {"name": str(uuid.uuid4())} data.update(kwargs) - return gen_object_with_cleanup(gem_repository_api_client, data) + return gen_object_with_cleanup(gem_repository_api_client, data, pulp_domain=pulp_domain) return _gem_repository_factory @@ -74,9 +69,10 @@ def gem_distribution_factory(gem_distribution_api_client, gen_object_with_cleanu """A factory to generate a Gem Distribution with auto-deletion after the test run.""" def _gem_distribution_factory(**body): + pulp_domain = kwargs.pop("pulp_domain", None) data = {"base_path": str(uuid.uuid4()), "name": str(uuid.uuid4())} data.update(body) - return gen_object_with_cleanup(gem_distribution_api_client, data) + return gen_object_with_cleanup(gem_distribution_api_client, data, pulp_domain=pulp_domain) return _gem_distribution_factory @@ -86,9 +82,10 @@ def gem_publication_factory(gem_publication_api_client, gen_object_with_cleanup) """A factory to generate a Gem Publication with auto-deletion after the test run.""" def _gem_publication_factory(**kwargs): + pulp_domain = kwargs.pop("pulp_domain", None) # XOR check on repository and repository_version assert bool("repository" in kwargs) ^ bool("repository_version" in kwargs) - return gen_object_with_cleanup(gem_publication_api_client, kwargs) + return gen_object_with_cleanup(gem_publication_api_client, kwargs, pulp_domain=pulp_domain) return _gem_publication_factory @@ -98,9 +95,10 @@ def gem_remote_factory(gem_remote_api_client, gen_object_with_cleanup): """A factory to generate a Gem Remote with auto-deletion after the test run.""" def _gem_remote_factory(*, url=GEM_FIXTURE_URL, policy="immediate", **kwargs): + pulp_domain = kwargs.pop("pulp_domain", None) data = {"url": url, "policy": policy, "name": str(uuid.uuid4())} data.update(kwargs) - return gen_object_with_cleanup(gem_remote_api_client, data) + return gen_object_with_cleanup(gem_remote_api_client, data, pulp_domain=pulp_domain) return _gem_remote_factory diff --git a/pulp_gem/tests/unit/test_models.py b/pulp_gem/tests/unit/test_models.py deleted file mode 100644 index 16e9754d..00000000 --- a/pulp_gem/tests/unit/test_models.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.test import TestCase - - -class TestNothing(TestCase): - """Test Nothing (placeholder).""" - - def test_nothing_at_all(self): - """Test that the tests are running and that's it.""" - self.assertTrue(True) diff --git a/pulp_gem/tests/unit/test_serializers.py b/pulp_gem/tests/unit/test_serializers.py index 0be5b0cc..39c3798d 100644 --- a/pulp_gem/tests/unit/test_serializers.py +++ b/pulp_gem/tests/unit/test_serializers.py @@ -36,6 +36,10 @@ def setUp(self): file=SimpleUploadedFile("test_filename_b", b"test content_b"), **_checksums("b"), ) + if settings.DOMAIN_ENABLED: + self.V3_API_ROOT = settings.API_ROOT + "default/api/v3/" + else: + self.V3_API_ROOT = settings.V3_API_ROOT @patch("pulp_gem.app.serializers._artifact_from_data") @patch("pulp_gem.app.serializers.analyse_gem") @@ -44,24 +48,14 @@ def test_valid_data(self, ANALYZE_GEM, _ARTIFACT_FROM_DATA): # Preparation ANALYZE_GEM.return_value = ({"name": "testname", "version": "1.2.3-test"}, "---\n...") _ARTIFACT_FROM_DATA.return_value = self.artifact2 - data = {"artifact": "{}artifacts/{}/".format(settings.V3_API_ROOT, self.artifact.pk)} + data = {"artifact": "{}artifacts/{}/".format(self.V3_API_ROOT, self.artifact.pk)} serializer = GemContentSerializer(data=data) - assert serializer.is_valid() + serializer.is_valid(raise_exception=True) # Verification ANALYZE_GEM.called_once_with(self.artifact) _ARTIFACT_FROM_DATA.called_once_with("---\n...") - @patch("pulp_gem.app.serializers._artifact_from_data") - @patch("pulp_gem.app.serializers.analyse_gem") - def test_duplicate_data(self, ANALYZE_GEM, _ARTIFACT_FROM_DATA): - """Test that the GemContentSerializer does accept duplicate data.""" - # Preparation - ANALYZE_GEM.return_value = ({"name": "testname", "version": "1.2.3-test"}, "---\n...") - _ARTIFACT_FROM_DATA.return_value = self.artifact2 - data = {"artifact": "{}artifacts/{}/".format(settings.V3_API_ROOT, self.artifact.pk)} - serializer = GemContentSerializer(data=data) - assert serializer.is_valid() + # Test that the GemContentSerializer does accept duplicate data. serializer.save() - # Test serializer = GemContentSerializer(data=data) - assert serializer.is_valid() + serializer.is_valid(raise_exception=True) diff --git a/template_config.yml b/template_config.yml index 27392408..daa9ab78 100644 --- a/template_config.yml +++ b/template_config.yml @@ -64,6 +64,7 @@ pulp_settings_s3: - sha256 - sha384 - sha512 + domain_enabled: true pulpprojectdotorg_key_id: null pydocstyle: true pypi_username: None