diff --git a/django/thunderstore/repository/package_manifest.py b/django/thunderstore/repository/package_manifest.py index 0eeca6c94..70107c103 100644 --- a/django/thunderstore/repository/package_manifest.py +++ b/django/thunderstore/repository/package_manifest.py @@ -13,7 +13,11 @@ PackageVersionField, StrictCharField, ) -from thunderstore.repository.utils import does_contain_package, has_duplicate_packages +from thunderstore.repository.utils import ( + does_contain_package, + has_different_case, + has_duplicate_packages, +) class PackageInstallerSerializer(serializers.Serializer): @@ -96,6 +100,10 @@ def validate(self, data): ) if does_contain_package(result["dependencies"], reference): raise ValidationError("Package depending on itself is not allowed") + if has_different_case(reference.without_version): + raise ValidationError( + "Package name already exists with different capitalization" + ) return result def update(self, instance, validated_data): diff --git a/django/thunderstore/repository/package_reference.py b/django/thunderstore/repository/package_reference.py index 98e75c0b6..f6233a81f 100644 --- a/django/thunderstore/repository/package_reference.py +++ b/django/thunderstore/repository/package_reference.py @@ -257,3 +257,22 @@ def exists(self) -> bool: :rtype: bool """ return self.queryset.exists() + + @cached_property + def exists_in_any_case(self) -> bool: + """ + Check if a package with a name with different capitalization exists in the db + + :return: True if a different case package exists, False otherwise + :rtype: bool + """ + if self.version: + return Package.objects.filter( + package__owner__name=self.namespace, + package__name__iexact=self.name, + version_number=self.version_str, + ).exists() + else: + return Package.objects.filter( + owner__name=self.namespace, name__iexact=self.name + ).exists() diff --git a/django/thunderstore/repository/tests/test_package_manifest.py b/django/thunderstore/repository/tests/test_package_manifest.py index 45beb333f..9808a80de 100644 --- a/django/thunderstore/repository/tests/test_package_manifest.py +++ b/django/thunderstore/repository/tests/test_package_manifest.py @@ -55,6 +55,28 @@ def test_manifest_v1_serializer_version_already_exists( ) +@pytest.mark.django_db +def test_manifest_v1_serializer_package_already_exists_with_different_case( + user, manifest_v1_data, package +): + TeamMember.objects.create( + user=user, + team=package.owner, + role=TeamMemberRole.owner, + ) + manifest_v1_data["name"] = package.name.swapcase() + serializer = ManifestV1Serializer( + user=user, + team=package.owner, + data=manifest_v1_data, + ) + assert serializer.is_valid() is False + assert len(serializer.errors["non_field_errors"]) == 1 + assert "Package name already exists with different capitalization" in str( + serializer.errors["non_field_errors"][0] + ) + + @pytest.mark.django_db def test_manifest_v1_serializer_duplicate_dependency( user, manifest_v1_data, package_version, namespace diff --git a/django/thunderstore/repository/utils.py b/django/thunderstore/repository/utils.py index 0fb669594..b643c08a8 100644 --- a/django/thunderstore/repository/utils.py +++ b/django/thunderstore/repository/utils.py @@ -42,6 +42,14 @@ def has_duplicate_packages(packages: List["PackageReference"]) -> bool: return False +def has_different_case(reference: "PackageReference") -> bool: + if reference.exists: + return False + elif reference.exists_in_any_case: + return True + return False + + def unpack_serializer_errors(field, errors, error_dict=None): if error_dict is None: error_dict = {}