def test_catalog_program_serializer(has_page, has_thumbnail):
    """Tests that the catalog serializer returns a correct data structure"""
    page = ProgramPageFactory.create(
        has_thumbnail=has_thumbnail) if has_page else None
    program = page.program if page else ProgramFactory.create()
    courses = CourseFactory.create_batch(3, program=program)
    for course in courses:
        CourseRunFactory.create_batch(2, course=course)
    faculty_name = "faculty"
    if has_page:
        ProgramFaculty.objects.create(
            program_page=page,
            name=faculty_name,
        )
    serialized = CatalogProgramSerializer(program).data
    # coerce OrderedDict objects to dict
    serialized = {
        **serialized, "courses": [{
            **course, "course_runs":
            [dict(run) for run in course["course_runs"]]
        } for course in serialized["courses"]]
    }
    assert serialized == {
        "id":
        program.id,
        "title":
        program.title,
        "programpage_url":
        page.get_full_url() if has_page else None,
        "thumbnail_url":
        (page.thumbnail_image.get_rendition('fill-300x186').url
         if has_page and has_thumbnail else None),
        "courses": [{
            "id":
            course.id,
            "edx_key":
            course.edx_key,
            "position_in_program":
            course.position_in_program,
            "course_runs": [{
                "id": course_run.id,
                "edx_course_key": course_run.edx_course_key,
            } for course_run in course.courserun_set.all()]
        } for course in courses],
        'topics': [{
            'name': topic.name
        } for topic in program.topics.iterator()],
        "instructors": [{
            "name": faculty_name
        }] if has_page else [],
        "start_date":
        courses[0].first_unexpired_run().start_date,
        "enrollment_start":
        courses[0].first_unexpired_run().enrollment_start,
        "end_date":
        courses[-1].courserun_set.last().end_date,
        "total_price":
        str(program.price * program.num_required_courses),
    }
示例#2
0
def test_program_view(client, user, home_page, is_enrolled, has_product,
                      has_unexpired_run, is_anonymous):
    """
    Test that the program detail view has the right context and shows the right HTML for the enroll/view button
    """
    program = ProgramFactory.create(live=True, page__parent=home_page)

    if has_unexpired_run:
        now = now_in_utc()
        CourseRunFactory.create_batch(
            3,
            course=CourseFactory.create(program=program,
                                        live=True,
                                        position_in_program=1),
            live=True,
            start_date=now + timedelta(hours=2),
        )

    if has_product:
        product_id = ProductVersionFactory.create(product=ProductFactory(
            content_object=program)).product.id
    else:
        product_id = None
    if is_enrolled:
        ProgramEnrollmentFactory.create(user=user, program=program)

    if not is_anonymous:
        client.force_login(user)
    resp = client.get(program.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 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)
    has_button = ((has_product and has_unexpired_run and not is_enrolled)
                  or is_enrolled) 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:
            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)
示例#3
0
def test_generate_program_certificate_failure(user, program):
    """
    Test that generate_program_certificate return (None, False) and not create program certificate
    if there is not any course_run certificate for the given course.
    """
    course = CourseFactory.create(program=program)
    CourseRunFactory.create_batch(3, course=course)

    result = generate_program_certificate(user=user, program=program)
    assert result == (None, False)
    assert len(ProgramCertificate.objects.all()) == 0
示例#4
0
    def test_lists_catalog(self):
        """Course Runs should show up"""
        program = ProgramFactory.create(live=True)
        for course in CourseFactory.create_batch(3, program=program):
            CourseRunFactory.create_batch(2, course=course)

        resp = self.client.get(reverse('catalog-list'))

        assert len(resp.json()) == 1
        data = CatalogProgramSerializer(program).data

        assert_drf_json_equal([data], resp.json())
示例#5
0
def test_create_run_enrollments_enroll_api_fail(mocker, user,
                                                keep_failed_enrollments,
                                                exception_cls,
                                                inner_exception):
    """
    create_run_enrollments should log a message and still create local enrollment records when an enrollment exception
    is raised if a flag is set to true
    """
    num_runs = 3
    runs = CourseRunFactory.create_batch(num_runs)
    patched_edx_enroll = mocker.patch(
        "courses.api.enroll_in_edx_course_runs",
        side_effect=exception_cls(user, runs[2], inner_exception),
    )
    patched_log_exception = mocker.patch("courses.api.log.exception")
    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=None,
        company=None,
        keep_failed_enrollments=keep_failed_enrollments,
    )
    patched_edx_enroll.assert_called_once_with(user, runs)
    patched_log_exception.assert_called_once()
    patched_send_enrollment_email.assert_not_called()
    expected_enrollments = 0 if not keep_failed_enrollments else num_runs
    assert len(successful_enrollments) == expected_enrollments
    assert edx_request_success is False
示例#6
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)
示例#7
0
 def generate_course_with_runs(program, course_params=None, course_run_count=1):
     """
     Helper method to generate a Course with CourseRuns for a Program
     """
     course_params = course_params or {}
     course = CourseFactory.create(program=program, **course_params)
     course_runs = CourseRunFactory.create_batch(course_run_count, course=course)
     return course, course_runs
示例#8
0
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
示例#9
0
def test_program_first_course_unexpired_runs():
    """
    first_course_unexpired_runs should return the unexpired course runs of the first course
    in the program (position_in_program=1)
    """
    program = ProgramFactory.create()

    now = now_in_utc()
    past_start_dates = [
        now + timedelta(days=-10),
        now + timedelta(days=-11),
        now + timedelta(days=-12),
    ]

    past_end_dates = [now + timedelta(days=-5), now + timedelta(days=-6)]
    future_end_dates = [
        now + timedelta(days=10),
        now + timedelta(days=11),
        now + timedelta(days=12),
    ]

    first_course = CourseFactory.create(live=True,
                                        program=program,
                                        position_in_program=1)
    second_course = CourseFactory.create(live=True,
                                         program=program,
                                         position_in_program=2)

    CourseRunFactory.create_batch(
        2,
        course=second_course,
        start_date=factory.Iterator(past_start_dates),
        end_date=factory.Iterator(past_end_dates),
        live=True,
    )
    CourseRunFactory.create_batch(
        3,
        course=first_course,
        start_date=factory.Iterator(past_start_dates),
        end_date=factory.Iterator(future_end_dates),
        enrollment_end=factory.Iterator(future_end_dates),
        live=True,
    )
    assert len(program.first_course_unexpired_runs) == 3
示例#10
0
def test_course_next_run_date():
    """
    next_run_date should return the date of the CourseRun with the nearest future start date
    """
    course = CourseFactory.create()
    CourseRunFactory.create_batch(2, course=course, past_start=True, live=True)
    assert course.next_run_date is None

    now = now_in_utc()
    future_dates = [now + timedelta(hours=1), now + timedelta(hours=2)]
    CourseRunFactory.create_batch(2,
                                  course=course,
                                  start_date=factory.Iterator(future_dates),
                                  live=True)

    # invlidate cached property
    del course.next_run_date

    assert course.next_run_date == future_dates[0]
示例#11
0
 def generate_course_with_runs(program,
                               course_params=None,
                               course_run_count=1):
     """
     Helper method to generate a Course with CourseRuns for a Program
     """
     course_params = course_params or {}
     course = CourseFactory.create(program=program, **course_params)
     course_runs = CourseRunFactory.create_batch(course_run_count,
                                                 course=course)
     return course, course_runs
示例#12
0
def test_course_unexpired_runs():
    """unexpired_runs should return expected value"""
    course = CourseFactory.create()
    now = now_in_utc()
    start_dates = [now, now + timedelta(days=-3)]
    end_dates = [now + timedelta(hours=1), now + timedelta(days=-2)]
    CourseRunFactory.create_batch(
        2,
        course=course,
        start_date=factory.Iterator(start_dates),
        end_date=factory.Iterator(end_dates),
        live=True,
    )

    # Add a run that is not live and shouldn't show up in unexpired list
    CourseRunFactory.create(course=course,
                            start_date=start_dates[0],
                            end_date=end_dates[0],
                            live=False)

    assert len(course.unexpired_runs) == 1
    course_run = course.unexpired_runs[0]
    assert course_run.start_date == start_dates[0]
    assert course_run.end_date == end_dates[0]
示例#13
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,
    )
示例#14
0
 def test_has_paid_for_any_in_program(self):
     """
     Assert that has_paid_for_any_in_program returns True if any CourseRun associated with a Program has been
     paid for.
     """
     new_program = ProgramFactory.create()
     new_course_runs = CourseRunFactory.create_batch(2, course__program=new_program)
     mmtrack = MMTrack(
         user=self.user,
         program=new_program,
         edx_user_data=self.cached_edx_user_data
     )
     assert mmtrack.has_paid_for_any_in_program() is False
     fg = FinalGradeFactory.create(user=self.user, course_run=new_course_runs[0], course_run_paid_on_edx=True)
     assert mmtrack.has_paid_for_any_in_program() is True
     fg.delete()
     FinalGradeFactory.create(user=self.user, course_run=new_course_runs[1], course_run_paid_on_edx=True)
     assert mmtrack.has_paid_for_any_in_program() is True
示例#15
0
 def test_has_paid_for_any_in_program(self):
     """
     Assert that has_paid_for_any_in_program returns True if any CourseRun associated with a Program has been
     paid for.
     """
     new_program = ProgramFactory.create()
     new_course_runs = CourseRunFactory.create_batch(2, course__program=new_program)
     mmtrack = MMTrack(
         user=self.user,
         program=new_program,
         edx_user_data=self.cached_edx_user_data
     )
     assert mmtrack.has_paid_for_any_in_program() is False
     fg = FinalGradeFactory.create(user=self.user, course_run=new_course_runs[0], course_run_paid_on_edx=True)
     assert mmtrack.has_paid_for_any_in_program() is True
     fg.delete()
     FinalGradeFactory.create(user=self.user, course_run=new_course_runs[1], course_run_paid_on_edx=True)
     assert mmtrack.has_paid_for_any_in_program() is True
示例#16
0
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
示例#17
0
def test_program_is_catalog_visible():
    """
    is_catalog_visible should return True if a program has any course run that has a start date or enrollment end
    date in the future
    """
    program = ProgramFactory.create()
    runs = CourseRunFactory.create_batch(2,
                                         course__program=program,
                                         past_start=True,
                                         past_enrollment_end=True)
    assert program.is_catalog_visible is False

    now = now_in_utc()
    run = runs[0]
    run.start_date = now + timedelta(hours=1)
    run.save()
    assert program.is_catalog_visible is True

    run.start_date = now - timedelta(hours=1)
    run.enrollment_end = now + timedelta(hours=1)
    run.save()
    assert program.is_catalog_visible is True
示例#18
0
def test_create_run_enrollments_creation_fail(mocker, user):
    """
    create_run_enrollments should log a message and send an admin email if there's an error during the
    creation of local enrollment records
    """
    runs = CourseRunFactory.create_batch(2)
    enrollment = CourseRunEnrollmentFactory.build(run=runs[1])
    mocker.patch(
        "courses.api.CourseRunEnrollment.all_objects.get_or_create",
        side_effect=[Exception(), (enrollment, True)],
    )
    patched_edx_enroll = mocker.patch("courses.api.enroll_in_edx_course_runs")
    patched_log_exception = mocker.patch("courses.api.log.exception")
    patched_mail_api = mocker.patch("courses.api.mail_api")

    successful_enrollments, edx_request_success = create_run_enrollments(
        user, runs, order=None, company=None)
    patched_edx_enroll.assert_called_once_with(user, runs)
    patched_log_exception.assert_called_once()
    patched_mail_api.send_course_run_enrollment_email.assert_not_called()
    patched_mail_api.send_enrollment_failure_message.assert_called_once()
    assert successful_enrollments == [enrollment]
    assert edx_request_success is True
示例#19
0
def test_program_next_run_date():
    """
    next_run_date should return the date of the CourseRun with the nearest future start date
    and first position in program (course__position_in_program=1)
    """
    program = ProgramFactory.create()
    CourseRunFactory.create_batch(
        2,
        course=CourseFactory.create(program=program, position_in_program=3),
        past_start=True,
    )
    assert program.next_run_date is None

    now = now_in_utc()
    second_course_future_dates = [
        now + timedelta(hours=1), now + timedelta(hours=3)
    ]
    CourseRunFactory.create_batch(
        2,
        course=CourseFactory.create(program=program, position_in_program=2),
        start_date=factory.Iterator(second_course_future_dates),
        live=True,
    )

    first_course_future_dates = [
        now + timedelta(hours=2), now + timedelta(hours=4)
    ]
    CourseRunFactory.create_batch(
        2,
        course=CourseFactory.create(program=program, position_in_program=1),
        start_date=factory.Iterator(first_course_future_dates),
        live=True,
    )

    # invalidate cached property
    del program.next_run_date

    assert program.next_run_date == first_course_future_dates[0]
示例#20
0
def course_runs():
    """Fixture for a set of CourseRuns in the database"""
    return CourseRunFactory.create_batch(3)
示例#21
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,
    )