bottleneckcrm/backend/tasks/tests/test_task_assignment_notify.py

262 lines
9.1 KiB
Python

"""Assigning somebody to a task or a board card emails them, and nobody else.
Accounts, contacts, cases, leads and deals all mail a newly added assignee.
Tasks sent nothing on any path: ``tasks.celery_tasks`` had a ``send_email``
task that no view called, keyed on User ids rather than Profile ids and
linking to a hard-coded demo domain. Create, PUT and PATCH on a task, and
create and update on a board card, now enqueue one job naming each newly
added assignee, as the sibling modules do. The actor is not skipped, because
the siblings do not skip them either.
"""
from unittest.mock import patch
import pytest
from django.core import mail
from common.models import Profile, User
from tasks.celery_tasks import (
send_board_card_email_to_assigned_user,
send_email_to_assigned_user,
)
from tasks.models import Board, BoardColumn, BoardMember, BoardTask, Task
URL = "/api/tasks/"
BODY = {"title": "Task", "status": "New", "priority": "High"}
NOTIFY = "tasks.updates.send_email_to_assigned_user.delay"
NOTIFY_CARD = "tasks.views.board_views.send_board_card_email_to_assigned_user.delay"
@pytest.fixture
def other_profile(org_a):
user = User.objects.create_user(email="other@test.com", password="testpass123")
return Profile.objects.create(user=user, org=org_a, role="USER", is_active=True)
@pytest.fixture
def task(org_a, admin_user, user_profile):
task = Task.objects.create(
title="Task", status="New", priority="High", org=org_a, created_by=admin_user
)
task.assigned_to.add(user_profile)
return task
@pytest.fixture
def column(org_a, admin_profile, admin_user, user_profile, other_profile):
board = Board.objects.create(
name="Board", owner=admin_profile, org=org_a, created_by=admin_user
)
BoardMember.objects.create(board=board, profile=admin_profile, role="owner")
# A card can be assigned only to people on its board.
for profile in (user_profile, other_profile):
BoardMember.objects.create(board=board, profile=profile, role="member")
return BoardColumn.objects.create(board=board, name="To Do", order=1, org=org_a)
@pytest.fixture
def card(column, org_a, admin_user, user_profile):
card = BoardTask.objects.create(
column=column, title="Card", org=org_a, created_by=admin_user
)
card.assigned_to.add(user_profile)
return card
def _ids(*profiles):
return sorted(str(p.id) for p in profiles)
@pytest.mark.django_db
class TestTaskCreateNotifies:
def test_each_assignee_is_named_once(
self,
admin_client,
org_a,
user_profile,
other_profile,
django_capture_on_commit_callbacks,
):
with patch(NOTIFY) as notify, django_capture_on_commit_callbacks(execute=True):
response = admin_client.post(
URL,
{**BODY, "assigned_to": _ids(user_profile, other_profile)},
format="json",
)
assert response.status_code == 200, response.data
task = Task.objects.get(org=org_a)
notify.assert_called_once_with(
_ids(user_profile, other_profile), str(task.id), str(org_a.id)
)
def test_no_assignees_enqueues_nothing(
self, admin_client, django_capture_on_commit_callbacks
):
with patch(NOTIFY) as notify, django_capture_on_commit_callbacks(execute=True):
response = admin_client.post(URL, BODY, format="json")
assert response.status_code == 200, response.data
notify.assert_not_called()
@pytest.mark.django_db
@pytest.mark.parametrize("method", ["put", "patch"])
class TestTaskUpdateNotifies:
def test_an_added_assignee_is_the_only_one_named(
self,
admin_client,
task,
org_a,
user_profile,
other_profile,
method,
django_capture_on_commit_callbacks,
):
with patch(NOTIFY) as notify, django_capture_on_commit_callbacks(execute=True):
response = getattr(admin_client, method)(
f"{URL}{task.id}/",
{**BODY, "assigned_to": _ids(user_profile, other_profile)},
format="json",
)
assert response.status_code == 200, response.data
notify.assert_called_once_with(
[str(other_profile.id)], str(task.id), str(org_a.id)
)
def test_unchanged_assignees_enqueue_nothing(
self,
admin_client,
task,
user_profile,
method,
django_capture_on_commit_callbacks,
):
with patch(NOTIFY) as notify, django_capture_on_commit_callbacks(execute=True):
response = getattr(admin_client, method)(
f"{URL}{task.id}/",
{**BODY, "assigned_to": _ids(user_profile)},
format="json",
)
assert response.status_code == 200, response.data
notify.assert_not_called()
def test_a_removed_assignee_enqueues_nothing(
self, admin_client, task, method, django_capture_on_commit_callbacks
):
with patch(NOTIFY) as notify, django_capture_on_commit_callbacks(execute=True):
response = getattr(admin_client, method)(
f"{URL}{task.id}/", {**BODY, "assigned_to": []}, format="json"
)
assert response.status_code == 200, response.data
assert task.assigned_to.count() == 0
notify.assert_not_called()
@pytest.mark.django_db
def test_patch_without_assigned_to_enqueues_nothing(
admin_client, task, user_profile, django_capture_on_commit_callbacks
):
with patch(NOTIFY) as notify, django_capture_on_commit_callbacks(execute=True):
response = admin_client.patch(
f"{URL}{task.id}/", {"title": "Renamed"}, format="json"
)
assert response.status_code == 200, response.data
assert list(task.assigned_to.all()) == [user_profile]
notify.assert_not_called()
@pytest.mark.django_db
class TestBoardCardNotifies:
def test_create_names_each_assignee(
self, admin_client, column, org_a, user_profile, other_profile
):
with patch(NOTIFY_CARD) as notify:
response = admin_client.post(
f"/api/boards/columns/{column.id}/tasks/",
{"title": "New", "assigned_to_ids": _ids(user_profile, other_profile)},
format="json",
)
assert response.status_code == 201, response.data
card = BoardTask.objects.get(title="New")
notify.assert_called_once_with(
_ids(user_profile, other_profile), str(card.id), str(org_a.id)
)
def test_create_without_assignees_enqueues_nothing(self, admin_client, column):
with patch(NOTIFY_CARD) as notify:
response = admin_client.post(
f"/api/boards/columns/{column.id}/tasks/",
{"title": "New"},
format="json",
)
assert response.status_code == 201, response.data
notify.assert_not_called()
def test_update_names_only_the_added_assignee(
self, admin_client, card, org_a, user_profile, other_profile
):
with patch(NOTIFY_CARD) as notify:
response = admin_client.put(
f"/api/boards/tasks/{card.id}/",
{"title": "Card", "assigned_to_ids": _ids(user_profile, other_profile)},
format="json",
)
assert response.status_code == 200, response.data
notify.assert_called_once_with(
[str(other_profile.id)], str(card.id), str(org_a.id)
)
@pytest.mark.parametrize("body", [{"assigned_to_ids": []}, {}])
def test_update_that_adds_nobody_enqueues_nothing(
self, admin_client, card, user_profile, body
):
with patch(NOTIFY_CARD) as notify:
response = admin_client.put(
f"/api/boards/tasks/{card.id}/",
{"title": "Card", **body},
format="json",
)
assert response.status_code == 200, response.data
notify.assert_not_called()
@pytest.mark.django_db
class TestTheCeleryTasks:
def test_task_email_goes_to_each_active_recipient(
self, task, org_a, user_profile, other_profile, admin_profile
):
admin_profile.is_active = False
admin_profile.save(update_fields=["is_active"])
send_email_to_assigned_user(
_ids(user_profile, other_profile, admin_profile),
str(task.id),
str(org_a.id),
)
assert sorted(m.to[0] for m in mail.outbox) == sorted(
[user_profile.user.email, other_profile.user.email]
)
assert all(f"/tasks/{task.id}" in m.body for m in mail.outbox)
def test_task_email_ignores_another_orgs_profile(self, task, org_a, profile_b):
send_email_to_assigned_user(_ids(profile_b), str(task.id), str(org_a.id))
assert mail.outbox == []
def test_card_email_links_to_its_board(self, card, org_a, user_profile):
send_board_card_email_to_assigned_user(
_ids(user_profile), str(card.id), str(org_a.id)
)
assert [m.to for m in mail.outbox] == [[user_profile.user.email]]
assert f"/tasks/board?board={card.column.board_id}" in mail.outbox[0].body