snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
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

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

View File

@@ -0,0 +1,73 @@
# 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 plane.db.models import (
Workspace,
Project,
Issue,
User,
IssueAssignee,
WorkspaceMember,
ProjectMember,
)
from plane.app.serializers.workspace import IssueRecentVisitSerializer
from django.utils import timezone
@pytest.mark.unit
class TestIssueRecentVisitSerializer:
"""Test the IssueRecentVisitSerializer"""
def test_issue_recent_visit_serializer_fields(self, db):
"""Test that the serializer includes the correct fields"""
test_user_1 = User.objects.create(email="test_user_1@example.com", first_name="Test", last_name="User")
# To test for deleted issue assignee
test_user_2 = User.objects.create(
email="test_user_2@example.com",
first_name="Other",
last_name="User",
username="some user name",
)
workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", owner=test_user_1)
WorkspaceMember.objects.create(member=test_user_2, role=15, workspace=workspace)
project = Project.objects.create(name="Test Project", identifier="test-project", workspace=workspace)
ProjectMember.objects.create(project=project, member=test_user_2)
issue = Issue.objects.create(
name="Test Issue",
workspace=workspace,
project=project,
)
IssueAssignee.objects.create(issue=issue, assignee=test_user_1, project=project)
# Deleted issue assignee
IssueAssignee.objects.create(
issue=issue,
assignee=test_user_2,
project=project,
deleted_at=timezone.now(),
)
serialized_data = IssueRecentVisitSerializer(
issue,
).data
# Check fields are present and correct
assert "name" in serialized_data
assert "assignees" in serialized_data
assert "project_identifier" in serialized_data
assert serialized_data["name"] == "Test Issue"
assert serialized_data["project_identifier"] == "TEST-PROJECT"
# Only including non-deleted issue assignees
assert serialized_data["assignees"] == [test_user_1.id]

View File

@@ -0,0 +1,61 @@
# 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 plane.api.serializers.issue import IssueSerializer
from plane.db.models import Project, ProjectMember, Label, User
@pytest.mark.unit
class TestIssueSerializerAssigneeAndLabelValidation:
"""Test that IssueSerializer rejects invalid assignees/labels instead of silently dropping them"""
@pytest.mark.django_db
def test_rejects_assignee_who_is_not_an_active_project_member(self, db, workspace, create_user):
"""An assignee id that isn't an active project member (role >= 15) must raise a validation error"""
project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
outsider = User.objects.create(
email="outsider@example.com", first_name="Out", last_name="Sider", username="outsider"
)
# Not a member of the project at all
serializer = IssueSerializer(
data={"name": "Test Issue", "assignees": [str(outsider.id)]},
context={"project_id": project.id, "workspace_id": workspace.id},
)
assert not serializer.is_valid()
assert "assignees" in str(serializer.errors).lower() or "assignee" in str(serializer.errors).lower()
@pytest.mark.django_db
def test_rejects_label_that_does_not_belong_to_project(self, db, workspace, create_user):
"""A label id that belongs to a different project must raise a validation error"""
project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
other_project = Project.objects.create(name="Other Project", identifier="OTHER", workspace=workspace)
foreign_label = Label.objects.create(name="Foreign Label", project=other_project)
serializer = IssueSerializer(
data={"name": "Test Issue", "labels": [str(foreign_label.id)]},
context={"project_id": project.id, "workspace_id": workspace.id},
)
assert not serializer.is_valid()
assert "labels" in str(serializer.errors).lower() or "label" in str(serializer.errors).lower()
@pytest.mark.django_db
def test_accepts_assignee_who_is_an_active_project_member(self, db, workspace, create_user):
"""A valid active project member id should still be accepted"""
project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
ProjectMember.objects.create(project=project, member=create_user, role=15, is_active=True)
serializer = IssueSerializer(
data={"name": "Test Issue", "assignees": [str(create_user.id)]},
context={"project_id": project.id, "workspace_id": workspace.id},
)
assert serializer.is_valid(), serializer.errors
assert list(serializer.validated_data["assignees"]) == [create_user.id]

View File

@@ -0,0 +1,41 @@
# 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 plane.app.serializers import LabelSerializer
from plane.db.models import Project, Label
@pytest.mark.unit
class TestLabelSerializer:
"""Test the LabelSerializer"""
@pytest.mark.django_db
def test_label_serializer_create_valid_data(self, db, workspace):
"""Test creating a label with valid data"""
project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
serializer = LabelSerializer(
data={"name": "Test Label"},
context={"project_id": project.id},
)
assert serializer.is_valid()
assert serializer.errors == {}
serializer.save(project_id=project.id)
label = Label.objects.all().first()
assert label.name == "Test Label"
assert label.project == project
assert label
@pytest.mark.django_db
def test_label_serializer_create_duplicate_name(self, db, workspace):
"""Test creating a label with a duplicate name"""
project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
Label.objects.create(name="Test Label", project=project)
serializer = LabelSerializer(data={"name": "Test Label"}, context={"project_id": project.id})
assert not serializer.is_valid()
assert serializer.errors == {"name": ["LABEL_NAME_ALREADY_EXISTS"]}

View File

@@ -0,0 +1,129 @@
# 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)