298 lines
8.8 KiB
Python
298 lines
8.8 KiB
Python
import pytest
|
|
from rest_framework.test import APIClient
|
|
|
|
from apps.notifications import services
|
|
from apps.notifications.services import RedisNotificationStore
|
|
from apps.notifications.tests.test_services import FakeRedis
|
|
from apps.projects.models import Project, ProjectMembership
|
|
from apps.users.models import User
|
|
from apps.workspaces.models import Workspace, WorkspaceMembership
|
|
|
|
|
|
@pytest.fixture()
|
|
def fake_redis(monkeypatch):
|
|
redis = FakeRedis()
|
|
monkeypatch.setattr(services, "redis_client", redis)
|
|
return redis
|
|
|
|
|
|
@pytest.fixture()
|
|
def api_client():
|
|
return APIClient()
|
|
|
|
|
|
def _create_user(index: int) -> User:
|
|
return User.objects.create_user(
|
|
mobile=f"091200000{index:02d}",
|
|
password="secret123",
|
|
first_name=f"User{index}",
|
|
)
|
|
|
|
|
|
def _notifications_for(user):
|
|
notifications, _ = RedisNotificationStore.list(
|
|
str(user.id),
|
|
paginate=False,
|
|
)
|
|
return notifications
|
|
|
|
|
|
@pytest.fixture()
|
|
def owner(db):
|
|
return _create_user(1)
|
|
|
|
|
|
@pytest.fixture()
|
|
def member(db):
|
|
return _create_user(2)
|
|
|
|
|
|
@pytest.fixture()
|
|
def another_member(db):
|
|
return _create_user(3)
|
|
|
|
|
|
@pytest.fixture()
|
|
def third_member(db):
|
|
return _create_user(4)
|
|
|
|
|
|
@pytest.fixture()
|
|
def fourth_member(db):
|
|
return _create_user(5)
|
|
|
|
|
|
def test_workspace_create_notifies_initial_members_not_owner(
|
|
fake_redis, api_client, owner, member
|
|
):
|
|
api_client.force_authenticate(user=owner)
|
|
|
|
response = api_client.post(
|
|
"/api/workspaces/",
|
|
{
|
|
"name": "Ops",
|
|
"description": "Workspace",
|
|
"members": [
|
|
{"user_id": str(member.id), "role": WorkspaceMembership.Role.ADMIN}
|
|
],
|
|
},
|
|
format="json",
|
|
)
|
|
|
|
assert response.status_code == 201
|
|
owner_notifications = _notifications_for(owner)
|
|
member_notifications = _notifications_for(member)
|
|
|
|
assert owner_notifications == []
|
|
assert len(member_notifications) == 1
|
|
assert member_notifications[0]["type"] == "workspace_membership_added"
|
|
assert member_notifications[0]["meta"]["workspace_name"] == "Ops"
|
|
assert member_notifications[0]["meta"]["new_role"] == WorkspaceMembership.Role.ADMIN
|
|
|
|
|
|
def test_workspace_membership_crud_emits_add_role_change_deactivate_and_remove(
|
|
fake_redis, api_client, owner, member
|
|
):
|
|
workspace = Workspace.objects.create(name="Design", description="", owner=owner)
|
|
api_client.force_authenticate(user=owner)
|
|
|
|
create_response = api_client.post(
|
|
"/api/workspace-memberships/",
|
|
{
|
|
"workspace": str(workspace.id),
|
|
"user": str(member.id),
|
|
"role": WorkspaceMembership.Role.MEMBER,
|
|
"is_active": True,
|
|
},
|
|
format="json",
|
|
)
|
|
assert create_response.status_code == 201
|
|
|
|
membership_id = create_response.data["id"]
|
|
notifications = _notifications_for(member)
|
|
assert [item["type"] for item in notifications] == ["workspace_membership_added"]
|
|
|
|
role_response = api_client.patch(
|
|
f"/api/workspace-memberships/{membership_id}/",
|
|
{"role": WorkspaceMembership.Role.ADMIN},
|
|
format="json",
|
|
)
|
|
assert role_response.status_code == 200
|
|
|
|
deactivate_response = api_client.patch(
|
|
f"/api/workspace-memberships/{membership_id}/",
|
|
{"is_active": False},
|
|
format="json",
|
|
)
|
|
assert deactivate_response.status_code == 200
|
|
|
|
remove_response = api_client.delete(f"/api/workspace-memberships/{membership_id}/")
|
|
assert remove_response.status_code == 204
|
|
|
|
notifications = _notifications_for(member)
|
|
assert [item["type"] for item in notifications] == [
|
|
"workspace_membership_removed",
|
|
"workspace_membership_deactivated",
|
|
"workspace_membership_role_changed",
|
|
"workspace_membership_added",
|
|
]
|
|
|
|
|
|
def test_workspace_membership_update_skips_self_notifications(
|
|
fake_redis, api_client, owner
|
|
):
|
|
workspace = Workspace.objects.create(name="Product", description="", owner=owner)
|
|
owner_membership = WorkspaceMembership.objects.get(
|
|
workspace=workspace,
|
|
user=owner,
|
|
is_deleted=False,
|
|
)
|
|
api_client.force_authenticate(user=owner)
|
|
|
|
response = api_client.patch(
|
|
f"/api/workspace-memberships/{owner_membership.id}/",
|
|
{"role": WorkspaceMembership.Role.OWNER},
|
|
format="json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert _notifications_for(owner) == []
|
|
|
|
|
|
def test_project_create_notifies_initial_members_not_creator(
|
|
fake_redis, api_client, owner, member
|
|
):
|
|
workspace = Workspace.objects.create(name="Engineering", description="", owner=owner)
|
|
api_client.force_authenticate(user=owner)
|
|
|
|
response = api_client.post(
|
|
"/api/projects/",
|
|
{
|
|
"workspace": str(workspace.id),
|
|
"name": "API",
|
|
"description": "",
|
|
"client": None,
|
|
"members": [
|
|
{"user_id": str(member.id), "role": ProjectMembership.Role.MEMBER}
|
|
],
|
|
},
|
|
format="json",
|
|
)
|
|
|
|
assert response.status_code == 201
|
|
assert _notifications_for(owner) == []
|
|
|
|
notifications = _notifications_for(member)
|
|
assert len(notifications) == 1
|
|
assert notifications[0]["type"] == "project_membership_added"
|
|
assert notifications[0]["meta"]["project_name"] == "API"
|
|
|
|
|
|
def test_project_update_full_sync_notifies_only_real_deltas(
|
|
fake_redis, api_client, owner, member, another_member, third_member, fourth_member
|
|
):
|
|
workspace = Workspace.objects.create(name="Build", description="", owner=owner)
|
|
project = Project.objects.create(workspace=workspace, name="Platform", 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.MEMBER,
|
|
is_active=True,
|
|
)
|
|
ProjectMembership.objects.create(
|
|
project=project,
|
|
user=another_member,
|
|
role=ProjectMembership.Role.MEMBER,
|
|
is_active=True,
|
|
)
|
|
ProjectMembership.objects.create(
|
|
project=project,
|
|
user=third_member,
|
|
role=ProjectMembership.Role.MEMBER,
|
|
is_active=True,
|
|
)
|
|
|
|
api_client.force_authenticate(user=owner)
|
|
response = api_client.patch(
|
|
f"/api/projects/{project.id}/",
|
|
{
|
|
"client": None,
|
|
"members": [
|
|
{"user_id": str(member.id), "role": ProjectMembership.Role.MANAGER},
|
|
{"user_id": str(third_member.id), "role": ProjectMembership.Role.MEMBER},
|
|
{"user_id": str(fourth_member.id), "role": ProjectMembership.Role.MEMBER},
|
|
],
|
|
},
|
|
format="json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert [item["type"] for item in _notifications_for(member)] == [
|
|
"project_membership_role_changed"
|
|
]
|
|
assert [item["type"] for item in _notifications_for(another_member)] == [
|
|
"project_membership_deactivated"
|
|
]
|
|
assert _notifications_for(third_member) == []
|
|
assert [item["type"] for item in _notifications_for(fourth_member)] == [
|
|
"project_membership_added"
|
|
]
|
|
assert _notifications_for(owner) == []
|
|
|
|
|
|
def test_project_membership_crud_emits_add_role_change_deactivate_and_remove(
|
|
fake_redis, api_client, owner, member
|
|
):
|
|
workspace = Workspace.objects.create(name="Data", description="", owner=owner)
|
|
project = Project.objects.create(workspace=workspace, name="Warehouse", description="")
|
|
ProjectMembership.objects.create(
|
|
project=project,
|
|
user=owner,
|
|
role=ProjectMembership.Role.MANAGER,
|
|
is_active=True,
|
|
)
|
|
api_client.force_authenticate(user=owner)
|
|
|
|
create_response = api_client.post(
|
|
"/api/memberships/",
|
|
{
|
|
"project_id": str(project.id),
|
|
"user_id": str(member.id),
|
|
"role": ProjectMembership.Role.MEMBER,
|
|
},
|
|
format="json",
|
|
)
|
|
assert create_response.status_code == 201
|
|
membership_id = create_response.data["id"]
|
|
|
|
role_response = api_client.patch(
|
|
f"/api/memberships/{membership_id}/",
|
|
{"role": ProjectMembership.Role.MANAGER},
|
|
format="json",
|
|
)
|
|
assert role_response.status_code == 200
|
|
|
|
deactivate_response = api_client.patch(
|
|
f"/api/memberships/{membership_id}/",
|
|
{"is_active": False},
|
|
format="json",
|
|
)
|
|
assert deactivate_response.status_code == 200
|
|
|
|
remove_response = api_client.delete(f"/api/memberships/{membership_id}/")
|
|
assert remove_response.status_code == 204
|
|
|
|
notifications = _notifications_for(member)
|
|
assert [item["type"] for item in notifications] == [
|
|
"project_membership_removed",
|
|
"project_membership_deactivated",
|
|
"project_membership_role_changed",
|
|
"project_membership_added",
|
|
]
|