From 6aeb02726e23920924806df7ea4b0f50be44f147 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Sun, 22 Jun 2025 17:25:33 +0200 Subject: [PATCH 1/8] feat(tests): Add reusable workspace fixture Introduces a new `workspace` fixture in `conftest.py` to provide a consistent and reusable setup for tests that require a workspace. --- apiserver/plane/tests/conftest.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/tests/conftest.py b/apiserver/plane/tests/conftest.py index 8325588104b..7caf9f33dc6 100644 --- a/apiserver/plane/tests/conftest.py +++ b/apiserver/plane/tests/conftest.py @@ -1,10 +1,12 @@ import pytest from django.conf import settings from rest_framework.test import APIClient +from rest_framework import status from pytest_django.fixtures import django_db_setup from unittest.mock import patch, MagicMock +from django.urls import reverse -from plane.db.models import User +from plane.db.models import User, Workspace from plane.db.models.api import APIToken @@ -118,3 +120,29 @@ def plane_server(live_server): Returns a live Django server for testing HTTP requests. """ return live_server + + +@pytest.fixture +def workspace(session_client): + """ + Create a new workspace via the API and return the + corresponding Workspace model instance. + """ + workspace_data = { + "name": "Test Default Workspace", + "slug": "test-default-workspace-slug", + "company_name": "Test Default Company" + } + + url = reverse("workspace") + + # Make the API request to create the workspace + response = session_client.post(url, workspace_data, format="json") + + # Ensure the workspace was created successfully before the test runs + assert response.status_code == status.HTTP_201_CREATED, \ + f"Failed to create workspace. Status: {response.status_code}, Response: {response.data}" + + # Return the actual Workspace model instance for the test to use + created_workspace = Workspace.objects.get(pk=response.data["id"]) + return created_workspace From 675fc41395d730cdcc9794564d9dd95bec8f6a71 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Sun, 22 Jun 2025 17:35:52 +0200 Subject: [PATCH 2/8] feat(tests): Add tests for project creation (POST) This commit introduces a comprehensive test suite for the project creation API endpoint. The suite covers a wide range of scenarios, including: - Successful creation and verification of side-effects (default states, project members, user properties). - Validation for invalid or missing data (400 Bad Request). - Permission checks for different user roles (e.g., guests are forbidden). - Authentication requirements (401 Unauthorized). - Uniqueness constraints for project names and identifiers (409 Conflict). - Successful creation with all optional fields populated. It leverages the `workspace`, `session_client` and `create_user` fixtures for a consistent test setup. --- .../tests/contract/app/test_project_app.py | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 apiserver/plane/tests/contract/app/test_project_app.py diff --git a/apiserver/plane/tests/contract/app/test_project_app.py b/apiserver/plane/tests/contract/app/test_project_app.py new file mode 100644 index 00000000000..067d611a973 --- /dev/null +++ b/apiserver/plane/tests/contract/app/test_project_app.py @@ -0,0 +1,229 @@ +import pytest +from rest_framework import status + +from plane.db.models import ( + Project, + ProjectMember, + IssueUserProperty, + State, + WorkspaceMember, + User, +) + + +@pytest.mark.contract +class TestProjectAPIPost(): + """Test project POST operations""" + + def get_project_url(self, workspace_slug: str) -> str: + """ + Constructs the project endpoint URL for the given workspace as reverse() is + unreliable due to duplicate 'name' values in URL patterns ('api' and 'app'). + """ + + return f"/api/workspaces/{workspace_slug}/projects/" + + @pytest.mark.django_db + def test_create_project_empty_data(self, session_client, workspace): + """Test creating a project with empty data""" + + url = self.get_project_url(workspace.slug) + print(url) + + # Test with empty data + response = session_client.post(url, {}, format="json") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + @pytest.mark.django_db + def test_create_project_valid_data(self, session_client, workspace, create_user): + url = self.get_project_url(workspace.slug) + + project_data = { + "name": "New Project Test", + "identifier": "NPT", + } + + user = create_user + + # Make the request + response = session_client.post(url, project_data, format="json") + + # Check response status + assert response.status_code == status.HTTP_201_CREATED + + # Verify project was created + assert Project.objects.count() == 1 + project = Project.objects.get(name=project_data["name"]) + assert project.workspace == workspace + + # Check if the member is created with the correct role + assert ProjectMember.objects.count() == 1 + project_member = ProjectMember.objects.filter( + project=project, member=user + ).first() + assert project_member.role == 20 # Administrator + assert project_member.is_active is True + + # Verify IssueUserProperty was created + assert IssueUserProperty.objects.filter(project=project, user=user).exists() + + # Verify default states were created + states = State.objects.filter(project=project) + assert states.count() == 5 + expected_states = ["Backlog", "Todo", "In Progress", "Done", "Cancelled"] + state_names = list(states.values_list("name", flat=True)) + assert set(state_names) == set(expected_states) + + @pytest.mark.django_db + def test_create_project_with_project_lead( + self, session_client, workspace, create_user + ): + """Test creating project with a different project lead""" + # Create another user to be project lead + project_lead = User.objects.create_user( + email="lead@example.com", username="projectlead" + ) + + # Add project lead to workspace + WorkspaceMember.objects.create( + workspace=workspace, member=project_lead, role=15 + ) + + url = self.get_project_url(workspace.slug) + project_data = { + "name": "Project with Lead", + "identifier": "PWL", + "project_lead": project_lead.id, + } + + response = session_client.post(url, project_data, format="json") + + assert response.status_code == status.HTTP_201_CREATED + + # Verify both creator and project lead are administrators + project = Project.objects.get(name=project_data["name"]) + assert ProjectMember.objects.filter(project=project, role=20).count() == 2 + + # Verify both have IssueUserProperty + assert IssueUserProperty.objects.filter(project=project).count() == 2 + + @pytest.mark.django_db + def test_create_project_guest_forbidden(self, session_client, workspace): + """Test that guests cannot create projects""" + guest_user = User.objects.create_user( + email="guest@example.com", username="guest" + ) + WorkspaceMember.objects.create(workspace=workspace, member=guest_user, role=5) + + session_client.force_authenticate(user=guest_user) + + url = self.get_project_url(workspace.slug) + project_data = { + "name": "Guest Project", + "identifier": "GP", + } + + response = session_client.post(url, project_data, format="json") + + assert response.status_code == status.HTTP_403_FORBIDDEN + assert Project.objects.count() == 0 + + @pytest.mark.django_db + def test_create_project_unauthenticated(self, client, workspace): + """Test unauthenticated access""" + url = self.get_project_url(workspace.slug) + project_data = { + "name": "Unauth Project", + "identifier": "UP", + } + + response = client.post(url, project_data, format="json") + + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + @pytest.mark.django_db + def test_create_project_duplicate_name( + self, session_client, workspace, create_user + ): + """Test creating project with duplicate name""" + # Create first project + Project.objects.create( + name="Duplicate Name", identifier="DN1", workspace=workspace + ) + + url = self.get_project_url(workspace.slug) + project_data = { + "name": "Duplicate Name", + "identifier": "DN2", + } + + response = session_client.post(url, project_data, format="json") + + assert response.status_code == status.HTTP_409_CONFLICT + + @pytest.mark.django_db + def test_create_project_duplicate_identifier( + self, session_client, workspace, create_user + ): + """Test creating project with duplicate identifier""" + Project.objects.create( + name="First Project", identifier="DUP", workspace=workspace + ) + + url = self.get_project_url(workspace.slug) + project_data = { + "name": "Second Project", + "identifier": "DUP", + } + + response = session_client.post(url, project_data, format="json") + + assert response.status_code == status.HTTP_409_CONFLICT + + @pytest.mark.django_db + def test_create_project_missing_required_fields( + self, session_client, workspace, create_user + ): + """Test validation with missing required fields""" + url = self.get_project_url(workspace.slug) + + # Test missing name + response = session_client.post(url, {"identifier": "MN"}, format="json") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + # Test missing identifier + response = session_client.post( + url, {"name": "Missing Identifier"}, format="json" + ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + + @pytest.mark.django_db + def test_create_project_with_all_optional_fields( + self, session_client, workspace, create_user + ): + """Test creating project with all optional fields""" + url = self.get_project_url(workspace.slug) + project_data = { + "name": "Full Project", + "identifier": "FP", + "description": "A comprehensive test project", + "network": 2, + "cycle_view": True, + "issue_views_view": False, + "module_view": True, + "page_view": False, + "inbox_view": True, + "guest_view_all_features": True, + "logo_props": { + "in_use": "emoji", + "emoji": {"value": "🚀", "unicode": "1f680"}, + }, + } + + response = session_client.post(url, project_data, format="json") + + assert response.status_code == status.HTTP_201_CREATED + + response_data = response.json() + assert response_data["description"] == project_data["description"] + assert response_data["network"] == project_data["network"] From 88b77424e7fb2b23b74991c6608a506819fcce64 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Sun, 22 Jun 2025 17:39:42 +0200 Subject: [PATCH 3/8] refactor(tests): Centralize project URL helper into a base class To avoid code duplication in upcoming tests, this commit introduces a `TestProjectBase` class. The `get_project_url` helper method is moved into this shared base class, and the existing `TestProjectAPIPost` class is updated to inherit from it. This ensures the URL generation logic is defined in a single place and preparing the suite for the upcoming GET tests. --- .../tests/contract/app/test_project_app.py | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/apiserver/plane/tests/contract/app/test_project_app.py b/apiserver/plane/tests/contract/app/test_project_app.py index 067d611a973..88d7e8c46c4 100644 --- a/apiserver/plane/tests/contract/app/test_project_app.py +++ b/apiserver/plane/tests/contract/app/test_project_app.py @@ -1,5 +1,6 @@ import pytest from rest_framework import status +import uuid from plane.db.models import ( Project, @@ -11,17 +12,38 @@ ) -@pytest.mark.contract -class TestProjectAPIPost(): - """Test project POST operations""" - - def get_project_url(self, workspace_slug: str) -> str: +class TestProjectBase: + def get_project_url( + self, workspace_slug: str, pk: uuid.UUID = None, details: bool = False + ) -> str: """ Constructs the project endpoint URL for the given workspace as reverse() is unreliable due to duplicate 'name' values in URL patterns ('api' and 'app'). + + Args: + workspace_slug (str): The slug of the workspace. + pk (uuid.UUID, optional): The primary key of a specific project. + details (bool, optional): If True, constructs the URL for the + project details endpoint. Defaults to False. """ - - return f"/api/workspaces/{workspace_slug}/projects/" + # Establish the common base URL for all project-related endpoints. + base_url = f"/api/workspaces/{workspace_slug}/projects/" + + # Specific project instance URL. + if pk: + return f"{base_url}{pk}/" + + # Append 'details/' to the base URL. + if details: + return f"{base_url}details/" + + # Return the base project list URL. + return base_url + + +@pytest.mark.contract +class TestProjectAPIPost(TestProjectBase): + """Test project POST operations""" @pytest.mark.django_db def test_create_project_empty_data(self, session_client, workspace): From bd89a464980de09414c57918e12324e871137658 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Sun, 22 Jun 2025 17:43:18 +0200 Subject: [PATCH 4/8] feat(tests): Add tests for project listing and retrieval (GET) This commit adds a suite for the GET method. It leverages the previously created `TestProjectBase` class for URL generation. The new test suite covers: - Listing projects: - Verifies that administrators see all projects. - Confirms guests only see projects they are members of. - Tests the separate detailed project list endpoint. - Retrieving a single project: - Checks for successful retrieval of a project by its ID. - Handles edge cases for non-existent and archived projects (404 Not Found). - Authentication: - Ensures authentication is required (401 Unauthorized). --- .../tests/contract/app/test_project_app.py | 154 +++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/tests/contract/app/test_project_app.py b/apiserver/plane/tests/contract/app/test_project_app.py index 88d7e8c46c4..d9329e3b6b7 100644 --- a/apiserver/plane/tests/contract/app/test_project_app.py +++ b/apiserver/plane/tests/contract/app/test_project_app.py @@ -1,6 +1,7 @@ import pytest from rest_framework import status import uuid +from django.utils import timezone from plane.db.models import ( Project, @@ -39,7 +40,7 @@ def get_project_url( # Return the base project list URL. return base_url - + @pytest.mark.contract class TestProjectAPIPost(TestProjectBase): @@ -249,3 +250,154 @@ def test_create_project_with_all_optional_fields( response_data = response.json() assert response_data["description"] == project_data["description"] assert response_data["network"] == project_data["network"] + + +@pytest.mark.contract +class TestProjectAPIGet(TestProjectBase): + """Test project GET operations""" + + @pytest.mark.django_db + def test_list_projects_authenticated_admin( + self, session_client, workspace, create_user + ): + """Test listing projects as workspace admin""" + # Create a project + project = Project.objects.create( + name="Test Project", identifier="TP", workspace=workspace + ) + + # Add user as project member + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug) + response = session_client.get(url) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + assert len(data) == 1 + assert data[0]["name"] == "Test Project" + assert data[0]["identifier"] == "TP" + + @pytest.mark.django_db + def test_list_projects_authenticated_guest(self, session_client, workspace): + """Test listing projects as workspace guest""" + # Create a guest user + guest_user = User.objects.create_user( + email="guest@example.com", username="guest" + ) + WorkspaceMember.objects.create( + workspace=workspace, member=guest_user, role=5, is_active=True + ) + + # Create projects + project1 = Project.objects.create( + name="Project 1", identifier="P1", workspace=workspace + ) + + Project.objects.create(name="Project 2", identifier="P2", workspace=workspace) + + # Add guest to only one project + ProjectMember.objects.create( + project=project1, member=guest_user, role=10, is_active=True + ) + + session_client.force_authenticate(user=guest_user) + + url = self.get_project_url(workspace.slug) + response = session_client.get(url) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + # Guest should only see projects they're members of + assert len(data) == 1 + assert data[0]["name"] == "Project 1" + + @pytest.mark.django_db + def test_list_projects_unauthenticated(self, client, workspace): + """Test listing projects without authentication""" + url = self.get_project_url(workspace.slug) + response = client.get(url) + + assert response.status_code == status.HTTP_401_UNAUTHORIZED + + @pytest.mark.django_db + def test_list_detail_projects(self, session_client, workspace, create_user): + """Test listing projects with detailed information""" + # Create a project + project = Project.objects.create( + name="Detailed Project", + identifier="DP", + workspace=workspace, + description="A detailed test project", + ) + + # Add user as project member + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, details=True) + response = session_client.get(url) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + assert len(data) == 1 + assert data[0]["name"] == "Detailed Project" + assert data[0]["description"] == "A detailed test project" + + @pytest.mark.django_db + def test_retrieve_project_success(self, session_client, workspace, create_user): + """Test retrieving a specific project""" + # Create a project + project = Project.objects.create( + name="Retrieve Test Project", + identifier="RTP", + workspace=workspace, + description="Test project for retrieval", + ) + + # Add user as project member + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project.id) + response = session_client.get(url) + + assert response.status_code == status.HTTP_200_OK + data = response.json() + assert data["name"] == "Retrieve Test Project" + assert data["identifier"] == "RTP" + assert data["description"] == "Test project for retrieval" + + @pytest.mark.django_db + def test_retrieve_project_not_found(self, session_client, workspace, create_user): + """Test retrieving a non-existent project""" + fake_uuid = uuid.uuid4() + url = self.get_project_url(workspace.slug, pk=fake_uuid) + response = session_client.get(url) + + assert response.status_code == status.HTTP_404_NOT_FOUND + + @pytest.mark.django_db + def test_retrieve_archived_project(self, session_client, workspace, create_user): + """Test retrieving an archived project""" + # Create an archived project + project = Project.objects.create( + name="Archived Project", + identifier="AP", + workspace=workspace, + archived_at=timezone.now(), + ) + + # Add user as project member + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project.id) + response = session_client.get(url) + + assert response.status_code == status.HTTP_404_NOT_FOUND From 8bece8407468c5bd5f5c8528b4167734c0319e27 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Sun, 22 Jun 2025 18:22:20 +0200 Subject: [PATCH 5/8] feat(tests): Add tests for project update (PATCH) and deletion (DELETE) Key scenarios tested for PATCH: - Successful partial updates by project administrators. - Forbidden access for non-admin members (403). - Conflict errors for duplicate names or identifiers on update (409). - Validation errors for invalid data (400). Key scenarios tested for DELETE: - Successful deletion by both project admins and workspace admins. - Forbidden access for non-admin members (403). - Authentication checks for unauthenticated users (401). --- .../tests/contract/app/test_project_app.py | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/apiserver/plane/tests/contract/app/test_project_app.py b/apiserver/plane/tests/contract/app/test_project_app.py index d9329e3b6b7..22cff45f4e3 100644 --- a/apiserver/plane/tests/contract/app/test_project_app.py +++ b/apiserver/plane/tests/contract/app/test_project_app.py @@ -401,3 +401,219 @@ def test_retrieve_archived_project(self, session_client, workspace, create_user) response = session_client.get(url) assert response.status_code == status.HTTP_404_NOT_FOUND + + +@pytest.mark.contract +class TestProjectAPIPatchDelete(TestProjectBase): + """Test project PATCH, and DELETE operations""" + + @pytest.mark.django_db + def test_partial_update_project_success( + self, session_client, workspace, create_user + ): + """Test successful partial update of project""" + # Create a project + project = Project.objects.create( + name="Original Project", + identifier="OP", + workspace=workspace, + description="Original description", + ) + + # Add user as project administrator + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project.id) + update_data = { + "name": "Updated Project", + "description": "Updated description", + "cycle_view": True, + "module_view": False, + } + + response = session_client.patch(url, update_data, format="json") + + assert response.status_code == status.HTTP_200_OK + + # Verify project was updated + project.refresh_from_db() + assert project.name == "Updated Project" + assert project.description == "Updated description" + assert project.cycle_view is True + assert project.module_view is False + + @pytest.mark.django_db + def test_partial_update_project_forbidden_non_admin( + self, session_client, workspace + ): + """Test that non-admin project members cannot update project""" + # Create a project + project = Project.objects.create( + name="Protected Project", identifier="PP", workspace=workspace + ) + + # Create a member user (not admin) + member_user = User.objects.create_user( + email="member@example.com", username="member" + ) + WorkspaceMember.objects.create( + workspace=workspace, member=member_user, role=15, is_active=True + ) + ProjectMember.objects.create( + project=project, member=member_user, role=15, is_active=True + ) + + session_client.force_authenticate(user=member_user) + + url = self.get_project_url(workspace.slug, pk=project.id) + update_data = {"name": "Hacked Project"} + + response = session_client.patch(url, update_data, format="json") + + assert response.status_code == status.HTTP_403_FORBIDDEN + + @pytest.mark.django_db + def test_partial_update_duplicate_name_conflict( + self, session_client, workspace, create_user + ): + """Test updating project with duplicate name returns conflict""" + # Create two projects + Project.objects.create(name="Project One", identifier="P1", workspace=workspace) + project2 = Project.objects.create( + name="Project Two", identifier="P2", workspace=workspace + ) + + ProjectMember.objects.create( + project=project2, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project2.id) + update_data = {"name": "Project One"} # Duplicate name + + response = session_client.patch(url, update_data, format="json") + + assert response.status_code == status.HTTP_409_CONFLICT + + @pytest.mark.django_db + def test_partial_update_duplicate_identifier_conflict( + self, session_client, workspace, create_user + ): + """Test updating project with duplicate identifier returns conflict""" + # Create two projects + Project.objects.create(name="Project One", identifier="P1", workspace=workspace) + project2 = Project.objects.create( + name="Project Two", identifier="P2", workspace=workspace + ) + + ProjectMember.objects.create( + project=project2, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project2.id) + update_data = {"identifier": "P1"} # Duplicate identifier + + response = session_client.patch(url, update_data, format="json") + + assert response.status_code == status.HTTP_409_CONFLICT + + @pytest.mark.django_db + def test_partial_update_invalid_data(self, session_client, workspace, create_user): + """Test partial update with invalid data""" + project = Project.objects.create( + name="Valid Project", identifier="VP", workspace=workspace + ) + + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project.id) + update_data = {"name": ""} + + response = session_client.patch(url, update_data, format="json") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + + @pytest.mark.django_db + def test_delete_project_success_project_admin( + self, session_client, workspace, create_user + ): + """Test successful project deletion by project admin""" + project = Project.objects.create( + name="Delete Me", identifier="DM", workspace=workspace + ) + + ProjectMember.objects.create( + project=project, member=create_user, role=20, is_active=True + ) + + url = self.get_project_url(workspace.slug, pk=project.id) + response = session_client.delete(url) + + assert response.status_code == status.HTTP_204_NO_CONTENT + assert not Project.objects.filter(id=project.id).exists() + + @pytest.mark.django_db + def test_delete_project_success_workspace_admin(self, session_client, workspace): + """Test successful project deletion by workspace admin""" + # Create workspace admin user + workspace_admin = User.objects.create_user( + email="admin@example.com", username="admin" + ) + WorkspaceMember.objects.create( + workspace=workspace, member=workspace_admin, role=20, is_active=True + ) + + project = Project.objects.create( + name="Delete Me", identifier="DM", workspace=workspace + ) + + session_client.force_authenticate(user=workspace_admin) + + url = self.get_project_url(workspace.slug, pk=project.id) + response = session_client.delete(url) + + assert response.status_code == status.HTTP_204_NO_CONTENT + assert not Project.objects.filter(id=project.id).exists() + + @pytest.mark.django_db + def test_delete_project_forbidden_non_admin(self, session_client, workspace): + """Test that non-admin users cannot delete projects""" + # Create a member user (not admin) + member_user = User.objects.create_user( + email="member@example.com", username="member" + ) + WorkspaceMember.objects.create( + workspace=workspace, member=member_user, role=15, is_active=True + ) + + project = Project.objects.create( + name="Protected Project", identifier="PP", workspace=workspace + ) + + ProjectMember.objects.create( + project=project, member=member_user, role=15, is_active=True + ) + + session_client.force_authenticate(user=member_user) + + url = self.get_project_url(workspace.slug, pk=project.id) + response = session_client.delete(url) + + assert response.status_code == status.HTTP_403_FORBIDDEN + assert Project.objects.filter(id=project.id).exists() + + @pytest.mark.django_db + def test_delete_project_unauthenticated(self, client, workspace): + """Test unauthenticated project deletion""" + project = Project.objects.create( + name="Protected Project", identifier="PP", workspace=workspace + ) + + url = self.get_project_url(workspace.slug, pk=project.id) + response = client.delete(url) + + assert response.status_code == status.HTTP_401_UNAUTHORIZED + assert Project.objects.filter(id=project.id).exists() From a5ac9c16f040a32a642cc2bfea77514c5c2cb4ff Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Mon, 23 Jun 2025 15:30:32 +0200 Subject: [PATCH 6/8] Remove unnecessary print statement --- apiserver/plane/tests/contract/app/test_project_app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apiserver/plane/tests/contract/app/test_project_app.py b/apiserver/plane/tests/contract/app/test_project_app.py index 22cff45f4e3..f7d41e2a290 100644 --- a/apiserver/plane/tests/contract/app/test_project_app.py +++ b/apiserver/plane/tests/contract/app/test_project_app.py @@ -51,7 +51,6 @@ def test_create_project_empty_data(self, session_client, workspace): """Test creating a project with empty data""" url = self.get_project_url(workspace.slug) - print(url) # Test with empty data response = session_client.post(url, {}, format="json") From e93a6d7f521dbecae06e6c645df46f3841faedb9 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Mon, 23 Jun 2025 15:32:39 +0200 Subject: [PATCH 7/8] refactor(tests): Update workspace fixture to use ORM Updates the `workspace` fixture to create the model instance directly via the ORM using the `Workspace` model instead of the API, as requested during code review. --- apiserver/plane/tests/conftest.py | 32 +++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/apiserver/plane/tests/conftest.py b/apiserver/plane/tests/conftest.py index 7caf9f33dc6..fa39eece005 100644 --- a/apiserver/plane/tests/conftest.py +++ b/apiserver/plane/tests/conftest.py @@ -6,7 +6,7 @@ from unittest.mock import patch, MagicMock from django.urls import reverse -from plane.db.models import User, Workspace +from plane.db.models import User, Workspace, WorkspaceMember from plane.db.models.api import APIToken @@ -123,26 +123,20 @@ def plane_server(live_server): @pytest.fixture -def workspace(session_client): +def workspace(create_user): """ - Create a new workspace via the API and return the + Create a new workspace and return the corresponding Workspace model instance. """ - workspace_data = { - "name": "Test Default Workspace", - "slug": "test-default-workspace-slug", - "company_name": "Test Default Company" - } - - url = reverse("workspace") - - # Make the API request to create the workspace - response = session_client.post(url, workspace_data, format="json") - - # Ensure the workspace was created successfully before the test runs - assert response.status_code == status.HTTP_201_CREATED, \ - f"Failed to create workspace. Status: {response.status_code}, Response: {response.data}" + # Create the workspace using the model + created_workspace = Workspace.objects.create( + name="Test Workspace", + owner=create_user, + slug="test-workspace", + ) - # Return the actual Workspace model instance for the test to use - created_workspace = Workspace.objects.get(pk=response.data["id"]) + WorkspaceMember.objects.create( + workspace=created_workspace, member=create_user, role=20 + ) + return created_workspace From 62405bea227426ba3bf8bc1fe004f01fd049bb80 Mon Sep 17 00:00:00 2001 From: mohamedhayballa Date: Mon, 23 Jun 2025 16:16:40 +0200 Subject: [PATCH 8/8] Refactor: Remove some unused imports Removes imports that I added while working on the test suite for the Project API but were ultimately not used. Note that other unused imports still exist from the state of the codebase when this branch was created/forked. --- apiserver/plane/tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apiserver/plane/tests/conftest.py b/apiserver/plane/tests/conftest.py index fa39eece005..a7d4394e295 100644 --- a/apiserver/plane/tests/conftest.py +++ b/apiserver/plane/tests/conftest.py @@ -1,10 +1,8 @@ import pytest from django.conf import settings from rest_framework.test import APIClient -from rest_framework import status from pytest_django.fixtures import django_db_setup from unittest.mock import patch, MagicMock -from django.urls import reverse from plane.db.models import User, Workspace, WorkspaceMember from plane.db.models.api import APIToken