test_utils.py 78.4 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
import datetime
3
import json
cahrens committed
4

5
import ddt
6
import mock
7 8
import pytest

9 10
from django.core.urlresolvers import reverse
from django.test import RequestFactory, TestCase
cahrens committed
11
from mock import Mock, patch
12
from nose.plugins.attrib import attr
13
from pytz import UTC
14

15
import django_comment_client.utils as utils
16 17
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
18
from courseware.tabs import get_course_tab_list
cahrens committed
19 20 21 22 23
from courseware.tests.factories import InstructorFactory
from django_comment_client.constants import TYPE_ENTRY, TYPE_SUBCATEGORY
from django_comment_client.tests.factories import RoleFactory
from django_comment_client.tests.unicode import UnicodeTestMixin
from django_comment_client.tests.utils import config_course_discussions, topic_name_to_id
24 25 26 27 28 29 30 31 32 33
from django_comment_common.models import (
    CourseDiscussionSettings,
    ForumsConfig,
    assign_role
)
from django_comment_common.utils import (
    get_course_discussion_settings,
    seed_permissions_roles,
    set_course_discussion_settings
)
34
from lms.djangoapps.teams.tests.factories import CourseTeamFactory
cahrens committed
35 36
from lms.lib.comment_client.utils import CommentClientMaintenanceError, perform_request
from openedx.core.djangoapps.content.course_structures.models import CourseStructure
37
from openedx.core.djangoapps.course_groups import cohorts
38
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
cahrens committed
39
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory, config_course_cohorts
40
from openedx.core.djangoapps.util.testing import ContentGroupTestCase
41
from student.roles import CourseStaffRole
cahrens committed
42
from student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
43
from xmodule.modulestore import ModuleStoreEnum
44
from xmodule.modulestore.django import modulestore
cahrens committed
45 46
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, ToyCourseFactory
Calen Pennington committed
47

48

49
@attr(shard=1)
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
class DictionaryTestCase(TestCase):
    def test_extract(self):
        d = {'cats': 'meow', 'dogs': 'woof'}
        k = ['cats', 'dogs', 'hamsters']
        expected = {'cats': 'meow', 'dogs': 'woof', 'hamsters': None}
        self.assertEqual(utils.extract(d, k), expected)

    def test_strip_none(self):
        d = {'cats': 'meow', 'dogs': 'woof', 'hamsters': None}
        expected = {'cats': 'meow', 'dogs': 'woof'}
        self.assertEqual(utils.strip_none(d), expected)

    def test_strip_blank(self):
        d = {'cats': 'meow', 'dogs': 'woof', 'hamsters': ' ', 'yetis': ''}
        expected = {'cats': 'meow', 'dogs': 'woof'}
        self.assertEqual(utils.strip_blank(d), expected)

    def test_merge_dict(self):
Calen Pennington committed
68 69 70
        d1 = {'cats': 'meow', 'dogs': 'woof'}
        d2 = {'lions': 'roar', 'ducks': 'quack'}
        expected = {'cats': 'meow', 'dogs': 'woof', 'lions': 'roar', 'ducks': 'quack'}
71 72
        self.assertEqual(utils.merge_dict(d1, d2), expected)

73

74
@attr(shard=1)
75
@pytest.mark.django111_expected_failure
Don Mitchell committed
76
class AccessUtilsTestCase(ModuleStoreTestCase):
77 78 79 80
    """
    Base testcase class for access and roles for the
    comment client service integration
    """
81 82
    CREATE_USER = False

83
    def setUp(self):
84
        super(AccessUtilsTestCase, self).setUp()
85

86 87
        self.course = CourseFactory.create()
        self.course_id = self.course.id
88 89
        self.student_role = RoleFactory(name='Student', course_id=self.course_id)
        self.moderator_role = RoleFactory(name='Moderator', course_id=self.course_id)
90
        self.community_ta_role = RoleFactory(name='Community TA', course_id=self.course_id)
91 92 93 94 95 96 97 98
        self.student1 = UserFactory(username='student', email='student@edx.org')
        self.student1_enrollment = CourseEnrollmentFactory(user=self.student1)
        self.student_role.users.add(self.student1)
        self.student2 = UserFactory(username='student2', email='student2@edx.org')
        self.student2_enrollment = CourseEnrollmentFactory(user=self.student2)
        self.moderator = UserFactory(username='moderator', email='staff@edx.org', is_staff=True)
        self.moderator_enrollment = CourseEnrollmentFactory(user=self.moderator)
        self.moderator_role.users.add(self.moderator)
99 100 101 102
        self.community_ta1 = UserFactory(username='community_ta1', email='community_ta1@edx.org')
        self.community_ta_role.users.add(self.community_ta1)
        self.community_ta2 = UserFactory(username='community_ta2', email='community_ta2@edx.org')
        self.community_ta_role.users.add(self.community_ta2)
103 104
        self.course_staff = UserFactory(username='course_staff', email='course_staff@edx.org')
        CourseStaffRole(self.course_id).add_users(self.course_staff)
105 106 107

    def test_get_role_ids(self):
        ret = utils.get_role_ids(self.course_id)
108
        expected = {u'Moderator': [3], u'Community TA': [4, 5]}
109 110
        self.assertEqual(ret, expected)

111 112 113
    def test_has_discussion_privileges(self):
        self.assertFalse(utils.has_discussion_privileges(self.student1, self.course_id))
        self.assertFalse(utils.has_discussion_privileges(self.student2, self.course_id))
114
        self.assertFalse(utils.has_discussion_privileges(self.course_staff, self.course_id))
115 116 117 118
        self.assertTrue(utils.has_discussion_privileges(self.moderator, self.course_id))
        self.assertTrue(utils.has_discussion_privileges(self.community_ta1, self.course_id))
        self.assertTrue(utils.has_discussion_privileges(self.community_ta2, self.course_id))

119 120 121 122 123 124 125 126 127
    def test_has_forum_access(self):
        ret = utils.has_forum_access('student', self.course_id, 'Student')
        self.assertTrue(ret)

        ret = utils.has_forum_access('not_a_student', self.course_id, 'Student')
        self.assertFalse(ret)

        ret = utils.has_forum_access('student', self.course_id, 'NotARole')
        self.assertFalse(ret)
128 129


130
@ddt.ddt
131
@attr(shard=1)
132
class CoursewareContextTestCase(ModuleStoreTestCase):
133 134 135 136
    """
    Base testcase class for courseware context for the
    comment client service integration
    """
137
    def setUp(self):
138
        super(CoursewareContextTestCase, self).setUp()
139

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
        self.course = CourseFactory.create(org="TestX", number="101", display_name="Test Course")
        self.discussion1 = ItemFactory.create(
            parent_location=self.course.location,
            category="discussion",
            discussion_id="discussion1",
            discussion_category="Chapter",
            discussion_target="Discussion 1"
        )
        self.discussion2 = ItemFactory.create(
            parent_location=self.course.location,
            category="discussion",
            discussion_id="discussion2",
            discussion_category="Chapter / Section / Subsection",
            discussion_target="Discussion 2"
        )

156
    def test_empty(self):
157
        utils.add_courseware_context([], self.course, self.user)
158

159
    def test_missing_commentable_id(self):
160 161
        orig = {"commentable_id": "non-inline"}
        modified = dict(orig)
162
        utils.add_courseware_context([modified], self.course, self.user)
163
        self.assertEqual(modified, orig)
164 165

    def test_basic(self):
166 167 168 169
        threads = [
            {"commentable_id": self.discussion1.discussion_id},
            {"commentable_id": self.discussion2.discussion_id}
        ]
170
        utils.add_courseware_context(threads, self.course, self.user)
171

172
        def assertThreadCorrect(thread, discussion, expected_title):  # pylint: disable=invalid-name
173
            """Asserts that the given thread has the expected set of properties"""
174 175 176 177
            self.assertEqual(
                set(thread.keys()),
                set(["commentable_id", "courseware_url", "courseware_title"])
            )
178
            self.assertEqual(
179
                thread.get("courseware_url"),
180 181 182
                reverse(
                    "jump_to",
                    kwargs={
183 184
                        "course_id": self.course.id.to_deprecated_string(),
                        "location": discussion.location.to_deprecated_string()
185 186 187
                    }
                )
            )
188
            self.assertEqual(thread.get("courseware_title"), expected_title)
189

190 191
        assertThreadCorrect(threads[0], self.discussion1, "Chapter / Discussion 1")
        assertThreadCorrect(threads[1], self.discussion2, "Subsection / Discussion 2")
192

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
    def test_empty_discussion_subcategory_title(self):
        """
        Test that for empty subcategory inline discussion modules,
        the divider " / " is not rendered on a post or inline discussion topic label.
        """
        discussion = ItemFactory.create(
            parent_location=self.course.location,
            category="discussion",
            discussion_id="discussion",
            discussion_category="Chapter",
            discussion_target=""  # discussion-subcategory
        )
        thread = {"commentable_id": discussion.discussion_id}
        utils.add_courseware_context([thread], self.course, self.user)
        self.assertNotIn('/', thread.get("courseware_title"))

209 210
    @ddt.data((ModuleStoreEnum.Type.mongo, 2), (ModuleStoreEnum.Type.split, 1))
    @ddt.unpack
211
    def test_get_accessible_discussion_xblocks(self, modulestore_type, expected_discussion_xblocks):
212
        """
213
        Tests that the accessible discussion xblocks having no parents do not get fetched for split modulestore.
214 215 216
        """
        course = CourseFactory.create(default_store=modulestore_type)

217
        # Create a discussion xblock.
218 219
        test_discussion = self.store.create_child(self.user.id, course.location, 'discussion', 'test_discussion')

220
        # Assert that created discussion xblock is not an orphan.
221 222
        self.assertNotIn(test_discussion.location, self.store.get_orphans(course.id))

223 224
        # Assert that there is only one discussion xblock in the course at the moment.
        self.assertEqual(len(utils.get_accessible_discussion_xblocks(course, self.user)), 1)
225

226
        # Add an orphan discussion xblock to that course
227 228 229
        orphan = course.id.make_usage_key('discussion', 'orphan_discussion')
        self.store.create_item(self.user.id, orphan.course_key, orphan.block_type, block_id=orphan.block_id)

230
        # Assert that the discussion xblock is an orphan.
231 232
        self.assertIn(orphan, self.store.get_orphans(course.id))

233
        self.assertEqual(len(utils.get_accessible_discussion_xblocks(course, self.user)), expected_discussion_xblocks)
234

235

236
@attr(shard=3)
237 238 239 240
class CachedDiscussionIdMapTestCase(ModuleStoreTestCase):
    """
    Tests that using the cache of discussion id mappings has the same behavior as searching through the course.
    """
241 242
    ENABLED_SIGNALS = ['course_published']

243
    def setUp(self):
244
        super(CachedDiscussionIdMapTestCase, self).setUp()
245 246 247 248 249 250 251 252 253

        self.course = CourseFactory.create(org='TestX', number='101', display_name='Test Course')
        self.discussion = ItemFactory.create(
            parent_location=self.course.location,
            category='discussion',
            discussion_id='test_discussion_id',
            discussion_category='Chapter',
            discussion_target='Discussion 1'
        )
254 255 256 257 258 259 260
        self.discussion2 = ItemFactory.create(
            parent_location=self.course.location,
            category='discussion',
            discussion_id='test_discussion_id_2',
            discussion_category='Chapter 2',
            discussion_target='Discussion 2'
        )
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
        self.private_discussion = ItemFactory.create(
            parent_location=self.course.location,
            category='discussion',
            discussion_id='private_discussion_id',
            discussion_category='Chapter 3',
            discussion_target='Beta Testing',
            visible_to_staff_only=True
        )
        self.bad_discussion = ItemFactory.create(
            parent_location=self.course.location,
            category='discussion',
            discussion_id='bad_discussion_id',
            discussion_category=None,
            discussion_target=None
        )

    def test_cache_returns_correct_key(self):
278
        usage_key = utils.get_cached_discussion_key(self.course.id, 'test_discussion_id')
279 280 281
        self.assertEqual(usage_key, self.discussion.location)

    def test_cache_returns_none_if_id_is_not_present(self):
282
        usage_key = utils.get_cached_discussion_key(self.course.id, 'bogus_id')
283 284 285 286 287
        self.assertIsNone(usage_key)

    def test_cache_raises_exception_if_course_structure_not_cached(self):
        CourseStructure.objects.all().delete()
        with self.assertRaises(utils.DiscussionIdMapIsNotCached):
288
            utils.get_cached_discussion_key(self.course.id, 'test_discussion_id')
289 290 291 292 293 294 295

    def test_cache_raises_exception_if_discussion_id_not_cached(self):
        cache = CourseStructure.objects.get(course_id=self.course.id)
        cache.discussion_id_map_json = None
        cache.save()

        with self.assertRaises(utils.DiscussionIdMapIsNotCached):
296
            utils.get_cached_discussion_key(self.course.id, 'test_discussion_id')
297

298
    def test_xblock_does_not_have_required_keys(self):
299 300 301 302
        self.assertTrue(utils.has_required_keys(self.discussion))
        self.assertFalse(utils.has_required_keys(self.bad_discussion))

    def verify_discussion_metadata(self):
303 304 305 306 307 308 309 310 311 312 313 314
        """Retrieves the metadata for self.discussion and self.discussion2 and verifies that it is correct"""
        metadata = utils.get_cached_discussion_id_map(
            self.course,
            ['test_discussion_id', 'test_discussion_id_2'],
            self.user
        )
        discussion1 = metadata[self.discussion.discussion_id]
        discussion2 = metadata[self.discussion2.discussion_id]
        self.assertEqual(discussion1['location'], self.discussion.location)
        self.assertEqual(discussion1['title'], 'Chapter / Discussion 1')
        self.assertEqual(discussion2['location'], self.discussion2.location)
        self.assertEqual(discussion2['title'], 'Chapter 2 / Discussion 2')
315 316 317 318 319 320 321 322 323

    def test_get_discussion_id_map_from_cache(self):
        self.verify_discussion_metadata()

    def test_get_discussion_id_map_without_cache(self):
        CourseStructure.objects.all().delete()
        self.verify_discussion_metadata()

    def test_get_missing_discussion_id_map_from_cache(self):
324
        metadata = utils.get_cached_discussion_id_map(self.course, ['bogus_id'], self.user)
325 326 327 328 329
        self.assertEqual(metadata, {})

    def test_get_discussion_id_map_from_cache_without_access(self):
        user = UserFactory.create()

330
        metadata = utils.get_cached_discussion_id_map(self.course, ['private_discussion_id'], self.user)
331 332
        self.assertEqual(metadata['private_discussion_id']['title'], 'Chapter 3 / Beta Testing')

333
        metadata = utils.get_cached_discussion_id_map(self.course, ['private_discussion_id'], user)
334 335 336
        self.assertEqual(metadata, {})

    def test_get_bad_discussion_id(self):
337
        metadata = utils.get_cached_discussion_id_map(self.course, ['bad_discussion_id'], self.user)
338 339
        self.assertEqual(metadata, {})

340 341 342 343 344 345 346 347 348 349 350 351 352 353
    def test_discussion_id_accessible(self):
        self.assertTrue(utils.discussion_category_id_access(self.course, self.user, 'test_discussion_id'))

    def test_bad_discussion_id_not_accessible(self):
        self.assertFalse(utils.discussion_category_id_access(self.course, self.user, 'bad_discussion_id'))

    def test_missing_discussion_id_not_accessible(self):
        self.assertFalse(utils.discussion_category_id_access(self.course, self.user, 'bogus_id'))

    def test_discussion_id_not_accessible_without_access(self):
        user = UserFactory.create()
        self.assertTrue(utils.discussion_category_id_access(self.course, self.user, 'private_discussion_id'))
        self.assertFalse(utils.discussion_category_id_access(self.course, user, 'private_discussion_id'))

354

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
class CategoryMapTestMixin(object):
    """
    Provides functionality for classes that test
    `get_discussion_category_map`.
    """
    def assert_category_map_equals(self, expected, requesting_user=None):
        """
        Call `get_discussion_category_map`, and verify that it returns
        what is expected.
        """
        self.assertEqual(
            utils.get_discussion_category_map(self.course, requesting_user or self.user),
            expected
        )


371
@attr(shard=1)
372
class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
373 374 375 376
    """
    Base testcase class for discussion categories for the
    comment client service integration
    """
377
    def setUp(self):
378
        super(CategoryMapTestCase, self).setUp()
379

380 381 382 383 384
        self.course = CourseFactory.create(
            org="TestX", number="101", display_name="Test Course",
            # This test needs to use a course that has already started --
            # discussion topics only show up if the course has already started,
            # and the default start date for courses is Jan 1, 2030.
385
            start=datetime.datetime(2012, 2, 3, tzinfo=UTC)
386
        )
387 388 389
        # Courses get a default discussion topic on creation, so remove it
        self.course.discussion_topics = {}
        self.discussion_num = 0
Usman Khalid committed
390
        self.instructor = InstructorFactory(course_key=self.course.id)
391
        self.maxDiff = None  # pylint: disable=invalid-name
392
        self.later = datetime.datetime(2050, 1, 1, tzinfo=UTC)
393 394 395

    def create_discussion(self, discussion_category, discussion_target, **kwargs):
        self.discussion_num += 1
396
        return ItemFactory.create(
397 398 399 400 401 402 403 404
            parent_location=self.course.location,
            category="discussion",
            discussion_id="discussion{}".format(self.discussion_num),
            discussion_category=discussion_category,
            discussion_target=discussion_target,
            **kwargs
        )

405
    def assert_category_map_equals(self, expected, divided_only_if_explicit=False, exclude_unstarted=True):  # pylint: disable=arguments-differ
406 407 408 409
        """
        Asserts the expected map with the map returned by get_discussion_category_map method.
        """
        self.assertEqual(
410 411 412
            utils.get_discussion_category_map(
                self.course, self.instructor, divided_only_if_explicit, exclude_unstarted
            ),
413 414 415
            expected
        )

416
    def test_empty(self):
417
        self.assert_category_map_equals({"entries": {}, "subcategories": {}, "children": []})
418 419 420 421 422 423 424

    def test_configured_topics(self):
        self.course.discussion_topics = {
            "Topic A": {"id": "Topic_A"},
            "Topic B": {"id": "Topic_B"},
            "Topic C": {"id": "Topic_C"}
        }
425

426
        def check_cohorted_topics(expected_ids):  # pylint: disable=missing-docstring
427
            self.assert_category_map_equals(
428 429
                {
                    "entries": {
430 431 432
                        "Topic A": {"id": "Topic_A", "sort_key": "Topic A", "is_divided": "Topic_A" in expected_ids},
                        "Topic B": {"id": "Topic_B", "sort_key": "Topic B", "is_divided": "Topic_B" in expected_ids},
                        "Topic C": {"id": "Topic_C", "sort_key": "Topic C", "is_divided": "Topic_C" in expected_ids},
433 434
                    },
                    "subcategories": {},
435
                    "children": [("Topic A", TYPE_ENTRY), ("Topic B", TYPE_ENTRY), ("Topic C", TYPE_ENTRY)]
436 437 438
                }
            )

polesye committed
439
        check_cohorted_topics([])  # default (empty) cohort config
440

441
        set_discussion_division_settings(self.course.id, enable_cohorts=False)
442 443
        check_cohorted_topics([])

444
        set_discussion_division_settings(self.course.id, enable_cohorts=True)
445 446
        check_cohorted_topics([])

447 448 449 450
        set_discussion_division_settings(
            self.course.id,
            enable_cohorts=True,
            divided_discussions=["Topic_B", "Topic_C"]
451
        )
452 453
        check_cohorted_topics(["Topic_B", "Topic_C"])

454 455 456 457
        set_discussion_division_settings(
            self.course.id,
            enable_cohorts=True,
            divided_discussions=["Topic_A", "Some_Other_Topic"]
458
        )
459 460 461
        check_cohorted_topics(["Topic_A"])

        # unlikely case, but make sure it works.
462 463 464 465
        set_discussion_division_settings(
            self.course.id,
            enable_cohorts=False,
            divided_discussions=["Topic_A"]
466
        )
467 468
        check_cohorted_topics([])

469 470
    def test_single_inline(self):
        self.create_discussion("Chapter", "Discussion")
471
        self.assert_category_map_equals(
472 473 474 475 476 477 478
            {
                "entries": {},
                "subcategories": {
                    "Chapter": {
                        "entries": {
                            "Discussion": {
                                "id": "discussion1",
479
                                "sort_key": None,
480
                                "is_divided": False,
481 482 483
                            }
                        },
                        "subcategories": {},
484
                        "children": [("Discussion", TYPE_ENTRY)]
485 486
                    }
                },
487
                "children": [("Chapter", TYPE_SUBCATEGORY)]
488 489 490
            }
        )

491
    def test_inline_with_always_divide_inline_discussion_flag(self):
492
        self.create_discussion("Chapter", "Discussion")
493
        set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True)
494 495 496 497 498 499 500 501 502 503

        self.assert_category_map_equals(
            {
                "entries": {},
                "subcategories": {
                    "Chapter": {
                        "entries": {
                            "Discussion": {
                                "id": "discussion1",
                                "sort_key": None,
504
                                "is_divided": True,
505 506 507
                            }
                        },
                        "subcategories": {},
508
                        "children": [("Discussion", TYPE_ENTRY)]
509 510
                    }
                },
511
                "children": [("Chapter", TYPE_SUBCATEGORY)]
512 513 514
            }
        )

515
    def test_inline_without_always_divide_inline_discussion_flag(self):
516
        self.create_discussion("Chapter", "Discussion")
517
        set_discussion_division_settings(self.course.id, enable_cohorts=True)
518 519 520 521 522 523 524 525 526 527

        self.assert_category_map_equals(
            {
                "entries": {},
                "subcategories": {
                    "Chapter": {
                        "entries": {
                            "Discussion": {
                                "id": "discussion1",
                                "sort_key": None,
528
                                "is_divided": False,
529 530 531
                            }
                        },
                        "subcategories": {},
532
                        "children": [("Discussion", TYPE_ENTRY)]
533 534
                    }
                },
535
                "children": [("Chapter", TYPE_SUBCATEGORY)]
536
            },
537
            divided_only_if_explicit=True
538 539
        )

540
    def test_get_unstarted_discussion_xblocks(self):
541

542
        self.create_discussion("Chapter 1", "Discussion 1", start=self.later)
543 544 545 546 547 548 549 550 551 552

        self.assert_category_map_equals(
            {
                "entries": {},
                "subcategories": {
                    "Chapter 1": {
                        "entries": {
                            "Discussion 1": {
                                "id": "discussion1",
                                "sort_key": None,
553
                                "is_divided": False,
554
                                "start_date": self.later
555 556 557
                            }
                        },
                        "subcategories": {},
558
                        "children": [("Discussion 1", TYPE_ENTRY)],
559
                        "start_date": self.later,
560 561 562
                        "sort_key": "Chapter 1"
                    }
                },
563
                "children": [("Chapter 1", TYPE_SUBCATEGORY)]
564
            },
565
            divided_only_if_explicit=True,
566 567 568
            exclude_unstarted=False
        )

569 570 571 572 573 574 575 576
    def test_tree(self):
        self.create_discussion("Chapter 1", "Discussion 1")
        self.create_discussion("Chapter 1", "Discussion 2")
        self.create_discussion("Chapter 2", "Discussion")
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion")
        self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "Discussion")
        self.create_discussion("Chapter 3 / Section 1", "Discussion")

577
        def check_divided(is_divided):
578

579
            self.assert_category_map_equals(
580 581 582 583 584 585 586 587
                {
                    "entries": {},
                    "subcategories": {
                        "Chapter 1": {
                            "entries": {
                                "Discussion 1": {
                                    "id": "discussion1",
                                    "sort_key": None,
588
                                    "is_divided": is_divided,
589 590 591 592
                                },
                                "Discussion 2": {
                                    "id": "discussion2",
                                    "sort_key": None,
593
                                    "is_divided": is_divided,
594
                                }
595
                            },
596
                            "subcategories": {},
597
                            "children": [("Discussion 1", TYPE_ENTRY), ("Discussion 2", TYPE_ENTRY)]
598
                        },
599 600 601 602 603
                        "Chapter 2": {
                            "entries": {
                                "Discussion": {
                                    "id": "discussion3",
                                    "sort_key": None,
604
                                    "is_divided": is_divided,
605 606 607 608 609 610 611 612 613 614 615
                                }
                            },
                            "subcategories": {
                                "Section 1": {
                                    "entries": {},
                                    "subcategories": {
                                        "Subsection 1": {
                                            "entries": {
                                                "Discussion": {
                                                    "id": "discussion4",
                                                    "sort_key": None,
616
                                                    "is_divided": is_divided,
617 618 619
                                                }
                                            },
                                            "subcategories": {},
620
                                            "children": [("Discussion", TYPE_ENTRY)]
621
                                        },
622 623 624 625 626
                                        "Subsection 2": {
                                            "entries": {
                                                "Discussion": {
                                                    "id": "discussion5",
                                                    "sort_key": None,
627
                                                    "is_divided": is_divided,
628 629 630
                                                }
                                            },
                                            "subcategories": {},
631
                                            "children": [("Discussion", TYPE_ENTRY)]
632
                                        }
633
                                    },
634
                                    "children": [("Subsection 1", TYPE_SUBCATEGORY), ("Subsection 2", TYPE_SUBCATEGORY)]
635 636
                                }
                            },
637
                            "children": [("Discussion", TYPE_ENTRY), ("Section 1", TYPE_SUBCATEGORY)]
638
                        },
639 640 641 642 643 644 645 646
                        "Chapter 3": {
                            "entries": {},
                            "subcategories": {
                                "Section 1": {
                                    "entries": {
                                        "Discussion": {
                                            "id": "discussion6",
                                            "sort_key": None,
647
                                            "is_divided": is_divided,
648 649 650
                                        }
                                    },
                                    "subcategories": {},
651
                                    "children": [("Discussion", TYPE_ENTRY)]
652 653
                                }
                            },
654
                            "children": [("Section 1", TYPE_SUBCATEGORY)]
655
                        }
656
                    },
657 658
                    "children": [("Chapter 1", TYPE_SUBCATEGORY), ("Chapter 2", TYPE_SUBCATEGORY),
                                 ("Chapter 3", TYPE_SUBCATEGORY)]
659 660 661 662
                }
            )

        # empty / default config
663
        check_divided(False)
664 665

        # explicitly disabled cohorting
666 667
        set_discussion_division_settings(self.course.id, enable_cohorts=False)
        check_divided(False)
668

669 670 671
        # explicitly enable courses divided by Cohort with inline discusssions also divided.
        set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True)
        check_divided(True)
672

673 674 675 676 677 678 679 680
    def test_tree_with_duplicate_targets(self):
        self.create_discussion("Chapter 1", "Discussion A")
        self.create_discussion("Chapter 1", "Discussion B")
        self.create_discussion("Chapter 1", "Discussion A")  # duplicate
        self.create_discussion("Chapter 1", "Discussion A")  # another duplicate
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion")
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion")  # duplicate

681
        category_map = utils.get_discussion_category_map(self.course, self.user)
682 683 684

        chapter1 = category_map["subcategories"]["Chapter 1"]
        chapter1_discussions = set(["Discussion A", "Discussion B", "Discussion A (1)", "Discussion A (2)"])
685 686 687
        chapter1_discussions_with_types = set([("Discussion A", TYPE_ENTRY), ("Discussion B", TYPE_ENTRY),
                                               ("Discussion A (1)", TYPE_ENTRY), ("Discussion A (2)", TYPE_ENTRY)])
        self.assertEqual(set(chapter1["children"]), chapter1_discussions_with_types)
688 689 690 691 692
        self.assertEqual(set(chapter1["entries"].keys()), chapter1_discussions)

        chapter2 = category_map["subcategories"]["Chapter 2"]
        subsection1 = chapter2["subcategories"]["Section 1"]["subcategories"]["Subsection 1"]
        subsection1_discussions = set(["Discussion", "Discussion (1)"])
693 694
        subsection1_discussions_with_types = set([("Discussion", TYPE_ENTRY), ("Discussion (1)", TYPE_ENTRY)])
        self.assertEqual(set(subsection1["children"]), subsection1_discussions_with_types)
695 696
        self.assertEqual(set(subsection1["entries"].keys()), subsection1_discussions)

697
    def test_start_date_filter(self):
698
        now = datetime.datetime.now()
699
        self.create_discussion("Chapter 1", "Discussion 1", start=now)
700
        self.create_discussion("Chapter 1", "Discussion 2 обсуждение", start=self.later)
701
        self.create_discussion("Chapter 2", "Discussion", start=now)
702 703 704
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion", start=self.later)
        self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "Discussion", start=self.later)
        self.create_discussion("Chapter 3 / Section 1", "Discussion", start=self.later)
705

706
        self.assertFalse(self.course.self_paced)
707
        self.assert_category_map_equals(
708 709 710 711 712 713 714
            {
                "entries": {},
                "subcategories": {
                    "Chapter 1": {
                        "entries": {
                            "Discussion 1": {
                                "id": "discussion1",
715
                                "sort_key": None,
716
                                "is_divided": False,
717 718 719
                            }
                        },
                        "subcategories": {},
720
                        "children": [("Discussion 1", TYPE_ENTRY)]
721 722 723 724 725
                    },
                    "Chapter 2": {
                        "entries": {
                            "Discussion": {
                                "id": "discussion3",
726
                                "sort_key": None,
727
                                "is_divided": False,
728 729 730
                            }
                        },
                        "subcategories": {},
731
                        "children": [("Discussion", TYPE_ENTRY)]
732 733
                    }
                },
734
                "children": [("Chapter 1", TYPE_SUBCATEGORY), ("Chapter 2", TYPE_SUBCATEGORY)]
735 736
            }
        )
737 738 739 740 741 742

    def test_self_paced_start_date_filter(self):
        self.course.self_paced = True

        now = datetime.datetime.now()
        self.create_discussion("Chapter 1", "Discussion 1", start=now)
743
        self.create_discussion("Chapter 1", "Discussion 2", start=self.later)
744
        self.create_discussion("Chapter 2", "Discussion", start=now)
745 746 747
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion", start=self.later)
        self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "Discussion", start=self.later)
        self.create_discussion("Chapter 3 / Section 1", "Discussion", start=self.later)
748 749 750 751 752 753 754 755 756 757 758

        self.assertTrue(self.course.self_paced)
        self.assert_category_map_equals(
            {
                "entries": {},
                "subcategories": {
                    "Chapter 1": {
                        "entries": {
                            "Discussion 1": {
                                "id": "discussion1",
                                "sort_key": None,
759
                                "is_divided": False,
760 761 762 763
                            },
                            "Discussion 2": {
                                "id": "discussion2",
                                "sort_key": None,
764
                                "is_divided": False,
765 766 767
                            }
                        },
                        "subcategories": {},
768
                        "children": [("Discussion 1", TYPE_ENTRY), ("Discussion 2", TYPE_ENTRY)]
769 770 771 772 773 774
                    },
                    "Chapter 2": {
                        "entries": {
                            "Discussion": {
                                "id": "discussion3",
                                "sort_key": None,
775
                                "is_divided": False,
776 777 778 779 780 781 782 783 784 785 786
                            }
                        },
                        "subcategories": {
                            "Section 1": {
                                "entries": {},
                                "subcategories": {
                                    "Subsection 1": {
                                        "entries": {
                                            "Discussion": {
                                                "id": "discussion4",
                                                "sort_key": None,
787
                                                "is_divided": False,
788 789 790
                                            }
                                        },
                                        "subcategories": {},
791
                                        "children": [("Discussion", TYPE_ENTRY)]
792 793 794 795 796 797
                                    },
                                    "Subsection 2": {
                                        "entries": {
                                            "Discussion": {
                                                "id": "discussion5",
                                                "sort_key": None,
798
                                                "is_divided": False,
799 800 801
                                            }
                                        },
                                        "subcategories": {},
802
                                        "children": [("Discussion", TYPE_ENTRY)]
803 804
                                    }
                                },
805
                                "children": [("Subsection 1", TYPE_SUBCATEGORY), ("Subsection 2", TYPE_SUBCATEGORY)]
806 807
                            }
                        },
808
                        "children": [("Discussion", TYPE_ENTRY), ("Section 1", TYPE_SUBCATEGORY)]
809 810 811 812 813 814 815 816 817
                    },
                    "Chapter 3": {
                        "entries": {},
                        "subcategories": {
                            "Section 1": {
                                "entries": {
                                    "Discussion": {
                                        "id": "discussion6",
                                        "sort_key": None,
818
                                        "is_divided": False,
819 820 821
                                    }
                                },
                                "subcategories": {},
822
                                "children": [("Discussion", TYPE_ENTRY)]
823 824
                            }
                        },
825
                        "children": [("Section 1", TYPE_SUBCATEGORY)]
826 827
                    }
                },
828 829
                "children": [("Chapter 1", TYPE_SUBCATEGORY), ("Chapter 2", TYPE_SUBCATEGORY),
                             ("Chapter 3", TYPE_SUBCATEGORY)]
830 831
            }
        )
832 833 834 835 836 837 838 839

    def test_sort_inline_explicit(self):
        self.create_discussion("Chapter", "Discussion 1", sort_key="D")
        self.create_discussion("Chapter", "Discussion 2", sort_key="A")
        self.create_discussion("Chapter", "Discussion 3", sort_key="E")
        self.create_discussion("Chapter", "Discussion 4", sort_key="C")
        self.create_discussion("Chapter", "Discussion 5", sort_key="B")

840
        self.assert_category_map_equals(
841 842 843 844 845 846 847
            {
                "entries": {},
                "subcategories": {
                    "Chapter": {
                        "entries": {
                            "Discussion 1": {
                                "id": "discussion1",
848
                                "sort_key": "D",
849
                                "is_divided": False,
850 851 852
                            },
                            "Discussion 2": {
                                "id": "discussion2",
853
                                "sort_key": "A",
854
                                "is_divided": False,
855 856 857
                            },
                            "Discussion 3": {
                                "id": "discussion3",
858
                                "sort_key": "E",
859
                                "is_divided": False,
860 861 862
                            },
                            "Discussion 4": {
                                "id": "discussion4",
863
                                "sort_key": "C",
864
                                "is_divided": False,
865 866 867
                            },
                            "Discussion 5": {
                                "id": "discussion5",
868
                                "sort_key": "B",
869
                                "is_divided": False,
870 871 872 873
                            }
                        },
                        "subcategories": {},
                        "children": [
874 875 876 877 878
                            ("Discussion 2", TYPE_ENTRY),
                            ("Discussion 5", TYPE_ENTRY),
                            ("Discussion 4", TYPE_ENTRY),
                            ("Discussion 1", TYPE_ENTRY),
                            ("Discussion 3", TYPE_ENTRY)
879 880 881
                        ]
                    }
                },
882
                "children": [("Chapter", TYPE_SUBCATEGORY)]
883 884 885 886 887 888 889 890 891
            }
        )

    def test_sort_configured_topics_explicit(self):
        self.course.discussion_topics = {
            "Topic A": {"id": "Topic_A", "sort_key": "B"},
            "Topic B": {"id": "Topic_B", "sort_key": "C"},
            "Topic C": {"id": "Topic_C", "sort_key": "A"}
        }
892
        self.assert_category_map_equals(
893 894
            {
                "entries": {
895 896 897
                    "Topic A": {"id": "Topic_A", "sort_key": "B", "is_divided": False},
                    "Topic B": {"id": "Topic_B", "sort_key": "C", "is_divided": False},
                    "Topic C": {"id": "Topic_C", "sort_key": "A", "is_divided": False},
898 899
                },
                "subcategories": {},
900
                "children": [("Topic C", TYPE_ENTRY), ("Topic A", TYPE_ENTRY), ("Topic B", TYPE_ENTRY)]
901 902 903 904 905 906 907 908 909 910 911
            }
        )

    def test_sort_alpha(self):
        self.course.discussion_sort_alpha = True
        self.create_discussion("Chapter", "Discussion D")
        self.create_discussion("Chapter", "Discussion A")
        self.create_discussion("Chapter", "Discussion E")
        self.create_discussion("Chapter", "Discussion C")
        self.create_discussion("Chapter", "Discussion B")

912
        self.assert_category_map_equals(
913 914 915 916 917 918 919
            {
                "entries": {},
                "subcategories": {
                    "Chapter": {
                        "entries": {
                            "Discussion D": {
                                "id": "discussion1",
920
                                "sort_key": "Discussion D",
921
                                "is_divided": False,
922 923 924
                            },
                            "Discussion A": {
                                "id": "discussion2",
925
                                "sort_key": "Discussion A",
926
                                "is_divided": False,
927 928 929
                            },
                            "Discussion E": {
                                "id": "discussion3",
930
                                "sort_key": "Discussion E",
931
                                "is_divided": False,
932 933 934
                            },
                            "Discussion C": {
                                "id": "discussion4",
935
                                "sort_key": "Discussion C",
936
                                "is_divided": False,
937 938 939
                            },
                            "Discussion B": {
                                "id": "discussion5",
940
                                "sort_key": "Discussion B",
941
                                "is_divided": False,
942 943 944 945
                            }
                        },
                        "subcategories": {},
                        "children": [
946 947 948 949 950
                            ("Discussion A", TYPE_ENTRY),
                            ("Discussion B", TYPE_ENTRY),
                            ("Discussion C", TYPE_ENTRY),
                            ("Discussion D", TYPE_ENTRY),
                            ("Discussion E", TYPE_ENTRY)
951 952 953
                        ]
                    }
                },
954
                "children": [("Chapter", TYPE_SUBCATEGORY)]
955 956 957 958 959 960 961 962 963 964
            }
        )

    def test_sort_intermediates(self):
        self.create_discussion("Chapter B", "Discussion 2")
        self.create_discussion("Chapter C", "Discussion")
        self.create_discussion("Chapter A", "Discussion 1")
        self.create_discussion("Chapter B", "Discussion 1")
        self.create_discussion("Chapter A", "Discussion 2")

965
        self.assert_category_map_equals(
966 967 968 969 970 971 972
            {
                "entries": {},
                "subcategories": {
                    "Chapter A": {
                        "entries": {
                            "Discussion 1": {
                                "id": "discussion3",
973
                                "sort_key": None,
974
                                "is_divided": False,
975 976 977
                            },
                            "Discussion 2": {
                                "id": "discussion5",
978
                                "sort_key": None,
979
                                "is_divided": False,
980 981 982
                            }
                        },
                        "subcategories": {},
983
                        "children": [("Discussion 1", TYPE_ENTRY), ("Discussion 2", TYPE_ENTRY)]
984 985 986 987 988
                    },
                    "Chapter B": {
                        "entries": {
                            "Discussion 1": {
                                "id": "discussion4",
989
                                "sort_key": None,
990
                                "is_divided": False,
991 992 993
                            },
                            "Discussion 2": {
                                "id": "discussion1",
994
                                "sort_key": None,
995
                                "is_divided": False,
996 997 998
                            }
                        },
                        "subcategories": {},
999
                        "children": [("Discussion 1", TYPE_ENTRY), ("Discussion 2", TYPE_ENTRY)]
1000 1001 1002 1003 1004
                    },
                    "Chapter C": {
                        "entries": {
                            "Discussion": {
                                "id": "discussion2",
1005
                                "sort_key": None,
1006
                                "is_divided": False,
1007 1008 1009
                            }
                        },
                        "subcategories": {},
1010
                        "children": [("Discussion", TYPE_ENTRY)]
1011 1012
                    }
                },
1013 1014
                "children": [("Chapter A", TYPE_SUBCATEGORY), ("Chapter B", TYPE_SUBCATEGORY),
                             ("Chapter C", TYPE_SUBCATEGORY)]
1015 1016
            }
        )
1017

polesye committed
1018
    def test_ids_empty(self):
1019
        self.assertEqual(utils.get_discussion_categories_ids(self.course, self.user), [])
polesye committed
1020 1021 1022 1023 1024 1025 1026 1027

    def test_ids_configured_topics(self):
        self.course.discussion_topics = {
            "Topic A": {"id": "Topic_A"},
            "Topic B": {"id": "Topic_B"},
            "Topic C": {"id": "Topic_C"}
        }
        self.assertItemsEqual(
1028
            utils.get_discussion_categories_ids(self.course, self.user),
polesye committed
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
            ["Topic_A", "Topic_B", "Topic_C"]
        )

    def test_ids_inline(self):
        self.create_discussion("Chapter 1", "Discussion 1")
        self.create_discussion("Chapter 1", "Discussion 2")
        self.create_discussion("Chapter 2", "Discussion")
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion")
        self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "Discussion")
        self.create_discussion("Chapter 3 / Section 1", "Discussion")
        self.assertItemsEqual(
1040
            utils.get_discussion_categories_ids(self.course, self.user),
polesye committed
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
            ["discussion1", "discussion2", "discussion3", "discussion4", "discussion5", "discussion6"]
        )

    def test_ids_mixed(self):
        self.course.discussion_topics = {
            "Topic A": {"id": "Topic_A"},
            "Topic B": {"id": "Topic_B"},
            "Topic C": {"id": "Topic_C"}
        }
        self.create_discussion("Chapter 1", "Discussion 1")
        self.create_discussion("Chapter 2", "Discussion")
        self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion")
        self.assertItemsEqual(
1054
            utils.get_discussion_categories_ids(self.course, self.user),
polesye committed
1055 1056 1057
            ["Topic_A", "Topic_B", "Topic_C", "discussion1", "discussion2", "discussion3"]
        )

1058

1059
@attr(shard=1)
1060 1061
class ContentGroupCategoryMapTestCase(CategoryMapTestMixin, ContentGroupTestCase):
    """
1062
    Tests `get_discussion_category_map` on discussion xblocks which are
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
    only visible to some content groups.
    """
    def test_staff_user(self):
        """
        Verify that the staff user can access the alpha, beta, and
        global discussion topics.
        """
        self.assert_category_map_equals(
            {
                'subcategories': {
                    'Week 1': {
                        'subcategories': {},
                        'children': [
1076 1077 1078
                            ('Visible to Alpha', 'entry'),
                            ('Visible to Beta', 'entry'),
                            ('Visible to Everyone', 'entry')
1079 1080 1081 1082
                        ],
                        'entries': {
                            'Visible to Alpha': {
                                'sort_key': None,
1083
                                'is_divided': False,
1084 1085 1086 1087
                                'id': 'alpha_group_discussion'
                            },
                            'Visible to Beta': {
                                'sort_key': None,
1088
                                'is_divided': False,
1089 1090 1091 1092
                                'id': 'beta_group_discussion'
                            },
                            'Visible to Everyone': {
                                'sort_key': None,
1093
                                'is_divided': False,
1094 1095 1096 1097 1098
                                'id': 'global_group_discussion'
                            }
                        }
                    }
                },
1099
                'children': [('General', 'entry'), ('Week 1', 'subcategory')],
1100 1101 1102
                'entries': {
                    'General': {
                        'sort_key': 'General',
1103
                        'is_divided': False,
1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
                        'id': 'i4x-org-number-course-run'
                    }
                }
            },
            requesting_user=self.staff_user
        )

    def test_alpha_user(self):
        """
        Verify that the alpha user can access the alpha and global
        discussion topics.
        """
        self.assert_category_map_equals(
            {
                'subcategories': {
                    'Week 1': {
                        'subcategories': {},
                        'children': [
1122 1123
                            ('Visible to Alpha', 'entry'),
                            ('Visible to Everyone', 'entry')
1124 1125 1126 1127
                        ],
                        'entries': {
                            'Visible to Alpha': {
                                'sort_key': None,
1128
                                'is_divided': False,
1129 1130 1131 1132
                                'id': 'alpha_group_discussion'
                            },
                            'Visible to Everyone': {
                                'sort_key': None,
1133
                                'is_divided': False,
1134 1135 1136 1137 1138
                                'id': 'global_group_discussion'
                            }
                        }
                    }
                },
1139
                'children': [('General', 'entry'), ('Week 1', 'subcategory')],
1140 1141 1142
                'entries': {
                    'General': {
                        'sort_key': 'General',
1143
                        'is_divided': False,
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
                        'id': 'i4x-org-number-course-run'
                    }
                }
            },
            requesting_user=self.alpha_user
        )

    def test_beta_user(self):
        """
        Verify that the beta user can access the beta and global
        discussion topics.
        """
        self.assert_category_map_equals(
            {
                'subcategories': {
                    'Week 1': {
                        'subcategories': {},
                        'children': [
1162 1163
                            ('Visible to Beta', 'entry'),
                            ('Visible to Everyone', 'entry')
1164 1165 1166 1167
                        ],
                        'entries': {
                            'Visible to Beta': {
                                'sort_key': None,
1168
                                'is_divided': False,
1169 1170 1171 1172
                                'id': 'beta_group_discussion'
                            },
                            'Visible to Everyone': {
                                'sort_key': None,
1173
                                'is_divided': False,
1174 1175 1176 1177 1178
                                'id': 'global_group_discussion'
                            }
                        }
                    }
                },
1179
                'children': [('General', 'entry'), ('Week 1', 'subcategory')],
1180 1181 1182
                'entries': {
                    'General': {
                        'sort_key': 'General',
1183
                        'is_divided': False,
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
                        'id': 'i4x-org-number-course-run'
                    }
                }
            },
            requesting_user=self.beta_user
        )

    def test_non_cohorted_user(self):
        """
        Verify that the non-cohorted user can access the global
        discussion topic.
        """
        self.assert_category_map_equals(
            {
                'subcategories': {
                    'Week 1': {
                        'subcategories': {},
                        'children': [
1202
                            ('Visible to Everyone', 'entry')
1203 1204 1205 1206
                        ],
                        'entries': {
                            'Visible to Everyone': {
                                'sort_key': None,
1207
                                'is_divided': False,
1208 1209 1210 1211 1212
                                'id': 'global_group_discussion'
                            }
                        }
                    }
                },
1213
                'children': [('General', 'entry'), ('Week 1', 'subcategory')],
1214 1215 1216
                'entries': {
                    'General': {
                        'sort_key': 'General',
1217
                        'is_divided': False,
1218 1219 1220 1221 1222 1223 1224 1225
                        'id': 'i4x-org-number-course-run'
                    }
                }
            },
            requesting_user=self.non_cohorted_user
        )


1226 1227 1228 1229 1230
class JsonResponseTestCase(TestCase, UnicodeTestMixin):
    def _test_unicode_data(self, text):
        response = utils.JsonResponse(text)
        reparsed = json.loads(response.content)
        self.assertEqual(reparsed, text)
1231 1232


1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264
class DiscussionTabTestCase(ModuleStoreTestCase):
    """ Test visibility of the discussion tab. """

    def setUp(self):
        super(DiscussionTabTestCase, self).setUp()
        self.course = CourseFactory.create()
        self.enrolled_user = UserFactory.create()
        self.staff_user = AdminFactory.create()
        CourseEnrollmentFactory.create(user=self.enrolled_user, course_id=self.course.id)
        self.unenrolled_user = UserFactory.create()

    def discussion_tab_present(self, user):
        """ Returns true if the user has access to the discussion tab. """
        request = RequestFactory().request()
        request.user = user
        all_tabs = get_course_tab_list(request, self.course)
        return any(tab.type == 'discussion' for tab in all_tabs)

    def test_tab_access(self):
        with self.settings(FEATURES={'ENABLE_DISCUSSION_SERVICE': True}):
            self.assertTrue(self.discussion_tab_present(self.staff_user))
            self.assertTrue(self.discussion_tab_present(self.enrolled_user))
            self.assertFalse(self.discussion_tab_present(self.unenrolled_user))

    @mock.patch('ccx.overrides.get_current_ccx')
    def test_tab_settings(self, mock_get_ccx):
        mock_get_ccx.return_value = True
        with self.settings(FEATURES={'ENABLE_DISCUSSION_SERVICE': False}):
            self.assertFalse(self.discussion_tab_present(self.enrolled_user))

        with self.settings(FEATURES={'CUSTOM_COURSES_EDX': True}):
            self.assertFalse(self.discussion_tab_present(self.enrolled_user))
1265 1266


1267
class IsCommentableDividedTestCase(ModuleStoreTestCase):
1268
    """
1269
    Test the is_commentable_divided function.
1270 1271
    """

1272
    MODULESTORE = TEST_DATA_MIXED_MODULESTORE
1273 1274 1275 1276 1277

    def setUp(self):
        """
        Make sure that course is reloaded every time--clear out the modulestore.
        """
1278
        super(IsCommentableDividedTestCase, self).setUp()
1279
        self.toy_course_key = ToyCourseFactory.create().id
1280

1281
    def test_is_commentable_divided(self):
1282 1283 1284 1285 1286 1287 1288 1289 1290
        course = modulestore().get_course(self.toy_course_key)
        self.assertFalse(cohorts.is_course_cohorted(course.id))

        def to_id(name):
            """Helper for topic_name_to_id that uses course."""
            return topic_name_to_id(course, name)

        # no topics
        self.assertFalse(
1291
            utils.is_commentable_divided(course.id, to_id("General")),
1292 1293 1294 1295
            "Course doesn't even have a 'General' topic"
        )

        # not cohorted
1296 1297
        config_course_cohorts(course, is_cohorted=False)
        config_course_discussions(course, discussion_topics=["General", "Feedback"])
1298
        self.assertFalse(
1299
            utils.is_commentable_divided(course.id, to_id("General")),
1300 1301 1302 1303
            "Course isn't cohorted"
        )

        # cohorted, but top level topics aren't
1304 1305
        config_course_cohorts(course, is_cohorted=True)
        config_course_discussions(course, discussion_topics=["General", "Feedback"])
1306 1307 1308

        self.assertTrue(cohorts.is_course_cohorted(course.id))
        self.assertFalse(
1309
            utils.is_commentable_divided(course.id, to_id("General")),
1310 1311 1312 1313 1314 1315
            "Course is cohorted, but 'General' isn't."
        )

        # cohorted, including "Feedback" top-level topics aren't
        config_course_cohorts(
            course,
1316
            is_cohorted=True
1317
        )
1318
        config_course_discussions(course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback"])
1319 1320 1321

        self.assertTrue(cohorts.is_course_cohorted(course.id))
        self.assertFalse(
1322
            utils.is_commentable_divided(course.id, to_id("General")),
1323 1324 1325
            "Course is cohorted, but 'General' isn't."
        )
        self.assertTrue(
1326
            utils.is_commentable_divided(course.id, to_id("Feedback")),
1327 1328 1329
            "Feedback was listed as cohorted.  Should be."
        )

1330
    def test_is_commentable_divided_inline_discussion(self):
1331 1332 1333 1334 1335 1336 1337 1338 1339
        course = modulestore().get_course(self.toy_course_key)
        self.assertFalse(cohorts.is_course_cohorted(course.id))

        def to_id(name):  # pylint: disable=missing-docstring
            return topic_name_to_id(course, name)

        config_course_cohorts(
            course,
            is_cohorted=True,
1340 1341 1342
        )
        config_course_discussions(
            course,
1343
            discussion_topics=["General", "Feedback"],
1344
            divided_discussions=["Feedback", "random_inline"]
1345
        )
1346

1347
        self.assertFalse(
1348
            utils.is_commentable_divided(course.id, to_id("random")),
1349
            "By default, Non-top-level discussions are not cohorted in a cohorted courses."
1350 1351
        )

1352 1353
        # if always_divide_inline_discussions is set to False, non-top-level discussion are always
        # not divided unless they are explicitly set in divided_discussions
1354 1355 1356
        config_course_cohorts(
            course,
            is_cohorted=True,
1357 1358 1359
        )
        config_course_discussions(
            course,
1360
            discussion_topics=["General", "Feedback"],
1361 1362
            divided_discussions=["Feedback", "random_inline"],
            always_divide_inline_discussions=False
1363
        )
1364

1365
        self.assertFalse(
1366
            utils.is_commentable_divided(course.id, to_id("random")),
1367
            "Non-top-level discussion is not cohorted if always_divide_inline_discussions is False."
1368 1369
        )
        self.assertTrue(
1370
            utils.is_commentable_divided(course.id, to_id("random_inline")),
1371
            "If always_divide_inline_discussions set to False, Non-top-level discussion is "
1372 1373 1374
            "cohorted if explicitly set in cohorted_discussions."
        )
        self.assertTrue(
1375
            utils.is_commentable_divided(course.id, to_id("Feedback")),
1376
            "If always_divide_inline_discussions set to False, top-level discussion are not affected."
1377
        )
1378

1379
    def test_is_commentable_divided_team(self):
1380 1381 1382
        course = modulestore().get_course(self.toy_course_key)
        self.assertFalse(cohorts.is_course_cohorted(course.id))

1383 1384 1385
        config_course_cohorts(course, is_cohorted=True)
        config_course_discussions(course, always_divide_inline_discussions=True)

1386 1387 1388
        team = CourseTeamFactory(course_id=course.id)

        # Verify that team discussions are not cohorted, but other discussions are
1389
        # if "always cohort inline discussions" is set to true.
1390 1391
        self.assertFalse(utils.is_commentable_divided(course.id, team.discussion_topic_id))
        self.assertTrue(utils.is_commentable_divided(course.id, "random"))
1392

1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483
    def test_is_commentable_divided_cohorts(self):
        course = modulestore().get_course(self.toy_course_key)
        set_discussion_division_settings(
            course.id,
            enable_cohorts=True,
            divided_discussions=[],
            always_divide_inline_discussions=True,
            division_scheme=CourseDiscussionSettings.NONE,
        )

        # Although Cohorts are enabled, discussion division is explicitly disabled.
        self.assertFalse(utils.is_commentable_divided(course.id, "random"))

        # Now set the discussion division scheme.
        set_discussion_division_settings(
            course.id,
            enable_cohorts=True,
            divided_discussions=[],
            always_divide_inline_discussions=True,
            division_scheme=CourseDiscussionSettings.COHORT,
        )
        self.assertTrue(utils.is_commentable_divided(course.id, "random"))

    def test_is_commentable_divided_enrollment_track(self):
        course = modulestore().get_course(self.toy_course_key)
        set_discussion_division_settings(
            course.id,
            divided_discussions=[],
            always_divide_inline_discussions=True,
            division_scheme=CourseDiscussionSettings.ENROLLMENT_TRACK,
        )

        # Although division scheme is set to ENROLLMENT_TRACK, divided returns
        # False because there is only a single enrollment mode.
        self.assertFalse(utils.is_commentable_divided(course.id, "random"))

        # Now create 2 explicit course modes.
        CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.AUDIT)
        CourseModeFactory.create(course_id=course.id, mode_slug=CourseMode.VERIFIED)
        self.assertTrue(utils.is_commentable_divided(course.id, "random"))


@attr(shard=1)
class GroupIdForUserTestCase(ModuleStoreTestCase):
    """ Test the get_group_id_for_user method. """

    def setUp(self):
        super(GroupIdForUserTestCase, self).setUp()
        self.course = CourseFactory.create()
        CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT)
        CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
        self.test_user = UserFactory.create()
        CourseEnrollmentFactory.create(
            mode=CourseMode.VERIFIED, user=self.test_user, course_id=self.course.id
        )
        self.test_cohort = CohortFactory(
            course_id=self.course.id,
            name='Test Cohort',
            users=[self.test_user]
        )

    def test_discussion_division_disabled(self):
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertEqual(CourseDiscussionSettings.NONE, course_discussion_settings.division_scheme)
        self.assertIsNone(utils.get_group_id_for_user(self.test_user, course_discussion_settings))

    def test_discussion_division_by_cohort(self):
        set_discussion_division_settings(
            self.course.id, enable_cohorts=True, division_scheme=CourseDiscussionSettings.COHORT
        )
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertEqual(CourseDiscussionSettings.COHORT, course_discussion_settings.division_scheme)
        self.assertEqual(
            self.test_cohort.id,
            utils.get_group_id_for_user(self.test_user, course_discussion_settings)
        )

    def test_discussion_division_by_enrollment_track(self):
        set_discussion_division_settings(
            self.course.id, division_scheme=CourseDiscussionSettings.ENROLLMENT_TRACK
        )
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertEqual(CourseDiscussionSettings.ENROLLMENT_TRACK, course_discussion_settings.division_scheme)
        self.assertEqual(
            -2,  # Verified has group ID 2, and we negate that value to ensure unique IDs
            utils.get_group_id_for_user(self.test_user, course_discussion_settings)
        )


@attr(shard=1)
class CourseDiscussionDivisionEnabledTestCase(ModuleStoreTestCase):
1484
    """ Test the course_discussion_division_enabled and available_division_schemes methods. """
1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498

    def setUp(self):
        super(CourseDiscussionDivisionEnabledTestCase, self).setUp()
        self.course = CourseFactory.create()
        CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT)
        self.test_cohort = CohortFactory(
            course_id=self.course.id,
            name='Test Cohort',
            users=[]
        )

    def test_discussion_division_disabled(self):
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertFalse(utils.course_discussion_division_enabled(course_discussion_settings))
1499
        self.assertEqual([], utils.available_division_schemes(self.course.id))
1500 1501 1502 1503 1504 1505 1506

    def test_discussion_division_by_cohort(self):
        set_discussion_division_settings(
            self.course.id, enable_cohorts=False, division_scheme=CourseDiscussionSettings.COHORT
        )
        # Because cohorts are disabled, discussion division is not enabled.
        self.assertFalse(utils.course_discussion_division_enabled(get_course_discussion_settings(self.course.id)))
1507
        self.assertEqual([], utils.available_division_schemes(self.course.id))
1508 1509 1510 1511 1512
        # Now enable cohorts, which will cause discussions to be divided.
        set_discussion_division_settings(
            self.course.id, enable_cohorts=True, division_scheme=CourseDiscussionSettings.COHORT
        )
        self.assertTrue(utils.course_discussion_division_enabled(get_course_discussion_settings(self.course.id)))
1513
        self.assertEqual([CourseDiscussionSettings.COHORT], utils.available_division_schemes(self.course.id))
1514 1515 1516 1517 1518 1519 1520

    def test_discussion_division_by_enrollment_track(self):
        set_discussion_division_settings(
            self.course.id, division_scheme=CourseDiscussionSettings.ENROLLMENT_TRACK
        )
        # Only a single enrollment track exists, so discussion division is not enabled.
        self.assertFalse(utils.course_discussion_division_enabled(get_course_discussion_settings(self.course.id)))
1521
        self.assertEqual([], utils.available_division_schemes(self.course.id))
1522 1523 1524 1525

        # Now create a second CourseMode, which will cause discussions to be divided.
        CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
        self.assertTrue(utils.course_discussion_division_enabled(get_course_discussion_settings(self.course.id)))
1526
        self.assertEqual([CourseDiscussionSettings.ENROLLMENT_TRACK], utils.available_division_schemes(self.course.id))
1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596


@attr(shard=1)
class GroupNameTestCase(ModuleStoreTestCase):
    """ Test the get_group_name and get_group_names_by_id methods. """

    def setUp(self):
        super(GroupNameTestCase, self).setUp()
        self.course = CourseFactory.create()
        CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT)
        CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
        self.test_cohort_1 = CohortFactory(
            course_id=self.course.id,
            name='Cohort 1',
            users=[]
        )
        self.test_cohort_2 = CohortFactory(
            course_id=self.course.id,
            name='Cohort 2',
            users=[]
        )

    def test_discussion_division_disabled(self):
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertEqual({}, utils.get_group_names_by_id(course_discussion_settings))
        self.assertIsNone(utils.get_group_name(-1000, course_discussion_settings))

    def test_discussion_division_by_cohort(self):
        set_discussion_division_settings(
            self.course.id, enable_cohorts=True, division_scheme=CourseDiscussionSettings.COHORT
        )
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertEqual(
            {
                self.test_cohort_1.id: self.test_cohort_1.name,
                self.test_cohort_2.id: self.test_cohort_2.name
            },
            utils.get_group_names_by_id(course_discussion_settings)
        )
        self.assertEqual(
            self.test_cohort_2.name,
            utils.get_group_name(self.test_cohort_2.id, course_discussion_settings)
        )
        # Test also with a group_id that doesn't exist.
        self.assertIsNone(
            utils.get_group_name(-1000, course_discussion_settings)
        )

    def test_discussion_division_by_enrollment_track(self):
        set_discussion_division_settings(
            self.course.id, division_scheme=CourseDiscussionSettings.ENROLLMENT_TRACK
        )
        course_discussion_settings = get_course_discussion_settings(self.course.id)
        self.assertEqual(
            {
                -1: "audit course",
                -2: "verified course"
            },
            utils.get_group_names_by_id(course_discussion_settings)
        )

        self.assertEqual(
            "verified course",
            utils.get_group_name(-2, course_discussion_settings)
        )
        # Test also with a group_id that doesn't exist.
        self.assertIsNone(
            utils.get_group_name(-1000, course_discussion_settings)
        )

1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629

class PermissionsTestCase(ModuleStoreTestCase):
    """Test utils functionality related to forums "abilities" (permissions)"""

    def test_get_ability(self):
        content = {}
        content['user_id'] = '1'
        content['type'] = 'thread'

        user = mock.Mock()
        user.id = 1

        with mock.patch('django_comment_client.utils.check_permissions_by_view') as check_perm:
            check_perm.return_value = True
            self.assertEqual(utils.get_ability(None, content, user), {
                'editable': True,
                'can_reply': True,
                'can_delete': True,
                'can_openclose': True,
                'can_vote': False,
                'can_report': False
            })

            content['user_id'] = '2'
            self.assertEqual(utils.get_ability(None, content, user), {
                'editable': True,
                'can_reply': True,
                'can_delete': True,
                'can_openclose': True,
                'can_vote': True,
                'can_report': True
            })

1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649
    def test_get_ability_with_global_staff(self):
        """
        Tests that global staff has rights to report other user's post inspite
        of enrolled in the course or not.
        """
        content = {'user_id': '1', 'type': 'thread'}

        with mock.patch('django_comment_client.utils.check_permissions_by_view') as check_perm:
            # check_permissions_by_view returns false because user is not enrolled in the course.
            check_perm.return_value = False
            global_staff = UserFactory(username='global_staff', email='global_staff@edx.org', is_staff=True)
            self.assertEqual(utils.get_ability(None, content, global_staff), {
                'editable': False,
                'can_reply': False,
                'can_delete': False,
                'can_openclose': False,
                'can_vote': False,
                'can_report': True
            })

1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673
    def test_is_content_authored_by(self):
        content = {}
        user = mock.Mock()
        user.id = 1

        # strict equality checking
        content['user_id'] = 1
        self.assertTrue(utils.is_content_authored_by(content, user))

        # cast from string to int
        content['user_id'] = '1'
        self.assertTrue(utils.is_content_authored_by(content, user))

        # strict equality checking, fails
        content['user_id'] = 2
        self.assertFalse(utils.is_content_authored_by(content, user))

        # cast from string to int, fails
        content['user_id'] = 'string'
        self.assertFalse(utils.is_content_authored_by(content, user))

        # content has no known author
        del content['user_id']
        self.assertFalse(utils.is_content_authored_by(content, user))
1674 1675


1676 1677 1678 1679
class GroupModeratorPermissionsTestCase(ModuleStoreTestCase):
    """Test utils functionality related to forums "abilities" (permissions) for group moderators"""

    def _check_condition(user, condition, content):
Sofiya Semenova committed
1680 1681 1682 1683
        """
        Mocks check_condition method because is_open and is_team_member_if_applicable must always be true
        in order to interact with a thread or comment.
        """
1684 1685 1686 1687 1688 1689 1690 1691
        return True if condition == 'is_open' or condition == 'is_team_member_if_applicable' else False

    def setUp(self):
        super(GroupModeratorPermissionsTestCase, self).setUp()

        # Create course, seed permissions roles, and create team
        self.course = CourseFactory.create()
        seed_permissions_roles(self.course.id)
Sofiya Semenova committed
1692 1693 1694 1695 1696 1697 1698 1699
        verified_coursemode = CourseModeFactory.create(
            course_id=self.course.id,
            mode_slug=CourseMode.VERIFIED
        )
        audit_coursemode = CourseModeFactory.create(
            course_id=self.course.id,
            mode_slug=CourseMode.AUDIT
        )
1700 1701 1702 1703

        # Create four users: group_moderator (who is within the verified enrollment track and in the cohort),
        # verified_user (who is in the verified enrollment track but not the cohort),
        # cohorted_user (who is in the cohort but not the verified enrollment track),
Sofiya Semenova committed
1704
        # and plain_user (who is neither in the cohort nor the verified enrollment track)
1705 1706 1707 1708
        self.group_moderator = UserFactory(username='group_moderator', email='group_moderator@edx.org')
        CourseEnrollmentFactory(
            course_id=self.course.id,
            user=self.group_moderator,
Sofiya Semenova committed
1709
            mode=verified_coursemode
1710 1711 1712 1713 1714
        )
        self.verified_user = UserFactory(username='verified', email='verified@edx.org')
        CourseEnrollmentFactory(
            course_id=self.course.id,
            user=self.verified_user,
Sofiya Semenova committed
1715
            mode=verified_coursemode
1716 1717 1718 1719 1720
        )
        self.cohorted_user = UserFactory(username='cohort', email='cohort@edx.org')
        CourseEnrollmentFactory(
            course_id=self.course.id,
            user=self.cohorted_user,
Sofiya Semenova committed
1721
            mode=audit_coursemode
1722 1723 1724 1725 1726
        )
        self.plain_user = UserFactory(username='plain', email='plain@edx.org')
        CourseEnrollmentFactory(
            course_id=self.course.id,
            user=self.plain_user,
Sofiya Semenova committed
1727
            mode=audit_coursemode
1728 1729 1730 1731
        )
        CohortFactory(
            course_id=self.course.id,
            name='Test Cohort',
1732
            users=[self.group_moderator, self.cohorted_user]
1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816
        )

        # Give group moderator permissions to group_moderator
        assign_role(self.course.id, self.group_moderator, 'Group Moderator')

    @mock.patch('django_comment_client.permissions._check_condition', side_effect=_check_condition)
    def test_not_divided(self, check_condition_function):
        """
        Group moderator should not have moderator permissions if the discussions are not divided.
        """
        content = {'user_id': self.plain_user.id, 'type': 'thread', 'username': self.plain_user.username}
        self.assertEqual(utils.get_ability(self.course.id, content, self.group_moderator), {
            'editable': False,
            'can_reply': True,
            'can_delete': False,
            'can_openclose': False,
            'can_vote': True,
            'can_report': True
        })
        content = {'user_id': self.cohorted_user.id, 'type': 'thread'}
        self.assertEqual(utils.get_ability(self.course.id, content, self.group_moderator), {
            'editable': False,
            'can_reply': True,
            'can_delete': False,
            'can_openclose': False,
            'can_vote': True,
            'can_report': True
        })
        content = {'user_id': self.verified_user.id, 'type': 'thread'}
        self.assertEqual(utils.get_ability(self.course.id, content, self.group_moderator), {
            'editable': False,
            'can_reply': True,
            'can_delete': False,
            'can_openclose': False,
            'can_vote': True,
            'can_report': True
        })

    @mock.patch('django_comment_client.permissions._check_condition', side_effect=_check_condition)
    def test_divided_within_group(self, check_condition_function):
        """
        Group moderator should have moderator permissions within their group if the discussions are divided.
        """
        set_discussion_division_settings(self.course.id, enable_cohorts=True,
                                         division_scheme=CourseDiscussionSettings.COHORT)
        content = {'user_id': self.cohorted_user.id, 'type': 'thread', 'username': self.cohorted_user.username}
        self.assertEqual(utils.get_ability(self.course.id, content, self.group_moderator), {
            'editable': True,
            'can_reply': True,
            'can_delete': True,
            'can_openclose': True,
            'can_vote': True,
            'can_report': True
        })

        set_discussion_division_settings(self.course.id, division_scheme=CourseDiscussionSettings.ENROLLMENT_TRACK)
        content = {'user_id': self.verified_user.id, 'type': 'thread', 'username': self.verified_user.username}
        self.assertEqual(utils.get_ability(self.course.id, content, self.group_moderator), {
            'editable': True,
            'can_reply': True,
            'can_delete': True,
            'can_openclose': True,
            'can_vote': True,
            'can_report': True
        })

    @mock.patch('django_comment_client.permissions._check_condition', side_effect=_check_condition)
    def test_divided_outside_group(self, check_condition_function):
        """
        Group moderator should not have moderator permissions outside of their group.
        """
        content = {'user_id': self.plain_user.id, 'type': 'thread', 'username': self.plain_user.username}
        set_discussion_division_settings(self.course.id, division_scheme=CourseDiscussionSettings.NONE)

        self.assertEqual(utils.get_ability(self.course.id, content, self.group_moderator), {
            'editable': False,
            'can_reply': True,
            'can_delete': False,
            'can_openclose': False,
            'can_vote': True,
            'can_report': True
        })


1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843
class ClientConfigurationTestCase(TestCase):
    """Simple test cases to ensure enabling/disabling the use of the comment service works as intended."""

    def test_disabled(self):
        """Ensures that an exception is raised when forums are disabled."""
        config = ForumsConfig.current()
        config.enabled = False
        config.save()

        with self.assertRaises(CommentClientMaintenanceError):
            perform_request('GET', 'http://www.google.com')

    @patch('requests.request')
    def test_enabled(self, mock_request):
        """Ensures that requests proceed normally when forums are enabled."""
        config = ForumsConfig.current()
        config.enabled = True
        config.save()

        response = Mock()
        response.status_code = 200
        response.json = lambda: {}

        mock_request.return_value = response

        result = perform_request('GET', 'http://www.google.com')
        self.assertEqual(result, {})
1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861


def set_discussion_division_settings(
        course_key, enable_cohorts=False, always_divide_inline_discussions=False,
        divided_discussions=[], division_scheme=CourseDiscussionSettings.COHORT
):
    """
    Convenience method for setting cohort enablement and discussion settings.
    COHORT is the default division_scheme, as no other schemes were supported at
    the time that the unit tests were originally written.
    """
    set_course_discussion_settings(
        course_key=course_key,
        divided_discussions=divided_discussions,
        division_scheme=division_scheme,
        always_divide_inline_discussions=always_divide_inline_discussions,
    )
    set_course_cohorted(course_key, enable_cohorts)