Пример #1
0
    def test_see_own_courses_perm(self, mock, fetch_mock):  # pylint: disable=unused-argument
        """
        Assert that ownership isn't enough to see not live courses, you need
        the permission too.
        """

        # Create an instructor
        not_live_course = CourseFactory.create(live=False)
        ModuleFactory.create(course=not_live_course, price_without_tax=1)
        not_live_owned_course = CourseFactory.create(live=False)
        ModuleFactory.create(course=not_live_owned_course, price_without_tax=3)
        self.teacher.courses_owned.add(not_live_owned_course)

        visibility_pairs = [(self.course.uuid, True),
                            (not_live_course.uuid, False),
                            (not_live_owned_course.uuid, False)]
        for course_uuid, _ in visibility_pairs:
            fetch_mock.get("{base}v1/coursexs/{course_uuid}/".format(
                base=FAKE_CCXCON_API,
                course_uuid=course_uuid,
            ),
                           json={})
            fetch_mock.get("{base}v1/coursexs/{course_uuid}/modules/".format(
                base=FAKE_CCXCON_API, course_uuid=course_uuid),
                           json=[])
        self.assert_course_visibility(visibility_pairs)
Пример #2
0
    def test_update_modules(self):
        "Should set prices"
        ModuleFactory.create_batch(10, price_without_tax=None)
        Command().handle(course_uuid=None)

        assert not Module.objects.filter(
            price_without_tax__isnull=True).exists()
Пример #3
0
    def test_course_visibility(self, mock, fetch_mock):  # pylint: disable=unused-argument
        """
        Assert that an instructor can also see courses which are not live
        but which they own, in both course list and detail view.
        """
        # Create an instructor
        instructor = User.objects.create_user(username="******",
                                              password="******")
        instructor.groups.add(Group.objects.get(name="Instructor"))
        self.client.login(username="******", password="******")

        not_live_course = CourseFactory.create(live=False)
        ModuleFactory.create(course=not_live_course, price_without_tax=1)
        not_live_owned_course = CourseFactory.create(live=False)
        ModuleFactory.create(course=not_live_owned_course, price_without_tax=3)
        instructor.courses_owned.add(not_live_owned_course)

        visibility_pairs = [(self.course.uuid, True),
                            (not_live_course.uuid, False),
                            (not_live_owned_course.uuid, True)]
        for course_uuid, _ in visibility_pairs:
            fetch_mock.get("{base}v1/coursexs/{course_uuid}/".format(
                base=FAKE_CCXCON_API,
                course_uuid=course_uuid,
            ),
                           json={})
            fetch_mock.get("{base}v1/coursexs/{course_uuid}/modules/".format(
                base=FAKE_CCXCON_API, course_uuid=course_uuid),
                           json=[])
        self.assert_course_visibility(visibility_pairs)
Пример #4
0
    def test_not_logged_in(self):
        """
        If the user is not logged in, return a 403.
        """
        course = CourseFactory.create(live=True)
        ModuleFactory.create(price_without_tax=3, course=course)
        assert course.is_available_for_purchase

        resp = self.client.get(reverse('course-permissions', kwargs={'uuid': course.uuid}))
        assert resp.status_code == 403, resp.content.decode('utf-8')
Пример #5
0
    def test_makes_modules(self):
        "should respect course_uuid param"
        course1 = CourseFactory.create()
        course2 = CourseFactory.create()

        ModuleFactory.create_batch(5, course=course1, price_without_tax=None)
        ModuleFactory.create_batch(5, course=course2, price_without_tax=None)
        Command().handle(course_uuid=course1.uuid)

        assert Module.objects.filter(
            price_without_tax__isnull=True).count() == 5
Пример #6
0
 def test_course_module_mismatch(self):
     """
     Assert that we don't allow duplicate items in cart
     """
     course2 = CourseFactory.create(live=True)
     ModuleFactory.create(price_without_tax=123, course=course2)
     with self.assertRaises(ValidationError) as ex:
         validate_cart([{
             "uuids": [self.module.uuid],
             "seats": 10,
             "course_uuid": course2.uuid
         }], self.user)
     assert ex.exception.detail[0] == "Course does not match up with module"
Пример #7
0
    def test_non_200_propagates_to_response(self, ccxcon_api):
        """
        If ccx POST returns a non-200, the response will have this information.
        """
        error_message = "This is an error"
        ccxcon_api.return_value.create_ccx.return_value = [
            False, 500, error_message
        ]
        total_seats = 5

        # Add an extra module to assert we only POST to ccx endpoint once per course.
        module2 = ModuleFactory.create(course=self.course,
                                       price_without_tax=123)

        cart_item = {
            "uuids": [self.module.uuid, module2.uuid],
            "seats": total_seats,
            "course_uuid": self.course.uuid
        }
        cart = [cart_item]
        total = calculate_cart_subtotal(cart)
        # Note: autospec intentionally not used, we need an unbound method here
        with patch.object(Charge, 'create') as create_mock:

            resp = self.client.post(reverse('checkout'),
                                    content_type='application/json',
                                    data=json.dumps({
                                        "cart": cart,
                                        "token": "token",
                                        "total": float(total)
                                    }))

        assert resp.status_code == 500, resp.content.decode('utf-8')
        assert error_message in resp.content.decode('utf-8')
        assert create_mock.call_count == 1
Пример #8
0
    def test_errors_propagate_to_response(self, ccxcon_api):
        """
        If there are errors on checkout, they make it to the response.
        """
        course2 = CourseFactory.create(live=True)
        module2 = ModuleFactory.create(course=course2, price_without_tax=345)
        ccxcon_api.return_value.create_ccx.side_effect = [
            AttributeError("Example Error"),
            AttributeError("Another Error"),
        ]
        cart = [{
            "uuids": [self.module.uuid],
            "seats": 5,
            "course_uuid": self.course.uuid
        }, {
            "uuids": [module2.uuid],
            "seats": 9,
            "course_uuid": module2.course.uuid
        }]

        with patch.object(Charge, 'create'):
            resp = self.client.post(reverse('checkout'),
                                    content_type='application/json',
                                    data=json.dumps({
                                        "cart":
                                        cart,
                                        "token":
                                        "token",
                                        "total":
                                        float(calculate_cart_subtotal(cart))
                                    }))

        assert resp.status_code == 500, resp.content.decode('utf-8')
        assert "Example Error" in resp.content.decode('utf-8')
        assert "Another Error" in resp.content.decode('utf-8')
Пример #9
0
 def test_module_str(self):  # pylint: disable=no-self-use
     """
     Assert str(Module)
     """
     module = ModuleFactory.create()
     expected = "{title} ({uuid})".format(title=module.title,
                                          uuid=module.uuid)
     assert str(module) == expected
Пример #10
0
 def test_module_serializer(self):  # pylint: disable=no-self-use
     """Assert behavior of ModuleSerializer"""
     module = ModuleFactory.create()
     assert dict(ModuleSerializer().to_representation(module)) == {
         "uuid": module.uuid,
         "title": module.title,
         "price_without_tax": float(module.price_without_tax)
     }
Пример #11
0
 def test_module_course_mismatch(self):
     """Error if the module uuid doesn't match the course"""
     module2 = ModuleFactory.create()
     course_dict = {
         'uuid': self.course.uuid,
         'modules': [{
             'uuid': module2.uuid
         }]
     }
     self.assert_patch_validation(
         self.course.uuid, course_dict,
         "Unable to find module {}".format(module2.uuid))
Пример #12
0
    def test_permissions(self):
        """
        Test all possible combinations of permissions.
        """
        User.objects.create_user(username="******", password="******")
        self.client.login(username="******", password="******")

        course = CourseFactory.create(live=True)
        ModuleFactory.create(price_without_tax=3, course=course)
        assert course.is_available_for_purchase

        def assert_permissions(edit_content, edit_liveness, edit_price, is_owner, see_not_live):
            """Mock and assert these set of permissions"""

            @patch.object(Helpers, 'is_owner', return_value=is_owner)
            @patch.object(Helpers, 'can_edit_own_content', return_value=edit_content)
            @patch.object(Helpers, 'can_edit_own_liveness', return_value=edit_liveness)
            @patch.object(Helpers, 'can_edit_own_price', return_value=edit_price)
            @patch.object(Helpers, 'can_see_own_not_live', return_value=see_not_live)
            def run_assert_permissions(*args):  # pylint: disable=unused-argument
                """Something to attach our patch objects to so we don't indent each time"""
                resp = self.client.get(
                    reverse('course-permissions', kwargs={"uuid": course.uuid})
                )
                assert resp.status_code == 200, resp.content.decode('utf-8')
                result = json.loads(resp.content.decode('utf-8'))
                assert result == {
                    "is_owner": is_owner,
                    EDIT_OWN_CONTENT[0]: edit_content,
                    EDIT_OWN_PRICE[0]: edit_price,
                    EDIT_OWN_LIVENESS[0]: edit_liveness,
                    SEE_OWN_NOT_LIVE[0]: see_not_live,
                }
            run_assert_permissions()

        num_args = get_function_code(assert_permissions).co_argcount  # pylint: disable=no-member
        args = [(True, False)] * num_args
        for tup in product(*args):
            assert_permissions(*tup)
Пример #13
0
 def test_cart_total(self):
     """
     Assert that the cart total is calculated correctly (seats * price)
     """
     module2 = ModuleFactory.create(course=self.course, )
     seats = 10
     assert calculate_cart_subtotal([{
         "uuids": [self.module.uuid, module2.uuid],
         "seats":
         seats,
         "course_uuid":
         self.course.uuid
     }]) == (self.module.price_without_tax * seats +
             module2.price_without_tax * seats)
Пример #14
0
 def test_duplicate_courses(self):
     """
     Assert that we don't allow duplicate courses in cart
     """
     module2 = ModuleFactory.create(course=self.course)
     with self.assertRaises(ValidationError) as ex:
         validate_cart([{
             "uuids": [self.module.uuid],
             "seats": 10,
             "course_uuid": self.course.uuid
         }, {
             "uuids": [module2.uuid],
             "seats": 15,
             "course_uuid": self.course.uuid
         }], self.user)
     assert ex.exception.detail[0] == "Duplicate course in cart"
Пример #15
0
    def test_order_course(self):
        """
        Assert that an order is created in the database
        """
        # Create second module to test cart with multiple items
        title = "other product title"
        second_price = 345
        second_course = CourseFactory.create(live=True)
        second_module = ModuleFactory.create(course=second_course,
                                             title=title,
                                             price_without_tax=second_price)

        first_seats = 5
        second_seats = 10
        order = create_order([
            {
                "uuids": [self.module.uuid],
                "seats": first_seats,
                "course_uuid": self.course.uuid
            },
            {
                "uuids": [second_module.uuid],
                "seats": second_seats,
                "course_uuid": second_module.course.uuid
            },
        ], self.user)
        first_line_total = self.module.price_without_tax * first_seats
        second_line_total = second_price * second_seats

        total = first_line_total + second_line_total
        assert order.purchaser == self.user
        assert order.total_paid == total
        assert order.subtotal == total
        assert order.orderline_set.count() == 2

        first_line = order.orderline_set.get(module=self.module)
        assert first_line.line_total == first_line_total
        assert first_line.price_without_tax == self.module.price_without_tax
        assert first_line.seats == first_seats

        second_line = order.orderline_set.get(module=second_module)
        assert second_line.line_total == second_line_total
        assert second_line.price_without_tax == second_price
        assert second_line.seats == second_seats
Пример #16
0
 def test_course_serializer(self):  # pylint: disable=no-self-use
     """Assert behavior of CourseSerializer"""
     course = CourseFactory.create()
     module = ModuleFactory.create(course=course)
     rep = CourseSerializer(course).data
     assert isinstance(rep['instructors'], list)
     assert dict(rep) == {
         "uuid": course.uuid,
         "title": course.title,
         "author_name": course.author_name,
         "overview": course.overview,
         "description": course.description,
         "image_url": course.image_url,
         "edx_instance": course.instance.instance_url,
         "instructors": course.instructors,
         "course_id": course.course_id,
         "live": course.live,
         "modules": [ModuleSerializer().to_representation(module)]
     }
Пример #17
0
    def test_price_one_module(self):
        """
        Make sure we allow patching only the modules we care about.
        """
        # Get dict when we have one module, then create a second one
        module2 = ModuleFactory.create(course=self.course)
        second_old_price = module2.price_without_tax
        new_price = '4'
        course_dict = self.patch_dict_for_price(new_price)
        assert self.module.price_without_tax != new_price

        resp = self.client.patch(reverse('course-detail',
                                         kwargs={"uuid": self.course.uuid}),
                                 content_type="application/json",
                                 data=json.dumps(course_dict))
        assert resp.status_code == 200, resp.content.decode('utf-8')

        self.module.refresh_from_db()
        assert self.module.price_without_tax == Decimal(new_price)
        assert module2.price_without_tax == second_old_price
Пример #18
0
    def test_ccx_creation(self, ccxcon_api):
        """
        Assert that CCX is created, on successful checkout.
        """
        total_seats = 5

        # Add an extra module to assert we only POST to ccx endpoint once per course.
        module2 = ModuleFactory.create(course=self.course,
                                       price_without_tax=123)

        ccxcon_api.return_value.create_ccx.return_value = (True, 200, '')

        cart_item = {
            "uuids": [self.module.uuid, module2.uuid],
            "seats": total_seats,
            "course_uuid": self.course.uuid
        }
        cart = [cart_item]
        total = calculate_cart_subtotal(cart)
        # Note: autospec intentionally not used, we need an unbound method here
        with patch.object(Charge, 'create') as create_mock:

            resp = self.client.post(reverse('checkout'),
                                    content_type='application/json',
                                    data=json.dumps({
                                        "cart": cart,
                                        "token": "token",
                                        "total": float(total)
                                    }))

        assert resp.status_code == 200, resp.content.decode('utf-8')
        assert create_mock.call_count == 1
        assert ccxcon_api.return_value.create_ccx.call_count == 1
        ccxcon_api.return_value.create_ccx.assert_called_with(
            self.course.uuid,
            self.user.email,
            total_seats,
            self.course.title,
            course_modules=[self.module.uuid, module2.uuid],
        )
Пример #19
0
    def test_total_is_a_number_string(self):  # pylint: disable=unused-argument
        """
        Assert that the total can be a string with a number representation
        """
        total_seats = 5

        # Add an extra module to assert we only POST to ccx endpoint once per course.
        module2 = ModuleFactory.create(course=self.course,
                                       price_without_tax=123)

        cart_item = {
            "uuids": [self.module.uuid, module2.uuid],
            "seats": total_seats,
            "course_uuid": self.course.uuid
        }
        cart = [cart_item]
        total = calculate_cart_subtotal(cart)
        # Note: autospec intentionally not used, we need an unbound method here
        with patch.object(Charge, 'create') as create_mock:
            with patch(
                    'portal.views.checkout_api.CheckoutView.notify_external_services'
            ) as notify_mock:
                resp = self.client.post(reverse('checkout'),
                                        content_type='application/json',
                                        data=json.dumps({
                                            "cart":
                                            cart,
                                            "token":
                                            "token",
                                            "total":
                                            str(float(total))
                                        }))

        assert resp.status_code == 200, resp.content.decode('utf-8')
        assert create_mock.call_count == 1
        assert notify_mock.call_count == 1
Пример #20
0
 def setUp(self):
     """
     Create a course and module which aren't live.
     """
     self.course = CourseFactory.create(live=False)
     self.module = ModuleFactory.create(course=self.course)