Skip to content

Commit

Permalink
fix: ensure project names are unique (#5039)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewelwell authored Jan 27, 2025
1 parent ea5cbee commit aa665c0
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 2 deletions.
9 changes: 9 additions & 0 deletions api/projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ class ProjectCreateSerializer(ReadOnlyIfNotValidPlanMixin, ProjectListSerializer
invalid_plans_regex = r"^(free|startup.*|scale-up.*)$"
field_names = ("stale_flags_limit_days", "enable_realtime_updates")

class Meta(ProjectListSerializer.Meta):
validators = [
serializers.UniqueTogetherValidator(
queryset=ProjectListSerializer.Meta.model.objects.all(),
fields=("name", "organisation"),
message="A project with this name already exists.",
)
]

def get_subscription(self) -> typing.Optional[Subscription]:
view = self.context["view"]

Expand Down
2 changes: 1 addition & 1 deletion api/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def dynamo_enabled_project(
):
settings.EDGE_ENABLED = True
project_data = {
"name": "Test Project",
"name": "Dynamo Enabled Project",
"organisation": organisation,
}
url = reverse("api-v1:projects:project-list")
Expand Down
50 changes: 49 additions & 1 deletion api/tests/unit/projects/test_unit_projects_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from environments.dynamodb.types import ProjectIdentityMigrationStatus
from environments.identities.models import Identity
from features.models import Feature, FeatureSegment
from organisations.models import Organisation, Subscription
from organisations.models import Organisation, OrganisationRole, Subscription
from organisations.permissions.models import (
OrganisationPermissionModel,
UserOrganisationPermission,
Expand Down Expand Up @@ -875,3 +875,51 @@ def test_delete_project_delete_handles_cascade_delete(
mocked_handle_cascade_delete.delay.assert_called_once_with(
kwargs={"project_id": project.id}
)


def test_cannot_create_duplicate_project_name(
admin_client: APIClient,
project: Project,
) -> None:
# Given
data = {
"name": project.name,
"organisation": project.organisation_id,
}
url = reverse("api-v1:projects:project-list")

# When
response = admin_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json() == {
"non_field_errors": ["A project with this name already exists."]
}


def test_can_create_project_with_duplicate_name_in_another_organisation(
admin_user: FFAdminUser,
admin_client: APIClient,
project: Project,
organisation_two: Organisation,
) -> None:
# Given
assert project.organisation != organisation_two
admin_user.add_organisation(organisation_two, OrganisationRole.ADMIN)

data = {
"name": project.name,
"organisation": organisation_two.id,
}
url = reverse("api-v1:projects:project-list")

# When
response = admin_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_201_CREATED

0 comments on commit aa665c0

Please sign in to comment.