|
| 1 | +import pytest |
| 2 | +from rest_framework import status |
| 3 | +from django.db import IntegrityError |
| 4 | +from uuid import uuid4 |
| 5 | + |
| 6 | +from plane.db.models import Label, Project, ProjectMember |
| 7 | + |
| 8 | + |
| 9 | +@pytest.fixture |
| 10 | +def project(db, workspace, create_user): |
| 11 | + """Create a test project with the user as a member""" |
| 12 | + project = Project.objects.create( |
| 13 | + name="Test Project", |
| 14 | + identifier="TP", |
| 15 | + workspace=workspace, |
| 16 | + created_by=create_user, |
| 17 | + ) |
| 18 | + ProjectMember.objects.create( |
| 19 | + project=project, |
| 20 | + member=create_user, |
| 21 | + role=20, # Admin role |
| 22 | + is_active=True, |
| 23 | + ) |
| 24 | + return project |
| 25 | + |
| 26 | + |
| 27 | +@pytest.fixture |
| 28 | +def label_data(): |
| 29 | + """Sample label data for tests""" |
| 30 | + return { |
| 31 | + "name": "Test Label", |
| 32 | + "color": "#FF5733", |
| 33 | + "description": "A test label for unit tests", |
| 34 | + } |
| 35 | + |
| 36 | + |
| 37 | +@pytest.fixture |
| 38 | +def create_label(db, project, create_user): |
| 39 | + """Create a test label""" |
| 40 | + return Label.objects.create( |
| 41 | + name="Existing Label", |
| 42 | + color="#00FF00", |
| 43 | + description="An existing label", |
| 44 | + project=project, |
| 45 | + workspace=project.workspace, |
| 46 | + created_by=create_user, |
| 47 | + ) |
| 48 | + |
| 49 | + |
| 50 | +@pytest.mark.contract |
| 51 | +class TestLabelListCreateAPIEndpoint: |
| 52 | + """Test Label List and Create API Endpoint""" |
| 53 | + |
| 54 | + def get_label_url(self, workspace_slug, project_id): |
| 55 | + """Helper to get label endpoint URL""" |
| 56 | + return f"/api/v1/workspaces/{workspace_slug}/projects/{project_id}/labels/" |
| 57 | + |
| 58 | + @pytest.mark.django_db |
| 59 | + def test_create_label_success(self, api_key_client, workspace, project, label_data): |
| 60 | + """Test successful label creation""" |
| 61 | + url = self.get_label_url(workspace.slug, project.id) |
| 62 | + |
| 63 | + response = api_key_client.post(url, label_data, format="json") |
| 64 | + |
| 65 | + assert response.status_code == status.HTTP_201_CREATED |
| 66 | + assert Label.objects.count() == 1 |
| 67 | + |
| 68 | + created_label = Label.objects.first() |
| 69 | + assert created_label.name == label_data["name"] |
| 70 | + assert created_label.color == label_data["color"] |
| 71 | + assert created_label.description == label_data["description"] |
| 72 | + assert created_label.project == project |
| 73 | + |
| 74 | + @pytest.mark.django_db |
| 75 | + def test_create_label_invalid_data(self, api_key_client, workspace, project): |
| 76 | + """Test label creation with invalid data""" |
| 77 | + url = self.get_label_url(workspace.slug, project.id) |
| 78 | + |
| 79 | + # Test with empty data |
| 80 | + response = api_key_client.post(url, {}, format="json") |
| 81 | + assert response.status_code == status.HTTP_400_BAD_REQUEST |
| 82 | + |
| 83 | + # Test with missing name |
| 84 | + response = api_key_client.post(url, {"color": "#FF5733"}, format="json") |
| 85 | + assert response.status_code == status.HTTP_400_BAD_REQUEST |
| 86 | + |
| 87 | + @pytest.mark.django_db |
| 88 | + def test_create_label_with_external_id(self, api_key_client, workspace, project): |
| 89 | + """Test creating label with external ID""" |
| 90 | + url = self.get_label_url(workspace.slug, project.id) |
| 91 | + |
| 92 | + label_data = { |
| 93 | + "name": "External Label", |
| 94 | + "color": "#FF5733", |
| 95 | + "external_id": "ext-123", |
| 96 | + "external_source": "github", |
| 97 | + } |
| 98 | + |
| 99 | + response = api_key_client.post(url, label_data, format="json") |
| 100 | + |
| 101 | + assert response.status_code == status.HTTP_201_CREATED |
| 102 | + created_label = Label.objects.first() |
| 103 | + assert created_label.external_id == "ext-123" |
| 104 | + assert created_label.external_source == "github" |
| 105 | + |
| 106 | + @pytest.mark.django_db |
| 107 | + def test_create_label_duplicate_external_id( |
| 108 | + self, api_key_client, workspace, project |
| 109 | + ): |
| 110 | + """Test creating label with duplicate external ID""" |
| 111 | + url = self.get_label_url(workspace.slug, project.id) |
| 112 | + |
| 113 | + # Create first label |
| 114 | + Label.objects.create( |
| 115 | + name="First Label", |
| 116 | + project=project, |
| 117 | + workspace=workspace, |
| 118 | + external_id="ext-123", |
| 119 | + external_source="github", |
| 120 | + ) |
| 121 | + |
| 122 | + # Try to create second label with same external ID |
| 123 | + label_data = { |
| 124 | + "name": "Second Label", |
| 125 | + "external_id": "ext-123", |
| 126 | + "external_source": "github", |
| 127 | + } |
| 128 | + |
| 129 | + response = api_key_client.post(url, label_data, format="json") |
| 130 | + |
| 131 | + assert response.status_code == status.HTTP_409_CONFLICT |
| 132 | + assert "same external id" in response.data["error"] |
| 133 | + |
| 134 | + @pytest.mark.django_db |
| 135 | + def test_list_labels_success( |
| 136 | + self, api_key_client, workspace, project, create_label |
| 137 | + ): |
| 138 | + """Test successful label listing""" |
| 139 | + url = self.get_label_url(workspace.slug, project.id) |
| 140 | + |
| 141 | + # Create additional labels |
| 142 | + Label.objects.create( |
| 143 | + name="Label 2", project=project, workspace=workspace, color="#00FF00" |
| 144 | + ) |
| 145 | + Label.objects.create( |
| 146 | + name="Label 3", project=project, workspace=workspace, color="#0000FF" |
| 147 | + ) |
| 148 | + |
| 149 | + response = api_key_client.get(url) |
| 150 | + |
| 151 | + assert response.status_code == status.HTTP_200_OK |
| 152 | + assert "results" in response.data |
| 153 | + assert len(response.data["results"]) == 3 # Including create_label fixture |
| 154 | + |
| 155 | + |
| 156 | +@pytest.mark.contract |
| 157 | +class TestLabelDetailAPIEndpoint: |
| 158 | + """Test Label Detail API Endpoint""" |
| 159 | + |
| 160 | + def get_label_detail_url(self, workspace_slug, project_id, label_id): |
| 161 | + """Helper to get label detail endpoint URL""" |
| 162 | + return f"/api/v1/workspaces/{workspace_slug}/projects/{project_id}/labels/{label_id}/" |
| 163 | + |
| 164 | + @pytest.mark.django_db |
| 165 | + def test_get_label_success(self, api_key_client, workspace, project, create_label): |
| 166 | + """Test successful label retrieval""" |
| 167 | + url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) |
| 168 | + |
| 169 | + response = api_key_client.get(url) |
| 170 | + |
| 171 | + assert response.status_code == status.HTTP_200_OK |
| 172 | + assert response.data["id"] == create_label.id |
| 173 | + assert response.data["name"] == create_label.name |
| 174 | + assert response.data["color"] == create_label.color |
| 175 | + |
| 176 | + @pytest.mark.django_db |
| 177 | + def test_get_label_not_found(self, api_key_client, workspace, project): |
| 178 | + """Test getting non-existent label""" |
| 179 | + from uuid import uuid4 |
| 180 | + |
| 181 | + fake_id = uuid4() |
| 182 | + url = self.get_label_detail_url(workspace.slug, project.id, fake_id) |
| 183 | + |
| 184 | + response = api_key_client.get(url) |
| 185 | + assert response.status_code == status.HTTP_404_NOT_FOUND |
| 186 | + |
| 187 | + @pytest.mark.django_db |
| 188 | + def test_update_label_success( |
| 189 | + self, api_key_client, workspace, project, create_label |
| 190 | + ): |
| 191 | + """Test successful label update""" |
| 192 | + url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) |
| 193 | + |
| 194 | + update_data = { |
| 195 | + "name": f"Updated Label {uuid4()}", |
| 196 | + } |
| 197 | + |
| 198 | + response = api_key_client.patch(url, update_data, format="json") |
| 199 | + |
| 200 | + assert response.status_code == status.HTTP_200_OK |
| 201 | + |
| 202 | + create_label.refresh_from_db() |
| 203 | + assert create_label.name == update_data["name"] |
| 204 | + |
| 205 | + @pytest.mark.django_db |
| 206 | + def test_update_label_invalid_data( |
| 207 | + self, api_key_client, workspace, project, create_label |
| 208 | + ): |
| 209 | + """Test label update with invalid data""" |
| 210 | + url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) |
| 211 | + |
| 212 | + update_data = {"name": ""} |
| 213 | + response = api_key_client.patch(url, update_data, format="json") |
| 214 | + |
| 215 | + # This might be 400 if name is required, or 200 if empty names are allowed |
| 216 | + assert response.status_code in [status.HTTP_400_BAD_REQUEST, status.HTTP_200_OK] |
| 217 | + |
| 218 | + @pytest.mark.django_db |
| 219 | + def test_delete_label_success( |
| 220 | + self, api_key_client, workspace, project, create_label |
| 221 | + ): |
| 222 | + """Test successful label deletion""" |
| 223 | + url = self.get_label_detail_url(workspace.slug, project.id, create_label.id) |
| 224 | + |
| 225 | + response = api_key_client.delete(url) |
| 226 | + |
| 227 | + assert response.status_code == status.HTTP_204_NO_CONTENT |
| 228 | + assert not Label.objects.filter(id=create_label.id).exists() |
0 commit comments