Skip to content
Snippets Groups Projects
test_models.py 13.3 KiB
Newer Older
Usman Khalid's avatar
Usman Khalid committed
# -*- coding: utf-8 -*-
"""
Tests for the teams API at the HTTP request level.
"""
Stu Young's avatar
Stu Young committed

import itertools
from contextlib import contextmanager
from datetime import datetime
Usman Khalid's avatar
Usman Khalid committed
import ddt
from mock import Mock, patch
from opaque_keys.edx.keys import CourseKey
Usman Khalid's avatar
Usman Khalid committed

from course_modes.models import CourseMode
Stu Young's avatar
Stu Young committed
from lms.djangoapps.teams import TEAM_DISCUSSION_CONTEXT
from lms.djangoapps.teams.errors import AddToIncompatibleTeamError
Stu Young's avatar
Stu Young committed
from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
from openedx.core.djangoapps.django_comment_common.signals import (
    comment_created,
    comment_deleted,
    comment_edited,
    comment_endorsed,
    comment_voted,
    thread_created,
    thread_deleted,
    thread_edited,
    thread_voted
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from util.testing import EventTestMixin
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
Usman Khalid's avatar
Usman Khalid committed
COURSE_KEY1 = CourseKey.from_string('edx/history/1')
COURSE_KEY2 = CourseKey.from_string('edx/history/2')
TEAMSET_1_ID = "the-teamset"
TEAMSET_2_ID = "the-teamset-2"
Usman Khalid's avatar
Usman Khalid committed


class TestModelStrings(SharedModuleStoreTestCase):
    """
    Test `__repr__` and `__str__` methods of this app's models.
    """
    @classmethod
    def setUpClass(cls):
        super(TestModelStrings, cls).setUpClass()
        cls.user = UserFactory.create(username="the-user")
        CourseEnrollmentFactory.create(user=cls.user, course_id="edx/the-course/1")
        cls.team = CourseTeamFactory(
            course_id="edx/the-course/1",
            team_id="the-team",
            name="The Team"
        )
        cls.team_membership = cls.team.add_user(cls.user)

    def test_team_repr(self):
        assert repr(self.team) == (
            "<CourseTeam"
            " id=1"
            " team_id=the-team"
            " team_size=1"
            " topic_id=the-teamset"
            " course_id=edx/the-course/1"
            ">"
        )

    def test_team_text(self):
        assert six.text_type(self.team) == (
            "The Team in edx/the-course/1"
        )

    def test_team_membership_repr(self):
        assert repr(self.team_membership) == (
            "<CourseTeamMembership id=1 user_id=1 team_id=1>"
        )

    def test_team_membership_text_type(self):
        assert six.text_type(self.team_membership) == (
            "the-user is member of The Team in edx/the-course/1"
        )


class CourseTeamTest(SharedModuleStoreTestCase):
    """Tests for the CourseTeam model."""

    @classmethod
    def setUpClass(cls):
        super(CourseTeamTest, cls).setUpClass()

        cls.audit_learner = UserFactory.create(username="audit")
        CourseEnrollmentFactory.create(user=cls.audit_learner, course_id="edx/the-course/1", mode=CourseMode.AUDIT)
        cls.audit_team = CourseTeamFactory(
            course_id="edx/the-course/1",
            team_id="audit-team",
            topic_id=TEAMSET_1_ID,
            name="The Team"
        )

        cls.masters_learner = UserFactory.create(username="masters")
        CourseEnrollmentFactory.create(user=cls.masters_learner, course_id="edx/the-course/1", mode=CourseMode.MASTERS)
        cls.masters_team = CourseTeamFactory(
            course_id="edx/the-course/1",
            team_id="masters-team",
            topic_id=TEAMSET_1_ID,
            name="The Team",
            organization_protected=True
        )

    def test_add_user(self):
        """Test that we can add users with correct protection status to a team"""
        self.assertIsNotNone(self.masters_team.add_user(self.masters_learner))
        self.assertIsNotNone(self.audit_team.add_user(self.audit_learner))

    def test_add_user_bad_team_access(self):
        """Test that we are blocked from adding a user to a team of mixed enrollment types"""

        with self.assertRaises(AddToIncompatibleTeamError):
            self.audit_team.add_user(self.masters_learner)

        with self.assertRaises(AddToIncompatibleTeamError):
            self.masters_team.add_user(self.audit_learner)

    def test_defaults_with_create(self):
        field = CourseTeam._meta.get_field('last_activity_at')
        default_date = datetime(1978, 1, 1)
        mock_now = lambda: default_date
        with patch.object(field, 'default', new=mock_now):
            team = CourseTeam.create(
                name='foo',
                course_id='sedx/the-course/1',
                description='Import from csv',
                topic_id=TEAMSET_1_ID,
                organization_protected=True
            )
        self.assert_team_default_values(default_date, team)

    def test_defaults_with_constructor(self):
        field = CourseTeam._meta.get_field('last_activity_at')
        default_date = datetime(1978, 1, 1)
        mock_now = lambda: default_date
        with patch.object(field, 'default', new=mock_now):
            team = CourseTeam(
                name='foo',
                course_id='sedx/the-course/1',
                description='Import from csv',
                topic_id=TEAMSET_1_ID,
                organization_protected=True
            )
        self.assert_team_default_values(default_date, team)

    def assert_team_default_values(self, default_date, team):
        self.assertEqual(default_date, team.last_activity_at)
        self.assertEqual('', team.language)
        self.assertEqual('', team.country)

Usman Khalid's avatar
Usman Khalid committed
@ddt.ddt
class TeamMembershipTest(SharedModuleStoreTestCase):
    """Tests for the TeamMembership model."""

    def setUp(self):
        """
        Set up tests.
        """
        super(TeamMembershipTest, self).setUp()

        self.user1 = UserFactory.create(username='user1')
        self.user2 = UserFactory.create(username='user2')
        self.user3 = UserFactory.create(username='user3')

        for user in (self.user1, self.user2, self.user3):
            CourseEnrollmentFactory.create(user=user, course_id=COURSE_KEY1)
        CourseEnrollmentFactory.create(user=self.user1, course_id=COURSE_KEY2)
Usman Khalid's avatar
Usman Khalid committed

        self.team1 = CourseTeamFactory(
            course_id=COURSE_KEY1,
            team_id='team1',
            topic_id=TEAMSET_1_ID,
        )
        self.team2 = CourseTeamFactory(
            course_id=COURSE_KEY2,
            team_id='team2',
            topic_id=TEAMSET_2_ID,
        )
Usman Khalid's avatar
Usman Khalid committed

        self.team_membership11 = self.team1.add_user(self.user1)
        self.team_membership12 = self.team1.add_user(self.user2)
        self.team_membership21 = self.team2.add_user(self.user1)

    def test_membership_last_activity_set(self):
        current_last_activity = self.team_membership11.last_activity_at
        # Assert that the first save in the setUp sets a value.
        self.assertIsNotNone(current_last_activity)

        self.team_membership11.save()

        # Verify that we only change the last activity_at when it doesn't
        # already exist.
        self.assertEqual(self.team_membership11.last_activity_at, current_last_activity)
Usman Khalid's avatar
Usman Khalid committed

    def test_team_size_delete_membership(self):
        """Test that the team size field is correctly updated when deleting a
        team membership.
        """
        self.assertEqual(self.team1.team_size, 2)
        self.team_membership11.delete()
        team = CourseTeam.objects.get(id=self.team1.id)
        self.assertEqual(team.team_size, 1)

    def test_team_size_create_membership(self):
        """Test that the team size field is correctly updated when creating a
        team membership.
        """
        self.assertEqual(self.team1.team_size, 2)
        self.team1.add_user(self.user3)
        team = CourseTeam.objects.get(id=self.team1.id)
        self.assertEqual(team.team_size, 3)

Usman Khalid's avatar
Usman Khalid committed
    @ddt.data(
        (None, None, None, 3),
        ('user1', None, None, 2),
        ('user1', [COURSE_KEY1], None, 1),
        ('user1', None, 'team1', 1),
        ('user2', None, None, 1),
    )
    @ddt.unpack
    def test_get_memberships(self, username, course_ids, team_id, expected_count):
        self.assertEqual(
            CourseTeamMembership.get_memberships(username=username, course_ids=course_ids, team_id=team_id).count(),
            expected_count
        )

    @ddt.data(
        ('user1', COURSE_KEY1, True),
        ('user2', COURSE_KEY1, True),
        ('user2', COURSE_KEY2, False),
    )
    @ddt.unpack
    def test_user_in_team_for_course(self, username, course_id, expected_value):
        user = getattr(self, username)
        self.assertEqual(
            CourseTeamMembership.user_in_team_for_course(user, course_id),
            expected_value
        )
    @ddt.data(
        ('user1', COURSE_KEY1, TEAMSET_1_ID, True),
        ('user1', COURSE_KEY1, TEAMSET_2_ID, False),
        ('user2', COURSE_KEY1, TEAMSET_1_ID, True),
        ('user2', COURSE_KEY1, TEAMSET_2_ID, False),
        ('user1', COURSE_KEY2, TEAMSET_1_ID, False),
        ('user2', COURSE_KEY2, TEAMSET_1_ID, False),
    )
    @ddt.unpack
    def test_user_in_team_for_course_teamset(self, username, course_id, teamset_id, expected_value):
        user = getattr(self, username)
        self.assertEqual(
            CourseTeamMembership.user_in_team_for_course(user, course_id, teamset_id),
            expected_value
        )

class TeamSignalsTest(EventTestMixin, SharedModuleStoreTestCase):
    """Tests for handling of team-related signals."""

Jeremy Bowman's avatar
Jeremy Bowman committed
    SIGNALS = {
        'thread_created': thread_created,
        'thread_edited': thread_edited,
        'thread_deleted': thread_deleted,
        'thread_voted': thread_voted,
        'comment_created': comment_created,
        'comment_edited': comment_edited,
        'comment_deleted': comment_deleted,
        'comment_voted': comment_voted,
        'comment_endorsed': comment_endorsed,
    }

    DISCUSSION_TOPIC_ID = 'test_topic'

    def setUp(self):  # pylint: disable=arguments-differ
        """Create a user with a team to test signals."""
        super(TeamSignalsTest, self).setUp('lms.djangoapps.teams.utils.tracker')
        self.user = UserFactory.create(username="user")
        self.moderator = UserFactory.create(username="moderator")
        self.team = CourseTeamFactory(discussion_topic_id=self.DISCUSSION_TOPIC_ID)
        self.team_membership = CourseTeamMembershipFactory(user=self.user, team=self.team)

    def mock_comment(self, context=TEAM_DISCUSSION_CONTEXT, user=None):
        """Create a mock comment service object with the given context."""
        if user is None:
            user = self.user
        return Mock(
            user_id=user.id,
            commentable_id=self.DISCUSSION_TOPIC_ID,
            context=context,
            **{'thread.user_id': self.user.id}
        )

    @contextmanager
    def assert_last_activity_updated(self, should_update):
        """If `should_update` is True, assert that the team and team
        membership have had their `last_activity_at` updated. Otherwise,
        assert that it was not updated.
        """
        team_last_activity = self.team.last_activity_at
        team_membership_last_activity = self.team_membership.last_activity_at
        yield
        # Reload team and team membership from the database in order to pick up changes
        team = CourseTeam.objects.get(id=self.team.id)
        team_membership = CourseTeamMembership.objects.get(id=self.team_membership.id)
        if should_update:
            self.assertGreater(team.last_activity_at, team_last_activity)
            self.assertGreater(team_membership.last_activity_at, team_membership_last_activity)
            now = datetime.utcnow().replace(tzinfo=pytz.utc)
            self.assertGreater(now, team.last_activity_at)
            self.assertGreater(now, team_membership.last_activity_at)
            self.assert_event_emitted(
                'edx.team.activity_updated',
        else:
            self.assertEqual(team.last_activity_at, team_last_activity)
            self.assertEqual(team_membership.last_activity_at, team_membership_last_activity)
            self.assert_no_events_were_emitted()

    @ddt.data(
        *itertools.product(
Stu Young's avatar
Stu Young committed
            list(SIGNALS.keys()),
            (('user', True), ('moderator', False))
        )
    )
    @ddt.unpack
    def test_signals(self, signal_name, user_should_update):
        """Test that `last_activity_at` is correctly updated when team-related
        signals are sent.
        """
        (user, should_update) = user_should_update
        with self.assert_last_activity_updated(should_update):
            user = getattr(self, user)
Jeremy Bowman's avatar
Jeremy Bowman committed
            signal = self.SIGNALS[signal_name]
            signal.send(sender=None, user=user, post=self.mock_comment())

Jeremy Bowman's avatar
Jeremy Bowman committed
    @ddt.data('thread_voted', 'comment_voted')
    def test_vote_others_post(self, signal_name):
        """Test that voting on another user's post correctly fires a
        signal."""
        with self.assert_last_activity_updated(True):
Jeremy Bowman's avatar
Jeremy Bowman committed
            signal = self.SIGNALS[signal_name]
            signal.send(sender=None, user=self.user, post=self.mock_comment(user=self.moderator))

Stu Young's avatar
Stu Young committed
    @ddt.data(*list(SIGNALS.keys()))
Jeremy Bowman's avatar
Jeremy Bowman committed
    def test_signals_course_context(self, signal_name):
        """Test that `last_activity_at` is not updated when activity takes
        place in discussions outside of a team.
        """
        with self.assert_last_activity_updated(False):
Jeremy Bowman's avatar
Jeremy Bowman committed
            signal = self.SIGNALS[signal_name]
            signal.send(sender=None, user=self.user, post=self.mock_comment(context='course'))