Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
130 lines
4.9 KiB
Python
130 lines
4.9 KiB
Python
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# See the LICENSE file for details.
|
|
|
|
import pytest
|
|
from uuid import uuid4
|
|
|
|
from rest_framework import serializers
|
|
|
|
from plane.api.serializers import WorkspaceLiteSerializer
|
|
from plane.app.serializers import WorkSpaceSerializer
|
|
from plane.license.api.serializers import WorkspaceSerializer as InstanceWorkspaceSerializer
|
|
from plane.db.models import Workspace, User
|
|
|
|
# Names with no letter or digit — must be rejected (issue #9255)
|
|
SYMBOL_ONLY_NAMES = ["-_________-", "---", "___", "- - -", " ", " -_ "]
|
|
|
|
# Names containing at least one letter or digit — must be accepted, including
|
|
# international scripts, so the rule does not regress non-Latin workspace names
|
|
VALID_NAMES = [
|
|
"Acme Corp",
|
|
"Acme_Corp-123",
|
|
"123",
|
|
"a",
|
|
"R&D",
|
|
"José",
|
|
"Müller GmbH",
|
|
"日本語",
|
|
"株式会社",
|
|
"محمد",
|
|
]
|
|
|
|
# Names embedding a URL — must be rejected on both create paths
|
|
URL_NAMES = ["https://evil.com", "www.example.com", "example.com"]
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestWorkspaceLiteSerializer:
|
|
"""Test the WorkspaceLiteSerializer"""
|
|
|
|
def test_workspace_lite_serializer_fields(self, db):
|
|
"""Test that the serializer includes the correct fields"""
|
|
# Create a user to be the owner
|
|
owner = User.objects.create(email="test@example.com", first_name="Test", last_name="User")
|
|
|
|
# Create a workspace with explicit ID to test serialization
|
|
workspace_id = uuid4()
|
|
workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", id=workspace_id, owner=owner)
|
|
|
|
# Serialize the workspace
|
|
serialized_data = WorkspaceLiteSerializer(workspace).data
|
|
|
|
# Check fields are present and correct
|
|
assert "name" in serialized_data
|
|
assert "slug" in serialized_data
|
|
assert "id" in serialized_data
|
|
|
|
assert serialized_data["name"] == "Test Workspace"
|
|
assert serialized_data["slug"] == "test-workspace"
|
|
assert str(serialized_data["id"]) == str(workspace_id)
|
|
|
|
def test_workspace_lite_serializer_read_only(self, db):
|
|
"""Test that the serializer fields are read-only"""
|
|
# Create a user to be the owner
|
|
owner = User.objects.create(email="test2@example.com", first_name="Test", last_name="User")
|
|
|
|
# Create a workspace
|
|
workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", id=uuid4(), owner=owner)
|
|
|
|
# Try to update via serializer
|
|
serializer = WorkspaceLiteSerializer(workspace, data={"name": "Updated Name", "slug": "updated-slug"})
|
|
|
|
# Serializer should be valid (since read-only fields are ignored)
|
|
assert serializer.is_valid()
|
|
|
|
# Save should not update the read-only fields
|
|
updated_workspace = serializer.save()
|
|
assert updated_workspace.name == "Test Workspace"
|
|
assert updated_workspace.slug == "test-workspace"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestWorkSpaceSerializerNameValidation:
|
|
"""validate_name must reject symbol-only workspace names (issue #9255).
|
|
|
|
Frontend validation is bypassable via a direct API call, so the rule is
|
|
enforced server-side on both the create and rename (partial_update) paths,
|
|
which share this serializer's field-level validation.
|
|
"""
|
|
|
|
@pytest.mark.parametrize("name", SYMBOL_ONLY_NAMES)
|
|
def test_rejects_symbol_only_names(self, name):
|
|
serializer = WorkSpaceSerializer()
|
|
with pytest.raises(serializers.ValidationError):
|
|
serializer.validate_name(name)
|
|
|
|
@pytest.mark.parametrize("name", VALID_NAMES)
|
|
def test_accepts_names_with_alphanumeric(self, name):
|
|
serializer = WorkSpaceSerializer()
|
|
assert serializer.validate_name(name) == name
|
|
|
|
@pytest.mark.parametrize("name", URL_NAMES)
|
|
def test_rejects_names_containing_urls(self, name):
|
|
serializer = WorkSpaceSerializer()
|
|
with pytest.raises(serializers.ValidationError):
|
|
serializer.validate_name(name)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestInstanceWorkspaceSerializerNameValidation:
|
|
"""The instance/license workspace create path must enforce the same rules
|
|
as the app serializer (symbol-only rejection AND URL rejection)."""
|
|
|
|
@pytest.mark.parametrize("name", SYMBOL_ONLY_NAMES)
|
|
def test_rejects_symbol_only_names(self, name):
|
|
serializer = InstanceWorkspaceSerializer()
|
|
with pytest.raises(serializers.ValidationError):
|
|
serializer.validate_name(name)
|
|
|
|
@pytest.mark.parametrize("name", VALID_NAMES)
|
|
def test_accepts_names_with_alphanumeric(self, name):
|
|
serializer = InstanceWorkspaceSerializer()
|
|
assert serializer.validate_name(name) == name
|
|
|
|
@pytest.mark.parametrize("name", URL_NAMES)
|
|
def test_rejects_names_containing_urls(self, name):
|
|
serializer = InstanceWorkspaceSerializer()
|
|
with pytest.raises(serializers.ValidationError):
|
|
serializer.validate_name(name)
|