"""This module holds the test cases for the apiv2-related views of the ``ideas`` app"""

from mock import patch

from django.core.urlresolvers import reverse

from rest_framework.status import (
    HTTP_200_OK,
    HTTP_201_CREATED,
    HTTP_204_NO_CONTENT,
    HTTP_400_BAD_REQUEST,
    HTTP_403_FORBIDDEN,
)

from mtp.apps.api.tests.test_cases import MTPAPITestCase
from mtp.apps.ideas.factories import (
    BrainstormingFactory,
    BrainstormingIdeaFactory,
    BrainstormingTagFactory,
    QuestionWallFactory,
    QuestionWallIdeaFactory,
    WordCloudFactory,
    WordCloudIdeaFactory,
)
from mtp.apps.ideas.models import (
    Brainstorming,
    BrainstormingIdea,
    BrainstormingTag,
    QuestionWall,
    QuestionWallIdea,
    WordCloud,
    WordCloudIdea,
)
from mtp.apps.ideas.tests.test_cases import BaseIdeasTestCase
from mtp.apps.trainings.factories import TrainingSessionFactory


class BrainstormingViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``BrainstormingViewSet`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """Make the ``Brainstorming`` editable (training to ``DRAFT`` state)

        Also, store the "list" and "detail" urls.

        """

        super().setUpTestData()

        cls.training.version_state = cls.training.VERSION_STATES.DRAFT
        cls.training.save()

        cls.list_url = reverse('apiv2:brainstorming-list')
        cls.detail_url = reverse('apiv2:brainstorming-detail', kwargs={'pk': cls.brainstorming.pk})

    def test_list_view_should_output_the_list_of_brainstorming(self):
        """Test that the list view should output the list of ``Brainstorming`` instances"""

        self.login_as_trainer()

        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        self.assertEqual(response.status_code, HTTP_200_OK)

        # We got one brainstorming
        self.assertEqual(len(response.data), 1)

        # This is the one from our test case
        self.assertEqual(response.data[0]['id'], self.brainstorming.id)

        # New brainstorming
        new_brainstorming = BrainstormingFactory(training=None, session=self.training_session)

        # Still 4 queries !
        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        # We got two brainstorming
        self.assertEqual(len(response.data), 2)

        # Soft-delete the second brainstorming
        new_brainstorming.remove()

        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        # We got one brainstorming
        self.assertEqual(len(response.data), 1)

    def test_list_view_should_allow_to_create_new_brainstorming(self):
        """Test that the list view should allow us to create a new ``Brainstorming`` instance"""

        new_brainstorming_data = {
            'name': 'Brand new brainstorming',
            'description': 'Brainstorming description',
            'is_anonymous': True,
            'session': self.training_session.pk
        }

        self.login_as_trainer()

        response = self.client.post(
            self.list_url,
            new_brainstorming_data
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        # Our new ``Brainstorming`` exists...
        new_brainstorming = Brainstorming.objects.get(name='Brand new brainstorming')

        # ...and got correct attributes
        self.assertEqual(new_brainstorming.description, 'Brainstorming description')
        self.assertTrue(new_brainstorming.is_anonymous)
        self.assertEqual(new_brainstorming.session, self.training_session)

        # A trainee cannot create a new brainstorming
        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            new_brainstorming_data
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_output_a_single_brainstorming(self):
        """Test that the detail view should output a single ``Brainstorming`` instance"""

        self.login_as_trainer()

        with self.assertNumQueries(4):
            response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.brainstorming.id)

    def test_detail_view_should_allow_us_to_modify_the_brainstorming(self):
        """Test that the detail view should allow us to update the ``Brainstorming`` instance"""

        brainstorming_edit_data = {
            'name': 'Brand new name',
            'is_anonymous': True
        }

        self.login_as_trainer()

        old_name = self.brainstorming.name

        response = self.client.patch(
            self.detail_url,
            brainstorming_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        self.brainstorming.refresh_from_db()

        self.assertNotEqual(self.brainstorming.name, old_name)
        self.assertEqual(self.brainstorming.name, 'Brand new name')

        # A trainee cannot edit a brainstorming
        self.login_as_trainee()

        response = self.client.patch(
            self.detail_url,
            brainstorming_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_allow_us_to_soft_delete_the_brainstorming(self):
        """Test that the detail view should allow us to delete the ``Brainstorming`` instance"""

        self.login_as_trainer()

        # We got one non-deleted brainstorming...
        self.assertEqual(Brainstorming.objects.non_deleted().count(), 1)

        # ...and one in total
        self.assertEqual(Brainstorming.objects.count(), 1)

        self.client.delete(self.detail_url)

        # We got 0 non-deleted brainstorming...
        self.assertEqual(Brainstorming.objects.non_deleted().count(), 0)

        # ...but still one in total
        self.assertEqual(Brainstorming.objects.count(), 1)


class BrainstormingIdeaViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``BrainstormingIdeaViewSet`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """Store the "list", "detail" and "like" ideas related urls"""

        super().setUpTestData()

        cls.list_url = reverse('apiv2:brainstorming_idea-list', kwargs={
            'parent_lookup_brainstorming': cls.brainstorming.pk
        })
        cls.detail_url = reverse('apiv2:brainstorming_idea-detail', kwargs={
            'parent_lookup_brainstorming': cls.brainstorming.pk,
            'pk': cls.brainstorming_idea.pk,
        })
        cls.likes_url = reverse('apiv2:brainstorming_idea-likes', kwargs={
            'parent_lookup_brainstorming': cls.brainstorming.pk,
            'pk': cls.brainstorming_idea.pk,
        })

    def test_list_url_should_output_the_ideas_of_the_brainstormer(self):
        """Test that the list view should output the ``Ideas`` of the ``Brainstorming`` instance"""

        self.login_as_trainer()

        with self.assertNumQueries(6):
            response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 1)  # Only one idea

        # Create a new ``BrainstormingIdea``
        BrainstormingIdeaFactory(brainstorming=self.brainstorming, session=self.training_session)

        with self.assertNumQueries(7):
            response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 2)  # Two ideas now

        # Create a new ``BrainstormingIdea`` related to another ``Brainstorming``
        BrainstormingIdeaFactory(brainstorming=BrainstormingFactory())

        # We should still get two ideas
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

        # Create a new ``BrainstormingIdea`` related to another training session
        BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=TrainingSessionFactory()
        )

        # We should still get two ideas (the "session" filter works)
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

    def test_list_url_should_allow_create_a_new_idea(self):
        """Test that the list view should allow creating a new ``BrainstormingIdea``"""

        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            {
                'text': 'new idea',
                'user': self.trainee.id,
                'session': self.training_session.id,
                'brainstorming': self.brainstorming.id,
            },
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        new_idea = BrainstormingIdea.objects.get(text='new idea')
        self.assertEqual(new_idea.user, self.trainee)
        self.assertEqual(new_idea.session, self.training_session)
        self.assertEqual(new_idea.brainstorming, self.brainstorming)
        self.assertEqual(new_idea.tags.count(), 0)

        self.assertTrue(BrainstormingIdea.objects.filter(text='new idea').exists())

        # Now, with one tag
        response = self.client.post(
            self.list_url,
            {
                'text': 'new idea with tag',
                'user': self.trainee.id,
                'session': self.training_session.id,
                'brainstorming': self.brainstorming.id,
                'tags': [self.brainstorming_tag.id]
            },
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        new_idea_with_tag = BrainstormingIdea.objects.get(text='new idea with tag')
        self.assertEqual(new_idea_with_tag.tags.count(), 1)
        self.assertEqual(new_idea_with_tag.tags.first(), self.brainstorming_tag)

    def test_detail_url_should_allow_get_a_single_idea(self):
        """Test that the detail view should allow get a single ``BrainstormingIdea``"""

        self.login_as_trainee()

        response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.brainstorming_idea.pk)

    def test_detail_url_should_allow_update_an_idea(self):
        """Test that the detail url should allow to update a ``BrainstormingIdea``"""

        idea_edit_data = {
            'text': 'new text',
            'user': self.trainee.pk,
            'brainstorming': self.brainstorming.id
        }
        new_idea = BrainstormingIdeaFactory(
            user=self.trainee,
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        new_idea_edit_url = reverse('apiv2:brainstorming_idea-detail', kwargs={
            'parent_lookup_brainstorming': self.brainstorming.pk,
            'pk': new_idea.pk,
        })

        self.login_as_trainer()

        old_text = new_idea.text

        response = self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        new_idea.refresh_from_db()
        self.assertNotEqual(new_idea.text, old_text)
        self.assertEqual(new_idea.text, 'new text')

        # The trainee should not be able to update an idea
        self.login_as_trainee()
        response = self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_url_should_allow_us_to_remove_a_tag_from_an_idea(self):
        """Test the the detail view should allow to associate/dissociate a tag from an idea"""

        idea_edit_data = {
            'text': 'new text',
            'user': self.trainee.pk,
            'brainstorming': self.brainstorming.id,
            'tags': []
        }
        new_idea = BrainstormingIdeaFactory(
            user=self.trainee,
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        new_idea_edit_url = reverse('apiv2:brainstorming_idea-detail', kwargs={
            'parent_lookup_brainstorming': self.brainstorming.pk,
            'pk': new_idea.pk,
        })

        # At first, our idea got 0 tags
        self.assertEqual(new_idea.tags.non_deleted().count(), 0)

        self.login_as_trainer()

        idea_edit_data['tags'].append(self.brainstorming_tag.id)
        self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )

        # Our idea should have one new tag
        self.assertEqual(new_idea.tags.non_deleted().count(), 1)

        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

        idea_edit_data['tags'].append(new_tag.id)
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.client.patch(
                new_idea_edit_url,
                idea_edit_data,
                format='json'
            )

        new_idea.refresh_from_db()

        mocked.assert_called_with(
            new_idea.get_serialized_representation(),
            new_idea.get_broker_topic(),
            'UPDATE'
        )

        # Our idea should have now two tags
        self.assertEqual(new_idea.tags.non_deleted().count(), 2)

        # Remove all our tags from our idea
        idea_edit_data['tags'] = []
        self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )

        # Our idea should have now 0 tag
        self.assertEqual(new_idea.tags.non_deleted().count(), 0)

    def test_detail_url_should_allow_soft_delete_an_idea(self):
        """Test that the detail url should allow to soft-delete a ``BrainstormingIdea``"""

        new_idea = BrainstormingIdeaFactory(
            user=self.trainee,
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        session_idea_delete_url = reverse('apiv2:brainstorming_idea-detail', kwargs={
            'parent_lookup_brainstorming': self.brainstorming.pk,
            'pk': new_idea.pk,
        })

        self.login_as_trainer()

        # We got two ideas in total, two non-deleted ideas
        self.assertEqual(BrainstormingIdea.objects.count(), 2)
        self.assertEqual(BrainstormingIdea.objects.non_deleted().count(), 2)

        response = self.client.delete(session_idea_delete_url)

        self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)

        # We got two ideas in total, but only on non-deleted idea
        self.assertEqual(BrainstormingIdea.objects.count(), 2)
        self.assertEqual(BrainstormingIdea.objects.non_deleted().count(), 1)

        # The trainee should not be able to delete an idea
        self.login_as_trainee()
        response = self.client.delete(session_idea_delete_url)
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_like_url_should_allow_like_an_idea(self):
        """Test that the like url should allow like a ``BrainstormingIdea``"""

        self.login_as_trainee()

        # At first, no likes
        self.assertEqual(self.brainstorming_idea.likes.non_deleted().count(), 0)

        response = self.client.post(self.likes_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(self.brainstorming_idea.likes.non_deleted().count(), 1)

        # Now with the trainer
        self.login_as_trainer()
        self.client.post(self.likes_url)

        self.assertEqual(self.brainstorming_idea.likes.non_deleted().count(), 2)

        # Delete one of the likes
        self.client.delete(self.likes_url)

        self.assertEqual(self.brainstorming_idea.likes.non_deleted().count(), 1)


class BrainstormingTagViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``BrainstormingTagViewSet`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """store the "list" and "detail" tags related urls

        Also, create a new ``BrainstormingTag`` linked to a ``TrainingSession``.

        """

        super().setUpTestData()

        cls.list_url = reverse('apiv2:brainstorming_tag-list', kwargs={
            'parent_lookup_brainstorming': cls.brainstorming.pk
        })
        cls.detail_url = reverse('apiv2:brainstorming_tag-detail', kwargs={
            'parent_lookup_brainstorming': cls.brainstorming.pk,
            'pk': cls.brainstorming_tag.pk,
        })

    def test_list_view_should_allow_list_the_tags(self):
        """Test that the list view should allow query the ``BrainstormingTag``"""

        self.login_as_trainer()

        response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 1)  # One tag

        # Create a new tag for the current ``Brainstorming``
        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

        # We should now get two tags
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

        # Create a new tag for another brainstorming
        BrainstormingTagFactory(brainstorming=BrainstormingFactory())

        # We should still get two tags
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

        # Soft delete the second tag
        new_tag.remove()

        # We should now have only one tag
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 1)

        # Create a new tag for the current ``Brainstorming`` but for another training session
        BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=TrainingSessionFactory()
        )

        # We should still get one tag (because of the filter)
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 1)

    def test_list_view_should_allow_create_new_tag(self):
        """Test that the list view should allow creating a new ``BrainstormingTag``"""

        new_tag_data = {
            'name': 'new tag',
            'color': '#fff',
            'brainstorming': self.brainstorming.pk,
            'session': self.training_session.pk
        }

        self.login_as_trainer()

        # First, only one tag
        self.assertEqual(BrainstormingTag.objects.count(), 1)

        response = self.client.post(
            self.list_url,
            new_tag_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        # Got the newly created tag...
        self.assertEqual(BrainstormingTag.objects.count(), 2)
        new_tag = BrainstormingTag.objects.get(name='new tag')

        # ...with the correct attributes
        self.assertEqual(new_tag.color, '#fff')
        self.assertEqual(new_tag.brainstorming, self.brainstorming)
        self.assertEqual(new_tag.session, self.training_session)

        # The trainee should not be able to create a new tag
        self.login_as_trainee()
        response = self.client.post(
            self.list_url,
            new_tag_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_allow_query_a_single_tag(self):
        """Test that the the detail view should allow query a single ``BrainstormingTag``"""

        self.login_as_trainer()

        response = self.client.get(self.detail_url)
        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.brainstorming_tag.id)

    def test_detail_view_should_allow_update_a_tag(self):
        """Test that the detail view should allow update a ``BrainstormingTag``"""

        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        tag_update_url = reverse('apiv2:brainstorming_tag-detail', kwargs={
            'parent_lookup_brainstorming': self.brainstorming.pk,
            'pk': new_tag.pk,
        })
        tag_update_data = {
            'name': 'new name',
            'brainstorming': self.brainstorming.pk,
            'session': self.training_session.pk
        }

        self.login_as_trainer()

        old_name = new_tag.name

        response = self.client.patch(
            tag_update_url,
            tag_update_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        new_tag.refresh_from_db()
        self.assertNotEqual(new_tag.name, old_name)
        self.assertEqual(new_tag.name, 'new name')

        # The trainee cannot update a tag
        self.login_as_trainee()
        response = self.client.patch(
            tag_update_url,
            tag_update_data,
            format='json'
        )
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_allow_soft_delete_a_tag(self):
        """Test that the detail view should allow soft-delete a ``BrainstormingTag``"""

        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        tag_delete_url = reverse('apiv2:brainstorming_tag-detail', kwargs={
            'parent_lookup_brainstorming': self.brainstorming.pk,
            'pk': new_tag.pk,
        })

        self.login_as_trainer()

        # Two tags in total, two tags non-deleted
        self.assertEqual(BrainstormingTag.objects.count(), 2)
        self.assertEqual(BrainstormingTag.objects.non_deleted().count(), 2)

        response = self.client.delete(tag_delete_url)

        self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)

        # Two tags in total, only one non-deleted
        self.assertEqual(BrainstormingTag.objects.count(), 2)
        self.assertEqual(BrainstormingTag.objects.non_deleted().count(), 1)

        # The trainee should not be able to delete a tag
        self.login_as_trainee()
        response = self.client.delete(tag_delete_url)
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)


class QuestionWallViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``QuestionWallViewSet`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """Make the ``QuestionWall`` editable (training to ``DRAFT`` state)

        Also, store the "list" and "detail" urls.

        """

        super().setUpTestData()

        cls.training.version_state = cls.training.VERSION_STATES.DRAFT
        cls.training.save()

        cls.list_url = reverse('apiv2:questionwall-list')
        cls.detail_url = reverse(
            'apiv2:questionwall-detail',
            kwargs={'pk': cls.questionwall.pk}
        )

    def test_list_view_should_output_the_list_of_questionwall(self):
        """Test that the list view should output the list of ``QuestionWall`` instances"""

        self.login_as_trainer()

        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        self.assertEqual(response.status_code, HTTP_200_OK)

        # We got one QuestionWall
        self.assertEqual(len(response.data), 1)

        # This is the one from our test case
        self.assertEqual(response.data[0]['id'], self.questionwall.id)

        # New QuestionWall
        new_questionwall = QuestionWallFactory(training=None, session=self.training_session)

        # Still 4 queries !
        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        # We got two questionwall
        self.assertEqual(len(response.data), 2)

        # Soft-delete the second questionwall
        new_questionwall.remove()

        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        # We got one QuestionWall
        self.assertEqual(len(response.data), 1)

    def test_list_view_should_allow_to_create_new_questionwall(self):
        """Test that the list view should allow us to create a new ``QuestionWall`` instance"""

        new_questionwall_data = {
            'name': 'Brand new questionwall',
            'description': 'QuestionWall description',
            'is_anonymous': True,
            'session': self.training_session.pk
        }

        self.login_as_trainer()

        response = self.client.post(
            self.list_url,
            new_questionwall_data
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        # Our new ``QuestionWall`` exists...
        new_questionwall = QuestionWall.objects.get(name='Brand new questionwall')

        # ...and got correct attributes
        self.assertEqual(new_questionwall.description, 'QuestionWall description')
        self.assertTrue(new_questionwall.is_anonymous)
        self.assertEqual(new_questionwall.session, self.training_session)

        # A trainee cannot create a new QuestionWall
        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            new_questionwall_data
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_output_a_single_questionwall(self):
        """Test that the detail view should output a single ``QuestionWall`` instance"""

        self.login_as_trainer()

        with self.assertNumQueries(4):
            response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.questionwall.id)

    def test_detail_view_should_allow_us_to_modify_the_questionwall(self):
        """Test that the detail view should allow us to update the ``QuestionWall`` instance"""

        questionwall_edit_data = {
            'name': 'Brand new name',
            'is_anonymous': True
        }

        self.login_as_trainer()

        old_name = self.questionwall.name

        response = self.client.patch(
            self.detail_url,
            questionwall_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        self.questionwall.refresh_from_db()

        self.assertNotEqual(self.questionwall.name, old_name)
        self.assertEqual(self.questionwall.name, 'Brand new name')

        # A trainee cannot edit a questionwall
        self.login_as_trainee()

        response = self.client.patch(
            self.detail_url,
            questionwall_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_allow_us_to_soft_delete_the_questionwall(self):
        """Test that the detail view should allow us to delete the ``QuestionWall`` instance"""

        self.login_as_trainer()

        # We got one non-deleted questionwall...
        self.assertEqual(QuestionWall.objects.non_deleted().count(), 1)

        # ...and one in total
        self.assertEqual(QuestionWall.objects.count(), 1)

        self.client.delete(self.detail_url)

        # We got 0 non-deleted questionwall...
        self.assertEqual(QuestionWall.objects.non_deleted().count(), 0)

        # ...but still one in total
        self.assertEqual(QuestionWall.objects.count(), 1)


class QuestionWallIdeaViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``QuestionWallIdea`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """Store the "list", "detail" and "like" ideas related urls

        Also, create a brand new ``QuestionWallIdea``

        """

        super().setUpTestData()

        cls.list_url = reverse('apiv2:questionwall_idea-list', kwargs={
            'parent_lookup_questionwall': cls.questionwall.pk
        })

        cls.questionwall_idea = QuestionWallIdeaFactory(questionwall=cls.questionwall)

        cls.detail_url = reverse('apiv2:questionwall_idea-detail', kwargs={
            'parent_lookup_questionwall': cls.questionwall.pk,
            'pk': cls.questionwall_idea.pk,
        })
        cls.likes_url = reverse('apiv2:questionwall_idea-likes', kwargs={
            'parent_lookup_questionwall': cls.questionwall.pk,
            'pk': cls.questionwall_idea.pk,
        })

    def test_list_url_should_output_the_ideas_of_the_questionwall(self):
        """Test that the list view should output the ``Ideas`` of the ``QuestionWall`` instance"""

        self.login_as_trainer()

        with self.assertNumQueries(5):
            response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 1)  # Only one idea

        # Create a new ``QuestionWallIdea``
        QuestionWallIdeaFactory(
            questionwall=self.questionwall,
            session=self.training_session
        )

        with self.assertNumQueries(6):  # One more query for the likes
            response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 2)  # Two ideas now

        # Create a new ``QuestionWallIdea`` related to another ``QuestionWall``
        QuestionWallIdeaFactory(questionwall=QuestionWallFactory())

        # We should still get two ideas
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

        # Create a new ``QuestionWallIdea`` related to another training session
        QuestionWallIdeaFactory(questionwall=self.questionwall, session=TrainingSessionFactory())

        # We should still get two ideas
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

    def test_list_url_should_allow_create_a_new_idea(self):
        """Test that the list view should allow creating a new ``QuestionWallIdea``"""

        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            {
                'text': 'new idea',
                'user': self.trainee.id,
                'session': self.training_session.id,
                'questionwall': self.questionwall.id,
            },
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        new_idea = QuestionWallIdea.objects.get(text='new idea')
        self.assertEqual(new_idea.user, self.trainee)
        self.assertEqual(new_idea.session, self.training_session)
        self.assertEqual(new_idea.questionwall, self.questionwall)

        self.assertTrue(QuestionWallIdea.objects.filter(text='new idea').exists())

    def test_detail_url_should_allow_get_a_single_idea(self):
        """Test that the detail view should allow get a single ``QuestionWallIdea``"""

        self.login_as_trainee()

        response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.questionwall_idea.pk)

    def test_detail_url_should_allow_update_an_idea(self):
        """Test that the detail url should allow to update a ``QuestionWallIdea``"""

        idea_edit_data = {
            'text': 'new text',
            'user': self.trainee.pk,
            'questionwall': self.questionwall.id
        }
        new_idea = QuestionWallIdeaFactory(
            user=self.trainee,
            questionwall=self.questionwall,
            session=self.training_session
        )
        new_idea_edit_url = reverse('apiv2:questionwall_idea-detail', kwargs={
            'parent_lookup_questionwall': self.questionwall.pk,
            'pk': new_idea.pk,
        })

        self.login_as_trainer()

        old_text = new_idea.text

        response = self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        new_idea.refresh_from_db()
        self.assertNotEqual(new_idea.text, old_text)
        self.assertEqual(new_idea.text, 'new text')

        # The trainee should not be able to update an idea
        self.login_as_trainee()
        response = self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_url_should_allow_soft_delete_an_idea(self):
        """Test that the detail url should allow to soft-delete a ``QuestionWallIdea``"""

        new_idea = QuestionWallIdeaFactory(
            user=self.trainee,
            questionwall=self.questionwall,
            session=self.training_session
        )
        session_idea_delete_url = reverse('apiv2:questionwall_idea-detail', kwargs={
            'parent_lookup_questionwall': self.questionwall.pk,
            'pk': new_idea.pk,
        })

        self.login_as_trainer()

        # We got two ideas in total, two non-deleted ideas
        self.assertEqual(QuestionWallIdea.objects.count(), 2)
        self.assertEqual(QuestionWallIdea.objects.non_deleted().count(), 2)

        response = self.client.delete(session_idea_delete_url)

        self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)

        # We got two ideas in total, but only one non-deleted idea
        self.assertEqual(QuestionWallIdea.objects.count(), 2)
        self.assertEqual(QuestionWallIdea.objects.non_deleted().count(), 1)

        # The trainee should not be able to delete an idea
        self.login_as_trainee()
        response = self.client.delete(session_idea_delete_url)
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_like_url_should_allow_like_an_idea(self):
        """Test that the like url should allow like a ``QuestionWallIdea``"""

        self.login_as_trainee()

        # At first, no likes
        self.assertEqual(self.questionwall_idea.likes.non_deleted().count(), 0)

        response = self.client.post(self.likes_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(self.questionwall_idea.likes.non_deleted().count(), 1)

        # Now with the trainer
        self.login_as_trainer()
        self.client.post(self.likes_url)

        self.assertEqual(self.questionwall_idea.likes.non_deleted().count(), 2)

        # Delete one of the likes
        self.client.delete(self.likes_url)

        self.assertEqual(self.questionwall_idea.likes.non_deleted().count(), 1)


class WordCloudViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``WordCloudViewSet`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """Make the ``WordCloud`` editable (training to ``DRAFT`` state)

        Also, store the "list" and "detail" urls.

        """

        super().setUpTestData()

        cls.training.version_state = cls.training.VERSION_STATES.DRAFT
        cls.training.save()

        cls.list_url = reverse('apiv2:wordcloud-list')
        cls.detail_url = reverse(
            'apiv2:wordcloud-detail',
            kwargs={'pk': cls.wordcloud.pk}
        )

    def test_list_view_should_output_the_list_of_wordcloud(self):
        """Test that the list view should output the list of ``WordCloud`` instances"""

        self.login_as_trainer()

        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        self.assertEqual(response.status_code, HTTP_200_OK)

        # We got one WordCloud
        self.assertEqual(len(response.data), 1)

        # This is the one from our test case
        self.assertEqual(response.data[0]['id'], self.wordcloud.id)

        # New WordCloud
        new_wordcloud = WordCloudFactory(training=None, session=self.training_session)

        # Still 4 queries !
        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        # We got two wordcloud
        self.assertEqual(len(response.data), 2)

        # Soft-delete the second wordcloud
        new_wordcloud.remove()

        with self.assertNumQueries(4):
            response = self.client.get(self.list_url)

        # We got one WordCloud
        self.assertEqual(len(response.data), 1)

    def test_list_view_should_allow_to_create_new_wordcloud(self):
        """Test that the list view should allow us to create a new ``WordCloud`` instance"""

        new_wordcloud_data = {
            'name': 'Brand new wordcloud',
            'description': 'WordCloud description',
            'is_anonymous': True,
            'session': self.training_session.pk
        }

        self.login_as_trainer()

        response = self.client.post(
            self.list_url,
            new_wordcloud_data
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        # Our new ``Wordcloud`` exists...
        new_wordcloud = WordCloud.objects.get(name='Brand new wordcloud')

        # ...and got correct attributes
        self.assertEqual(new_wordcloud.description, 'WordCloud description')
        self.assertTrue(new_wordcloud.is_anonymous)
        self.assertEqual(new_wordcloud.session, self.training_session)

        # A trainee cannot create a new WordCloud
        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            new_wordcloud_data
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_output_a_single_wordcloud(self):
        """Test that the detail view should output a single ``WordCloud`` instance"""

        self.login_as_trainer()

        with self.assertNumQueries(4):
            response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.wordcloud.id)

    def test_detail_view_should_allow_us_to_modify_the_wordcloud(self):
        """Test that the detail view should allow us to update the ``WordCloud`` instance"""

        wordcloud_edit_data = {
            'name': 'Brand new name',
            'is_anonymous': True
        }

        self.login_as_trainer()

        old_name = self.questionwall.name

        response = self.client.patch(
            self.detail_url,
            wordcloud_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        self.wordcloud.refresh_from_db()

        self.assertNotEqual(self.wordcloud.name, old_name)
        self.assertEqual(self.wordcloud.name, 'Brand new name')

        # A trainee cannot edit a wordcloud
        self.login_as_trainee()

        response = self.client.patch(
            self.detail_url,
            wordcloud_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_view_should_allow_us_to_soft_delete_the_wordcloud(self):
        """Test that the detail view should allow us to delete the ``WordCloud`` instance"""

        self.login_as_trainer()

        # We got one non-deleted wordcloud...
        self.assertEqual(WordCloud.objects.non_deleted().count(), 1)

        # ...and one in total
        self.assertEqual(WordCloud.objects.count(), 1)

        self.client.delete(self.detail_url)

        # We got 0 non-deleted wordcloud...
        self.assertEqual(WordCloud.objects.non_deleted().count(), 0)

        # ...but still one in total
        self.assertEqual(WordCloud.objects.count(), 1)


class WordCloudIdeaViewSetTestCase(BaseIdeasTestCase, MTPAPITestCase):
    """Test case for the ``WordCloudIdea`` ViewSet"""

    @classmethod
    def setUpTestData(cls):
        """Store the "list", "detail" and "like" ideas related urls

        Also, create a brand new ``WordCloudIdea``

        """

        super().setUpTestData()

        cls.list_url = reverse('apiv2:wordcloud_idea-list', kwargs={
            'parent_lookup_wordcloud': cls.wordcloud.pk
        })

        cls.wordcloud_idea = WordCloudIdeaFactory(wordcloud=cls.wordcloud)

        cls.detail_url = reverse('apiv2:wordcloud_idea-detail', kwargs={
            'parent_lookup_wordcloud': cls.wordcloud.pk,
            'pk': cls.wordcloud_idea.pk,
        })
        cls.likes_url = reverse('apiv2:wordcloud_idea-likes', kwargs={
            'parent_lookup_wordcloud': cls.wordcloud.pk,
            'pk': cls.wordcloud_idea.pk,
        })

    def test_list_url_should_output_the_ideas_of_the_wordcloud(self):
        """Test that the list view should output the ``Ideas`` of the ``WordCloud`` instance"""

        self.login_as_trainer()

        with self.assertNumQueries(5):
            response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 1)  # Only one idea

        # Create a new ``WordCloudIdea``
        WordCloudIdeaFactory(
            wordcloud=self.wordcloud,
            session=self.training_session
        )

        with self.assertNumQueries(6):  # One more query for the likes
            response = self.client.get(self.list_url, {'session': self.training_session.id})

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data), 2)  # Two ideas now

        # Create a new ``WordCloudIdea`` related to another ``WordCloud``
        WordCloudIdeaFactory(wordcloud=WordCloudFactory())

        # We should still get two ideas
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

        # Create a new ``WordCloudIdea`` related to another training session
        WordCloudIdeaFactory(wordcloud=self.wordcloud, session=TrainingSessionFactory())

        # We should still get two ideas
        response = self.client.get(self.list_url, {'session': self.training_session.id})
        self.assertEqual(len(response.data), 2)

    def test_list_url_should_allow_create_a_new_idea(self):
        """Test that the list view should allow creating a new ``WordCloudIdea``"""

        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            {
                'text': 'newidea',
                'user': self.trainee.id,
                'session': self.training_session.id,
                'wordcloud': self.wordcloud.id,
            },
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)

        new_idea = WordCloudIdea.objects.get(text='newidea')
        self.assertEqual(new_idea.user, self.trainee)
        self.assertEqual(new_idea.session, self.training_session)
        self.assertEqual(new_idea.wordcloud, self.wordcloud)

    def test_create_new_idea_with_space_whould_not_be_allowed(self):
        """Test that we shouldn't be able to create a new ``WordCloudIdea`` with spaces"""

        self.login_as_trainee()

        response = self.client.post(
            self.list_url,
            {
                'text': 'new idea',
                'user': self.trainee.id,
                'session': self.training_session.id,
                'wordcloud': self.wordcloud.id,
            },
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)

    def test_detail_url_should_allow_get_a_single_idea(self):
        """Test that the detail view should allow get a single ``WordCloudIdea``"""

        self.login_as_trainee()

        response = self.client.get(self.detail_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(response.data['id'], self.wordcloud_idea.pk)

    def test_detail_url_should_allow_update_an_idea(self):
        """Test that the detail url should allow to update a ``WordCloudIdea``"""

        idea_edit_data = {
            'text': 'newtext',
            'user': self.trainee.pk,
            'wordcloud': self.wordcloud.id
        }
        new_idea = WordCloudIdeaFactory(
            user=self.trainee,
            wordcloud=self.wordcloud,
            session=self.training_session
        )
        new_idea_edit_url = reverse('apiv2:wordcloud_idea-detail', kwargs={
            'parent_lookup_wordcloud': self.wordcloud.pk,
            'pk': new_idea.pk,
        })

        self.login_as_trainer()

        old_text = new_idea.text

        response = self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )

        self.assertEqual(response.status_code, HTTP_200_OK)

        new_idea.refresh_from_db()
        self.assertNotEqual(new_idea.text, old_text)
        self.assertEqual(new_idea.text, 'newtext')

        # The trainee should not be able to update an idea
        self.login_as_trainee()
        response = self.client.patch(
            new_idea_edit_url,
            idea_edit_data,
            format='json'
        )
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_detail_url_should_allow_soft_delete_an_idea(self):
        """Test that the detail url should allow to soft-delete a ``WordcloudIdea``"""

        new_idea = WordCloudIdeaFactory(
            user=self.trainee,
            wordcloud=self.wordcloud,
            session=self.training_session
        )
        session_idea_delete_url = reverse('apiv2:wordcloud_idea-detail', kwargs={
            'parent_lookup_wordcloud': self.wordcloud.pk,
            'pk': new_idea.pk,
        })

        self.login_as_trainer()

        # We got two ideas in total, two non-deleted ideas
        self.assertEqual(WordCloudIdea.objects.count(), 2)
        self.assertEqual(WordCloudIdea.objects.non_deleted().count(), 2)

        response = self.client.delete(session_idea_delete_url)

        self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)

        # We got two ideas in total, but only one non-deleted idea
        self.assertEqual(WordCloudIdea.objects.count(), 2)
        self.assertEqual(WordCloudIdea.objects.non_deleted().count(), 1)

        # The trainee should not be able to delete an idea
        self.login_as_trainee()
        response = self.client.delete(session_idea_delete_url)
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_like_url_should_allow_like_an_idea(self):
        """Test that the like url should allow like a ``WordCloudIdea``"""

        self.login_as_trainee()

        # At first, no likes
        self.assertEqual(self.wordcloud_idea.likes.non_deleted().count(), 0)

        response = self.client.post(self.likes_url)

        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(self.wordcloud_idea.likes.non_deleted().count(), 1)

        # Now with the trainer
        self.login_as_trainer()
        self.client.post(self.likes_url)

        self.assertEqual(self.wordcloud_idea.likes.non_deleted().count(), 2)

        # Delete one of the likes
        self.client.delete(self.likes_url)

        self.assertEqual(self.wordcloud_idea.likes.non_deleted().count(), 1)
