#  Copyright (C) 2015 Catalyst IT Ltd
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from unittest import mock

from confspirator.tests import utils as conf_utils
from django.core import mail
from rest_framework import status

from adjutant.api.models import Token, Notification
from adjutant.tasks.models import Task
from adjutant.tasks.v1.projects import CreateProjectAndUser
from adjutant.common.tests.fake_clients import FakeManager, setup_identity_cache
from adjutant.common.tests import fake_clients
from adjutant.common.tests.utils import AdjutantAPITestCase
from adjutant.config import CONF


@mock.patch("adjutant.common.user_store.IdentityManager", FakeManager)
class DelegateAPITests(AdjutantAPITestCase):
    """
    Tests to ensure the approval/token workflow does what is
    expected with the given DelegateAPIs. These test don't check
    final results for actions, simply that the tasks, action,
    and tokens are created/updated.
    """

    def test_bad_data(self):
        """
        Simple test to confirm the serializers are correctly processing
        wrong data or missing fields.
        """
        project = fake_clients.FakeProject(name="test_project")

        setup_identity_cache(projects=[project])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "wrong_email_field": "test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(
            response.json(), {"errors": {"email": ["This field is required."]}}
        )

        data = {
            "email": "not_a_valid_email",
            "roles": ["not_a_valid_role"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(
            response.json(),
            {
                "errors": {
                    "email": ["Enter a valid email address."],
                    "roles": ['"not_a_valid_role" is not a valid choice.'],
                }
            },
        )

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.invite_user_to_project.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "invite_user_to_project"},
                    },
                },
            ],
        },
    )
    def test_new_user(self):
        """
        Ensure the new user workflow goes as expected.
        Create task, create token, submit token.
        """
        project = fake_clients.FakeProject(name="test_project")

        setup_identity_cache(projects=[project])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, "invite_user_to_project")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "testpassword"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(mail.outbox), 2)
        self.assertEqual(
            fake_clients.identity_cache["new_users"][0].name, "test@example.com"
        )

    def test_new_user_no_project(self):
        """
        Can't create a user for a non-existent project.
        """
        setup_identity_cache()

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": "test_project_id",
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {"errors": ["actions invalid"]})

    def test_new_user_not_my_project(self):
        """
        Can't create a user for project that user isn't'
        project admin or mod on.
        """
        setup_identity_cache()

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "member",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": "test_project_id",
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_new_user_not_authenticated(self):
        """
        Can't create a user if unauthenticated.
        """

        setup_identity_cache()

        url = "/v1/actions/InviteUser"
        headers = {}
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": "test_project_id",
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
        self.assertEqual(
            response.json(), {"errors": ["Credentials incorrect or none given."]}
        )

    def test_add_user_existing(self):
        """
        Adding existing user to project.
        """
        project = fake_clients.FakeProject(name="parent_project")

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(projects=[project], users=[user])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"confirm": True}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_add_user_existing_with_role(self):
        """
        Adding existing user to project.
        Already has role.
        Should 'complete' anyway but do nothing.
        """

        project = fake_clients.FakeProject(name="test_project")

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        assignment = fake_clients.FakeRoleAssignment(
            scope={"project": {"id": project.id}},
            role_name="member",
            user={"id": user.id},
        )

        setup_identity_cache(
            projects=[project], users=[user], role_assignments=[assignment]
        )

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        tasks = Task.objects.all()
        self.assertEqual(1, len(tasks))
        self.assertTrue(tasks[0].completed)

    def test_new_project(self):
        """
        Ensure the new project workflow goes as expected.
        """

        setup_identity_cache()

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "admin,member",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        new_task = Task.objects.all()[0]
        url = "/v1/tasks/" + new_task.uuid
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["created token"]})

        new_project = fake_clients.identity_cache["new_projects"][0]
        self.assertEqual(new_project.name, "test_project")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "testpassword"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.create_project_and_user.notifications": [
                {
                    "operation": "override",
                    "value": {
                        "standard_handler_config": {
                            "EmailNotification": {
                                "emails": ["example_notification@example.com"],
                                "reply": "no-reply@example.com",
                            }
                        }
                    },
                },
            ],
        },
    )
    def test_new_project_invalid_on_submit(self):
        """
        Ensures that when a project becomes invalid at the submit stage
        that the a 400 is recieved and no final emails are sent.
        """

        setup_identity_cache()

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "admin,member",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        new_task = Task.objects.all()[0]
        url = "/v1/tasks/" + new_task.uuid
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.data, {"notes": ["created token"]})
        self.assertEqual(len(mail.outbox), 3)

        fake_clients.identity_cache["projects"] = {}

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "testpassword"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(len(mail.outbox), 3)

    def test_new_project_existing(self):
        """
        Test to ensure validation marks actions as invalid
        if project is already present.
        """

        project = fake_clients.FakeProject(name="test_project")

        setup_identity_cache(projects=[project])

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "admin,member",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        new_task = Task.objects.all()[0]
        url = "/v1/tasks/" + new_task.uuid
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {"errors": ["actions invalid"]})

    def test_new_project_existing_user(self):
        """
        Project created if not present, existing user attached.
        No token should be needed.
        """

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        # unauthenticated sign up as existing user
        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": user.email}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        # approve the sign-up as admin
        headers = {
            "project_name": "admin_project",
            "project_id": "admin_project_id",
            "roles": "admin,member",
            "username": "admin",
            "user_id": "admin_id",
            "authenticated": True,
        }
        new_task = Task.objects.all()[0]
        url = "/v1/tasks/" + new_task.uuid
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.json(), {"notes": ["Task completed successfully."]})

    def test_new_project_existing_project_new_user(self):
        """
        Project already exists but new user attempting to create it.
        """
        setup_identity_cache()

        # create signup#1 - project1 with user 1
        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        # Create signup#2 - project1 with user 2
        data = {"project_name": "test_project", "email": "test2@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        headers = {
            "project_name": "admin_project",
            "project_id": "admin_project_id",
            "roles": "admin,member",
            "username": "admin",
            "user_id": "admin_id",
            "authenticated": True,
        }
        # approve signup #1
        new_task1 = Task.objects.all()[0]
        url = "/v1/tasks/" + new_task1.uuid
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["created token"]})

        # Attempt to approve signup #2
        new_task2 = Task.objects.all()[1]
        url = "/v1/tasks/" + new_task2.uuid
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {"errors": ["actions invalid"]})

    def test_reset_user(self):
        """
        Ensure the reset user workflow goes as expected.
        Create task + create token, submit token.
        """

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/ResetPassword"
        data = {"email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(
            response.json()["notes"],
            ["If user with email exists, reset token will be issued."],
        )

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "new_test_password"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.password, "new_test_password")

    def test_reset_user_duplicate(self):
        """
        Request password reset twice in a row
        The first token should become invalid, with the second replacing it.

        """

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        # Submit password reset
        url = "/v1/actions/ResetPassword"
        data = {"email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(
            response.json()["notes"],
            ["If user with email exists, reset token will be issued."],
        )

        # Verify the first token doesn't work
        first_token = Token.objects.all()[0]

        # Submit password reset again
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(
            response.json()["notes"],
            ["If user with email exists, reset token will be issued."],
        )

        # confirm the old toke has been cleared:
        second_token = Token.objects.all()[0]
        self.assertNotEqual(first_token.token, second_token.token)

        # Now reset with the second token
        url = "/v1/tokens/" + second_token.token
        data = {"password": "new_test_password2"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.password, "new_test_password2")

    def test_reset_user_no_existing(self):
        """
        Actions should be successful, so usernames are not exposed.
        """

        setup_identity_cache()

        url = "/v1/actions/ResetPassword"
        data = {"email": "test@exampleinvalid.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(
            response.json()["notes"],
            ["If user with email exists, reset token will be issued."],
        )

        self.assertFalse(len(Token.objects.all()))

    def test_notification_CreateProjectAndUser(self):
        """
        CreateProjectAndUser should create a notification.
        We should be able to grab it.
        """
        setup_identity_cache()

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        new_task = Task.objects.all()[0]

        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "admin,member",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }

        url = "/v1/notifications"
        response = self.client.get(url, headers=headers)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.json()["notifications"][0]["task"], new_task.uuid)

    def test_duplicate_tasks_new_project(self):
        """
        Ensure we can't submit duplicate tasks
        """

        setup_identity_cache()

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)

        data = {"project_name": "test_project_2", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

    def test_duplicate_tasks_new_user(self):
        """
        Ensure we can't submit duplicate tasks
        """
        project = fake_clients.FakeProject(name="test_project")

        setup_identity_cache(projects=[project])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)

        data = {
            "email": "test2@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)

    def test_update_email_task(self):
        """
        Ensure the update email workflow goes as expected.
        Create task, create token, submit token.
        """

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": user.id,
            "authenticated": True,
        }

        data = {"new_email": "new_test@example.com"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.name, "new_test@example.com")

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.update_user_email.additional_actions": [
                {"operation": "append", "value": "SendAdditionalEmailAction"},
            ],
            "adjutant.workflow.tasks.update_user_email.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "update_user_email_token"},
                    },
                },
            ],
            "adjutant.workflow.tasks.update_user_email.actions": [
                {
                    "operation": "update",
                    "value": {
                        "SendAdditionalEmailAction": {
                            "prepare": {
                                "subject": "update_user_email_additional",
                                "template": "update_user_email_started.txt",
                                "email_roles": [],
                                "email_current_user": True,
                            }
                        }
                    },
                },
            ],
        },
    )
    def test_update_email_task_send_email_to_current_user(self):
        """
        Tests the email update workflow, and ensures that when setup
        to send a confirmation email to the old email address it does.
        """

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": user.id,
            "authenticated": True,
        }

        data = {"new_email": "new_test@example.com"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.data, {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 2)

        self.assertEqual(mail.outbox[0].to, ["test@example.com"])
        self.assertEqual(mail.outbox[0].subject, "update_user_email_additional")

        self.assertEqual(mail.outbox[1].to, ["new_test@example.com"])
        self.assertEqual(mail.outbox[1].subject, "update_user_email_token")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.name, "new_test@example.com")

        self.assertEqual(len(mail.outbox), 3)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.update_user_email.additional_actions": [
                {"operation": "append", "value": "SendAdditionalEmailAction"},
            ],
            "adjutant.workflow.tasks.update_user_email.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "update_user_email_token"},
                    },
                },
            ],
            "adjutant.workflow.tasks.update_user_email.actions": [
                {
                    "operation": "update",
                    "value": {
                        "SendAdditionalEmailAction": {
                            "prepare": {
                                "subject": "update_user_email_additional",
                                "template": "update_user_email_started.txt",
                                "email_roles": [],
                                "email_current_user": True,
                            }
                        }
                    },
                },
            ],
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_update_email_task_send_email_current_name_not_email(self):
        """
        Tests the email update workflow when USERNAME_IS_EMAIL=False, and
        ensures that when setup to send a confirmation email to the old
        email address it does.
        """

        user = fake_clients.FakeUser(
            name="nkdfslnkls", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "nkdfslnkls",
            "user_id": user.id,
            "authenticated": True,
            "email": "test@example.com",
        }

        data = {"new_email": "new_test@example.com"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.data, {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 2)
        self.assertEqual(mail.outbox[0].to, ["test@example.com"])
        self.assertEqual(mail.outbox[0].subject, "update_user_email_additional")

        self.assertEqual(mail.outbox[1].to, ["new_test@example.com"])
        self.assertEqual(mail.outbox[1].subject, "update_user_email_token")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(len(mail.outbox), 3)

    def test_update_email_task_invalid_email(self):
        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": user.id,
            "authenticated": True,
        }

        data = {"new_email": "new_test@examplecom"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(
            response.json(), {"errors": {"new_email": ["Enter a valid email address."]}}
        )

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
            "adjutant.workflow.tasks.update_user_email.emails": [
                {"operation": "update", "value": {"initial": None}},
            ],
        },
    )
    def test_update_email_pre_existing_user_with_email(self):
        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        user2 = fake_clients.FakeUser(
            name="new_test@example.com", password="123", email="new_test@example.com"
        )

        setup_identity_cache(users=[user, user2])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
            "project_domain_id": "default",
        }

        data = {"new_email": "new_test@example.com"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {"errors": ["actions invalid"]})
        self.assertEqual(len(Token.objects.all()), 0)

        self.assertEqual(len(mail.outbox), 0)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
            "adjutant.workflow.tasks.update_user_email.emails": [
                {"operation": "update", "value": {"initial": None}},
            ],
        },
    )
    def test_update_email_user_with_email_username_not_email(self):
        user = fake_clients.FakeUser(
            name="test", password="123", email="test@example.com"
        )

        user2 = fake_clients.FakeUser(
            name="new_test", password="123", email="new_test@example.com"
        )

        setup_identity_cache(users=[user, user2])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": user.id,
            "authenticated": True,
        }

        data = {"new_email": "new_test@example.com"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 2)

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.email, "new_test@example.com")
        self.assertEqual(len(mail.outbox), 3)

    def test_update_email_task_not_authenticated(self):
        """
        Ensure that an unauthenticated user cant access the endpoint.
        """

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/UpdateEmail"
        headers = {}

        data = {"new_email": "new_test@examplecom"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_update_email_task_username_not_email(self):
        user = fake_clients.FakeUser(
            name="test_user", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/UpdateEmail"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test_user",
            "user_id": user.id,
            "authenticated": True,
        }

        data = {"new_email": "new_test@example.com"}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.name, "test_user")
        self.assertEqual(user.email, "new_test@example.com")

    # Tests for USERNAME_IS_EMAIL=False
    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "invite_user_to_project"},
                    },
                },
            ],
        },
    )
    def test_invite_user_to_project_email_not_username(self):
        """
        Invites a user where the email is different to the username.
        """
        project = fake_clients.FakeProject(name="test_project")

        setup_identity_cache(projects=[project])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "user",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "username": "new_user",
            "email": "new@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, "invite_user_to_project")
        self.assertEqual(mail.outbox[0].to[0], "new@example.com")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "testpassword"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(mail.outbox), 2)

        self.assertEqual(fake_clients.identity_cache["new_users"][0].name, "new_user")

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
            "adjutant.workflow.tasks.reset_user_password.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "Password Reset for OpenStack"},
                    },
                },
            ],
        },
    )
    def test_reset_user_username_not_email(self):
        """
        Ensure the reset user workflow goes as expected.
        Create task + create token, submit token.
        """

        user = fake_clients.FakeUser(
            name="test_user", password="123", email="test@example.com"
        )

        setup_identity_cache(users=[user])

        url = "/v1/actions/ResetPassword"
        # NOTE(amelia): Requiring both username and email here may be
        #               a slight issue for various UIs as typically a
        #               forgotten password screen only asks for the
        #               email address, however there isn't a very
        #               good way to address this as keystone doesn't
        #               store emails in their own field
        #               Currently this is an issue for the forked adjutant
        #               horizon
        data = {"email": "test@example.com", "username": "test_user"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(
            response.json()["notes"],
            ["If user with email exists, reset token will be issued."],
        )

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, "Password Reset for OpenStack")
        self.assertEqual(mail.outbox[0].to[0], "test@example.com")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "new_test_password"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(user.password, "new_test_password")

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.username_is_email": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_new_project_username_not_email(self):
        setup_identity_cache()

        url = "/v1/actions/CreateProjectAndUser"
        data = {
            "project_name": "test_project",
            "email": "test@example.com",
            "username": "test",
        }
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        data = {
            "email": "new_test@example.com",
            "username": "new",
            "project_name": "new_project",
        }
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        new_task = Task.objects.all()[0]
        url = "/v1/tasks/" + new_task.uuid

        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "admin",
            "username": "test",
            "user_id": "test_user_id",
            "email": "test@example.com",
            "authenticated": True,
        }
        response = self.client.post(
            url, {"approved": True}, format="json", headers=headers
        )
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True, "password": "1234"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.invite_user_to_project.additional_actions": [
                {"operation": "append", "value": "SendAdditionalEmailAction"},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.emails": [
                {"operation": "update", "value": {"initial": None}},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.actions": [
                {
                    "operation": "update",
                    "value": {
                        "SendAdditionalEmailAction": {
                            "prepare": {
                                "subject": "invite_user_to_project_additional",
                                "template": "update_user_email_started.txt",
                                "email_roles": ["project_admin"],
                            }
                        }
                    },
                },
            ],
        },
    )
    def test_additional_emails_roles(self):
        """
        Tests the sending of additional emails to a set of roles in a project
        """

        # NOTE(amelia): sending this email here is probably not the intended
        # case. It would be more useful in utils such as a quota update or a
        # child project being created that all the project admins should be
        # notified of

        project = fake_clients.FakeProject(name="test_project")

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        user2 = fake_clients.FakeUser(
            name="test2@example.com", password="123", email="test2@example.com"
        )

        user3 = fake_clients.FakeUser(
            name="test3@example.com", password="123", email="test2@example.com"
        )

        assignments = [
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="member",
                user={"id": user.id},
            ),
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="project_admin",
                user={"id": user.id},
            ),
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="member",
                user={"id": user2.id},
            ),
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="project_admin",
                user={"id": user2.id},
            ),
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="member",
                user={"id": user3.id},
            ),
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="project_mod",
                user={"id": user3.id},
            ),
        ]

        setup_identity_cache(
            projects=[project], users=[user, user2, user3], role_assignments=assignments
        )

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }

        data = {
            "email": "new_test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 2)

        self.assertEqual(len(mail.outbox[0].to), 2)
        self.assertEqual(set(mail.outbox[0].to), set([user.email, user2.email]))
        self.assertEqual(mail.outbox[0].subject, "invite_user_to_project_additional")

        # Test that the token email gets sent to the other addresses
        self.assertEqual(mail.outbox[1].to[0], "new_test@example.com")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True, "password": "1234"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.invite_user_to_project.additional_actions": [
                {"operation": "append", "value": "SendAdditionalEmailAction"},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "invite_user_to_project_token"},
                    },
                },
            ],
            "adjutant.workflow.tasks.invite_user_to_project.actions": [
                {
                    "operation": "update",
                    "value": {
                        "SendAdditionalEmailAction": {
                            "prepare": {
                                "subject": "invite_user_to_project_additional",
                                "template": "update_user_email_started.txt",
                                "email_roles": ["project_admin"],
                            }
                        }
                    },
                },
            ],
        },
    )
    def test_additional_emails_role_no_email(self):
        """
        Tests that setting email roles to something that has no people to
        send to that the update action doesn't fall over
        """

        project = fake_clients.FakeProject(name="test_project")

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        assignment = fake_clients.FakeRoleAssignment(
            scope={"project": {"id": project.id}},
            role_name="member",
            user={"id": user.id},
        )

        setup_identity_cache(
            projects=[project], users=[user], role_assignments=[assignment]
        )

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }

        data = {"email": "new_test@example.com", "roles": ["member"]}
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.data, {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 1)

        # Test that the token email gets sent to the other addresses
        self.assertEqual(mail.outbox[0].to[0], "new_test@example.com")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token

        data = {"confirm": True, "password": "1234"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.invite_user_to_project.additional_actions": [
                {"operation": "append", "value": "SendAdditionalEmailAction"},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.emails": [
                {"operation": "update", "value": {"initial": None}},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.actions": [
                {
                    "operation": "update",
                    "value": {
                        "SendAdditionalEmailAction": {
                            "prepare": {
                                "subject": "invite_user_to_project_additional",
                                "template": "update_user_email_started.txt",
                                "email_additional_addresses": ["admin@example.com"],
                            }
                        }
                    },
                },
            ],
        },
    )
    def test_email_additional_addresses(self):
        """
        Tests the sending of additional emails an admin email set in
        the conf
        """
        project = fake_clients.FakeProject(name="test_project")

        user = fake_clients.FakeUser(
            name="test@example.com", password="123", email="test@example.com"
        )

        assignments = [
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="member",
                user={"id": user.id},
            ),
            fake_clients.FakeRoleAssignment(
                scope={"project": {"id": project.id}},
                role_name="project_admin",
                user={"id": user.id},
            ),
        ]

        setup_identity_cache(
            projects=[project], users=[user], role_assignments=assignments
        )

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }

        data = {"email": "new_test@example.com", "roles": ["member"]}
        response = self.client.post(url, data, format="json", headers=headers)

        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

        self.assertEqual(len(mail.outbox), 2)

        self.assertEqual(set(mail.outbox[0].to), set(["admin@example.com"]))
        self.assertEqual(mail.outbox[0].subject, "invite_user_to_project_additional")

        # Test that the token email gets sent to the other addresses
        self.assertEqual(mail.outbox[1].to[0], "new_test@example.com")

        new_token = Token.objects.all()[0]
        url = "/v1/tokens/" + new_token.token
        data = {"password": "testpassword"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.workflow.tasks.invite_user_to_project.additional_actions": [
                {"operation": "append", "value": "SendAdditionalEmailAction"},
            ],
            "adjutant.workflow.tasks.invite_user_to_project.emails": [
                {
                    "operation": "update",
                    "value": {
                        "initial": None,
                        "token": {"subject": "invite_user_to_project_token"},
                    },
                },
            ],
            "adjutant.workflow.tasks.invite_user_to_project.actions": [
                {
                    "operation": "update",
                    "value": {
                        "SendAdditionalEmailAction": {
                            "prepare": {
                                "subject": "invite_user_to_project_additional",
                                "template": "update_user_email_started.txt",
                                "email_additional_addresses": ["admin@example.com"],
                            }
                        }
                    },
                },
            ],
        },
    )
    def test_email_additional_action_invalid(self):
        """
        The additional email actions should not send an email if the
        action is invalid.
        """

        setup_identity_cache()

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": "test_project_id",
            "roles": "project_admin,member,project_mod",
            "username": "test@example.com",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": "test_project_id",
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {"errors": ["actions invalid"]})
        self.assertEqual(len(mail.outbox), 0)

    @mock.patch("adjutant.common.tests.fake_clients.FakeManager.find_project")
    def test_all_actions_setup(self, mocked_find):
        """
        Ensures that all actions have been setup before prepare is
        run on any actions, even if we have a prepare failure.

        Deals with: bug/1745053
        """

        setup_identity_cache()

        mocked_find.side_effect = KeyError("Error forced for testing")

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_503_SERVICE_UNAVAILABLE)

        new_task = Task.objects.all()[0]

        class_conf = new_task.config
        expected_action_names = CreateProjectAndUser.default_actions[:]
        expected_action_names += class_conf.additional_actions

        actions = new_task.actions
        observed_action_names = [a.action_name for a in actions]
        self.assertEqual(observed_action_names, expected_action_names)

    @mock.patch("adjutant.common.tests.fake_clients.FakeManager.find_project")
    def test_task_error_handler(self, mocked_find):
        """
        Ensure the _handle_task_error function works as expected.
        """

        setup_identity_cache()

        mocked_find.side_effect = KeyError("Error forced for testing")

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_503_SERVICE_UNAVAILABLE)

        self.assertEqual(
            response.json(),
            {"errors": ["Service temporarily unavailable, try again later."]},
        )

        new_task = Task.objects.all()[0]
        new_notification = Notification.objects.all()[0]

        self.assertTrue(new_notification.error)
        self.assertEqual(
            new_notification.notes,
            {
                "errors": [
                    "Error: KeyError('Error forced for testing') while setting up "
                    "task. See task itself for details."
                ]
            },
        )
        self.assertEqual(new_notification.task, new_task)

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.can_edit_users": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_user_invite_cant_edit_users(self):
        """
        When can_edit_users is false, and a new user is invited,
        the task should be marked as invalid if the user doesn't
        already exist.
        """
        project = fake_clients.FakeProject(name="test_project")

        setup_identity_cache(projects=[project])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "user",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "username": "new_user",
            "email": "new@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.json(), {"errors": ["actions invalid"]})

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.can_edit_users": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_user_invite_cant_edit_users_existing_user(self):
        """
        When can_edit_users is false, and a new user is invited,
        the task should be marked as valid if the user exists.
        """
        project = fake_clients.FakeProject(name="test_project")

        user = fake_clients.FakeUser(name="test@example.com")

        setup_identity_cache(projects=[project], users=[user])

        url = "/v1/actions/InviteUser"
        headers = {
            "project_name": "test_project",
            "project_id": project.id,
            "roles": "project_admin,member,project_mod",
            "username": "user",
            "user_id": "test_user_id",
            "authenticated": True,
        }
        data = {
            "username": "new_user",
            "email": "test@example.com",
            "roles": ["member"],
            "project_id": project.id,
        }
        response = self.client.post(url, data, format="json", headers=headers)
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.can_edit_users": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_project_create_cant_edit_users(self):
        """
        When can_edit_users is false, and a new signup comes in,
        the task should be marked as invalid if it needs to
        create a new user.

        Will return OK (as task doesn't auto_approve), but task will
        actually be invalid.
        """
        setup_identity_cache()

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})
        task = Task.objects.all()[0]
        action_models = task.actions
        actions = [act.get_action() for act in action_models]
        self.assertFalse(all([act.valid for act in actions]))

    @conf_utils.modify_conf(
        CONF,
        operations={
            "adjutant.identity.can_edit_users": [
                {"operation": "override", "value": False},
            ],
        },
    )
    def test_project_create_cant_edit_users_existing_user(self):
        """
        When can_edit_users is false, and a new signup comes in,
        the task should be marked as valid if the user already
        exists.

        Will return OK (as task doesn't auto_approve), but task will
        actually be valid.
        """
        user = fake_clients.FakeUser(name="test@example.com")

        setup_identity_cache(users=[user])

        url = "/v1/actions/CreateProjectAndUser"
        data = {"project_name": "test_project", "email": "test@example.com"}
        response = self.client.post(url, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertEqual(response.json(), {"notes": ["task created"]})
        task = Task.objects.all()[0]
        action_models = task.actions
        actions = [act.get_action() for act in action_models]
        self.assertTrue(all([act.valid for act in actions]))
