コード例 #1
0
ファイル: api_test.py プロジェクト: mitodl/mitxpro
def test_retry_failed_edx_enrollments(mocker, exception_raised):
    """
    Tests that retry_failed_edx_enrollments loops through enrollments that failed in edX
    and attempts to enroll them again
    """
    with freeze_time(now_in_utc() - timedelta(days=1)):
        failed_enrollments = CourseRunEnrollmentFactory.create_batch(
            3, edx_enrolled=False, user__is_active=True)
        CourseRunEnrollmentFactory.create(edx_enrolled=False,
                                          user__is_active=False)
    patched_enroll_in_edx = mocker.patch(
        "courseware.api.enroll_in_edx_course_runs",
        side_effect=[None, exception_raised or None, None],
    )
    patched_log_exception = mocker.patch("courseware.api.log.exception")
    successful_enrollments = retry_failed_edx_enrollments()

    assert patched_enroll_in_edx.call_count == len(failed_enrollments)
    assert len(successful_enrollments) == (3
                                           if exception_raised is None else 2)
    assert patched_log_exception.called == bool(exception_raised)
    if exception_raised:
        failed_enroll_user, failed_enroll_runs = patched_enroll_in_edx.call_args_list[
            1][0]
        expected_successful_enrollments = [
            e for e in failed_enrollments
            if e.user != failed_enroll_user and e.run != failed_enroll_runs[0]
        ]
        assert {e.id
                for e in successful_enrollments
                } == {e.id
                      for e in expected_successful_enrollments}
コード例 #2
0
def test_create_run_enrollments(mocker, user):
    """
    create_run_enrollments should call the edX API to create enrollments, create or reactivate local
    enrollment records, and notify enrolled users via email
    """
    num_runs = 3
    order = OrderFactory.create()
    company = CompanyFactory.create()
    runs = CourseRunFactory.create_batch(num_runs)
    # Create an existing deactivate enrollment to test that it gets reactivated
    CourseRunEnrollmentFactory.create(
        user=user,
        run=runs[0],
        order=order,
        change_status=ENROLL_CHANGE_STATUS_REFUNDED,
        active=False,
    )
    patched_edx_enroll = mocker.patch("courses.api.enroll_in_edx_course_runs")
    patched_send_enrollment_email = mocker.patch(
        "courses.api.mail_api.send_course_run_enrollment_email")

    successful_enrollments, edx_request_success = create_run_enrollments(
        user, runs, order=order, company=company)
    patched_edx_enroll.assert_called_once_with(user, runs)
    assert patched_send_enrollment_email.call_count == num_runs
    assert edx_request_success is True
    assert len(successful_enrollments) == num_runs
    enrollments = CourseRunEnrollment.objects.order_by("run__id").all()
    for (run, enrollment) in zip(runs, enrollments):
        assert enrollment.change_status is None
        assert enrollment.active is True
        assert enrollment.edx_enrolled is True
        assert enrollment.run == run
        patched_send_enrollment_email.assert_any_call(enrollment)
コード例 #3
0
ファイル: models_test.py プロジェクト: mitodl/mitxpro
def test_course_available_runs():
    """enrolled runs for a user should not be in the list of available runs"""
    user = UserFactory.create()
    course = CourseFactory.create()
    runs = CourseRunFactory.create_batch(2, course=course, live=True)
    runs.sort(key=lambda run: run.start_date)
    CourseRunEnrollmentFactory.create(run=runs[0], user=user)
    assert course.available_runs(user) == [runs[1]]
    assert course.available_runs(UserFactory.create()) == runs
コード例 #4
0
def test_course_view(client, user, home_page, is_enrolled, has_unexpired_run,
                     has_product, is_anonymous):
    """
    Test that the course detail view has the right context and shows the right HTML for the enroll/view button
    """
    course = CourseFactory.create(live=True, page__parent=home_page)

    if has_unexpired_run:
        run = CourseRunFactory.create(course=course, live=True)
    else:
        run = None
    if has_product and has_unexpired_run:
        product_id = ProductVersionFactory.create(product=ProductFactory(
            content_object=run)).product.id
    else:
        product_id = None
    if is_enrolled and has_unexpired_run:
        CourseRunEnrollmentFactory.create(user=user, run=run)

    if not is_anonymous:
        client.force_login(user)
    resp = client.get(course.page.get_url())
    assert resp.context["user"] == user if not is_anonymous else AnonymousUser(
    )
    assert resp.context["product_id"] == product_id
    assert resp.context["enrolled"] == (is_enrolled and has_unexpired_run
                                        and not is_anonymous)

    # Anynoymous users don't see the enrolled/enroll-now button.
    # For logged in users:
    # a) product should exist, next courserun should be there, user not enrolled (enroll now button)
    # b) user is enrolled (enrolled button)
    # NOTE: added `has_unexpired_run` to test for case (b) only because of the way the test is written,
    #       enrollment isn't actually created unless the course has an unexpired run.
    has_button = ((has_product and has_unexpired_run and not is_enrolled) or
                  (is_enrolled and has_unexpired_run)) and not is_anonymous
    url = ""  # make linter happy
    class_name = ""
    if not is_anonymous:
        if not is_enrolled and has_product and has_unexpired_run:
            url = f'{reverse("checkout-page")}?product={product_id}'
            class_name = "enroll-now"
        if is_enrolled and has_unexpired_run:
            url = reverse("user-dashboard")
            class_name = "enrolled"

    assert (
        f'<a class="enroll-button {class_name}" href="{url}">'.encode("utf-8")
        in resp.content) is has_button
    assert ("Please Sign In to MITx PRO to enroll in a course".encode("utf-8")
            in resp.content) is (is_anonymous and has_product
                                 and has_unexpired_run)
コード例 #5
0
ファイル: api_test.py プロジェクト: mitodl/mitxpro
def test_unenroll_edx_course_run_failure(mocker, client_exception_raised,
                                         expected_exception):
    """Tests that unenroll_edx_course_run translates exceptions raised by the API client"""
    run_enrollment = CourseRunEnrollmentFactory.create(edx_enrolled=True)
    mock_client = mocker.MagicMock()
    mock_client.enrollments.deactivate_enrollment = mocker.Mock(
        side_effect=client_exception_raised)
    mocker.patch("courseware.api.get_edx_api_client", return_value=mock_client)
    with pytest.raises(expected_exception):
        unenroll_edx_course_run(run_enrollment)
コード例 #6
0
ファイル: mail_api_test.py プロジェクト: mitodl/mitxpro
def test_send_course_run_enrollment_email_error(mocker):
    """send_course_run_enrollment_email handle and log errors"""
    patched_mail_api = mocker.patch("ecommerce.mail_api.api")
    patched_log = mocker.patch("ecommerce.mail_api.log")
    patched_mail_api.send_message.side_effect = Exception("error")
    enrollment = CourseRunEnrollmentFactory.create()

    send_course_run_enrollment_email(enrollment)

    patched_log.exception.assert_called_once_with(
        "Error sending enrollment success email")
コード例 #7
0
ファイル: api_test.py プロジェクト: mitodl/mitxpro
def test_retry_failed_enroll_grace_period(mocker):
    """
    Tests that retry_failed_edx_enrollments does not attempt to repair any enrollments that were recently created
    """
    now = now_in_utc()
    with freeze_time(now - timedelta(
            minutes=COURSEWARE_REPAIR_GRACE_PERIOD_MINS - 1)):
        CourseRunEnrollmentFactory.create(edx_enrolled=False,
                                          user__is_active=True)
    with freeze_time(now - timedelta(
            minutes=COURSEWARE_REPAIR_GRACE_PERIOD_MINS + 1)):
        older_enrollment = CourseRunEnrollmentFactory.create(
            edx_enrolled=False, user__is_active=True)
    patched_enroll_in_edx = mocker.patch(
        "courseware.api.enroll_in_edx_course_runs")
    successful_enrollments = retry_failed_edx_enrollments()

    assert successful_enrollments == [older_enrollment]
    patched_enroll_in_edx.assert_called_once_with(older_enrollment.user,
                                                  [older_enrollment.run])
コード例 #8
0
ファイル: models_test.py プロジェクト: mitodl/mitxpro
def test_deactivate_and_save():
    """Test that the deactivate_and_save method in enrollment models sets properties and saves"""
    course_run_enrollment = CourseRunEnrollmentFactory.create(
        active=True, change_status=None)
    program_enrollment = ProgramEnrollmentFactory.create(active=True,
                                                         change_status=None)
    enrollments = [course_run_enrollment, program_enrollment]
    for enrollment in enrollments:
        enrollment.deactivate_and_save(ENROLL_CHANGE_STATUS_REFUNDED)
        enrollment.refresh_from_db()
        enrollment.active = False
        enrollment.change_status = ENROLL_CHANGE_STATUS_REFUNDED
コード例 #9
0
ファイル: api_test.py プロジェクト: mitodl/mitxpro
def test_unenroll_edx_course_run(mocker):
    """Tests that unenroll_edx_course_run makes a call to unenroll in edX via the API client"""
    mock_client = mocker.MagicMock()
    run_enrollment = CourseRunEnrollmentFactory.create(edx_enrolled=True)
    courseware_id = run_enrollment.run.courseware_id
    enroll_return_value = mocker.Mock(json={"course_id": courseware_id})
    mock_client.enrollments.deactivate_enrollment = mocker.Mock(
        return_value=enroll_return_value)
    mocker.patch("courseware.api.get_edx_api_client", return_value=mock_client)
    deactivated_enrollment = unenroll_edx_course_run(run_enrollment)

    mock_client.enrollments.deactivate_enrollment.assert_called_once_with(
        courseware_id)
    assert deactivated_enrollment == enroll_return_value
コード例 #10
0
def test_defer_enrollment(mocker, keep_failed_enrollments):
    """
    defer_enrollment should deactivate a user's existing enrollment and create an enrollment in another
    course run
    """
    course = CourseFactory.create()
    course_runs = CourseRunFactory.create_batch(3, course=course)
    order = OrderFactory.create()
    company = CompanyFactory.create()
    existing_enrollment = CourseRunEnrollmentFactory.create(run=course_runs[0],
                                                            order=order,
                                                            company=company)
    target_run = course_runs[1]
    mock_new_enrollment = mocker.Mock()
    patched_create_enrollments = mocker.patch(
        "courses.api.create_run_enrollments",
        autospec=True,
        return_value=([
            mock_new_enrollment if keep_failed_enrollments else None
        ], True),
    )
    patched_deactivate_enrollments = mocker.patch(
        "courses.api.deactivate_run_enrollment",
        autospec=True,
        return_value=existing_enrollment if keep_failed_enrollments else None,
    )

    returned_from_enrollment, returned_to_enrollment = defer_enrollment(
        existing_enrollment.user,
        existing_enrollment.run.courseware_id,
        course_runs[1].courseware_id,
        keep_failed_enrollments=keep_failed_enrollments,
    )
    assert returned_from_enrollment == patched_deactivate_enrollments.return_value
    assert returned_to_enrollment == patched_create_enrollments.return_value[
        0][0]
    patched_create_enrollments.assert_called_once_with(
        existing_enrollment.user,
        [target_run],
        order=order,
        company=company,
        keep_failed_enrollments=keep_failed_enrollments,
    )
    patched_deactivate_enrollments.assert_called_once_with(
        existing_enrollment,
        ENROLL_CHANGE_STATUS_DEFERRED,
        keep_failed_enrollments=keep_failed_enrollments,
    )
コード例 #11
0
ファイル: mail_api_test.py プロジェクト: mitodl/mitxpro
def test_send_course_run_enrollment_email(mocker):
    """send_course_run_enrollment_email should send an email for the given enrollment"""
    patched_mail_api = mocker.patch("ecommerce.mail_api.api")
    enrollment = CourseRunEnrollmentFactory.create()

    send_course_run_enrollment_email(enrollment)

    patched_mail_api.context_for_user.assert_called_once_with(
        user=enrollment.user, extra_context={"enrollment": enrollment})
    patched_mail_api.message_for_recipient.assert_called_once_with(
        enrollment.user.email,
        patched_mail_api.context_for_user.return_value,
        EMAIL_COURSE_RUN_ENROLLMENT,
    )
    patched_mail_api.send_message.assert_called_once_with(
        patched_mail_api.message_for_recipient.return_value)
コード例 #12
0
    def test_deactivate_run_enrollment(self, patches):
        """
        deactivate_run_enrollment should attempt to unenroll a user in a course run in edX and set the
        local enrollment record to inactive
        """
        enrollment = CourseRunEnrollmentFactory.create(edx_enrolled=True)

        returned_enrollment = deactivate_run_enrollment(
            enrollment, change_status=ENROLL_CHANGE_STATUS_REFUNDED)
        patches.edx_unenroll.assert_called_once_with(enrollment)
        patches.send_unenrollment_email.assert_called_once_with(enrollment)
        enrollment.refresh_from_db()
        assert enrollment.change_status == ENROLL_CHANGE_STATUS_REFUNDED
        assert enrollment.active is False
        assert enrollment.edx_enrolled is False
        assert returned_enrollment == enrollment
コード例 #13
0
ファイル: models_test.py プロジェクト: mitodl/mitxpro
def test_audit(user, is_program, has_company):
    """Test audit table serialization"""
    enrollment = (ProgramEnrollmentFactory.create()
                  if is_program else CourseRunEnrollmentFactory.create())
    if has_company:
        enrollment.company = CompanyFactory.create()

    enrollment.save_and_log(user)

    expected = {
        "active":
        enrollment.active,
        "change_status":
        enrollment.change_status,
        "created_on":
        format_as_iso8601(enrollment.created_on),
        "company":
        enrollment.company.id if has_company else None,
        "company_name":
        enrollment.company.name if has_company else None,
        "email":
        enrollment.user.email,
        "full_name":
        enrollment.user.name,
        "id":
        enrollment.id,
        "order":
        enrollment.order.id,
        "text_id":
        enrollment.program.readable_id
        if is_program else enrollment.run.courseware_id,
        "updated_on":
        format_as_iso8601(enrollment.updated_on),
        "user":
        enrollment.user.id,
        "username":
        enrollment.user.username,
    }
    if not is_program:
        expected["edx_enrolled"] = enrollment.edx_enrolled
        expected["run"] = enrollment.run.id
    else:
        expected["program"] = enrollment.program.id
    assert (enrollment.get_audit_class().objects.get(
        enrollment=enrollment).data_after == expected)
コード例 #14
0
    def test_deactivate_run_enrollment_api_fail(self, patches,
                                                keep_failed_enrollments):
        """
        If a flag is provided, deactivate_run_enrollment should set local enrollment record to inactive even if the API call fails
        """
        enrollment = CourseRunEnrollmentFactory.create(edx_enrolled=True)
        patches.edx_unenroll.side_effect = Exception

        deactivate_run_enrollment(
            enrollment,
            change_status=ENROLL_CHANGE_STATUS_REFUNDED,
            keep_failed_enrollments=keep_failed_enrollments,
        )
        patches.edx_unenroll.assert_called_once_with(enrollment)
        patches.send_unenrollment_email.assert_not_called()
        patches.log_exception.assert_called_once()
        enrollment.refresh_from_db()
        assert enrollment.active is not keep_failed_enrollments
コード例 #15
0
ファイル: models_test.py プロジェクト: mitodl/mitxpro
def test_enrollment_is_ended():
    """Verify that is_ended returns True, if all of course runs in a program/course are ended."""
    past_date = now_in_utc() - timedelta(days=1)
    past_program = ProgramFactory.create()
    past_course = CourseFactory.create()

    past_course_runs = CourseRunFactory.create_batch(
        3,
        end_date=past_date,
        course=past_course,
        course__program=past_program)

    program_enrollment = ProgramEnrollmentFactory.create(program=past_program)
    course_enrollment = CourseRunEnrollmentFactory.create(
        run=past_course_runs[0])

    assert program_enrollment.is_ended
    assert course_enrollment.is_ended
コード例 #16
0
def test_serialize_course_run_enrollments(settings, has_company,
                                          receipts_enabled):
    """Test that CourseRunEnrollmentSerializer has correct data"""
    settings.ENABLE_ORDER_RECEIPTS = receipts_enabled
    course_run_enrollment = CourseRunEnrollmentFactory.create(
        has_company_affiliation=has_company)
    serialized_data = CourseRunEnrollmentSerializer(course_run_enrollment).data
    assert serialized_data == {
        "run":
        CourseRunDetailSerializer(course_run_enrollment.run).data,
        "company": (CompanySerializer(course_run_enrollment.company).data
                    if has_company else None),
        "certificate":
        None,
        "receipt":
        course_run_enrollment.order_id
        if course_run_enrollment.order.status == Order.FULFILLED
        and receipts_enabled else None,
    }
コード例 #17
0
def test_get_user_enrollments(user):
    """Test that get_user_enrollments returns an object with a user's program and course enrollments"""
    past_date = now_in_utc() - timedelta(days=1)
    past_start_dates = [
        now_in_utc() - timedelta(days=2),
        now_in_utc() - timedelta(days=3),
        now_in_utc() - timedelta(days=4),
    ]
    program = ProgramFactory.create()
    past_program = ProgramFactory.create()

    program_course_runs = CourseRunFactory.create_batch(
        3, course__program=program)
    past_program_course_runs = CourseRunFactory.create_batch(
        3,
        start_date=factory.Iterator(past_start_dates),
        end_date=past_date,
        course__program=past_program,
    )
    non_program_course_runs = CourseRunFactory.create_batch(
        2, course__program=None)
    past_non_program_course_runs = CourseRunFactory.create_batch(
        2,
        start_date=factory.Iterator(past_start_dates),
        end_date=past_date,
        course__program=None,
    )
    all_course_runs = (program_course_runs + past_program_course_runs +
                       non_program_course_runs + past_non_program_course_runs)
    course_run_enrollments = CourseRunEnrollmentFactory.create_batch(
        len(all_course_runs), run=factory.Iterator(all_course_runs), user=user)
    program_enrollment = ProgramEnrollmentFactory.create(program=program,
                                                         user=user)
    past_program_enrollment = ProgramEnrollmentFactory.create(
        program=past_program, user=user)
    # Add a non-active enrollment so we can confirm that it isn't returned
    CourseRunEnrollmentFactory.create(user=user, active=False)

    def key_func(enrollment):
        """ Function for sorting runs by start_date"""
        return enrollment.run.start_date

    user_enrollments = get_user_enrollments(user)
    assert list(user_enrollments.programs) == [program_enrollment]
    assert list(user_enrollments.past_programs) == [past_program_enrollment]
    assert list(user_enrollments.program_runs) == sorted(
        [
            run_enrollment for run_enrollment in course_run_enrollments
            if run_enrollment.run in program_course_runs +
            past_program_course_runs
        ],
        key=key_func,
    )
    assert list(user_enrollments.non_program_runs) == sorted(
        [
            run_enrollment for run_enrollment in course_run_enrollments
            if run_enrollment.run in non_program_course_runs
        ],
        key=key_func,
    )

    assert list(user_enrollments.past_non_program_runs) == sorted(
        [
            run_enrollment for run_enrollment in course_run_enrollments
            if run_enrollment.run in past_non_program_course_runs
        ],
        key=key_func,
    )
コード例 #18
0
def test_serialize_course(mock_context, is_anonymous, all_runs):
    """Test Course serialization"""
    now = datetime.now(tz=pytz.UTC)
    if is_anonymous:
        mock_context["request"].user = AnonymousUser()
    if all_runs:
        mock_context["all_runs"] = True
    user = mock_context["request"].user
    course_run = CourseRunFactory.create(course__no_program=True, live=True)
    course = course_run.course
    topic = "a course topic"
    course.topics.set([CourseTopic.objects.create(name=topic)])

    # Create expired, enrollment_ended, future, and enrolled course runs
    CourseRunFactory.create(course=course,
                            end_date=now - timedelta(1),
                            live=True)
    CourseRunFactory.create(course=course,
                            enrollment_end=now - timedelta(1),
                            live=True)
    CourseRunFactory.create(course=course,
                            enrollment_start=now + timedelta(1),
                            live=True)
    enrolled_run = CourseRunFactory.create(course=course, live=True)
    unexpired_runs = [enrolled_run, course_run]
    CourseRunEnrollmentFactory.create(run=enrolled_run,
                                      **({} if is_anonymous else {
                                          "user": user
                                      }))

    # create products for all courses so the serializer shows them
    for run in CourseRun.objects.all():
        ProductVersionFactory.create(product__content_object=run)

    data = CourseSerializer(instance=course, context=mock_context).data

    if all_runs or is_anonymous:
        expected_runs = unexpired_runs
    else:
        expected_runs = [course_run]

    assert_drf_json_equal(
        data,
        {
            "title":
            course.title,
            "description":
            course.page.description,
            "readable_id":
            course.readable_id,
            "id":
            course.id,
            "courseruns": [
                CourseRunSerializer(run).data
                for run in sorted(expected_runs,
                                  key=lambda run: run.start_date)
            ],
            "thumbnail_url":
            f"http://localhost:8053{course.page.thumbnail_image.file.url}",
            "next_run_id":
            course.first_unexpired_run.id,
            "topics": [{
                "name": topic
            }],
        },
    )