From ca95206779fbec6e6b6a454a8f648eba4ff6a8de Mon Sep 17 00:00:00 2001 From: David Davis <86290+daviddavis@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:15:25 +0000 Subject: [PATCH] Allow users to specify distribution and component when modifying content Allow repositories/deb/apt/{pulp_id}/modify/ requests to add/remove packages using optional distribution and component parameters. The task will create or remove matching release structure content while preserving package-only behavior when both parameters are omitted. fixes #1491 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGES/1491.feature | 4 + pulp_deb/app/serializers/__init__.py | 1 + .../app/serializers/repository_serializers.py | 31 ++ pulp_deb/app/tasks/signing.py | 119 +++++- pulp_deb/app/viewsets/repository.py | 47 ++- .../functional/api/test_repository_modify.py | 368 ++++++++++++++++++ .../functional/api/test_source_package.py | 47 +++ 7 files changed, 594 insertions(+), 23 deletions(-) create mode 100644 CHANGES/1491.feature create mode 100644 pulp_deb/tests/functional/api/test_repository_modify.py diff --git a/CHANGES/1491.feature b/CHANGES/1491.feature new file mode 100644 index 000000000..c43933ab8 --- /dev/null +++ b/CHANGES/1491.feature @@ -0,0 +1,4 @@ +Allow `repositories/deb/apt/{pulp_id}/modify/` requests to assign added packages using the optional +`distribution` and `component` parameters. Supplying either parameter creates the corresponding +release component, release architecture, and package-release-component content; omitting both +preserves the existing package-only behavior. \ No newline at end of file diff --git a/pulp_deb/app/serializers/__init__.py b/pulp_deb/app/serializers/__init__.py index de860cfe8..863ae8572 100644 --- a/pulp_deb/app/serializers/__init__.py +++ b/pulp_deb/app/serializers/__init__.py @@ -28,6 +28,7 @@ from .remote_serializers import AptRemoteSerializer from .repository_serializers import ( + AptRepositoryAddRemoveContentSerializer, AptRepositorySerializer, AptRepositorySyncURLSerializer, CopySerializer, diff --git a/pulp_deb/app/serializers/repository_serializers.py b/pulp_deb/app/serializers/repository_serializers.py index a6e4ef730..8c068bd60 100644 --- a/pulp_deb/app/serializers/repository_serializers.py +++ b/pulp_deb/app/serializers/repository_serializers.py @@ -10,22 +10,53 @@ from pulpcore.plugin.serializers import ( PgpKeyFingerprintField, RelatedField, + RepositoryAddRemoveContentSerializer, RepositorySerializer, RepositorySyncURLSerializer, ValidateFieldsMixin, ) from pulpcore.plugin.util import get_domain, get_url +from pulp_deb.app.constants import PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION from pulp_deb.app.models import ( AptPackageSigningService, AptReleaseSigningService, AptRepository, AptRepositoryReleasePackageSigningFingerprintOverride, AptRepositoryReleaseServiceOverride, + Release, ) from pulp_deb.app.schema import COPY_CONFIG_SCHEMA +class AptRepositoryAddRemoveContentSerializer(RepositoryAddRemoveContentSerializer): + distribution = serializers.CharField(help_text=_("Name of the distribution."), required=False) + component = serializers.CharField(help_text=_("Name of the component."), required=False) + + def validate(self, data): + data = super().validate(data) + if not (data.get("distribution") or data.get("component")): + return data + + # A request that only names a component is scoped to the default distribution. + distribution = data.get("distribution") or PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION + releases = Release.objects.filter(distribution=distribution) + repository = self.context.get("repository") + repository_version = data.get("base_version") or ( + repository.latest_version() if repository else None + ) + added = releases.filter(pk__in=data.get("add_content_units", [])).exists() + present = repository_version and releases.filter(pk__in=repository_version.content).exists() + if not (added or present): + raise DRFValidationError( + {"distribution": _("This distribution has no Release in the repository.")} + ) + return data + + class Meta(RepositoryAddRemoveContentSerializer.Meta): + fields = RepositoryAddRemoveContentSerializer.Meta.fields + ["distribution", "component"] + + class ServiceOverrideField(serializers.DictField): child = RelatedField( view_name="signing-services-detail", diff --git a/pulp_deb/app/tasks/signing.py b/pulp_deb/app/tasks/signing.py index 491b0ef2d..d2b6116b3 100644 --- a/pulp_deb/app/tasks/signing.py +++ b/pulp_deb/app/tasks/signing.py @@ -20,7 +20,19 @@ from pulpcore.plugin.tasking import add_and_remove, general_create from pulpcore.plugin.util import get_url -from pulp_deb.app.models import AptRepository, Package, PackageReleaseComponent +from pulp_deb.app.constants import ( + PACKAGE_UPLOAD_DEFAULT_COMPONENT, + PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION, +) +from pulp_deb.app.models import ( + AptRepository, + Package, + PackageReleaseComponent, + ReleaseArchitecture, + ReleaseComponent, + SourcePackage, + SourcePackageReleaseComponent, +) from pulp_deb.app.models.signing_service import ( AptPackageSigningService, DebPackageSigningResult, @@ -29,6 +41,100 @@ log = logging.getLogger(__name__) +def _prepare_package_removals(repo, remove_content_units, base_version_pk, distribution, component): + """Expand the removal list to include the release component relationships of each package. + + Removing a (source) package also requires removing its PackageReleaseComponent / + SourcePackageReleaseComponent links. When a distribution/component is given, the removal is + scoped to that component: a package is only removed from the repository if the scope held its + last relationship, so packages linked elsewhere or not linked at all are kept. + """ + # "*" removes all content, so there is nothing to resolve here. + if "*" in remove_content_units: + return + + repository_version = ( + repo.versions.get(pk=base_version_pk) if base_version_pk else repo.latest_version() + ) + # A distribution/component narrows the removal to a single release component. + scoped = distribution is not None or component is not None + if scoped: + distribution = distribution or PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION + component = component or PACKAGE_UPLOAD_DEFAULT_COMPONENT + + for model, relationship_model, relationship_field in ( + (Package, PackageReleaseComponent, "package"), + (SourcePackage, SourcePackageReleaseComponent, "source_package"), + ): + units = model.objects.filter(pk__in=remove_content_units) + relationships = relationship_model.objects.filter( + **{ + f"{relationship_field}__in": units, + "pk__in": repository_version.content, + } + ) + if scoped: + scoped_relationships = relationships.filter( + release_component__distribution=distribution, + release_component__component=component, + ) + # Relationships named in the request are removed alongside the scoped ones. + removed_relationship_ids = set( + relationship_model.objects.filter(pk__in=remove_content_units).values_list( + "pk", flat=True + ) + ) + removed_relationship_ids.update(scoped_relationships.values_list("pk", flat=True)) + still_linked = relationships.exclude(pk__in=removed_relationship_ids) + orphaned_unit_ids = { + str(pk) + for pk in scoped_relationships.values_list(f"{relationship_field}_id", flat=True) + } - {str(pk) for pk in still_linked.values_list(f"{relationship_field}_id", flat=True)} + kept_unit_ids = { + str(pk) for pk in units.values_list("pk", flat=True) + } - orphaned_unit_ids + remove_content_units[:] = [ + content_id for content_id in remove_content_units if content_id not in kept_unit_ids + ] + relationships = scoped_relationships + remove_content_units.extend(str(pk) for pk in relationships.values_list("pk", flat=True)) + + +def _prepare_package_additions(add_content_units, distribution, component): + """Expand the addition list with the metadata needed to publish the packages in a component. + + For each (source) package being added under a distribution/component, ensure the matching + ReleaseComponent, ReleaseArchitecture and *ReleaseComponent relationships exist and add them + to the content being added. + """ + packages = list(Package.objects.filter(pk__in=add_content_units)) + source_packages = list(SourcePackage.objects.filter(pk__in=add_content_units)) + if not (packages or source_packages) or (distribution is None and component is None): + return + + distribution = distribution or PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION + component = component or PACKAGE_UPLOAD_DEFAULT_COMPONENT + release_component, _ = ReleaseComponent.objects.get_or_create( + distribution=distribution, component=component + ) + add_content_units.append(str(release_component.pk)) + # Each binary package needs its architecture and a link to the release component. + for package in packages: + architecture, _ = ReleaseArchitecture.objects.get_or_create( + distribution=distribution, architecture=package.architecture + ) + package_component, _ = PackageReleaseComponent.objects.get_or_create( + release_component=release_component, package=package + ) + add_content_units.extend([str(architecture.pk), str(package_component.pk)]) + # Source packages only need a link to the release component. + for source_package in source_packages: + source_package_component, _ = SourcePackageReleaseComponent.objects.get_or_create( + release_component=release_component, source_package=source_package + ) + add_content_units.append(str(source_package_component.pk)) + + def _save_file(fileobj, final_package): with fileobj.file.open() as fd: final_package.write(fd.read()) @@ -205,10 +311,19 @@ def _sign_package(package, signing_service, signing_fingerprint, package_release def signed_add_and_remove( - repository_pk, add_content_units, remove_content_units, base_version_pk=None, overwrite=True + repository_pk, + add_content_units, + remove_content_units, + base_version_pk=None, + overwrite=True, + distribution=None, + component=None, ): repo = AptRepository.objects.get(pk=repository_pk) + _prepare_package_removals(repo, remove_content_units, base_version_pk, distribution, component) + _prepare_package_additions(add_content_units, distribution, component) + if repo.package_signing_service: log.info( f"Signing packages for repository {repo.name} with {repo.package_signing_service}." diff --git a/pulp_deb/app/viewsets/repository.py b/pulp_deb/app/viewsets/repository.py index e99716190..9cb94504e 100644 --- a/pulp_deb/app/viewsets/repository.py +++ b/pulp_deb/app/viewsets/repository.py @@ -5,16 +5,13 @@ from rest_framework import viewsets from rest_framework.serializers import ValidationError as DRFValidationError -from pulp_deb.app.models.content.content import Package -from pulp_deb.app.models.content.structure_content import PackageReleaseComponent from pulp_deb.app.serializers import AptRepositorySyncURLSerializer from pulp_deb.app.tasks import signed_add_and_remove -from pulpcore.plugin.util import extract_pk, get_url +from pulpcore.plugin.util import extract_pk from pulpcore.plugin.actions import ModifyRepositoryActionMixin from pulpcore.plugin.serializers import ( AsyncOperationResponseSerializer, - RepositoryAddRemoveContentSerializer, ) from pulpcore.plugin.models import ContentArtifact, RepositoryVersion from pulpcore.plugin.tasking import dispatch @@ -37,16 +34,12 @@ class AptModifyRepositoryActionMixin(ModifyRepositoryActionMixin): summary="Modify Repository Content", responses={202: AsyncOperationResponseSerializer}, ) - @action(detail=True, methods=["post"], serializer_class=RepositoryAddRemoveContentSerializer) + @action( + detail=True, + methods=["post"], + serializer_class=serializers.AptRepositoryAddRemoveContentSerializer, + ) def modify(self, request, pk, **kwargs): - remove_content_units = request.data.get("remove_content_units", []) - remove_package_hrefs = [href for href in remove_content_units if "/packages/" in href] - - if remove_package_hrefs: - prc_hrefs = self._get_matching_prc_hrefs(remove_package_hrefs) - remove_content_units.extend(prc_hrefs) - request.data["remove_content_units"] = remove_content_units - add_content_units = request.data.get("add_content_units", []) package_ids = [extract_pk(href) for href in add_content_units if "/packages/" in href] repository = self.get_object() @@ -59,14 +52,26 @@ def modify(self, request, pk, **kwargs): _("Cannot add on-demand content to repo with set package signing service.") ) - return super().modify(request, pk) - - def _get_matching_prc_hrefs(self, package_hrefs): - package_ids = [extract_pk(href) for href in package_hrefs] - matching_packages = Package.objects.filter(pulp_id__in=package_ids) - matching_prcs = PackageReleaseComponent.objects.filter(package__in=matching_packages) - prc_hrefs = [get_url(component) for component in matching_prcs] - return prc_hrefs + serializer = self.get_serializer( + data=request.data, + context={**self.get_serializer_context(), "repository": repository}, + ) + serializer.is_valid(raise_exception=True) + base_version = serializer.validated_data.get("base_version") + task = dispatch( + self.modify_task, + exclusive_resources=[repository], + kwargs={ + "repository_pk": pk, + "base_version_pk": base_version.pk if base_version else None, + "add_content_units": serializer.validated_data.get("add_content_units", []), + "remove_content_units": serializer.validated_data.get("remove_content_units", []), + "overwrite": serializer.validated_data.get("overwrite", True), + "distribution": serializer.validated_data.get("distribution"), + "component": serializer.validated_data.get("component"), + }, + ) + return OperationPostponedResponse(task, request) class AptRepositoryViewSet(AptModifyRepositoryActionMixin, RepositoryViewSet, RolesMixin): diff --git a/pulp_deb/tests/functional/api/test_repository_modify.py b/pulp_deb/tests/functional/api/test_repository_modify.py new file mode 100644 index 000000000..d2567c8cc --- /dev/null +++ b/pulp_deb/tests/functional/api/test_repository_modify.py @@ -0,0 +1,368 @@ +from uuid import uuid4 + +import pytest + +from pulpcore.client.pulp_deb.exceptions import ApiException +from pulpcore.tests.functional.utils import PulpTaskError + +from pulp_deb.app.constants import ( + PACKAGE_UPLOAD_DEFAULT_COMPONENT, + PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION, +) +from pulp_deb.tests.functional.constants import DEB_PACKAGE_RELPATH +from pulp_deb.tests.functional.utils import get_local_package_absolute_path + + +def _modify_with_package(repository, package, deb_modify_repository, **kwargs): + deb_modify_repository( + repository, + {"add_content_units": [package.pulp_href], **kwargs}, + ) + + +def test_modify_package_creates_structure( + apt_package_api, + apt_package_release_components_api, + apt_release_architecture_api, + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + component = str(uuid4()) + + with pytest.raises(ApiException, match="This distribution has no Release"): + _modify_with_package( + repository, + package, + deb_modify_repository, + distribution=distribution, + component=component, + ) + + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + _modify_with_package( + repository, + package, + deb_modify_repository, + distribution=distribution, + component=component, + ) + repository = deb_get_repository_by_href(repository.pulp_href) + + components = apt_release_component_api.list(repository_version=repository.latest_version_href) + architectures = apt_release_architecture_api.list( + repository_version=repository.latest_version_href + ) + package_components = apt_package_release_components_api.list( + repository_version=repository.latest_version_href + ) + packages = apt_package_api.list(repository_version=repository.latest_version_href) + assert [(item.distribution, item.component) for item in components.results] == [ + (distribution, component) + ] + assert [(item.distribution, item.architecture) for item in architectures.results] == [ + (distribution, package.architecture) + ] + assert package_components.results[0].package == package.pulp_href + assert package_components.results[0].release_component == components.results[0].pulp_href + assert [item.pulp_href for item in packages.results] == [package.pulp_href] + + +def test_modify_package_without_structure_fields_only_adds_package( + apt_package_release_components_api, + apt_release_architecture_api, + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_repository_factory, +): + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + + _modify_with_package(repository, package, deb_modify_repository) + repository = deb_get_repository_by_href(repository.pulp_href) + + filters = {"repository_version": repository.latest_version_href} + assert apt_release_component_api.list(**filters).count == 0 + assert apt_release_architecture_api.list(**filters).count == 0 + assert apt_package_release_components_api.list(**filters).count == 0 + + +def test_modify_packages_reuses_structure( + apt_package_release_components_api, + apt_release_architecture_api, + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + component = str(uuid4()) + + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + for _ in range(2): + _modify_with_package( + repository, + package, + deb_modify_repository, + distribution=distribution, + component=component, + ) + repository = deb_get_repository_by_href(repository.pulp_href) + + filters = {"repository_version": repository.latest_version_href} + assert apt_release_component_api.list(**filters).count == 1 + assert apt_release_architecture_api.list(**filters).count == 1 + assert apt_package_release_components_api.list(**filters).count == 1 + + +def test_remove_package_from_component( + apt_package_api, + apt_package_release_components_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + components = [str(uuid4()), str(uuid4())] + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + for component in components: + _modify_with_package( + repository, + package, + deb_modify_repository, + distribution=distribution, + component=component, + ) + + for expected_count, component in zip((1, 0), components): + deb_modify_repository( + repository, + { + "remove_content_units": [package.pulp_href], + "distribution": distribution, + "component": component, + }, + ) + repository = deb_get_repository_by_href(repository.pulp_href) + filters = {"repository_version": repository.latest_version_href} + assert apt_package_api.list(**filters).count == expected_count + assert apt_package_release_components_api.list(**filters).count == expected_count + + +def test_remove_all_content_units( + apt_package_api, + apt_package_release_components_api, + apt_release_architecture_api, + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + _modify_with_package( + repository, + package, + deb_modify_repository, + distribution=distribution, + component=str(uuid4()), + ) + + deb_modify_repository(repository, {"remove_content_units": ["*"]}) + repository = deb_get_repository_by_href(repository.pulp_href) + + filters = {"repository_version": repository.latest_version_href} + assert apt_package_api.list(**filters).count == 0 + assert apt_package_release_components_api.list(**filters).count == 0 + assert apt_release_component_api.list(**filters).count == 0 + assert apt_release_architecture_api.list(**filters).count == 0 + + +def test_remove_package_outside_of_component_is_kept( + apt_package_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + """A package without a relationship in the given component is not removed.""" + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + _modify_with_package(repository, package, deb_modify_repository) + + deb_modify_repository( + repository, + { + "remove_content_units": [package.pulp_href], + "distribution": distribution, + "component": str(uuid4()), + }, + ) + repository = deb_get_repository_by_href(repository.pulp_href) + + assert apt_package_api.list(repository_version=repository.latest_version_href).count == 1 + + +def test_modify_component_only_uses_default_distribution( + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + """A request without a distribution is scoped to (and validated against) the default one.""" + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + component = str(uuid4()) + + with pytest.raises(ApiException, match="This distribution has no Release"): + _modify_with_package(repository, package, deb_modify_repository, component=component) + + deb_release_factory( + codename=str(uuid4()), + suite=str(uuid4()), + distribution=PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION, + repository=repository.pulp_href, + ) + _modify_with_package(repository, package, deb_modify_repository, component=component) + repository = deb_get_repository_by_href(repository.pulp_href) + + components = apt_release_component_api.list(repository_version=repository.latest_version_href) + assert [(item.distribution, item.component) for item in components.results] == [ + (PACKAGE_UPLOAD_DEFAULT_DISTRIBUTION, component) + ] + + +def test_modify_distribution_only_uses_default_component( + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + + _modify_with_package(repository, package, deb_modify_repository, distribution=distribution) + repository = deb_get_repository_by_href(repository.pulp_href) + + components = apt_release_component_api.list(repository_version=repository.latest_version_href) + assert [(item.distribution, item.component) for item in components.results] == [ + (distribution, PACKAGE_UPLOAD_DEFAULT_COMPONENT) + ] + + +def test_modify_accepts_release_added_in_same_request( + apt_release_component_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_package_factory, + deb_release_factory, + deb_repository_factory, +): + """The Release backing the distribution may be added by the very same request.""" + repository = deb_repository_factory() + package = deb_package_factory(file=str(get_local_package_absolute_path(DEB_PACKAGE_RELPATH))) + distribution = str(uuid4()) + component = str(uuid4()) + release = deb_release_factory( + codename=distribution, suite=distribution, distribution=distribution + ) + + deb_modify_repository( + repository, + { + "add_content_units": [release.pulp_href, package.pulp_href], + "distribution": distribution, + "component": component, + }, + ) + repository = deb_get_repository_by_href(repository.pulp_href) + + components = apt_release_component_api.list(repository_version=repository.latest_version_href) + assert [(item.distribution, item.component) for item in components.results] == [ + (distribution, component) + ] + + +def test_modify_forwards_overwrite( + deb_modify_repository, + deb_release_factory, + deb_repository_factory, +): + """overwrite=False must reach the task and reject conflicting content.""" + repository = deb_repository_factory() + distribution = str(uuid4()) + deb_release_factory( + codename=str(uuid4()), + suite=str(uuid4()), + distribution=distribution, + repository=repository.pulp_href, + ) + conflicting_release = deb_release_factory( + codename=str(uuid4()), suite=str(uuid4()), distribution=distribution + ) + + with pytest.raises(PulpTaskError) as exception: + deb_modify_repository( + repository, + {"add_content_units": [conflicting_release.pulp_href], "overwrite": False}, + ) + assert "Content overwrite rejected" in exception.value.task.error["description"] diff --git a/pulp_deb/tests/functional/api/test_source_package.py b/pulp_deb/tests/functional/api/test_source_package.py index 7c68ca093..4663b7292 100644 --- a/pulp_deb/tests/functional/api/test_source_package.py +++ b/pulp_deb/tests/functional/api/test_source_package.py @@ -151,3 +151,50 @@ def test_upload_same_source_package( # Verify the package count is one package_list = apt_source_package_api.list(relative_path=SOURCE_PACKAGE_RELPATH) assert package_list.count == 1 + + +def test_modify_source_package_creates_structure( + artifact_factory, + apt_release_component_api, + apt_source_release_components_api, + deb_get_repository_by_href, + deb_modify_repository, + deb_release_factory, + deb_repository_factory, + deb_source_package_factory, +): + repository = deb_repository_factory() + distribution = str(uuid4()) + component = str(uuid4()) + artifact_factory(SOURCE_PACKAGE_SOURCE) + artifact = artifact_factory(SOURCE_PACKAGE_RELPATH) + source_package = deb_source_package_factory( + artifact=artifact.pulp_href, + relative_path=SOURCE_PACKAGE_RELPATH, + ) + deb_release_factory( + codename=distribution, + suite=distribution, + distribution=distribution, + repository=repository.pulp_href, + ) + + deb_modify_repository( + repository, + { + "add_content_units": [source_package.pulp_href], + "distribution": distribution, + "component": component, + }, + ) + repository = deb_get_repository_by_href(repository.pulp_href) + filters = {"repository_version": repository.latest_version_href} + release_component = apt_release_component_api.list(**filters).results[0] + source_component = apt_source_release_components_api.list(**filters).results[0] + + assert (release_component.distribution, release_component.component) == ( + distribution, + component, + ) + assert source_component.source_package == source_package.pulp_href + assert source_component.release_component == release_component.pulp_href