Beispiel #1
0
def test_link_checking():
    """Checks for broken internal links"""
    # Perform all steps on course 10 up to validation steps
    course, errorstore, url_names = validate("testcourses/testcourse10", 6)
    handle_general_errors_in_10(errorstore)
    assert_caught_all_errors(errorstore)

    # Check the link-finding routines
    links = find_links(url_names['linktest'])
    assert set(links) == {
        '/course/courseware/testing', '/course/courseware/broken_chapter',
        '/course/courseware/chapter/sequential',
        '/course/courseware/chapter/sequential/',
        '/course/courseware/chapter/sequential/vertical',
        '/course/courseware/chapter/sequential/vertical/',
        '/course/courseware/chapter/sequential/vertical/1',
        '/course/courseware/chapter/sequential/vertical/10',
        '/course/courseware/chapter/sequential/vertical/0',
        '/course/courseware/chapter/sequential/vertical/html',
        '/course/courseware/chapter/sequential/vertical/1?last_child',
        '/course/courseware/chapter/sequential/vertical/?last_child',
        '/course/courseware/chapter/sequential/vertical?last_child',
        '/static/testing.png', '/static/testing2.css', '/jump_to_id/oravert',
        '/jump_to_id/oravert2', '/static/image.png',
        '/course/discussion/somewhere', '/course/pdfbook/0/chapter/9/11'
    }

    # Check the links
    validator = CheckLinks()
    validator(course, errorstore, url_names)

    # Catch everything!
    handle_link_errors_in_10(errorstore)
    assert_caught_all_errors(errorstore)
Beispiel #2
0
def test_validate_course10():
    """This test includes all validation steps. The course is designed to test the validators and slow validators."""
    course, errorstore, url_names = validate("testcourses/testcourse10")
    handle_general_errors_in_10(errorstore)
    handle_display_name_errors_in_10(errorstore)
    handle_discussion_id_errors_in_10(errorstore)
    handle_link_errors_in_10(errorstore)
    assert_caught_all_errors(errorstore)
Beispiel #3
0
def test_validate_course8():
    """This test includes individual component validation. Almost everything should pass."""
    course, errorstore, url_names = validate("testcourses/testcourse8", 6)
    assert_error(
        errorstore, InvalidSetting, 'course/mycourseurl.xml',
        "The tag <course url_name='mycourseurl'> does not have the required setting 'course_image'."
    )
    assert_caught_all_errors(errorstore)
Beispiel #4
0
def test_validate_course2_wiki_xblock_is_supported():
    """
    Test validate for course with wiki
    :return:
    """
    course, errorstore, url_names = validate(
        "testcourses/testcourse2/coursefile.xml", 1)
    assert_not_error(
        errorstore, UnexpectedTag, 'course/mycourseurl.xml',
        "A <wiki> tag was unexpectedly found inside the <course url_name='mycourseurl'> tag"
    )
Beispiel #5
0
def test_discussion_ids():
    # Perform all steps on course 10 up to validation steps
    course, errorstore, url_names = validate("testcourses/testcourse10", 6)
    handle_general_errors_in_10(errorstore)
    assert_caught_all_errors(errorstore)

    # Check the discussion IDs
    validator = CheckDiscussionIDs()
    validator(course, errorstore, url_names)

    # Catch everything!
    handle_discussion_id_errors_in_10(errorstore)
    assert_caught_all_errors(errorstore)
Beispiel #6
0
def test_validate_course11():
    """
    Tests validate when given a set of allowed xblocks.

    The test course uses currently unsupported xblocks which
    could fail the validate call.
    """
    allowed_xblocks = [
        "recommender", "edx_sga", "crowdsourcehinter", "done", "word_cloud"
    ]
    total_errors = len(allowed_xblocks)
    validate_kwargs = dict(filename="testcourses/testcourse11", steps=1)
    # recommender, edx_sga, crowdsourcehinter, done, word_cloud
    course, errorstore, url_names = validate(**validate_kwargs)
    assert len(errorstore.errors) == total_errors

    current_allowed_xblocks = []
    for xblock in allowed_xblocks:
        current_allowed_xblocks.append(xblock)
        # Do not throw error when an xblock is allowed.
        course, errorstore, url_names = validate(
            **validate_kwargs, allowed_xblocks=current_allowed_xblocks)
        assert len(
            errorstore.errors) == total_errors - len(current_allowed_xblocks)
Beispiel #7
0
def validate_course_olx(courselike_key, course_dir, status):
    """
    Validates course olx and records the errors as an artifact.

    Arguments:
        courselike_key: A locator identifies a course resource.
        course_dir: complete path to the course olx
        status: UserTaskStatus object.
    """
    olx_is_valid = True
    log_prefix = f'Course import {courselike_key}'
    validation_failed_mesg = 'CourseOlx validation failed.'
    is_library = isinstance(courselike_key, LibraryLocator)

    if is_library:
        return olx_is_valid

    if not course_import_olx_validation_is_enabled():
        return olx_is_valid
    try:
        __, errorstore, __ = olxcleaner.validate(
            filename=course_dir,
            steps=settings.COURSE_OLX_VALIDATION_STAGE,
            ignore=settings.COURSE_OLX_VALIDATION_IGNORE_LIST,
            allowed_xblocks=ALL_ALLOWED_XBLOCKS)
    except Exception:  # pylint: disable=broad-except
        LOGGER.exception(f'{log_prefix}: CourseOlx could not be validated')
        return olx_is_valid

    has_errors = errorstore.return_error(ErrorLevel.ERROR.value)
    if not has_errors:
        return olx_is_valid

    LOGGER.error(f'{log_prefix}: {validation_failed_mesg}')
    log_errors_to_artifact(errorstore, status)

    if bypass_olx_failure_enabled():
        return olx_is_valid

    monitor_import_failure(courselike_key,
                           status.state,
                           message=validation_failed_mesg)
    status.fail(UserErrors.OLX_VALIDATION_FAILED)
    return False
Beispiel #8
0
def validate_course_olx(courselike_key, course_dir, status):
    """
    Validates course olx and records the errors as an artifact.

    Arguments:
        courselike_key: A locator identifies a course resource.
        course_dir: complete path to the course olx
        status: UserTaskStatus object.
    """
    is_library = isinstance(courselike_key, LibraryLocator)
    olx_is_valid = True
    log_prefix = f'Course import {courselike_key}'

    if is_library:
        return olx_is_valid

    if not course_import_olx_validation_is_enabled():
        return olx_is_valid
    try:
        __, errorstore, __ = olxcleaner.validate(
            filename=course_dir,
            steps=settings.COURSE_OLX_VALIDATION_STAGE,
            ignore=settings.COURSE_OLX_VALIDATION_IGNORE_LIST,
            allowed_xblocks=ALL_ALLOWED_XBLOCKS)
    except Exception:  # pylint: disable=broad-except
        LOGGER.exception(f'{log_prefix}: CourseOlx Could not be validated')
        return olx_is_valid

    log_errors = len(errorstore.errors) > 0
    if log_errors:
        log_errors_to_artifact(errorstore, status)

    has_errors = errorstore.return_error(ErrorLevel.ERROR.value)
    if not has_errors:
        return olx_is_valid

    LOGGER.error(f'{log_prefix}: CourseOlx validation failed')

    # TODO: Do not fail the task until we have some data about kinds of
    #  olx validation failures. TNL-8151
    return olx_is_valid
Beispiel #9
0
def main():
    """Entry point for command line instantiation"""
    # Read the command line arguments
    args = handle_arguments()

    # Load the course
    # We need XML structure + policy information, so go to step 4
    course, _, _ = validate(args.course, steps=4)

    # Make sure we have a course to crawl
    if not course:
        print("Cannot load course - aborting")
        sys.exit(1)

    # Crawl the course extracting the desired information
    chapters = []
    for chapter in course.children:
        name = chapter.attributes.get('display_name', '(Unnamed)')
        chapter_log = {'title': sanitize(name), 'sequentials': []}

        for seq in chapter.children:
            name = seq.attributes.get('display_name', '(Unnamed)')
            seq_log = {'title': sanitize(name), 'verticals': []}

            # Total up the duration of all videos in the sequential
            duration = datetime.timedelta()
            for vert in seq.children:
                name = vert.attributes.get('display_name', '(Unnamed)')
                if args.url_names:
                    url_name = vert.attributes.get('url_name', '(no url_name)')
                    seq_log['verticals'].append(f"({url_name}) {sanitize(name)}")
                else:
                    seq_log['verticals'].append(sanitize(name))

                for entry in vert.children:
                    if entry.type == "video":
                        # Search for a duration in the display_name of the form "(mins:seconds)"
                        name = entry.attributes.get('display_name', '')
                        length = re.search(r'\(([0-9]+):([0-9]+)\)', name)
                        if length:
                            minutes = int(length.group(1))
                            seconds = int(length.group(2))
                            duration += datetime.timedelta(minutes=minutes, seconds=seconds)

            if duration.total_seconds() > 0:
                # Report the total time
                hours, remainder = divmod(duration.total_seconds(), 3600)
                minutes, seconds = divmod(remainder, 60)
                hours = int(hours)
                minutes = int(minutes)
                seconds = int(seconds)
                if hours == 0:
                    seq_log['title'] += f" ({minutes}:{seconds:02})"
                else:
                    seq_log['title'] += f" ({hours}:{minutes:02}:{seconds:02})"

            chapter_log['sequentials'].append(seq_log)
        chapters.append(chapter_log)

    # Now do the output
    coursetitle = course.attributes.get('display_name', 'Unnamed Course')
    coursetitle += r" \\ Overview of Course Content"
    print(header.replace('[coursetitlehere]', coursetitle))

    # Iterate over the content
    needsnewpage = False
    for chapter in chapters:

        # Each chapter should be on its own page
        if needsnewpage:
            print("\n" + r"\newpage" + "\n")

        print(r"\section*{" + chapter['title'] + "}")

        for sequential in chapter['sequentials']:
            print("\n" + r"\section*{" + sequential['title'] + "}\n")
            print(r"\begin{itemize}")

            for vertical in sequential['verticals']:
                print(r"\item " + vertical)

            print(r"\end{itemize}")

        needsnewpage = True

    print(footer)
Beispiel #10
0
def test_validate_course9():
    """This test includes individual component validation. This similar to course8, but riddled with errors."""
    course, errorstore, url_names = validate("testcourses/testcourse9", 6)
    assert_error(errorstore, InvalidSetting, 'course/mycourseurl.xml',
                 "Unable to recognize graceperiod format in policy.")
    assert_error(
        errorstore, InvalidSetting, 'problem/problem.xml',
        "The tag <problem url_name='problem'> has an invalid setting 'showanswer=bad'."
    )
    assert_error(
        errorstore, InvalidSetting, 'chapter/chapter.xml',
        "The tag <chapter url_name='chapter' display_name='Hi there!'> has an invalid date setting for start: 'Feb 20, 2019, 17:00zzz'."
    )
    assert_error(
        errorstore, DateOrdering, 'sequential/sequential.xml',
        "The tag <sequential url_name='sequential' display_name='Hi there!'> has a date out of order: start date cannot be before course start date"
    )
    assert_error(
        errorstore, DateOrdering, 'vertical/vertical.xml',
        "The tag <vertical url_name='vertical' display_name='Hi mom!'> has a date out of order: due date must be before course end date"
    )
    assert_error(
        errorstore, DateOrdering, 'problem/problem.xml',
        "The tag <problem url_name='problem'> has a date out of order: start date must be before due date"
    )
    assert_error(
        errorstore, MissingURLName, 'vertical/vertical.xml',
        "The tag <problem display_name='no url_name'> has no url_name.")
    assert_error(
        errorstore, InvalidSetting, 'vertical/vertical.xml',
        "The tag <problem display_name='no url_name'> has an invalid setting 'showanswer=hah!'."
    )
    assert_error(
        errorstore, DateOrdering, 'vertical/vertical.xml',
        "The tag <problem display_name='no url_name'> has a date out of order: due date must be before course end date"
    )
    assert_error(
        errorstore, Obsolete, 'vertical/vertical.xml',
        "The tag <discussion url_name='discussion'> should be included inline rather than through the discussion directory."
    )
    assert_error(
        errorstore, InvalidSetting, 'discussion/discussion.xml',
        "The tag <discussion url_name='discussion'> does not have the required setting 'discussion_id'."
    )
    assert_error(
        errorstore, InvalidSetting, 'discussion/discussion.xml',
        "The tag <discussion url_name='discussion'> does not have the required setting 'discussion_category'."
    )
    assert_error(
        errorstore, InvalidSetting, 'discussion/discussion.xml',
        "The tag <discussion url_name='discussion'> does not have the required setting 'discussion_target'."
    )
    assert_error(
        errorstore, Obsolete, 'lti/lti.xml',
        "The tag <lti url_name='lti'> should be converted to the newer lti_consumer Xblock."
    )
    assert_error(
        errorstore, InvalidSetting, 'lti/lti.xml',
        "The tag <lti url_name='lti'> does not have the required setting 'launch_url'."
    )
    assert_error(
        errorstore, LTIError, 'lti/lti.xml',
        "Course policy does not include an 'lti_passports' entry for 'nothere', required for <lti url_name='lti'>."
    )
    assert_error(
        errorstore, LTIError, 'lti/lti.xml',
        "Course policy does not include the 'lti' advanced module, required for <lti url_name='lti'>."
    )
    assert_error(
        errorstore, InvalidSetting, 'vertical/vertical.xml',
        "The tag <lti_consumer url_name='meep'> does not have the required setting 'launch_url'."
    )
    assert_error(
        errorstore, LTIError, 'vertical/vertical.xml',
        "Course policy does not include an 'lti_passports' entry for 'nothere', required for <lti_consumer url_name='meep'>."
    )
    assert_error(
        errorstore, LTIError, 'vertical/vertical.xml',
        "Course policy does not include the 'lti_consumer' advanced module, required for <lti_consumer url_name='meep'>."
    )
    assert_error(
        errorstore, MissingFile, 'course/mycourseurl.xml',
        "The <course url_name='mycourseurl'> tag contains a reference to a missing static file: course_image_small.jpg"
    )
    assert_error(
        errorstore, InvalidSetting, 'problem/problem.xml',
        "The tag <problem url_name='problem'> has a negative problem weight.")
    assert_error(
        errorstore, InvalidSetting, 'course/mycourseurl.xml',
        "The tag <course url_name='mycourseurl'> should have a positive number of attempts."
    )
    assert_error(
        errorstore, InvalidSetting, 'problem/problem.xml',
        "The tag <problem url_name='problem'> should have a positive number of attempts."
    )
    assert_error(
        errorstore, InvalidSetting, 'vertical/vertical.xml',
        "The tag <drag-and-drop-v2 url_name='studio_mess2' display_name='This is my title'> has an error in the data JSON: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)."
    )
    assert_error(
        errorstore, InvalidSetting, 'vertical/vertical.xml',
        "The tag <drag-and-drop-v2 url_name='studio_mess3' display_name='This is my title'> data JSON is not a dictionary."
    )
    assert_error(
        errorstore, InvalidSetting, 'sequential/examseq.xml',
        "The tag <sequential url_name='examseq' display_name='Exam'> is a timed exam, but the course policy does not have 'enable_timed_exams=true'."
    )
    assert_error(
        errorstore, DateOrdering, 'vertical/oravert.xml',
        "The tag <openassessment url_name='paper-draft'> has a date out of order: assessment 1 due date must be before course end date"
    )
    assert_caught_all_errors(errorstore)

    # Ensure that we got the scripts from the problem file
    assert set(
        url_names['problem'].scripts) == {'python', 'perl', 'javascript'}
    # Also make sure we detected the response types
    assert set(url_names['problem'].response_types) == {
        'customresponse', 'multiplechoiceresponse'
    }
    # as well as the input types
    assert set(url_names['problem'].input_types) == {'choicegroup', 'textline'}
    # We also found the solution
    assert url_names['problem'].has_solution

    # Make sure our exam sequential was detected
    assert url_names['examseq'].is_exam
Beispiel #11
0
def test_validate_course7():
    course, errorstore, url_names = validate(
        "testcourses/testcourse7/course.xml", 5)
    handle_course7_errors(errorstore)
    assert_caught_all_errors(errorstore)
Beispiel #12
0
def test_validate_course2():
    course, errorstore, url_names = validate(
        "testcourses/testcourse2/coursefile.xml", 1)
    handle_course2_errors(errorstore)
    assert_caught_all_errors(errorstore)
Beispiel #13
0
def test_validate_course1():
    course, errorstore, url_names = validate("testcourses/testcourse1", 2)
    handle_course1_errors(errorstore)
    assert_caught_all_errors(errorstore)
Beispiel #14
0
def test_validate_nocourse():
    course, errorstore, url_names = validate("testcourses/nocourse.xml", 2)
    handle_nocourse_errors(errorstore)
    assert_caught_all_errors(errorstore)