chore: format files using prettier (#7364)
* chore: format files using prettier * chore: api server files formatted
This commit is contained in:
parent
0225d806cc
commit
6ce700fd5d
149 changed files with 1518 additions and 919 deletions
|
|
@ -60,6 +60,7 @@ from plane.utils.host import base_host
|
|||
from plane.bgtasks.webhook_task import model_activity
|
||||
from plane.bgtasks.work_item_link_task import crawl_work_item_link_title
|
||||
|
||||
|
||||
class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
||||
"""
|
||||
This viewset provides `retrieveByIssueId` on workspace level
|
||||
|
|
|
|||
|
|
@ -102,4 +102,4 @@ class CycleUserPropertiesSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = CycleUserProperties
|
||||
fields = "__all__"
|
||||
read_only_fields = ["workspace", "project", "cycle" "user"]
|
||||
read_only_fields = ["workspace", "project", "cycle", "user"]
|
||||
|
|
|
|||
|
|
@ -726,7 +726,6 @@ class IssueSerializer(DynamicBaseSerializer):
|
|||
|
||||
|
||||
class IssueListDetailSerializer(serializers.Serializer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Extract expand parameter and store it as instance variable
|
||||
self.expand = kwargs.pop("expand", []) or []
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ class ProjectMemberAdminSerializer(BaseSerializer):
|
|||
fields = "__all__"
|
||||
|
||||
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
original_role = serializers.IntegerField(source='role', read_only=True)
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
original_role = serializers.IntegerField(source="role", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ProjectMember
|
||||
|
|
|
|||
|
|
@ -110,7 +110,11 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||
workspace_member__member=obj.id,
|
||||
workspace_member__is_active=True,
|
||||
).first()
|
||||
logo_asset_url = workspace.logo_asset.asset_url if workspace.logo_asset is not None else ""
|
||||
logo_asset_url = (
|
||||
workspace.logo_asset.asset_url
|
||||
if workspace.logo_asset is not None
|
||||
else ""
|
||||
)
|
||||
return {
|
||||
"last_workspace_id": profile.last_workspace_id,
|
||||
"last_workspace_slug": (
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from plane.utils.issue_filters import issue_filters
|
|||
|
||||
|
||||
class ViewIssueListSerializer(serializers.Serializer):
|
||||
|
||||
def get_assignee_ids(self, instance):
|
||||
return [assignee.assignee_id for assignee in instance.issue_assignee.all()]
|
||||
|
||||
|
|
|
|||
|
|
@ -160,7 +160,8 @@ class AdvanceAnalyticsStatsEndpoint(AdvanceAnalyticsBaseView):
|
|||
)
|
||||
|
||||
return (
|
||||
base_queryset.values("project_id", "project__name").annotate(
|
||||
base_queryset.values("project_id", "project__name")
|
||||
.annotate(
|
||||
cancelled_work_items=Count("id", filter=Q(state__group="cancelled")),
|
||||
completed_work_items=Count("id", filter=Q(state__group="completed")),
|
||||
backlog_work_items=Count("id", filter=Q(state__group="backlog")),
|
||||
|
|
@ -173,8 +174,7 @@ class AdvanceAnalyticsStatsEndpoint(AdvanceAnalyticsBaseView):
|
|||
def get_work_items_stats(self) -> Dict[str, Dict[str, int]]:
|
||||
base_queryset = Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
return (
|
||||
base_queryset
|
||||
.values("project_id", "project__name")
|
||||
base_queryset.values("project_id", "project__name")
|
||||
.annotate(
|
||||
cancelled_work_items=Count("id", filter=Q(state__group="cancelled")),
|
||||
completed_work_items=Count("id", filter=Q(state__group="completed")),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
workspace__slug=self.kwargs.get("slug"),
|
||||
receiver_id=self.request.user.id,
|
||||
)
|
||||
.select_related("workspace", "project," "triggered_by", "receiver")
|
||||
.select_related("workspace", "project", "triggered_by", "receiver")
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Command(BaseCommand):
|
|||
project_count = int(input("Number of projects to be created: "))
|
||||
|
||||
for i in range(project_count):
|
||||
print(f"Please provide the following details for project {i+1}:")
|
||||
print(f"Please provide the following details for project {i + 1}:")
|
||||
issue_count = int(input("Number of issues to be created: "))
|
||||
cycle_count = int(input("Number of cycles to be created: "))
|
||||
module_count = int(input("Number of modules to be created: "))
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ def workspace(create_user):
|
|||
)
|
||||
|
||||
WorkspaceMember.objects.create(
|
||||
workspace=created_workspace, member=create_user, role=20
|
||||
)
|
||||
|
||||
workspace=created_workspace, member=create_user, role=20
|
||||
)
|
||||
|
||||
return created_workspace
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def mock_redis():
|
|||
mock_redis_client.ttl.return_value = -1
|
||||
|
||||
# Start the patch
|
||||
with patch('plane.settings.redis.redis_instance', return_value=mock_redis_client):
|
||||
with patch("plane.settings.redis.redis_instance", return_value=mock_redis_client):
|
||||
yield mock_redis_client
|
||||
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ def mock_elasticsearch():
|
|||
mock_es_client.delete.return_value = {"_id": "test_id", "result": "deleted"}
|
||||
|
||||
# Start the patch
|
||||
with patch('elasticsearch.Elasticsearch', return_value=mock_es_client):
|
||||
with patch("elasticsearch.Elasticsearch", return_value=mock_es_client):
|
||||
yield mock_es_client
|
||||
|
||||
|
||||
|
|
@ -68,39 +68,30 @@ 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
|
||||
__iter__=lambda x: iter([]), count=lambda: 0
|
||||
)
|
||||
mock_mongo_collection.insert_one.return_value = MagicMock(
|
||||
inserted_id="mock_id_123",
|
||||
acknowledged=True
|
||||
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
|
||||
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
|
||||
modified_count=1, matched_count=1, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.update_many.return_value = MagicMock(
|
||||
modified_count=2,
|
||||
matched_count=2,
|
||||
acknowledged=True
|
||||
modified_count=2, matched_count=2, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.delete_one.return_value = MagicMock(
|
||||
deleted_count=1,
|
||||
acknowledged=True
|
||||
deleted_count=1, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.delete_many.return_value = MagicMock(
|
||||
deleted_count=2,
|
||||
acknowledged=True
|
||||
deleted_count=2, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.count_documents.return_value = 0
|
||||
|
||||
# Start the patch
|
||||
with patch('pymongo.MongoClient', return_value=mock_mongo_client):
|
||||
with patch("pymongo.MongoClient", return_value=mock_mongo_client):
|
||||
yield mock_mongo_client
|
||||
|
||||
|
||||
|
|
@ -112,6 +103,6 @@ def mock_celery():
|
|||
This fixture patches Celery's task.delay() to prevent actual task execution.
|
||||
"""
|
||||
# Start the patch
|
||||
with patch('celery.app.task.Task.delay') as mock_delay:
|
||||
with patch("celery.app.task.Task.delay") as mock_delay:
|
||||
mock_delay.return_value = MagicMock(id="mock-task-id")
|
||||
yield mock_delay
|
||||
yield mock_delay
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -16,7 +16,9 @@ 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(
|
||||
|
|
@ -28,7 +30,7 @@ def setup_instance(db):
|
|||
"domain": "http://localhost:8000",
|
||||
"last_checked_at": timezone.now(),
|
||||
"is_setup_done": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
return instance
|
||||
|
||||
|
|
@ -36,7 +38,9 @@ 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
|
||||
|
||||
|
||||
|
|
@ -79,7 +83,9 @@ 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")
|
||||
|
||||
|
|
@ -97,7 +103,9 @@ 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")
|
||||
|
||||
|
|
@ -163,10 +171,9 @@ class TestSignInEndpoint:
|
|||
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]
|
||||
redirect_contents = ' '.join(redirect_urls)
|
||||
redirect_contents = " ".join(redirect_urls)
|
||||
|
||||
# The actual error code for invalid password is AUTHENTICATION_FAILED_SIGN_IN
|
||||
assert "AUTHENTICATION_FAILED_SIGN_IN" in redirect_contents
|
||||
|
|
@ -201,14 +208,13 @@ class TestSignInEndpoint:
|
|||
response = django_client.post(
|
||||
url,
|
||||
{"email": "user@plane.so", "password": "user@123", "next_path": next_path},
|
||||
follow=False
|
||||
follow=False,
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect (302) without error code
|
||||
assert response.status_code == 302
|
||||
assert "error_code" not in response.url
|
||||
|
||||
|
||||
# In a real browser, the next_path would be used to build the absolute URL
|
||||
# Since we're just testing the authentication logic, we won't check for the exact URL structure
|
||||
# Instead, just verify that we're authenticated
|
||||
|
|
@ -237,16 +243,16 @@ 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
|
||||
url, {"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False
|
||||
)
|
||||
|
||||
# Check that we get a redirect
|
||||
|
|
@ -254,7 +260,10 @@ class TestMagicSignIn:
|
|||
|
||||
# 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):
|
||||
|
|
@ -263,7 +272,7 @@ class TestMagicSignIn:
|
|||
response = django_client.post(
|
||||
url,
|
||||
{"email": "nonexistent@plane.so", "code": "xxxx-xxxxx-xxxx"},
|
||||
follow=True
|
||||
follow=True,
|
||||
)
|
||||
|
||||
# Check redirect contains error code
|
||||
|
|
@ -271,7 +280,9 @@ 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")
|
||||
|
|
@ -288,9 +299,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
|
||||
url, {"email": "user@plane.so", "code": token}, follow=False
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -302,7 +311,9 @@ 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")
|
||||
|
|
@ -322,7 +333,7 @@ class TestMagicSignIn:
|
|||
response = django_client.post(
|
||||
url,
|
||||
{"email": "user@plane.so", "code": token, "next_path": next_path},
|
||||
follow=False
|
||||
follow=False,
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -357,9 +368,7 @@ class TestMagicSignUp:
|
|||
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"},
|
||||
follow=True
|
||||
url, {"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=True
|
||||
)
|
||||
|
||||
# Check redirect contains error code
|
||||
|
|
@ -370,9 +379,7 @@ class TestMagicSignUp:
|
|||
"""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
|
||||
url, {"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False
|
||||
)
|
||||
|
||||
# Check that we get a redirect
|
||||
|
|
@ -380,11 +387,16 @@ class TestMagicSignUp:
|
|||
|
||||
# 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"
|
||||
|
||||
|
|
@ -403,9 +415,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
|
||||
url, {"email": email, "code": token}, follow=False
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -420,7 +430,9 @@ 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"
|
||||
|
||||
|
|
@ -440,9 +452,7 @@ class TestMagicSignUp:
|
|||
url = reverse("magic-sign-up")
|
||||
next_path = "onboarding"
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": email, "code": token, "next_path": next_path},
|
||||
follow=False
|
||||
url, {"email": email, "code": token, "next_path": next_path}, follow=False
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -456,4 +466,4 @@ class TestMagicSignUp:
|
|||
assert User.objects.filter(email=email).exists()
|
||||
|
||||
# Check if user is authenticated
|
||||
assert "_auth_user_id" in django_client.session
|
||||
assert "_auth_user_id" in django_client.session
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ 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
|
||||
|
|
@ -30,7 +32,7 @@ class TestWorkspaceAPI:
|
|||
workspace_data = {
|
||||
"name": "Plane",
|
||||
"slug": "pla-ne-test",
|
||||
"company_name": "Plane Inc."
|
||||
"company_name": "Plane Inc.",
|
||||
}
|
||||
|
||||
# Make the request
|
||||
|
|
@ -57,15 +59,13 @@ class TestWorkspaceAPI:
|
|||
mock_workspace_seed.assert_called_once_with(response.data["id"])
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch('plane.bgtasks.workspace_seed_task.workspace_seed.delay')
|
||||
@patch("plane.bgtasks.workspace_seed_task.workspace_seed.delay")
|
||||
def test_create_duplicate_workspace(self, mock_workspace_seed, session_client):
|
||||
"""Test creating a duplicate workspace"""
|
||||
url = reverse("workspace")
|
||||
|
||||
# Create first workspace
|
||||
session_client.post(
|
||||
url, {"name": "Plane", "slug": "pla-ne"}, format="json"
|
||||
)
|
||||
session_client.post(url, {"name": "Plane", "slug": "pla-ne"}, format="json")
|
||||
|
||||
# Try to create a workspace with the same slug
|
||||
response = session_client.post(
|
||||
|
|
@ -76,4 +76,4 @@ class TestWorkspaceAPI:
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
# Optionally check the error message to confirm it's related to the duplicate slug
|
||||
assert "slug" in response.data
|
||||
assert "slug" in response.data
|
||||
|
|
|
|||
|
|
@ -2,26 +2,21 @@ import factory
|
|||
from uuid import uuid4
|
||||
from django.utils import timezone
|
||||
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
Project,
|
||||
ProjectMember
|
||||
)
|
||||
from plane.db.models import User, Workspace, WorkspaceMember, Project, ProjectMember
|
||||
|
||||
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating User instances"""
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
django_get_or_create = ('email',)
|
||||
django_get_or_create = ("email",)
|
||||
|
||||
id = factory.LazyFunction(uuid4)
|
||||
email = factory.Sequence(lambda n: f'user{n}@plane.so')
|
||||
password = factory.PostGenerationMethodCall('set_password', 'password')
|
||||
first_name = factory.Sequence(lambda n: f'First{n}')
|
||||
last_name = factory.Sequence(lambda n: f'Last{n}')
|
||||
email = factory.Sequence(lambda n: f"user{n}@plane.so")
|
||||
password = factory.PostGenerationMethodCall("set_password", "password")
|
||||
first_name = factory.Sequence(lambda n: f"First{n}")
|
||||
last_name = factory.Sequence(lambda n: f"Last{n}")
|
||||
is_active = True
|
||||
is_superuser = False
|
||||
is_staff = False
|
||||
|
|
@ -29,13 +24,14 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
class WorkspaceFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating Workspace instances"""
|
||||
|
||||
class Meta:
|
||||
model = Workspace
|
||||
django_get_or_create = ('slug',)
|
||||
django_get_or_create = ("slug",)
|
||||
|
||||
id = factory.LazyFunction(uuid4)
|
||||
name = factory.Sequence(lambda n: f'Workspace {n}')
|
||||
slug = factory.Sequence(lambda n: f'workspace-{n}')
|
||||
name = factory.Sequence(lambda n: f"Workspace {n}")
|
||||
slug = factory.Sequence(lambda n: f"workspace-{n}")
|
||||
owner = factory.SubFactory(UserFactory)
|
||||
created_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
|
|
@ -43,6 +39,7 @@ class WorkspaceFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
class WorkspaceMemberFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating WorkspaceMember instances"""
|
||||
|
||||
class Meta:
|
||||
model = WorkspaceMember
|
||||
|
||||
|
|
@ -56,21 +53,23 @@ class WorkspaceMemberFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
class ProjectFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating Project instances"""
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
django_get_or_create = ('name', 'workspace')
|
||||
django_get_or_create = ("name", "workspace")
|
||||
|
||||
id = factory.LazyFunction(uuid4)
|
||||
name = factory.Sequence(lambda n: f'Project {n}')
|
||||
name = factory.Sequence(lambda n: f"Project {n}")
|
||||
workspace = factory.SubFactory(WorkspaceFactory)
|
||||
created_by = factory.SelfAttribute('workspace.owner')
|
||||
updated_by = factory.SelfAttribute('workspace.owner')
|
||||
created_by = factory.SelfAttribute("workspace.owner")
|
||||
updated_by = factory.SelfAttribute("workspace.owner")
|
||||
created_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
|
||||
|
||||
class ProjectMemberFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating ProjectMember instances"""
|
||||
|
||||
class Meta:
|
||||
model = ProjectMember
|
||||
|
||||
|
|
@ -79,4 +78,4 @@ class ProjectMemberFactory(factory.django.DjangoModelFactory):
|
|||
member = factory.SubFactory(UserFactory)
|
||||
role = 20 # Admin role by default
|
||||
created_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
|
|
|
|||
|
|
@ -16,72 +16,79 @@ class TestAuthSmoke:
|
|||
|
||||
# 1. Test bad login - test with wrong password
|
||||
response = requests.post(
|
||||
url,
|
||||
data={
|
||||
"email": user_data["email"],
|
||||
"password": "wrong-password"
|
||||
}
|
||||
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:
|
||||
# If API returns 200 for failures, check the response body for error indication
|
||||
if hasattr(response, 'json'):
|
||||
if hasattr(response, "json"):
|
||||
try:
|
||||
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 response should contain error details"
|
||||
assert (
|
||||
"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
|
||||
pass
|
||||
elif response.status_code in [302, 303]:
|
||||
# If it's a redirect, it should redirect to a login page or error page
|
||||
redirect_url = response.headers.get('Location', '')
|
||||
assert "error" in redirect_url or "sign-in" in redirect_url, \
|
||||
redirect_url = response.headers.get("Location", "")
|
||||
assert "error" in redirect_url or "sign-in" in redirect_url, (
|
||||
"Failed login should redirect to login page or error page"
|
||||
)
|
||||
|
||||
# 2. Test good login with correct credentials
|
||||
response = requests.post(
|
||||
url,
|
||||
data={
|
||||
"email": user_data["email"],
|
||||
"password": user_data["password"]
|
||||
},
|
||||
allow_redirects=False # Don't follow redirects
|
||||
data={"email": user_data["email"], "password": user_data["password"]},
|
||||
allow_redirects=False, # Don't follow redirects
|
||||
)
|
||||
|
||||
# Successful auth should not be a client error or server error
|
||||
assert response.status_code not in range(400, 600), \
|
||||
assert response.status_code not in range(400, 600), (
|
||||
f"Authentication with valid credentials failed with status {response.status_code}"
|
||||
)
|
||||
|
||||
# Specific validation based on response type
|
||||
if response.status_code in [302, 303]:
|
||||
# Redirect-based auth: check that redirect URL doesn't contain error
|
||||
redirect_url = response.headers.get('Location', '')
|
||||
assert "error" not in redirect_url and "error_code" not in redirect_url, \
|
||||
redirect_url = response.headers.get("Location", "")
|
||||
assert "error" not in redirect_url and "error_code" not in redirect_url, (
|
||||
"Successful login redirect should not contain error parameters"
|
||||
)
|
||||
|
||||
elif response.status_code == 200:
|
||||
# API token-based auth: check for tokens or user session
|
||||
if hasattr(response, 'json'):
|
||||
if hasattr(response, "json"):
|
||||
try:
|
||||
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
|
||||
|
|
@ -97,4 +104,4 @@ class TestHealthCheckSmoke:
|
|||
response = requests.get(f"{plane_server.url}/")
|
||||
|
||||
# Should be OK
|
||||
assert response.status_code == 200, "Health check endpoint should return 200 OK"
|
||||
assert response.status_code == 200, "Health check endpoint should return 200 OK"
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ class TestWorkspaceModel:
|
|||
"""Test creating a workspace"""
|
||||
# Create a workspace
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace",
|
||||
slug="test-workspace",
|
||||
id=uuid4(),
|
||||
owner=create_user
|
||||
name="Test Workspace", slug="test-workspace", id=uuid4(), owner=create_user
|
||||
)
|
||||
|
||||
# Verify it was created
|
||||
|
|
@ -30,21 +27,18 @@ class TestWorkspaceModel:
|
|||
"""Test creating a workspace member"""
|
||||
# Create a workspace
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace",
|
||||
slug="test-workspace",
|
||||
id=uuid4(),
|
||||
owner=create_user
|
||||
name="Test Workspace", slug="test-workspace", id=uuid4(), owner=create_user
|
||||
)
|
||||
|
||||
# Create a workspace member
|
||||
workspace_member = WorkspaceMember.objects.create(
|
||||
workspace=workspace,
|
||||
member=create_user,
|
||||
role=20 # Admin role
|
||||
role=20, # Admin role
|
||||
)
|
||||
|
||||
# Verify it was created
|
||||
assert workspace_member.id is not None
|
||||
assert workspace_member.workspace == workspace
|
||||
assert workspace_member.member == create_user
|
||||
assert workspace_member.role == 20
|
||||
assert workspace_member.role == 20
|
||||
|
|
|
|||
|
|
@ -13,18 +13,13 @@ class TestWorkspaceLiteSerializer:
|
|||
"""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"
|
||||
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
|
||||
name="Test Workspace", slug="test-workspace", id=workspace_id, owner=owner
|
||||
)
|
||||
|
||||
# Serialize the workspace
|
||||
|
|
@ -43,23 +38,17 @@ class TestWorkspaceLiteSerializer:
|
|||
"""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"
|
||||
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
|
||||
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"}
|
||||
workspace, data={"name": "Updated Name", "slug": "updated-slug"}
|
||||
)
|
||||
|
||||
# Serializer should be valid (since read-only fields are ignored)
|
||||
|
|
@ -68,4 +57,4 @@ class TestWorkspaceLiteSerializer:
|
|||
# Save should not update the read-only fields
|
||||
updated_workspace = serializer.save()
|
||||
assert updated_workspace.name == "Test Workspace"
|
||||
assert updated_workspace.slug == "test-workspace"
|
||||
assert updated_workspace.slug == "test-workspace"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ 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"""
|
||||
|
|
@ -46,4 +48,6 @@ 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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ def paginate(base_queryset, queryset, cursor, on_result):
|
|||
paginated_data = queryset[start_index:end_index]
|
||||
|
||||
# Create the pagination info object
|
||||
prev_cursor = f"{page_size}:{cursor_object.current_page-1}:0"
|
||||
prev_cursor = f"{page_size}:{cursor_object.current_page - 1}:0"
|
||||
cursor = f"{page_size}:{cursor_object.current_page}:0"
|
||||
next_cursor = None
|
||||
if end_index < total_results:
|
||||
next_cursor = f"{page_size}:{cursor_object.current_page+1}:0"
|
||||
next_cursor = f"{page_size}:{cursor_object.current_page + 1}:0"
|
||||
|
||||
prev_page_results = False
|
||||
if cursor_object.current_page > 0:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Cursor:
|
|||
|
||||
# Return the representation of the cursor
|
||||
def __repr__(self):
|
||||
return f"{type(self).__name__,}: value={self.value} offset={self.offset}, is_prev={int(self.is_prev)}"
|
||||
return f"{(type(self).__name__,)}: value={self.value} offset={self.offset}, is_prev={int(self.is_prev)}" # noqa: E501
|
||||
|
||||
# Return if the cursor is true
|
||||
def __bool__(self):
|
||||
|
|
|
|||
|
|
@ -6,36 +6,20 @@ import sys
|
|||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run Plane tests")
|
||||
parser.add_argument("-u", "--unit", action="store_true", help="Run unit tests only")
|
||||
parser.add_argument(
|
||||
"-u", "--unit",
|
||||
action="store_true",
|
||||
help="Run unit tests only"
|
||||
"-c", "--contract", action="store_true", help="Run contract tests only"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--contract",
|
||||
action="store_true",
|
||||
help="Run contract tests only"
|
||||
"-s", "--smoke", action="store_true", help="Run smoke tests only"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--smoke",
|
||||
action="store_true",
|
||||
help="Run smoke tests only"
|
||||
"-o", "--coverage", action="store_true", help="Generate coverage report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--coverage",
|
||||
action="store_true",
|
||||
help="Generate coverage report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--parallel",
|
||||
action="store_true",
|
||||
help="Run tests in parallel"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Verbose output"
|
||||
"-p", "--parallel", action="store_true", help="Run tests in parallel"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Build command
|
||||
|
|
@ -71,10 +55,10 @@ def main():
|
|||
|
||||
# Print command
|
||||
print(f"Running: {' '.join(cmd)}")
|
||||
|
||||
|
||||
# Execute command
|
||||
result = subprocess.run(cmd)
|
||||
|
||||
|
||||
# Check coverage thresholds if coverage is enabled
|
||||
if args.coverage:
|
||||
print("Checking coverage thresholds...")
|
||||
|
|
@ -83,9 +67,9 @@ def main():
|
|||
if coverage_result.returncode != 0:
|
||||
print("Coverage below threshold (90%)")
|
||||
sys.exit(coverage_result.returncode)
|
||||
|
||||
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue