259 lines
7.8 KiB
Python
259 lines
7.8 KiB
Python
from datetime import timedelta
|
|
|
|
import pytest
|
|
from django.utils import timezone
|
|
from rest_framework.test import APIClient
|
|
|
|
from apps.clients.models import Client
|
|
from apps.projects.models import Project, ProjectMembership
|
|
from apps.tags.models import Tag
|
|
from apps.users.models import User
|
|
from apps.workspaces.models import Workspace, WorkspaceMembership
|
|
|
|
|
|
@pytest.fixture()
|
|
def api_client():
|
|
return APIClient()
|
|
|
|
|
|
def _user(index: int) -> User:
|
|
return User.objects.create_user(
|
|
mobile=f"091255500{index:02d}",
|
|
password="secret123",
|
|
first_name=f"User{index}",
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def owner(db):
|
|
return _user(1)
|
|
|
|
|
|
@pytest.fixture()
|
|
def admin(db):
|
|
return _user(2)
|
|
|
|
|
|
@pytest.fixture()
|
|
def member(db):
|
|
return _user(3)
|
|
|
|
|
|
@pytest.fixture()
|
|
def guest(db):
|
|
return _user(4)
|
|
|
|
|
|
@pytest.fixture()
|
|
def extra_owner(db):
|
|
return _user(5)
|
|
|
|
|
|
@pytest.fixture()
|
|
def workspace(owner, admin, member, guest):
|
|
workspace = Workspace.objects.create(name="Ops", description="", owner=owner)
|
|
WorkspaceMembership.objects.create(
|
|
workspace=workspace,
|
|
user=admin,
|
|
role=WorkspaceMembership.Role.ADMIN,
|
|
is_active=True,
|
|
)
|
|
WorkspaceMembership.objects.create(
|
|
workspace=workspace,
|
|
user=member,
|
|
role=WorkspaceMembership.Role.MEMBER,
|
|
is_active=True,
|
|
)
|
|
WorkspaceMembership.objects.create(
|
|
workspace=workspace,
|
|
user=guest,
|
|
role=WorkspaceMembership.Role.GUEST,
|
|
is_active=True,
|
|
)
|
|
return workspace
|
|
|
|
|
|
@pytest.fixture()
|
|
def project(workspace, owner, member):
|
|
project = Project.objects.create(workspace=workspace, name="Alpha", description="")
|
|
ProjectMembership.objects.create(
|
|
project=project,
|
|
user=owner,
|
|
role=ProjectMembership.Role.MANAGER,
|
|
is_active=True,
|
|
)
|
|
ProjectMembership.objects.create(
|
|
project=project,
|
|
user=member,
|
|
role=ProjectMembership.Role.MANAGER,
|
|
is_active=True,
|
|
)
|
|
return project
|
|
|
|
|
|
def test_member_is_read_only_for_clients_and_projects(api_client, member, workspace, project):
|
|
client = Client.objects.create(workspace=workspace, name="Existing Client", notes="")
|
|
api_client.force_authenticate(user=member)
|
|
|
|
client_response = api_client.post(
|
|
"/api/clients/",
|
|
{"workspace_id": str(workspace.id), "name": "Acme", "notes": ""},
|
|
format="json",
|
|
)
|
|
update_client_response = api_client.patch(
|
|
f"/api/clients/{client.id}/",
|
|
{"name": "Updated"},
|
|
format="json",
|
|
)
|
|
delete_client_response = api_client.delete(f"/api/clients/{client.id}/")
|
|
project_response = api_client.post(
|
|
"/api/projects/",
|
|
{"workspace": str(workspace.id), "name": "Beta", "description": "", "client": None},
|
|
format="json",
|
|
)
|
|
update_project_response = api_client.patch(
|
|
f"/api/projects/{project.id}/",
|
|
{"description": "Blocked edit"},
|
|
format="json",
|
|
)
|
|
archive_project_response = api_client.post(f"/api/projects/{project.id}/archive/")
|
|
delete_project_response = api_client.delete(f"/api/projects/{project.id}/")
|
|
membership_response = api_client.post(
|
|
"/api/memberships/",
|
|
{
|
|
"project_id": str(project.id),
|
|
"user_id": str(workspace.owner_id),
|
|
"role": ProjectMembership.Role.MEMBER,
|
|
},
|
|
format="json",
|
|
)
|
|
|
|
assert client_response.status_code == 403
|
|
assert update_client_response.status_code == 403
|
|
assert delete_client_response.status_code == 403
|
|
assert project_response.status_code == 403
|
|
assert update_project_response.status_code == 403
|
|
assert archive_project_response.status_code == 403
|
|
assert delete_project_response.status_code == 403
|
|
assert membership_response.status_code == 403
|
|
|
|
|
|
def test_member_can_create_tags_and_manage_own_time_entries(api_client, owner, member, workspace):
|
|
tag = Tag.objects.create(workspace=workspace, name="Existing", color="#000000")
|
|
api_client.force_authenticate(user=member)
|
|
|
|
create_tag_response = api_client.post(
|
|
"/api/tags/",
|
|
{"workspace_id": str(workspace.id), "name": "New Tag", "color": "#ffffff"},
|
|
format="json",
|
|
)
|
|
update_tag_response = api_client.patch(
|
|
f"/api/tags/{tag.id}/",
|
|
{"name": "Changed"},
|
|
format="json",
|
|
)
|
|
delete_tag_response = api_client.delete(f"/api/tags/{tag.id}/")
|
|
|
|
now = timezone.now()
|
|
create_entry_response = api_client.post(
|
|
"/api/time-entries/",
|
|
{
|
|
"workspace_id": str(workspace.id),
|
|
"start_time": now.isoformat(),
|
|
"end_time": (now + timedelta(hours=1)).isoformat(),
|
|
"description": "Focus block",
|
|
},
|
|
format="json",
|
|
)
|
|
|
|
assert create_tag_response.status_code == 201
|
|
assert update_tag_response.status_code == 403
|
|
assert delete_tag_response.status_code == 403
|
|
assert create_entry_response.status_code == 201
|
|
|
|
entry_id = create_entry_response.data["id"]
|
|
update_entry_response = api_client.patch(
|
|
f"/api/time-entries/{entry_id}/",
|
|
{"description": "Updated focus block"},
|
|
format="json",
|
|
)
|
|
delete_entry_response = api_client.delete(f"/api/time-entries/{entry_id}/")
|
|
|
|
assert update_entry_response.status_code == 200
|
|
assert delete_entry_response.status_code == 204
|
|
|
|
|
|
def test_guest_is_read_only_for_workspace_resources(api_client, owner, guest, workspace, project):
|
|
Client.objects.create(workspace=workspace, name="Visible Client", notes="")
|
|
Tag.objects.create(workspace=workspace, name="Visible Tag", color="#123456")
|
|
|
|
api_client.force_authenticate(user=guest)
|
|
|
|
list_clients_response = api_client.get(f"/api/clients/?workspace={workspace.id}")
|
|
list_projects_response = api_client.get(f"/api/projects/?workspace={workspace.id}")
|
|
create_tag_response = api_client.post(
|
|
"/api/tags/",
|
|
{"workspace_id": str(workspace.id), "name": "Blocked", "color": "#ffffff"},
|
|
format="json",
|
|
)
|
|
create_entry_response = api_client.post(
|
|
"/api/time-entries/",
|
|
{
|
|
"workspace_id": str(workspace.id),
|
|
"start_time": timezone.now().isoformat(),
|
|
"description": "Blocked guest entry",
|
|
},
|
|
format="json",
|
|
)
|
|
edit_project_response = api_client.patch(
|
|
f"/api/projects/{project.id}/",
|
|
{"description": "Blocked"},
|
|
format="json",
|
|
)
|
|
|
|
assert list_clients_response.status_code == 200
|
|
assert list_projects_response.status_code == 200
|
|
assert create_tag_response.status_code == 403
|
|
assert create_entry_response.status_code == 403
|
|
assert edit_project_response.status_code == 404
|
|
|
|
|
|
def test_member_project_manager_cannot_edit_project(api_client, member, project):
|
|
api_client.force_authenticate(user=member)
|
|
|
|
response = api_client.patch(
|
|
f"/api/projects/{project.id}/",
|
|
{"description": "Still blocked"},
|
|
format="json",
|
|
)
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
|
def test_admin_cannot_change_owner_membership_but_canonical_owner_can(
|
|
api_client, owner, admin, extra_owner, workspace
|
|
):
|
|
extra_owner_membership = WorkspaceMembership.objects.create(
|
|
workspace=workspace,
|
|
user=extra_owner,
|
|
role=WorkspaceMembership.Role.OWNER,
|
|
is_active=True,
|
|
)
|
|
|
|
api_client.force_authenticate(user=admin)
|
|
admin_response = api_client.patch(
|
|
f"/api/workspace-memberships/{extra_owner_membership.id}/",
|
|
{"role": WorkspaceMembership.Role.ADMIN},
|
|
format="json",
|
|
)
|
|
|
|
api_client.force_authenticate(user=owner)
|
|
owner_response = api_client.patch(
|
|
f"/api/workspace-memberships/{extra_owner_membership.id}/",
|
|
{"role": WorkspaceMembership.Role.ADMIN},
|
|
format="json",
|
|
)
|
|
|
|
assert admin_response.status_code == 403
|
|
assert owner_response.status_code == 200
|