import pytest from rest_framework.test import APIClient from apps.notifications.services import store as services from apps.notifications.services import RedisNotificationStore from apps.notifications.tests.test_services import FakeRedis 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 == 403 assert _notifications_for(owner) == []