[WEB-5044] fix: ruff lint and format errors (#7868)
* fix: lint errors * fix: file formatting * fix: code refactor
This commit is contained in:
parent
1fb22bd252
commit
9237f568dd
261 changed files with 2199 additions and 6378 deletions
|
|
@ -131,8 +131,6 @@ def workspace(create_user):
|
|||
slug="test-workspace",
|
||||
)
|
||||
|
||||
WorkspaceMember.objects.create(
|
||||
workspace=created_workspace, member=create_user, role=20
|
||||
)
|
||||
WorkspaceMember.objects.create(workspace=created_workspace, member=create_user, role=20)
|
||||
|
||||
return created_workspace
|
||||
|
|
|
|||
|
|
@ -66,27 +66,15 @@ def mock_mongodb():
|
|||
|
||||
# Configure common MongoDB collection operations
|
||||
mock_mongo_collection.find_one.return_value = None
|
||||
mock_mongo_collection.find.return_value = MagicMock(
|
||||
__iter__=lambda x: iter([]), count=lambda: 0
|
||||
)
|
||||
mock_mongo_collection.insert_one.return_value = MagicMock(
|
||||
inserted_id="mock_id_123", acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.find.return_value = MagicMock(__iter__=lambda x: iter([]), count=lambda: 0)
|
||||
mock_mongo_collection.insert_one.return_value = MagicMock(inserted_id="mock_id_123", acknowledged=True)
|
||||
mock_mongo_collection.insert_many.return_value = MagicMock(
|
||||
inserted_ids=["mock_id_123", "mock_id_456"], acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.update_one.return_value = MagicMock(
|
||||
modified_count=1, matched_count=1, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.update_many.return_value = MagicMock(
|
||||
modified_count=2, matched_count=2, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.delete_one.return_value = MagicMock(
|
||||
deleted_count=1, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.delete_many.return_value = MagicMock(
|
||||
deleted_count=2, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.update_one.return_value = MagicMock(modified_count=1, matched_count=1, acknowledged=True)
|
||||
mock_mongo_collection.update_many.return_value = MagicMock(modified_count=2, matched_count=2, acknowledged=True)
|
||||
mock_mongo_collection.delete_one.return_value = MagicMock(deleted_count=1, acknowledged=True)
|
||||
mock_mongo_collection.delete_many.return_value = MagicMock(deleted_count=2, acknowledged=True)
|
||||
mock_mongo_collection.count_documents.return_value = 0
|
||||
|
||||
# Start the patch
|
||||
|
|
|
|||
|
|
@ -103,9 +103,7 @@ class TestLabelListCreateAPIEndpoint:
|
|||
assert created_label.external_source == "github"
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_label_duplicate_external_id(
|
||||
self, api_key_client, workspace, project
|
||||
):
|
||||
def test_create_label_duplicate_external_id(self, api_key_client, workspace, project):
|
||||
"""Test creating label with duplicate external ID"""
|
||||
url = self.get_label_url(workspace.slug, project.id)
|
||||
|
||||
|
|
@ -131,19 +129,13 @@ class TestLabelListCreateAPIEndpoint:
|
|||
assert "same external id" in response.data["error"]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_labels_success(
|
||||
self, api_key_client, workspace, project, create_label
|
||||
):
|
||||
def test_list_labels_success(self, api_key_client, workspace, project, create_label):
|
||||
"""Test successful label listing"""
|
||||
url = self.get_label_url(workspace.slug, project.id)
|
||||
|
||||
# Create additional labels
|
||||
Label.objects.create(
|
||||
name="Label 2", project=project, workspace=workspace, color="#00FF00"
|
||||
)
|
||||
Label.objects.create(
|
||||
name="Label 3", project=project, workspace=workspace, color="#0000FF"
|
||||
)
|
||||
Label.objects.create(name="Label 2", project=project, workspace=workspace, color="#00FF00")
|
||||
Label.objects.create(name="Label 3", project=project, workspace=workspace, color="#0000FF")
|
||||
|
||||
response = api_key_client.get(url)
|
||||
|
||||
|
|
@ -184,9 +176,7 @@ class TestLabelDetailAPIEndpoint:
|
|||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_label_success(
|
||||
self, api_key_client, workspace, project, create_label
|
||||
):
|
||||
def test_update_label_success(self, api_key_client, workspace, project, create_label):
|
||||
"""Test successful label update"""
|
||||
url = self.get_label_detail_url(workspace.slug, project.id, create_label.id)
|
||||
|
||||
|
|
@ -202,9 +192,7 @@ class TestLabelDetailAPIEndpoint:
|
|||
assert create_label.name == update_data["name"]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_label_invalid_data(
|
||||
self, api_key_client, workspace, project, create_label
|
||||
):
|
||||
def test_update_label_invalid_data(self, api_key_client, workspace, project, create_label):
|
||||
"""Test label update with invalid data"""
|
||||
url = self.get_label_detail_url(workspace.slug, project.id, create_label.id)
|
||||
|
||||
|
|
@ -215,9 +203,7 @@ class TestLabelDetailAPIEndpoint:
|
|||
assert response.status_code in [status.HTTP_400_BAD_REQUEST, status.HTTP_200_OK]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_label_success(
|
||||
self, api_key_client, workspace, project, create_label
|
||||
):
|
||||
def test_delete_label_success(self, api_key_client, workspace, project, create_label):
|
||||
"""Test successful label deletion"""
|
||||
url = self.get_label_detail_url(workspace.slug, project.id, create_label.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ class TestApiTokenEndpoint:
|
|||
|
||||
# POST /user/api-tokens/ tests
|
||||
@pytest.mark.django_db
|
||||
def test_create_api_token_success(
|
||||
self, session_client, create_user, api_token_data
|
||||
):
|
||||
def test_create_api_token_success(self, session_client, create_user, api_token_data):
|
||||
"""Test successful API token creation"""
|
||||
# Arrange
|
||||
session_client.force_authenticate(user=create_user)
|
||||
|
|
@ -38,9 +36,7 @@ class TestApiTokenEndpoint:
|
|||
assert token.label == api_token_data["label"]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_api_token_for_bot_user(
|
||||
self, session_client, create_bot_user, api_token_data
|
||||
):
|
||||
def test_create_api_token_for_bot_user(self, session_client, create_bot_user, api_token_data):
|
||||
"""Test API token creation for bot user"""
|
||||
# Arrange
|
||||
session_client.force_authenticate(user=create_bot_user)
|
||||
|
|
@ -111,9 +107,7 @@ class TestApiTokenEndpoint:
|
|||
APIToken.objects.create(label="Token 1", user=create_user, user_type=0)
|
||||
APIToken.objects.create(label="Token 2", user=create_user, user_type=0)
|
||||
# Create a service token (should be excluded)
|
||||
APIToken.objects.create(
|
||||
label="Service Token", user=create_user, user_type=0, is_service=True
|
||||
)
|
||||
APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True)
|
||||
url = reverse("api-tokens")
|
||||
|
||||
# Act
|
||||
|
|
@ -140,9 +134,7 @@ class TestApiTokenEndpoint:
|
|||
|
||||
# GET /user/api-tokens/<pk>/ tests
|
||||
@pytest.mark.django_db
|
||||
def test_get_specific_api_token(
|
||||
self, session_client, create_user, create_api_token_for_user
|
||||
):
|
||||
def test_get_specific_api_token(self, session_client, create_user, create_api_token_for_user):
|
||||
"""Test retrieving a specific API token"""
|
||||
# Arrange
|
||||
session_client.force_authenticate(user=create_user)
|
||||
|
|
@ -155,9 +147,7 @@ class TestApiTokenEndpoint:
|
|||
assert response.status_code == status.HTTP_200_OK
|
||||
assert str(response.data["id"]) == str(create_api_token_for_user.pk)
|
||||
assert response.data["label"] == create_api_token_for_user.label
|
||||
assert (
|
||||
"token" not in response.data
|
||||
) # Token should not be visible in read serializer
|
||||
assert "token" not in response.data # Token should not be visible in read serializer
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_nonexistent_api_token(self, session_client, create_user):
|
||||
|
|
@ -182,9 +172,7 @@ class TestApiTokenEndpoint:
|
|||
unique_email = f"other-{unique_id}@plane.so"
|
||||
unique_username = f"other_user_{unique_id}"
|
||||
other_user = User.objects.create(email=unique_email, username=unique_username)
|
||||
other_token = APIToken.objects.create(
|
||||
label="Other Token", user=other_user, user_type=0
|
||||
)
|
||||
other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0)
|
||||
session_client.force_authenticate(user=create_user)
|
||||
url = reverse("api-tokens", kwargs={"pk": other_token.pk})
|
||||
|
||||
|
|
@ -196,9 +184,7 @@ class TestApiTokenEndpoint:
|
|||
|
||||
# DELETE /user/api-tokens/<pk>/ tests
|
||||
@pytest.mark.django_db
|
||||
def test_delete_api_token_success(
|
||||
self, session_client, create_user, create_api_token_for_user
|
||||
):
|
||||
def test_delete_api_token_success(self, session_client, create_user, create_api_token_for_user):
|
||||
"""Test successful API token deletion"""
|
||||
# Arrange
|
||||
session_client.force_authenticate(user=create_user)
|
||||
|
|
@ -234,9 +220,7 @@ class TestApiTokenEndpoint:
|
|||
unique_email = f"delete-other-{unique_id}@plane.so"
|
||||
unique_username = f"delete_other_user_{unique_id}"
|
||||
other_user = User.objects.create(email=unique_email, username=unique_username)
|
||||
other_token = APIToken.objects.create(
|
||||
label="Other Token", user=other_user, user_type=0
|
||||
)
|
||||
other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0)
|
||||
session_client.force_authenticate(user=create_user)
|
||||
url = reverse("api-tokens", kwargs={"pk": other_token.pk})
|
||||
|
||||
|
|
@ -252,9 +236,7 @@ class TestApiTokenEndpoint:
|
|||
def test_delete_service_api_token_forbidden(self, session_client, create_user):
|
||||
"""Test deleting a service API token (should fail)"""
|
||||
# Arrange
|
||||
service_token = APIToken.objects.create(
|
||||
label="Service Token", user=create_user, user_type=0, is_service=True
|
||||
)
|
||||
service_token = APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True)
|
||||
session_client.force_authenticate(user=create_user)
|
||||
url = reverse("api-tokens", kwargs={"pk": service_token.pk})
|
||||
|
||||
|
|
@ -268,9 +250,7 @@ class TestApiTokenEndpoint:
|
|||
|
||||
# PATCH /user/api-tokens/<pk>/ tests
|
||||
@pytest.mark.django_db
|
||||
def test_patch_api_token_success(
|
||||
self, session_client, create_user, create_api_token_for_user
|
||||
):
|
||||
def test_patch_api_token_success(self, session_client, create_user, create_api_token_for_user):
|
||||
"""Test successful API token update"""
|
||||
# Arrange
|
||||
session_client.force_authenticate(user=create_user)
|
||||
|
|
@ -294,9 +274,7 @@ class TestApiTokenEndpoint:
|
|||
assert create_api_token_for_user.description == update_data["description"]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_patch_api_token_partial_update(
|
||||
self, session_client, create_user, create_api_token_for_user
|
||||
):
|
||||
def test_patch_api_token_partial_update(self, session_client, create_user, create_api_token_for_user):
|
||||
"""Test partial API token update"""
|
||||
# Arrange
|
||||
session_client.force_authenticate(user=create_user)
|
||||
|
|
@ -336,9 +314,7 @@ class TestApiTokenEndpoint:
|
|||
unique_email = f"patch-other-{unique_id}@plane.so"
|
||||
unique_username = f"patch_other_user_{unique_id}"
|
||||
other_user = User.objects.create(email=unique_email, username=unique_username)
|
||||
other_token = APIToken.objects.create(
|
||||
label="Other Token", user=other_user, user_type=0
|
||||
)
|
||||
other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0)
|
||||
session_client.force_authenticate(user=create_user)
|
||||
url = reverse("api-tokens", kwargs={"pk": other_token.pk})
|
||||
update_data = {"label": "Hacked Label"}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ from plane.license.models import Instance
|
|||
@pytest.fixture
|
||||
def setup_instance(db):
|
||||
"""Create and configure an instance for authentication tests"""
|
||||
instance_id = (
|
||||
uuid.uuid4() if not Instance.objects.exists() else Instance.objects.first().id
|
||||
)
|
||||
instance_id = uuid.uuid4() if not Instance.objects.exists() else Instance.objects.first().id
|
||||
|
||||
# Create or update instance with all required fields
|
||||
instance, _ = Instance.objects.update_or_create(
|
||||
|
|
@ -38,9 +36,7 @@ def setup_instance(db):
|
|||
@pytest.fixture
|
||||
def django_client():
|
||||
"""Return a Django test client with User-Agent header for handling redirects"""
|
||||
client = Client(
|
||||
HTTP_USER_AGENT="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1"
|
||||
)
|
||||
client = Client(HTTP_USER_AGENT="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1")
|
||||
return client
|
||||
|
||||
|
||||
|
|
@ -83,9 +79,7 @@ class TestMagicLinkGenerate:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_generate(
|
||||
self, mock_magic_link, api_client, setup_user, setup_instance
|
||||
):
|
||||
def test_magic_generate(self, mock_magic_link, api_client, setup_user, setup_instance):
|
||||
"""Test successful magic link generation"""
|
||||
url = reverse("magic-generate")
|
||||
|
||||
|
|
@ -103,9 +97,7 @@ class TestMagicLinkGenerate:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_max_generate_attempt(
|
||||
self, mock_magic_link, api_client, setup_user, setup_instance
|
||||
):
|
||||
def test_max_generate_attempt(self, mock_magic_link, api_client, setup_user, setup_instance):
|
||||
"""Test exceeding maximum magic link generation attempts"""
|
||||
url = reverse("magic-generate")
|
||||
|
||||
|
|
@ -145,9 +137,7 @@ class TestSignInEndpoint:
|
|||
def test_email_validity(self, django_client, setup_user, setup_instance):
|
||||
"""Test sign-in with invalid email format"""
|
||||
url = reverse("sign-in")
|
||||
response = django_client.post(
|
||||
url, {"email": "useremail.com", "password": "user@123"}, follow=True
|
||||
)
|
||||
response = django_client.post(url, {"email": "useremail.com", "password": "user@123"}, follow=True)
|
||||
|
||||
# Check redirect contains error code
|
||||
assert "INVALID_EMAIL_SIGN_IN" in response.redirect_chain[-1][0]
|
||||
|
|
@ -156,9 +146,7 @@ class TestSignInEndpoint:
|
|||
def test_user_exists(self, django_client, setup_user, setup_instance):
|
||||
"""Test sign-in with non-existent user"""
|
||||
url = reverse("sign-in")
|
||||
response = django_client.post(
|
||||
url, {"email": "user@email.so", "password": "user123"}, follow=True
|
||||
)
|
||||
response = django_client.post(url, {"email": "user@email.so", "password": "user123"}, follow=True)
|
||||
|
||||
# Check redirect contains error code
|
||||
assert "USER_DOES_NOT_EXIST" in response.redirect_chain[-1][0]
|
||||
|
|
@ -167,9 +155,7 @@ class TestSignInEndpoint:
|
|||
def test_password_validity(self, django_client, setup_user, setup_instance):
|
||||
"""Test sign-in with incorrect password"""
|
||||
url = reverse("sign-in")
|
||||
response = django_client.post(
|
||||
url, {"email": "user@plane.so", "password": "user123"}, follow=True
|
||||
)
|
||||
response = django_client.post(url, {"email": "user@plane.so", "password": "user123"}, follow=True)
|
||||
|
||||
# Check for the specific authentication error in the URL
|
||||
redirect_urls = [url for url, _ in response.redirect_chain]
|
||||
|
|
@ -184,9 +170,7 @@ class TestSignInEndpoint:
|
|||
url = reverse("sign-in")
|
||||
|
||||
# First make the request without following redirects
|
||||
response = django_client.post(
|
||||
url, {"email": "user@plane.so", "password": "user@123"}, follow=False
|
||||
)
|
||||
response = django_client.post(url, {"email": "user@plane.so", "password": "user@123"}, follow=False)
|
||||
|
||||
# Check that the initial response is a redirect (302) without error code
|
||||
assert response.status_code == 302
|
||||
|
|
@ -243,27 +227,20 @@ class TestMagicSignIn:
|
|||
assert "MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED" in response.redirect_chain[-1][0]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_expired_invalid_magic_link(
|
||||
self, django_client, setup_user, setup_instance
|
||||
):
|
||||
def test_expired_invalid_magic_link(self, django_client, setup_user, setup_instance):
|
||||
"""Test magic link sign-in with expired/invalid link"""
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
|
||||
url = reverse("magic-sign-in")
|
||||
response = django_client.post(
|
||||
url, {"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False
|
||||
)
|
||||
response = django_client.post(url, {"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False)
|
||||
|
||||
# Check that we get a redirect
|
||||
assert response.status_code == 302
|
||||
|
||||
# The actual error code is EXPIRED_MAGIC_CODE_SIGN_IN (when key doesn't exist)
|
||||
# or INVALID_MAGIC_CODE_SIGN_IN (when key exists but code doesn't match)
|
||||
assert (
|
||||
"EXPIRED_MAGIC_CODE_SIGN_IN" in response.url
|
||||
or "INVALID_MAGIC_CODE_SIGN_IN" in response.url
|
||||
)
|
||||
assert "EXPIRED_MAGIC_CODE_SIGN_IN" in response.url or "INVALID_MAGIC_CODE_SIGN_IN" in response.url
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_does_not_exist(self, django_client, setup_instance):
|
||||
|
|
@ -280,9 +257,7 @@ class TestMagicSignIn:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_code_sign_in(
|
||||
self, mock_magic_link, django_client, api_client, setup_user, setup_instance
|
||||
):
|
||||
def test_magic_code_sign_in(self, mock_magic_link, django_client, api_client, setup_user, setup_instance):
|
||||
"""Test successful magic link sign-in process"""
|
||||
# First generate a magic link token
|
||||
gen_url = reverse("magic-generate")
|
||||
|
|
@ -298,9 +273,7 @@ class TestMagicSignIn:
|
|||
|
||||
# Use Django client to test the redirect flow without following redirects
|
||||
url = reverse("magic-sign-in")
|
||||
response = django_client.post(
|
||||
url, {"email": "user@plane.so", "code": token}, follow=False
|
||||
)
|
||||
response = django_client.post(url, {"email": "user@plane.so", "code": token}, follow=False)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
assert response.status_code == 302
|
||||
|
|
@ -311,9 +284,7 @@ class TestMagicSignIn:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_sign_in_with_next_path(
|
||||
self, mock_magic_link, django_client, api_client, setup_user, setup_instance
|
||||
):
|
||||
def test_magic_sign_in_with_next_path(self, mock_magic_link, django_client, api_client, setup_user, setup_instance):
|
||||
"""Test magic sign-in with next_path parameter"""
|
||||
# First generate a magic link token
|
||||
gen_url = reverse("magic-generate")
|
||||
|
|
@ -367,9 +338,7 @@ class TestMagicSignUp:
|
|||
User.objects.create(email="existing@plane.so")
|
||||
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url, {"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=True
|
||||
)
|
||||
response = django_client.post(url, {"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=True)
|
||||
|
||||
# Check redirect contains error code
|
||||
assert "USER_ALREADY_EXIST" in response.redirect_chain[-1][0]
|
||||
|
|
@ -378,25 +347,18 @@ class TestMagicSignUp:
|
|||
def test_expired_invalid_magic_link(self, django_client, setup_instance):
|
||||
"""Test magic link sign-up with expired/invalid link"""
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url, {"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False
|
||||
)
|
||||
response = django_client.post(url, {"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False)
|
||||
|
||||
# Check that we get a redirect
|
||||
assert response.status_code == 302
|
||||
|
||||
# The actual error code is EXPIRED_MAGIC_CODE_SIGN_UP (when key doesn't exist)
|
||||
# or INVALID_MAGIC_CODE_SIGN_UP (when key exists but code doesn't match)
|
||||
assert (
|
||||
"EXPIRED_MAGIC_CODE_SIGN_UP" in response.url
|
||||
or "INVALID_MAGIC_CODE_SIGN_UP" in response.url
|
||||
)
|
||||
assert "EXPIRED_MAGIC_CODE_SIGN_UP" in response.url or "INVALID_MAGIC_CODE_SIGN_UP" in response.url
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_code_sign_up(
|
||||
self, mock_magic_link, django_client, api_client, setup_instance
|
||||
):
|
||||
def test_magic_code_sign_up(self, mock_magic_link, django_client, api_client, setup_instance):
|
||||
"""Test successful magic link sign-up process"""
|
||||
email = "newuser@plane.so"
|
||||
|
||||
|
|
@ -414,9 +376,7 @@ class TestMagicSignUp:
|
|||
|
||||
# Use Django client to test the redirect flow without following redirects
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url, {"email": email, "code": token}, follow=False
|
||||
)
|
||||
response = django_client.post(url, {"email": email, "code": token}, follow=False)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
assert response.status_code == 302
|
||||
|
|
@ -430,9 +390,7 @@ class TestMagicSignUp:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_sign_up_with_next_path(
|
||||
self, mock_magic_link, django_client, api_client, setup_instance
|
||||
):
|
||||
def test_magic_sign_up_with_next_path(self, mock_magic_link, django_client, api_client, setup_instance):
|
||||
"""Test magic sign-up with next_path parameter"""
|
||||
email = "newuser2@plane.so"
|
||||
|
||||
|
|
@ -451,9 +409,7 @@ class TestMagicSignUp:
|
|||
# Use Django client to test the redirect flow without following redirects
|
||||
url = reverse("magic-sign-up")
|
||||
next_path = "onboarding"
|
||||
response = django_client.post(
|
||||
url, {"email": email, "code": token, "next_path": next_path}, follow=False
|
||||
)
|
||||
response = django_client.post(url, {"email": email, "code": token, "next_path": next_path}, follow=False)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
assert response.status_code == 302
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ from plane.db.models import (
|
|||
|
||||
|
||||
class TestProjectBase:
|
||||
def get_project_url(
|
||||
self, workspace_slug: str, pk: uuid.UUID = None, details: bool = False
|
||||
) -> str:
|
||||
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').
|
||||
|
|
@ -80,9 +78,7 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
|
||||
# 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()
|
||||
project_member = ProjectMember.objects.filter(project=project, member=user).first()
|
||||
assert project_member.role == 20 # Administrator
|
||||
assert project_member.is_active is True
|
||||
|
||||
|
|
@ -97,19 +93,13 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
assert set(state_names) == set(expected_states)
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_project_with_project_lead(
|
||||
self, session_client, workspace, create_user
|
||||
):
|
||||
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"
|
||||
)
|
||||
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
|
||||
)
|
||||
WorkspaceMember.objects.create(workspace=workspace, member=project_lead, role=15)
|
||||
|
||||
url = self.get_project_url(workspace.slug)
|
||||
project_data = {
|
||||
|
|
@ -132,9 +122,7 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
@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"
|
||||
)
|
||||
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)
|
||||
|
|
@ -164,14 +152,10 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_project_duplicate_name(
|
||||
self, session_client, workspace, create_user
|
||||
):
|
||||
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
|
||||
)
|
||||
Project.objects.create(name="Duplicate Name", identifier="DN1", workspace=workspace)
|
||||
|
||||
url = self.get_project_url(workspace.slug)
|
||||
project_data = {
|
||||
|
|
@ -184,13 +168,9 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_project_duplicate_identifier(
|
||||
self, session_client, workspace, create_user
|
||||
):
|
||||
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
|
||||
)
|
||||
Project.objects.create(name="First Project", identifier="DUP", workspace=workspace)
|
||||
|
||||
url = self.get_project_url(workspace.slug)
|
||||
project_data = {
|
||||
|
|
@ -203,9 +183,7 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_project_missing_required_fields(
|
||||
self, session_client, workspace, create_user
|
||||
):
|
||||
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)
|
||||
|
||||
|
|
@ -214,15 +192,11 @@ class TestProjectAPIPost(TestProjectBase):
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
# Test missing identifier
|
||||
response = session_client.post(
|
||||
url, {"name": "Missing Identifier"}, format="json"
|
||||
)
|
||||
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
|
||||
):
|
||||
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 = {
|
||||
|
|
@ -256,19 +230,13 @@ class TestProjectAPIGet(TestProjectBase):
|
|||
"""Test project GET operations"""
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_projects_authenticated_admin(
|
||||
self, session_client, workspace, create_user
|
||||
):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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)
|
||||
|
|
@ -283,24 +251,16 @@ class TestProjectAPIGet(TestProjectBase):
|
|||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
ProjectMember.objects.create(project=project1, member=guest_user, role=10, is_active=True)
|
||||
|
||||
session_client.force_authenticate(user=guest_user)
|
||||
|
||||
|
|
@ -333,9 +293,7 @@ class TestProjectAPIGet(TestProjectBase):
|
|||
)
|
||||
|
||||
# Add user as project member
|
||||
ProjectMember.objects.create(
|
||||
project=project, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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)
|
||||
|
|
@ -358,9 +316,7 @@ class TestProjectAPIGet(TestProjectBase):
|
|||
)
|
||||
|
||||
# Add user as project member
|
||||
ProjectMember.objects.create(
|
||||
project=project, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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)
|
||||
|
|
@ -392,9 +348,7 @@ class TestProjectAPIGet(TestProjectBase):
|
|||
)
|
||||
|
||||
# Add user as project member
|
||||
ProjectMember.objects.create(
|
||||
project=project, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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)
|
||||
|
|
@ -407,9 +361,7 @@ 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
|
||||
):
|
||||
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(
|
||||
|
|
@ -420,9 +372,7 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
)
|
||||
|
||||
# Add user as project administrator
|
||||
ProjectMember.objects.create(
|
||||
project=project, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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 = {
|
||||
|
|
@ -444,25 +394,15 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
assert project.module_view is False
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_partial_update_project_forbidden_non_admin(
|
||||
self, session_client, workspace
|
||||
):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
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)
|
||||
|
||||
|
|
@ -474,19 +414,13 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
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
|
||||
):
|
||||
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
|
||||
)
|
||||
project2 = Project.objects.create(name="Project Two", identifier="P2", workspace=workspace)
|
||||
|
||||
ProjectMember.objects.create(
|
||||
project=project2, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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
|
||||
|
|
@ -496,19 +430,13 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_partial_update_duplicate_identifier_conflict(
|
||||
self, session_client, workspace, create_user
|
||||
):
|
||||
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
|
||||
)
|
||||
project2 = Project.objects.create(name="Project Two", identifier="P2", workspace=workspace)
|
||||
|
||||
ProjectMember.objects.create(
|
||||
project=project2, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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
|
||||
|
|
@ -520,13 +448,9 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
@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
|
||||
)
|
||||
project = Project.objects.create(name="Valid Project", identifier="VP", workspace=workspace)
|
||||
|
||||
ProjectMember.objects.create(
|
||||
project=project, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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": ""}
|
||||
|
|
@ -536,17 +460,11 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
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
|
||||
):
|
||||
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
|
||||
)
|
||||
project = Project.objects.create(name="Delete Me", identifier="DM", workspace=workspace)
|
||||
|
||||
ProjectMember.objects.create(
|
||||
project=project, member=create_user, role=20, is_active=True
|
||||
)
|
||||
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)
|
||||
|
|
@ -558,16 +476,10 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
project = Project.objects.create(name="Delete Me", identifier="DM", workspace=workspace)
|
||||
|
||||
session_client.force_authenticate(user=workspace_admin)
|
||||
|
||||
|
|
@ -581,20 +493,12 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
project = Project.objects.create(name="Protected Project", identifier="PP", workspace=workspace)
|
||||
|
||||
ProjectMember.objects.create(
|
||||
project=project, 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)
|
||||
|
||||
|
|
@ -607,9 +511,7 @@ class TestProjectAPIPatchDelete(TestProjectBase):
|
|||
@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
|
||||
)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ class TestWorkspaceAPI:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.workspace_seed_task.workspace_seed.delay")
|
||||
def test_create_workspace_valid_data(
|
||||
self, mock_workspace_seed, session_client, create_user
|
||||
):
|
||||
def test_create_workspace_valid_data(self, mock_workspace_seed, session_client, create_user):
|
||||
"""Test creating a workspace with valid data"""
|
||||
url = reverse("workspace")
|
||||
user = create_user # Use the create_user fixture directly as it returns a user object
|
||||
|
|
@ -49,9 +47,7 @@ class TestWorkspaceAPI:
|
|||
|
||||
# Check other values
|
||||
workspace = Workspace.objects.get(slug=workspace_data["slug"])
|
||||
workspace_member = WorkspaceMember.objects.filter(
|
||||
workspace=workspace, member=user
|
||||
).first()
|
||||
workspace_member = WorkspaceMember.objects.filter(workspace=workspace, member=user).first()
|
||||
assert workspace.owner == user
|
||||
assert workspace_member.role == 20
|
||||
|
||||
|
|
@ -68,9 +64,7 @@ class TestWorkspaceAPI:
|
|||
session_client.post(url, {"name": "Plane", "slug": "pla-ne"}, format="json")
|
||||
|
||||
# Try to create a workspace with the same slug
|
||||
response = session_client.post(
|
||||
url, {"name": "Plane", "slug": "pla-ne"}, format="json"
|
||||
)
|
||||
response = session_client.post(url, {"name": "Plane", "slug": "pla-ne"}, format="json")
|
||||
|
||||
# The API returns 400 BAD REQUEST for duplicate slugs, not 409 CONFLICT
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
|
|
|||
|
|
@ -15,15 +15,11 @@ class TestAuthSmoke:
|
|||
url = f"{plane_server.url}{relative_url}"
|
||||
|
||||
# 1. Test bad login - test with wrong password
|
||||
response = requests.post(
|
||||
url, data={"email": user_data["email"], "password": "wrong-password"}
|
||||
)
|
||||
response = requests.post(url, data={"email": user_data["email"], "password": "wrong-password"})
|
||||
|
||||
# For bad credentials, any of these status codes would be valid
|
||||
# The test shouldn't be brittle to minor implementation changes
|
||||
assert response.status_code != 500, (
|
||||
"Authentication should not cause server errors"
|
||||
)
|
||||
assert response.status_code != 500, "Authentication should not cause server errors"
|
||||
assert response.status_code != 404, "Authentication endpoint should exist"
|
||||
|
||||
if response.status_code == 200:
|
||||
|
|
@ -33,10 +29,7 @@ class TestAuthSmoke:
|
|||
data = response.json()
|
||||
# JSON response might indicate error in its structure
|
||||
assert (
|
||||
"error" in data
|
||||
or "error_code" in data
|
||||
or "detail" in data
|
||||
or response.url.endswith("sign-in")
|
||||
"error" in data or "error_code" in data or "detail" in data or response.url.endswith("sign-in")
|
||||
), "Error response should contain error details"
|
||||
except ValueError:
|
||||
# It's ok if response isn't JSON format
|
||||
|
|
@ -75,20 +68,17 @@ class TestAuthSmoke:
|
|||
data = response.json()
|
||||
# If it's a token response
|
||||
if "access_token" in data:
|
||||
assert "refresh_token" in data, (
|
||||
"JWT auth should return both access and refresh tokens"
|
||||
)
|
||||
assert "refresh_token" in data, "JWT auth should return both access and refresh tokens"
|
||||
# If it's a user session response
|
||||
elif "user" in data:
|
||||
assert (
|
||||
"is_authenticated" in data and data["is_authenticated"]
|
||||
), "User session response should indicate authentication"
|
||||
assert "is_authenticated" in data and data["is_authenticated"], (
|
||||
"User session response should indicate authentication"
|
||||
)
|
||||
# Otherwise it should at least indicate success
|
||||
else:
|
||||
assert not any(
|
||||
error_key in data
|
||||
for error_key in ["error", "error_code", "detail"]
|
||||
), "Success response should not contain error keys"
|
||||
assert not any(error_key in data for error_key in ["error", "error_code", "detail"]), (
|
||||
"Success response should not contain error keys"
|
||||
)
|
||||
except ValueError:
|
||||
# Non-JSON is acceptable if it's a redirect or HTML response
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ class TestCopyS3Objects:
|
|||
|
||||
@pytest.fixture
|
||||
def project(self, create_user, workspace):
|
||||
project = Project.objects.create(
|
||||
name="Test Project", identifier="test-project", workspace=workspace
|
||||
)
|
||||
project = Project.objects.create(name="Test Project", identifier="test-project", workspace=workspace)
|
||||
|
||||
ProjectMember.objects.create(project=project, member=create_user)
|
||||
return project
|
||||
|
|
@ -27,7 +25,7 @@ class TestCopyS3Objects:
|
|||
name="Test Issue",
|
||||
workspace=workspace,
|
||||
project_id=project.id,
|
||||
description_html='<div><image-component src="35e8b958-6ee5-43ce-ae56-fb0e776f421e"></image-component><image-component src="97988198-274f-4dfe-aa7a-4c0ffc684214"></image-component></div>',
|
||||
description_html='<div><image-component src="35e8b958-6ee5-43ce-ae56-fb0e776f421e"></image-component><image-component src="97988198-274f-4dfe-aa7a-4c0ffc684214"></image-component></div>', # noqa: E501
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -72,18 +70,14 @@ class TestCopyS3Objects:
|
|||
mock_s3_storage.return_value = mock_storage_instance
|
||||
|
||||
# Mock the external service call to avoid actual HTTP requests
|
||||
with patch(
|
||||
"plane.bgtasks.copy_s3_object.sync_with_external_service"
|
||||
) as mock_sync:
|
||||
with patch("plane.bgtasks.copy_s3_object.sync_with_external_service") as mock_sync:
|
||||
mock_sync.return_value = {
|
||||
"description": "test description",
|
||||
"description_binary": base64.b64encode(b"test binary").decode(),
|
||||
}
|
||||
|
||||
# Call the actual function (not .delay())
|
||||
copy_s3_objects_of_description_and_assets(
|
||||
"ISSUE", issue.id, project.id, "test-workspace", create_user.id
|
||||
)
|
||||
copy_s3_objects_of_description_and_assets("ISSUE", issue.id, project.id, "test-workspace", create_user.id)
|
||||
|
||||
# Assert that copy_object was called for each asset
|
||||
assert mock_storage_instance.copy_object.call_count == 2
|
||||
|
|
@ -100,9 +94,7 @@ class TestCopyS3Objects:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.copy_s3_object.S3Storage")
|
||||
def test_copy_assets_successful(
|
||||
self, mock_s3_storage, workspace, project, issue, file_asset
|
||||
):
|
||||
def test_copy_assets_successful(self, mock_s3_storage, workspace, project, issue, file_asset):
|
||||
"""Test successful copying of assets"""
|
||||
# Arrange
|
||||
mock_storage_instance = MagicMock()
|
||||
|
|
@ -136,9 +128,7 @@ class TestCopyS3Objects:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.copy_s3_object.S3Storage")
|
||||
def test_copy_assets_empty_asset_ids(
|
||||
self, mock_s3_storage, workspace, project, issue
|
||||
):
|
||||
def test_copy_assets_empty_asset_ids(self, mock_s3_storage, workspace, project, issue):
|
||||
"""Test copying with empty asset_ids list"""
|
||||
# Arrange
|
||||
mock_storage_instance = MagicMock()
|
||||
|
|
@ -159,9 +149,7 @@ class TestCopyS3Objects:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.copy_s3_object.S3Storage")
|
||||
def test_copy_assets_nonexistent_asset(
|
||||
self, mock_s3_storage, workspace, project, issue
|
||||
):
|
||||
def test_copy_assets_nonexistent_asset(self, mock_s3_storage, workspace, project, issue):
|
||||
"""Test copying with non-existent asset ID"""
|
||||
# Arrange
|
||||
mock_storage_instance = MagicMock()
|
||||
|
|
|
|||
|
|
@ -111,9 +111,7 @@ class TestReadReplicaRoutingMiddleware:
|
|||
mock_clear.assert_called_once()
|
||||
|
||||
@patch("plane.middleware.db_routing.clear_read_replica_context")
|
||||
def test_call_cleans_up_context_on_exception(
|
||||
self, mock_clear, middleware, get_request, mock_get_response
|
||||
):
|
||||
def test_call_cleans_up_context_on_exception(self, mock_clear, middleware, get_request, mock_get_response):
|
||||
"""Test __call__ cleans up context even if get_response raises."""
|
||||
mock_get_response.side_effect = Exception("Test exception")
|
||||
|
||||
|
|
@ -139,9 +137,7 @@ class TestProcessView:
|
|||
assert result is None
|
||||
|
||||
@patch("plane.middleware.db_routing.set_use_read_replica")
|
||||
def test_with_read_method_and_replica_false(
|
||||
self, mock_set, middleware, get_request
|
||||
):
|
||||
def test_with_read_method_and_replica_false(self, mock_set, middleware, get_request):
|
||||
"""Test process_view with GET request and use_read_replica=False."""
|
||||
view_func = Mock()
|
||||
view_func.use_read_replica = False
|
||||
|
|
@ -152,9 +148,7 @@ class TestProcessView:
|
|||
assert result is None
|
||||
|
||||
@patch("plane.middleware.db_routing.set_use_read_replica")
|
||||
def test_with_read_method_and_no_replica_attribute(
|
||||
self, mock_set, middleware, get_request
|
||||
):
|
||||
def test_with_read_method_and_no_replica_attribute(self, mock_set, middleware, get_request):
|
||||
"""Test process_view with GET request and no use_read_replica attr."""
|
||||
view_func = Mock(spec=[]) # No use_read_replica attribute
|
||||
|
||||
|
|
@ -287,9 +281,7 @@ class TestAttributeDetection:
|
|||
(None, False),
|
||||
],
|
||||
)
|
||||
def test_should_use_read_replica_truthy_falsy_values(
|
||||
self, middleware, value, expected
|
||||
):
|
||||
def test_should_use_read_replica_truthy_falsy_values(self, middleware, value, expected):
|
||||
"""Test _should_use_read_replica with various truthy/falsy values."""
|
||||
|
||||
# Create a real object to test the attribute handling
|
||||
|
|
@ -309,9 +301,7 @@ class TestExceptionHandling:
|
|||
"""Test cases for exception handling and cleanup."""
|
||||
|
||||
@patch("plane.middleware.db_routing.clear_read_replica_context")
|
||||
def test_process_exception_cleans_up_context(
|
||||
self, mock_clear, middleware, request_factory
|
||||
):
|
||||
def test_process_exception_cleans_up_context(self, mock_clear, middleware, request_factory):
|
||||
"""Test process_exception cleans up context."""
|
||||
request = request_factory.get("/api/test/")
|
||||
exception = Exception("Test exception")
|
||||
|
|
@ -323,9 +313,7 @@ class TestExceptionHandling:
|
|||
|
||||
@patch("plane.middleware.db_routing.set_use_read_replica")
|
||||
@patch("plane.middleware.db_routing.clear_read_replica_context")
|
||||
def test_integration_full_request_cycle(
|
||||
self, mock_clear, mock_set, middleware, request_factory, mock_get_response
|
||||
):
|
||||
def test_integration_full_request_cycle(self, mock_clear, mock_set, middleware, request_factory, mock_get_response):
|
||||
"""Test complete request cycle from __call__ through process_view."""
|
||||
request = request_factory.get("/api/test/")
|
||||
view_func = Mock()
|
||||
|
|
@ -410,9 +398,7 @@ class TestEdgeCases:
|
|||
def __getattr__(self, name):
|
||||
if name == "use_read_replica":
|
||||
raise AttributeError("Simulated attribute error")
|
||||
raise AttributeError(
|
||||
f"'{type(self).__name__}' object has no attribute '{name}'"
|
||||
)
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
||||
|
||||
view_func = ProblematicView()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ class TestIssueRecentVisitSerializer:
|
|||
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"
|
||||
)
|
||||
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(
|
||||
|
|
@ -32,15 +30,11 @@ class TestIssueRecentVisitSerializer:
|
|||
username="some user name",
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace", slug="test-workspace", owner=test_user_1
|
||||
)
|
||||
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
|
||||
)
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -12,15 +12,11 @@ class TestWorkspaceLiteSerializer:
|
|||
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"
|
||||
)
|
||||
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
|
||||
)
|
||||
workspace = Workspace.objects.create(name="Test Workspace", slug="test-workspace", id=workspace_id, owner=owner)
|
||||
|
||||
# Serialize the workspace
|
||||
serialized_data = WorkspaceLiteSerializer(workspace).data
|
||||
|
|
@ -37,19 +33,13 @@ class TestWorkspaceLiteSerializer:
|
|||
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"
|
||||
)
|
||||
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
|
||||
)
|
||||
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 = WorkspaceLiteSerializer(workspace, data={"name": "Updated Name", "slug": "updated-slug"})
|
||||
|
||||
# Serializer should be valid (since read-only fields are ignored)
|
||||
assert serializer.is_valid()
|
||||
|
|
|
|||
|
|
@ -61,9 +61,7 @@ class TestContainsURL:
|
|||
assert contains_url("example.c") is False # TLD too short
|
||||
assert contains_url("999.999.999.999") is False # Invalid IP (octets > 255)
|
||||
assert contains_url("just-a-hyphen") is False # No domain
|
||||
assert (
|
||||
contains_url("www.") is False
|
||||
) # Incomplete www - needs at least one char after dot
|
||||
assert contains_url("www.") is False # Incomplete www - needs at least one char after dot
|
||||
|
||||
def test_contains_url_length_limit_under_1000(self):
|
||||
"""Test contains_url with input under 1000 characters containing URLs"""
|
||||
|
|
@ -108,9 +106,7 @@ class TestContainsURL:
|
|||
assert contains_url(multiline_short) is True
|
||||
|
||||
# Multiple lines under total limit
|
||||
multiline_text = (
|
||||
"a" * 200 + "\n" + "b" * 200 + "https://example.com\n" + "c" * 200
|
||||
)
|
||||
multiline_text = "a" * 200 + "\n" + "b" * 200 + "https://example.com\n" + "c" * 200
|
||||
assert len(multiline_text) < 1000
|
||||
assert contains_url(multiline_text) is True
|
||||
|
||||
|
|
@ -207,9 +203,7 @@ class TestNormalizeURLPath:
|
|||
|
||||
def test_normalize_url_path_with_query_and_fragment(self):
|
||||
"""Test normalize_url_path preserves query and fragment"""
|
||||
result = normalize_url_path(
|
||||
"https://example.com//foo///bar//baz?x=1&y=2#fragment"
|
||||
)
|
||||
result = normalize_url_path("https://example.com//foo///bar//baz?x=1&y=2#fragment")
|
||||
assert result == "https://example.com/foo/bar/baz?x=1&y=2#fragment"
|
||||
|
||||
def test_normalize_url_path_with_no_redundant_slashes(self):
|
||||
|
|
@ -230,9 +224,7 @@ class TestNormalizeURLPath:
|
|||
|
||||
def test_normalize_url_path_with_complex_path(self):
|
||||
"""Test normalize_url_path with complex path structure"""
|
||||
result = normalize_url_path(
|
||||
"https://example.com///api//v1///users//123//profile"
|
||||
)
|
||||
result = normalize_url_path("https://example.com///api//v1///users//123//profile")
|
||||
assert result == "https://example.com/api/v1/users/123/profile"
|
||||
|
||||
def test_normalize_url_path_with_different_schemes(self):
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ class TestUUIDUtils:
|
|||
assert is_valid_uuid("not-a-uuid") is False
|
||||
assert is_valid_uuid("123456789") is False
|
||||
assert is_valid_uuid("") is False
|
||||
assert (
|
||||
is_valid_uuid("00000000-0000-0000-0000-000000000000") is False
|
||||
) # This is a valid UUID but version 1
|
||||
assert is_valid_uuid("00000000-0000-0000-0000-000000000000") is False # This is a valid UUID but version 1
|
||||
|
||||
def test_convert_uuid_to_integer(self):
|
||||
"""Test convert_uuid_to_integer function"""
|
||||
|
|
@ -48,6 +46,4 @@ class TestUUIDUtils:
|
|||
test_uuid = uuid.UUID(test_uuid_str)
|
||||
|
||||
# Should get the same result whether passing UUID or string
|
||||
assert convert_uuid_to_integer(test_uuid) == convert_uuid_to_integer(
|
||||
test_uuid_str
|
||||
)
|
||||
assert convert_uuid_to_integer(test_uuid) == convert_uuid_to_integer(test_uuid_str)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue