Esempio n. 1
0
def test_submit_with_small_group(
    test_client, session, logged_in, teacher_user, course, error_template,
    assignment
):
    user_no_group = create_user_with_perms(
        session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course
    )
    user_with_group = create_user_with_perms(
        session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course
    )

    with logged_in(teacher_user):
        g_set = create_group_set(test_client, course.id, 1, 1)
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'group_set_id': g_set['id']}
        )
        g1 = create_group(
            test_client,
            g_set['id'],
            [user_with_group.id],
        )

    with logged_in(user_no_group):
        # Can submit without group, you are simply the author (not a group)
        sub = create_submission(test_client, assignment.id)
        assert sub['user']['id'] == user_no_group.id

    with logged_in(user_with_group):
        # But when submitting as a user in a group the group should still be
        # the author
        sub = create_submission(test_client, assignment.id)
        assert sub['user']['group']['id'] == g1['id']
Esempio n. 2
0
def test_archive_generated_files(basic, test_client, describe, logged_in):
    with describe('setup'):
        course, assig, teacher, student = basic

        with logged_in(student):
            helpers.create_submission(
                test_client,
                assignment_id=assig.id,
                submission_data=(
                    (f'{os.path.dirname(__file__)}/../test_data/test_submissions/'
                     'single_symlink_archive.tar.gz'),
                    'f.tar.gz',
                ),
            )
        sub = next(sub for sub in assig.get_all_latest_submissions()
                   if sub.user == student)

    with tempfile.TemporaryDirectory() as tmpdir:
        p.files.restore_directory_structure(sub, tmpdir)
        root = f'{tmpdir}/{os.listdir(tmpdir)[0]}'

        with describe('symbolic link replacement files'), open(
                f'{root}/test_file') as f:
            assert not os.path.islink(f'{root}/test_file')
            content = f.read()
            assert content.endswith('\n')
            assert 'symbolic link' in content
            assert 'non_existing' in content
Esempio n. 3
0
def test_peer_feedback_and_test_student(
    test_client, logged_in, describe, admin_user, session, tomorrow,
    enable_after, student_amount
):
    with describe('setup'), logged_in(admin_user):
        course = helpers.create_course(test_client)
        assignment = helpers.create_assignment(
            test_client, course, deadline=tomorrow
        )
        if not enable_after:
            helpers.enable_peer_feedback(test_client, assignment)

        users = [
            helpers.get_id(
                helpers.create_user_with_role(session, 'Student', course)
            ) for _ in range(student_amount)
        ]
        helpers.create_submission(
            test_client, assignment, is_test_submission=True
        )
        for user in users:
            helpers.create_submission(test_client, assignment, for_user=user)

        if enable_after:
            helpers.enable_peer_feedback(test_client, assignment)

    with describe('nobody should be connected to the test student'
                  ), logged_in(admin_user):
        conns = get_all_connections(assignment, 1)
        assert len(conns) == student_amount
        for user in users:
            assert user in conns
            assert all(conn in users for conn in users)
Esempio n. 4
0
def test_proxy_with_submission(test_client, logged_in, describe, session,
                               admin_user):
    with describe('setup'), logged_in(admin_user):
        course = helpers.create_course(test_client)
        assig = helpers.create_assignment(test_client,
                                          course,
                                          state='open',
                                          deadline='tomorrow')
        stud = helpers.create_user_with_role(session, 'Student', [course])
        stud2 = helpers.create_user_with_role(session, 'Student', [course])

        sub_data = (
            (f'{os.path.dirname(__file__)}/../test_data/test_submissions/'
             'html.tar.gz'),
            'f.tar.gz',
        )
        s1 = helpers.create_submission(test_client,
                                       assig,
                                       submission_data=sub_data,
                                       for_user=stud)['id']
        s2 = helpers.create_submission(test_client,
                                       assig,
                                       submission_data=sub_data,
                                       for_user=stud2)['id']

        data = {
            'allow_remote_resources': True,
            'allow_remote_scripts': True,
            'teacher_revision': False,
        }

    with describe('Students can only create proxy when they may see files'
                  ), logged_in(stud):
        test_client.req('post',
                        f'/api/v1/submissions/{s1}/proxy',
                        200,
                        data=data)
        test_client.req('post',
                        f'/api/v1/submissions/{s2}/proxy',
                        403,
                        data=data)

        # Student has no permission for teacher files
        data['teacher_revision'] = True
        test_client.req('post',
                        f'/api/v1/submissions/{s1}/proxy',
                        403,
                        data=data)
        data['teacher_revision'] = False

    with describe('remote_scripts and remote_resources connection'), logged_in(
            stud):
        data['allow_remote_resources'] = False
        err = test_client.req('post',
                              f'/api/v1/submissions/{s1}/proxy',
                              400,
                              data=data)
        assert 'The value "allow_remote_scripts" can only be true' in err[
            'message']
Esempio n. 5
0
def test_getting_peer_feedback_connections(test_client, admin_user, session,
                                           describe, logged_in, yesterday,
                                           tomorrow):
    with describe('setup'), logged_in(admin_user):
        course = helpers.create_course(test_client)
        assignment = helpers.create_assignment(test_client,
                                               course,
                                               deadline=yesterday,
                                               state='open')
        teacher = helpers.create_user_with_role(session, 'Teacher', course)
        users = [
            helpers.get_id(
                helpers.create_user_with_role(session, 'Student', course))
            for _ in range(5)
        ]
        user1, user2, *_ = users
        for user in users:
            helpers.create_submission(test_client, assignment, for_user=user)

        helpers.enable_peer_feedback(test_client, assignment, amount=1)
        url = (f'/api/v1/assignments/{helpers.get_id(assignment)}/users'
               f'/{helpers.get_id(user1)}/peer_feedback_subjects/')
        conns = get_all_connections(assignment, 1)[user1]

    with describe('Can get connections for own user'):
        with logged_in(user1):
            api_conns = test_client.req(
                'get',
                url,
                200,
                result=[{
                    'peer': helpers.to_db_object(user1, m.User),
                    'subject': helpers.to_db_object(conns[0], m.User),
                }])

    with describe('Cannot get connections for other user as student'):
        with logged_in(user2):
            test_client.req('get', url, 403)

    with describe('Can get connections for other user as teacher'):
        with logged_in(teacher):
            test_client.req('get', url, 200, result=api_conns)

    with describe(
            'No connections if assignments deadline has not expired just yet'):
        with logged_in(teacher):
            test_client.req(
                'patch',
                f'/api/v1/assignments/{helpers.get_id(assignment)}',
                200,
                data={'deadline': tomorrow.isoformat()})

        with logged_in(user1):
            test_client.req('get', url, 200, result=[])

        # Not even as teacher
        with logged_in(teacher):
            test_client.req('get', url, 200, result=[])
Esempio n. 6
0
def test_delete_submission_of_group(
    lti1p3_provider, describe, logged_in, admin_user, watch_signal,
    stub_function, test_client, session, tomorrow
):
    with describe('setup'), logged_in(admin_user):
        watch_signal(signals.WORK_CREATED, clear_all_but=[])
        watch_signal(signals.GRADE_UPDATED, clear_all_but=[])
        watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[])
        stub_function(
            pylti1p3.service_connector.ServiceConnector,
            'get_access_token', lambda: ''
        )
        stub_passback = stub_function(
            pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade'
        )

        course, course_conn = helpers.create_lti1p3_course(
            test_client, session, lti1p3_provider
        )
        assig = helpers.create_lti1p3_assignment(
            session, course, state='done', deadline=tomorrow
        )

        # Create some users and let them have individual submissions
        g_user1 = helpers.create_lti1p3_user(session, lti1p3_provider)
        g_user2 = helpers.create_lti1p3_user(session, lti1p3_provider)
        for user in [g_user1, g_user2]:
            course_conn.maybe_add_user_to_course(user, ['Learner'])
            sub = helpers.create_submission(test_client, assig, for_user=user)
            helpers.to_db_object(sub, m.Work).set_grade(
                5.0, m.User.resolve(admin_user)
            )

        # Place the users in a group, with a submission with a different grade
        # than their individual submission.
        gset = helpers.create_group_set(test_client, course, 1, 2, [assig])
        helpers.create_group(test_client, gset, [g_user1, g_user2])
        gsub = helpers.create_submission(test_client, assig, for_user=g_user2)
        helpers.to_db_object(gsub, m.Work).set_grade(
            6.0, m.User.resolve(admin_user)
        )

        session.commit()

    with describe(
        'deleting group submission passes back individual submissions'
    ):
        with logged_in(admin_user):
            test_client.req(
                'delete', f'/api/v1/submissions/{helpers.get_id(gsub)}', 204
            )

        assert stub_passback.called_amount == 2

        # The grade passed back is that of their individual submission
        assert stub_passback.args[0][0].get_score_given() == 5.0
        assert stub_passback.args[1][0].get_score_given() == 5.0
Esempio n. 7
0
def test_division_larger_assignment(
    test_client, admin_user, session, describe, logged_in, yesterday, students
):
    with describe('setup'), logged_in(admin_user):
        course = helpers.create_course(test_client)
        assignment = helpers.create_assignment(
            test_client, course, deadline=yesterday
        )
        all_users = [
            helpers.get_id(
                helpers.create_user_with_role(session, 'Student', course)
            ) for _ in range(students)
        ]
        user1, *rest_users = all_users
        rest_subs = [(
            user,
            helpers.create_submission(test_client, assignment, for_user=user)
        ) for user in rest_users]

    with describe('Enabling peer feedback should do divide'
                  ), logged_in(admin_user):
        helpers.enable_peer_feedback(test_client, assignment, amount=3)
        conns = get_all_connections(assignment, 3)
        assert len(conns) == len(rest_users)

    with describe('Submitting should still be possible'
                  ), logged_in(admin_user):
        helpers.create_submission(test_client, assignment, for_user=user1)
        conns = get_all_connections(assignment, 3)
        assert len(conns) == len(all_users)
        assert len(conns[user1]) == 3

    with describe('Can delete all rest users'), logged_in(admin_user):
        warning_amount = 0
        for idx, (user_id, sub) in enumerate(rest_subs):
            _, rv = test_client.req(
                'delete',
                f'/api/v1/submissions/{helpers.get_id(sub)}',
                204,
                include_response=True,
            )
            had_warning = 'warning' in rv.headers
            if had_warning:
                warning_amount += 1
            print('Deleting submission of', user_id, 'warning:', had_warning)
            conns = get_all_connections(assignment, 3)
            left = len(all_users) - (idx + 1)
            if left > 3:
                assert len(conns) == left, f'Got wrong amount of conns {conns}'
            else:
                assert len(conns) == 0, f'Got wrong amount of conns {conns}'

        assert warning_amount < len(all_users)
Esempio n. 8
0
def test_searching_test_student_in_course(logged_in, test_client, assignment,
                                          session, teacher_user, tomorrow):
    c_id = assignment.course_id

    with logged_in(teacher_user):
        actual_student_name = f'TEST_STUDENT_{str(uuid.uuid4())}'
        student = create_user_with_role(
            session,
            'Student',
            c_id,
            name=actual_student_name,
        )

        assig_id = create_assignment(test_client,
                                     c_id,
                                     'open',
                                     deadline=tomorrow)['id']

        res = create_submission(
            test_client,
            assig_id,
            is_test_submission=True,
        )
        test_user_id = res['user']['id']

        res = test_client.req(
            'get',
            f'/api/v1/courses/{c_id}/users/?q=TEST_STUDENT',
            200,
        )

        assert res
        for user in res:
            assert user['id'] != test_user_id
        assert student.id in set(user['id'] for user in res)
Esempio n. 9
0
def test_passback_single_submission(
    lti1p3_provider, describe, logged_in, admin_user, watch_signal,
    stub_function, test_client, session, tomorrow
):
    with describe('setup'), logged_in(admin_user):
        watch_signal(signals.WORK_CREATED, clear_all_but=[])
        watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[])
        signal = watch_signal(
            signals.GRADE_UPDATED,
            clear_all_but=[m.LTI1p3Provider._passback_submission]
        )

        stub_function(
            pylti1p3.service_connector.ServiceConnector,
            'get_access_token', lambda: ''
        )
        stub_passback = stub_function(
            pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade',
            raise_pylti1p3_exc
        )

        course, course_conn = helpers.create_lti1p3_course(
            test_client, session, lti1p3_provider
        )
        assig = helpers.create_lti1p3_assignment(
            session, course, state='done', deadline=tomorrow
        )

        user = helpers.create_lti1p3_user(session, lti1p3_provider)
        course_conn.maybe_add_user_to_course(user, [])

        older_sub, newest_sub = [
            helpers.to_db_object(
                helpers.create_submission(test_client, assig, for_user=user),
                m.Work
            ) for _ in range(2)
        ]

    with describe('calling directly with non existing assignment is a noop'):
        m.LTI1p3Provider._passback_submission((newest_sub.id, 100000))
        assert not stub_passback.called

    with describe('changing grade of non newest sub does not passback'):
        older_sub.set_grade(5.0, admin_user)
        assert signal.was_send_once
        assert not stub_passback.called

    with describe('changing grade on newest sub passes the new grade back'):
        newest_sub.set_grade(9.5, m.User.resolve(admin_user))
        assert signal.was_send_once
        assert stub_passback.called_amount == 1
        assert stub_passback.args[0][0].get_score_given() == 9.5

    with describe('unsetting grade should passback something without grade'):
        newest_sub.set_grade(None, m.User.resolve(admin_user))

        assert signal.was_send_once
        assert stub_passback.called_amount == 1
        assert stub_passback.args[0][0].get_score_given() is None
        assert stub_passback.args[0][0].get_activity_progress() == 'Submitted'
Esempio n. 10
0
def test_searching_test_student(
    logged_in, session, error_template, test_client, request, admin_user,
    ta_user, tomorrow, assignment
):
    with logged_in(admin_user):
        course = helpers.create_course(test_client)
        teacher = helpers.create_user_with_role(session, 'Teacher', course)

        actual_student_name = f'TEST_STUDENT_{str(uuid.uuid4())}'
        student = helpers.create_user_with_role(
            session, 'Student', course, name=actual_student_name
        )

    with logged_in(teacher):
        assig_id = helpers.create_assignment(
            test_client, course, 'open', deadline=tomorrow
        )['id']

        res = helpers.create_submission(
            test_client,
            assignment_id=assig_id,
            is_test_submission=True,
        )
        test_user_id = res['user']['id']

        res = test_client.req(
            'get',
            '/api/v1/users/?q=TEST_STUDENT',
            200,
        )

        assert res
        for user in res:
            assert user['id'] != test_user_id
        assert student.id in set(user['id'] for user in res)
Esempio n. 11
0
def test_reply_to_a_comment(logged_in, test_client, session, admin_user,
                            mail_functions, describe, tomorrow,
                            make_add_reply):
    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher = admin_user
        student = helpers.create_user_with_role(session, 'Student', course)
        ta = helpers.create_user_with_role(session, 'TA', course)

        work_id = helpers.get_id(
            helpers.create_submission(test_client,
                                      assignment,
                                      for_user=student))

        add_reply = make_add_reply(work_id)
        feedback_url = f'/api/v1/submissions/{work_id}/feedbacks/?with_replies'
        with logged_in(student):
            reply = add_reply('a reply')

    with describe('You set the in_reply_to of a comment'), logged_in(teacher):
        add_reply('a reply to', in_reply_to=reply)

    with describe('You cannot reply on a comment from a different line'
                  ), logged_in(teacher):
        add_reply('a reply to', line=10, in_reply_to=reply, expect_error=404)
Esempio n. 12
0
def test_add_test_student_to_group(
    session, test_client, logged_in, assignment, teacher_user, error_template,
    describe, monkeypatch_celery
):
    c_id = assignment.course.id

    with logged_in(teacher_user):
        g_set = create_group_set(test_client, c_id, 1, 4)

        res = create_submission(
            test_client,
            assignment.id,
            is_test_submission=True,
        )
        test_student = res['user']

        with describe('new group with a test student cannot be created'):
            res = test_client.req(
                'post',
                f'/api/v1/group_sets/{g_set["id"]}/group',
                400,
                result=error_template,
                data={
                    'member_ids': [test_student['id']],
                },
            )

        with describe(
            'new group with a test student and other students cannot be created'
        ):
            u1 = create_user_with_perms(
                session, [CPerm.can_edit_own_groups], assignment.course
            )
            res = test_client.req(
                'post',
                f'/api/v1/group_sets/{g_set["id"]}/group',
                400,
                result=error_template,
                data={
                    'member_ids': [test_student['id'], u1.id],
                },
            )

        with describe('test student can not be added to existing group'):
            g1 = create_group(
                test_client,
                g_set['id'],
                [],
            )
            res = test_client.req(
                'post',
                f'/api/v1/groups/{g1["id"]}/member',
                400,
                result=error_template,
                data={
                    'username': test_student['username'],
                },
            )
Esempio n. 13
0
def test_passback_with_bonus_points(
    lti1p3_provider, describe, logged_in, admin_user, watch_signal,
    stub_function, test_client, session, tomorrow
):
    with describe('setup'), logged_in(admin_user):
        watch_signal(signals.WORK_CREATED, clear_all_but=[])
        watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[])
        signal = watch_signal(
            signals.GRADE_UPDATED,
            clear_all_but=[m.LTI1p3Provider._passback_submission]
        )

        stub_function(
            pylti1p3.service_connector.ServiceConnector,
            'get_access_token', lambda: ''
        )
        stub_passback = stub_function(
            pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade',
            raise_pylti1p3_exc
        )

        course, course_conn = helpers.create_lti1p3_course(
            test_client, session, lti1p3_provider
        )
        assig = helpers.create_lti1p3_assignment(
            session, course, state='done', deadline=tomorrow
        )
        test_client.req(
            'patch',
            f'/api/v1/assignments/{helpers.get_id(assig)}',
            200,
            data={'max_grade': 15}
        )

        user = helpers.create_lti1p3_user(session, lti1p3_provider)
        course_conn.maybe_add_user_to_course(user, [])

        sub = helpers.to_db_object(
            helpers.create_submission(test_client, assig, for_user=user),
            m.Work
        )

    with describe('can passback bonus points'), logged_in(admin_user):
        test_client.req(
            'patch',
            f'/api/v1/submissions/{helpers.get_id(sub)}',
            200,
            data={'grade': 14},
        )
        assert signal.was_send_once
        assert stub_passback.called_amount == 1

        assert stub_passback.args[0][0].get_score_given() == 14
        assert stub_passback.args[0][0].get_score_maximum() == 10
Esempio n. 14
0
def test_direct_emails_in_thread(logged_in, test_client, session, admin_user,
                                 mail_functions, describe, tomorrow,
                                 make_add_reply):
    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher = admin_user
        student = helpers.create_user_with_role(session, 'Student', course)

        work_id = helpers.get_id(
            helpers.create_submission(test_client,
                                      assignment,
                                      for_user=student))

        add_reply = make_add_reply(work_id)
        add_reply('base comment', include_response=True)

    with describe('Initial reply should not be send in a thread'), logged_in(
            student):
        add_reply('first reply')
        first_msg, = mail_functions.assert_mailed(teacher)
        assert 'first reply' in first_msg.msg
        assert student.name in first_msg.msg
        assert first_msg.in_reply_to is None

    with describe('Own replies should not be mailed'), logged_in(teacher):
        add_reply('own reply')
        mail_functions.assert_mailed(teacher, amount=0)

    with describe('Second reply should be send as reply to first message'
                  ), logged_in(student):
        add_reply('second reply')
        second_msg, = mail_functions.assert_mailed(teacher)
        assert 'first reply' not in second_msg.msg
        assert 'second reply' in second_msg.msg
        assert student.name in second_msg.msg
        assert second_msg.in_reply_to == first_msg.message_id
        assert second_msg.references == [first_msg.message_id]

    with describe('Third reply should be send as reply to first message'
                  ), logged_in(student):
        add_reply('third reply')
        third_msg, = mail_functions.assert_mailed(teacher)
        assert third_msg.in_reply_to == first_msg.message_id
        assert third_msg.references == [first_msg.message_id]
Esempio n. 15
0
def test_failing_passback(
    lti1p3_provider, describe, logged_in, admin_user, watch_signal,
    stub_function, test_client, session, tomorrow
):
    with describe('setup'), logged_in(admin_user):
        watch_signal(signals.WORK_CREATED, clear_all_but=[])
        watch_signal(signals.GRADE_UPDATED, clear_all_but=[])
        watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[])
        stub_function(
            pylti1p3.service_connector.ServiceConnector,
            'get_access_token', lambda: ''
        )
        stub_passback = stub_function(
            pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade',
            raise_pylti1p3_exc
        )

        course, course_conn = helpers.create_lti1p3_course(
            test_client, session, lti1p3_provider
        )
        assig = helpers.create_lti1p3_assignment(
            session, course, state='done', deadline=tomorrow
        )

        user = helpers.create_lti1p3_user(session, lti1p3_provider)
        course_conn.maybe_add_user_to_course(user, [])

        sub = helpers.to_db_object(
            helpers.create_submission(test_client, assig, for_user=user),
            m.Work
        )
        sub.set_grade(10.0, m.User.resolve(admin_user))
        session.commit()
        hist = m.GradeHistory.query.filter_by(work=sub).one()
        assert hist
        assert not hist.passed_back

    with describe('failing passback should not update history'):

        m.LTI1p3Provider._passback_grades(assig.id)
        assert stub_passback.called
        hist = m.GradeHistory.query.filter_by(work=sub).one()
        assert hist
        assert not hist.passed_back
Esempio n. 16
0
    def make_assig(*args, **kwargs):
        hidden = kwargs.pop('has_hidden', False)
        with logged_in(admin_user):
            assig_id = create_assignment(*args, **kwargs)['id']
            create_auto_test(test_client, assig_id, has_hidden_steps=hidden)
        assig = m.Assignment.query.get(assig_id)
        run = m.AutoTestRun(_job_id=uuid.uuid4(),
                            auto_test=assig.auto_test,
                            batch_run_done=False)
        session.add(run)
        session.commit()

        with logged_in(admin_user):
            sub_id = create_submission(test_client, assig.id)['id']
        assert run.id is not None
        session.add(
            m.AutoTestResult(final_result=False,
                             work_id=sub_id,
                             auto_test_run_id=run.id))
        session.commit()

        return assig
Esempio n. 17
0
def test_update_test_student_role(
    teacher_user,
    logged_in,
    test_client,
    describe,
    tomorrow,
    assignment,
    error_template,
):
    c_id = assignment.course.id

    with logged_in(teacher_user):
        test_sub = create_submission(
            test_client,
            assignment.id,
            is_test_submission=True,
        )
        test_user = test_sub['user']

        roles = test_client.req(
            'get',
            f'/api/v1/courses/{c_id}/roles/',
            200,
        )

        role = next(r for r in roles if not r['hidden'])

        test_client.req(
            'put',
            f'/api/v1/courses/{assignment.course.id}/users/',
            400,
            result=error_template,
            data={
                'user_id': test_user['id'],
                'role_id': role['id'],
            },
        )
Esempio n. 18
0
plt.boxplot(results)
ax.set_xticklabels(names, fontsize=9, rotation='vertical')
plt.subplots_adjust(bottom=0.15)
plt.savefig(pltfname)

# Block: find the best model
print("Determining best model")
best = dict(name=None, mean=0, estimator=None)
for name, result, estimator in zip(names, results, estimators):
    if (result.mean() > best['mean']):
        best = dict(name=name, mean=result.mean(), estimator=estimator)

print("Best model: %s, %f" % (best['name'], best['mean']))

# Block: use the best estimator on validation data
print("Evaluating valdiation data with %s" % (best['name']))
model = best['estimator']
model.fit(Xtrn, Ytrn)
predictions = model.predict(Xval)
print("accuracy = %f" % (accuracy_score(Yval, predictions)))
print("classification report")
print(classification_report(Yval, predictions))

# Block: use the estimator on holdout data
print("Making predictions for test (holdout) data with: %s" % (best['name']))
_, Xtest = helpers.get_test_data()
predictions = model.predict(Xtest)
submissionfname = 'tmp-submission.csv'
print("Saving predictions to %s" % (submissionfname))
helpers.create_submission(predictions, submissionfname=submissionfname)
Esempio n. 19
0
def test_passing_back_all_grades(
    lti1p3_provider, describe, logged_in, admin_user, watch_signal,
    stub_function, test_client, session, tomorrow, make_function_spy
):
    with describe('setup'), logged_in(admin_user):
        watch_signal(signals.WORK_CREATED, clear_all_but=[])
        watch_signal(signals.GRADE_UPDATED, clear_all_but=[])
        watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[])
        watch_signal(signals.WORK_DELETED, clear_all_but=[])
        signal = watch_signal(
            signals.ASSIGNMENT_STATE_CHANGED,
            clear_all_but=[m.LTI1p3Provider._passback_grades]
        )

        stub_get_acccess_token = stub_function(
            pylti1p3.service_connector.ServiceConnector,
            'get_access_token', lambda: ''
        )
        stub_passback = make_function_spy(
            pylti1p3.assignments_grades.AssignmentsGradesService,
            'put_grade',
            pass_self=True
        )

        # Make a session with a response that returns json when the `json`
        # method is called
        req_session = requests_stubs.session_maker()()
        req_session.Response.json = lambda _=None: {}
        stub_requests_post = stub_function(requests, 'post', req_session.post)

        course, course_conn = helpers.create_lti1p3_course(
            test_client, session, lti1p3_provider
        )
        assig = helpers.create_lti1p3_assignment(
            session, course, state='hidden', deadline=tomorrow
        )

        user1 = helpers.create_lti1p3_user(session, lti1p3_provider)
        user2 = helpers.create_lti1p3_user(session, lti1p3_provider)
        user3 = helpers.create_lti1p3_user(session, lti1p3_provider)
        all_students = [user1, user2, user3]

        for u in all_students:
            course_conn.maybe_add_user_to_course(u, ['Learner'])

        lti_user_ids = [
            m.UserLTIProvider.query.filter_by(user=u).one().lti_user_id
            for u in all_students
        ]

        gset = helpers.create_group_set(test_client, course, 1, 2, [assig])
        helpers.create_group(test_client, gset, [user1, user3])

        sub = helpers.to_db_object(
            helpers.create_submission(test_client, assig, for_user=user1),
            m.Work
        )
        sub.set_grade(2.5, admin_user)

        # Make sure user2 does not have a non deleted submission
        user2_sub = helpers.create_submission(
            test_client, assig, for_user=user2
        )
        test_client.req(
            'delete', f'/api/v1/submissions/{helpers.get_id(user2_sub)}', 204
        )

    with describe('changing assignment state to "open" does not passback'):
        assig.set_state_with_string('open')
        assert signal.was_send_once
        assert not stub_passback.called
        assert not stub_get_acccess_token.called

    with describe('changing to done does passback'):
        assig.set_state_with_string('done')
        assert signal.was_send_once
        assert stub_passback.called_amount == len(all_students)
        # Calls should be cached
        assert stub_get_acccess_token.called_amount == 1

        p1, p2, p3 = stub_passback.args

        assert p1[0] != p2[0] != p3[0]

        assert p1[0].get_score_given() == 2.5
        assert p2[0].get_score_given() == 2.5
        assert {p1[0].get_user_id(),
                p2[0].get_user_id()} == {lti_user_ids[0], lti_user_ids[2]}

        # Does not have a submission
        assert p3[0].get_score_given() is None
        assert p3[0].get_user_id() == lti_user_ids[1]

    with describe('toggling to open and done should do a new passback'):
        assig.set_state_with_string('open')
        assert signal.was_send_once
        assert not stub_passback.called
        assert not stub_get_acccess_token.called

        assig.set_state_with_string('done')
        assert signal.was_send_n_times(2)
        assert stub_passback.called
        # access token should still be cached
        assert not stub_get_acccess_token.called
Esempio n. 20
0
def test_delete_submission_passback(
    lti1p3_provider, describe, logged_in, admin_user, watch_signal,
    stub_function, test_client, session, tomorrow
):
    with describe('setup'), logged_in(admin_user):
        watch_signal(signals.WORK_CREATED, clear_all_but=[])
        watch_signal(signals.GRADE_UPDATED, clear_all_but=[])
        watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[])
        stub_function(
            pylti1p3.service_connector.ServiceConnector,
            'get_access_token', lambda: ''
        )
        stub_passback = stub_function(
            pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade'
        )

        course, course_conn = helpers.create_lti1p3_course(
            test_client, session, lti1p3_provider
        )
        assig = helpers.create_lti1p3_assignment(
            session, course, state='done', deadline=tomorrow
        )
        user = helpers.create_lti1p3_user(session, lti1p3_provider)
        lti_user_id = m.UserLTIProvider.query.filter_by(
            user=m.User.resolve(user)
        ).one().lti_user_id
        course_conn.maybe_add_user_to_course(user, ['Learner'])

        sub_oldest, sub_older, sub_middle, sub_newest = [
            helpers.to_db_object(
                helpers.create_submission(test_client, assig, for_user=user),
                m.Work
            ) for _ in range(4)
        ]
        signal = watch_signal(
            signals.WORK_DELETED,
            clear_all_but=[m.LTI1p3Provider._delete_submission]
        )

        def do_delete(sub):
            with logged_in(admin_user):
                test_client.req(
                    'delete', f'/api/v1/submissions/{helpers.get_id(sub)}', 204
                )

    with describe('Delete non newest'):
        do_delete(sub_older)
        assert signal.was_send_once
        assert not stub_passback.called

    with describe('Calling method for non existing work simply does nothing'):
        m.LTI1p3Provider._delete_submission(1000000)
        assert not stub_passback.called

    with describe('Calling method for non deleted work does nothing'):
        m.LTI1p3Provider._delete_submission(helpers.get_id(sub_newest))
        assert not stub_passback.called

    with describe('Delete newest should passback grade of new newest'):
        sub_middle.set_grade(5.0, m.User.resolve(admin_user))
        session.commit()
        # We should have removed the grade_updated signal
        assert not stub_passback.called

        do_delete(sub_newest)
        assert signal.was_send_once
        assert stub_passback.called_amount == 1
        grade, = stub_passback.all_args[0].values()
        assert grade.get_score_given() == 5.0
        assert grade.get_user_id() == lti_user_id

    with describe('Deleting new newest should passback next non deleted'):
        sub_older.set_grade(6.0, m.User.resolve(admin_user))
        sub_oldest.set_grade(8.0, m.User.resolve(admin_user))
        session.commit()
        assert not m.GradeHistory.query.filter_by(work=sub_oldest
                                                  ).one().passed_back

        do_delete(sub_middle)
        assert signal.was_send_once
        assert stub_passback.called_amount == 1
        grade, = stub_passback.all_args[0].values()
        # Should passback oldest as we deleted older in an earlier block
        assert grade.get_score_given() == 8.0
        assert grade.get_user_id() == lti_user_id

        # Should update history
        assert m.GradeHistory.query.filter_by(work=sub_oldest
                                              ).one().passed_back

    with describe('Deleting without any existing submission should passback'):
        do_delete(sub_oldest)
        assert signal.was_send_once
        assert stub_passback.called_amount == 1
        grade, = stub_passback.all_args[0].values()
        assert grade.get_score_given() is None
        assert grade.get_grading_progress() == 'NotReady'
        assert grade.get_user_id() == lti_user_id

    with describe('Passing back deleted sub should do nothing'):
        lti1p3_provider._passback_grade(
            assignment=assig,
            sub=sub_newest,
            timestamp=DatetimeWithTimezone.utcnow()
        )
        assert not stub_passback.called
Esempio n. 21
0
def test_delete_feedback(logged_in, test_client, session, admin_user,
                         mail_functions, describe, tomorrow, make_add_reply):
    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher = admin_user
        student = helpers.create_user_with_role(session, 'Student', course)
        ta = helpers.create_user_with_role(session, 'TA', course)

        work_id = helpers.get_id(
            helpers.create_submission(test_client,
                                      assignment,
                                      for_user=student))

        add_reply = make_add_reply(work_id)
        feedback_url = f'/api/v1/submissions/{work_id}/feedbacks/?with_replies'

    with describe('Users can delete own replies'):
        for user in [teacher, student, ta]:
            with logged_in(user):
                reply = add_reply('a reply')
                test_client.req(
                    'delete', (f'/api/v1/comments/{reply["comment_base_id"]}/'
                               f'replies/{reply["id"]}'), 204)
                with logged_in(teacher):
                    test_client.req(
                        'get',
                        feedback_url,
                        200,
                        result={
                            'general': '',
                            'linter': {},
                            'authors': [],
                            'user': []
                        },
                    )

    with describe('Only teachers can delete replies of others'):
        with logged_in(student):
            reply, base = add_reply('a reply', include_base=True)
        with logged_in(ta):
            test_client.req('delete',
                            (f'/api/v1/comments/{reply["comment_base_id"]}/'
                             f'replies/{reply["id"]}'), 403)
        with logged_in(teacher):
            test_client.req(
                'get',
                feedback_url,
                200,
                result={
                    'general': '',
                    'linter': {},
                    'authors': [student],
                    'user': [base],
                },
            )

        with logged_in(teacher):
            test_client.req('delete',
                            (f'/api/v1/comments/{reply["comment_base_id"]}/'
                             f'replies/{reply["id"]}'), 204)
            test_client.req(
                'get',
                feedback_url,
                200,
                result={
                    'general': '',
                    'linter': {},
                    'authors': [],
                    'user': []
                },
            )

    with describe('Cannot delete a reply twice'), logged_in(student):
        reply = add_reply('To delete')
        test_client.req('delete',
                        (f'/api/v1/comments/{reply["comment_base_id"]}/'
                         f'replies/{reply["id"]}'), 204)
        test_client.req('delete',
                        (f'/api/v1/comments/{reply["comment_base_id"]}/'
                         f'replies/{reply["id"]}'), 404)
Esempio n. 22
0
def test_edit_feedback(logged_in, test_client, session, admin_user,
                       mail_functions, describe, tomorrow, make_add_reply):
    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher = admin_user
        student = helpers.create_user_with_role(session, 'Student', course)
        ta = helpers.create_user_with_role(session, 'TA', course)

        work_id = helpers.get_id(
            helpers.create_submission(test_client,
                                      assignment,
                                      for_user=student))

        add_reply = make_add_reply(work_id)
        feedback_url = f'/api/v1/submissions/{work_id}/feedbacks/?with_replies'
        with logged_in(student):
            reply = add_reply('a reply')
        reply_url = (f'/api/v1/comments/{reply["comment_base_id"]}/'
                     f'replies/{reply["id"]}')

    with describe('Editing with the same content does not create an edit'
                  ), logged_in(teacher):
        test_client.req('patch', reply_url, 200, data={'comment': 'a reply'})
        test_client.req(
            'get',
            feedback_url,
            200,
            result={
                '__allow_extra__':
                True,
                'user': [{
                    '__allow_extra__':
                    True,
                    'replies': [{
                        'id': reply['id'],
                        'comment': 'a reply',
                        'last_edit': None,
                        '__allow_extra__': True,
                    }],
                }]
            },
        )
    with describe('teachers and own user can edit reply'):
        with logged_in(student):
            test_client.req('patch',
                            reply_url,
                            200,
                            data={'comment': 'updated1'})
        with logged_in(teacher):
            test_client.req('patch',
                            reply_url,
                            200,
                            data={'comment': 'updated2'})
        with logged_in(ta):
            test_client.req('patch',
                            reply_url,
                            403,
                            data={'comment': 'updated3'})

        with logged_in(teacher):
            test_client.req(
                'get',
                feedback_url,
                200,
                result={
                    'general':
                    '',
                    'linter': {},
                    'authors': [student],
                    'user': [{
                        '__allow_extra__':
                        True,
                        'replies': [{
                            'id': reply['id'],
                            'comment': 'updated2',
                            'last_edit': str,
                            '__allow_extra__': True,
                        }],
                    }]
                },
            )

    with describe('Students can only see own edits'):
        with logged_in(teacher):
            other_reply = add_reply('A reply jooo')
            other_reply_url = (
                f'/api/v1/comments/{other_reply["comment_base_id"]}/'
                f'replies/{other_reply["id"]}')

        with logged_in(student):
            edits = test_client.req('get',
                                    reply_url + '/edits/',
                                    200,
                                    result=[
                                        {
                                            'id': int,
                                            'old_text': 'updated1',
                                            'created_at': str,
                                            'new_text': 'updated2',
                                            'editor': teacher,
                                        },
                                        {
                                            'id': int,
                                            'old_text': 'a reply',
                                            'created_at': str,
                                            'new_text': 'updated1',
                                            'editor': student,
                                        },
                                    ])

            test_client.req('get', other_reply_url + '/edits/', 403)

    with describe('teachers can see others edits'), logged_in(teacher):
        test_client.req('get', reply_url + '/edits/', 200, result=edits)
        test_client.req('get', other_reply_url + '/edits/', 200, result=[])

    with describe('last_edit should really be the last edit'), logged_in(
            student):
        last_edit = test_client.req('get', feedback_url,
                                    200)['user'][0]['replies'][0]['last_edit']

        now = cg_dt_utils.DatetimeWithTimezone.utcnow()
        with freeze_time(now):
            test_client.req('patch',
                            reply_url,
                            200,
                            data={'comment': 'updated4'})

        new_last_edit = test_client.req(
            'get', feedback_url, 200)['user'][0]['replies'][0]['last_edit']
        assert new_last_edit > last_edit
        assert new_last_edit == now.isoformat()
Esempio n. 23
0
def test_delete_group(
    test_client, logged_in, prog_course, session, teacher_user, error_template,
    app, monkeypatch_celery
):
    user_only_own = create_user_with_perms(
        session, [
            CPerm.can_edit_own_groups, CPerm.can_submit_own_work,
            CPerm.can_see_assignments
        ], prog_course
    )
    user_other_too = create_user_with_perms(
        session, [CPerm.can_edit_others_groups, CPerm.can_see_assignments],
        prog_course
    )

    with logged_in(teacher_user):
        assig_id = [
            a for a in test_client.
            req('get', f'/api/v1/courses/{prog_course.id}/assignments/', 200)
            if a['state'] == 'submitting'
        ][0]['id']

    with logged_in(teacher_user):
        group_set = create_group_set(test_client, prog_course.id, 2, 4)
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assig_id}',
            200,
            data={'group_set_id': group_set['id']}
        )

        group_own_id = create_group(
            test_client, group_set["id"], [
                create_user_with_perms(session, [], prog_course).id,
                user_only_own.id,
            ]
        )['id']
        group_others_id = create_group(
            test_client, group_set["id"], [
                create_user_with_perms(session, [], prog_course).id,
                create_user_with_perms(session, [], prog_course).id,
            ]
        )['id']
        group_empty_id = create_group(test_client, group_set["id"], [])['id']

    with logged_in(user_only_own):
        sub = create_submission(test_client, assig_id)
        assert sub['user']['group']

    with logged_in(user_only_own):
        # Cannot delete with submission
        test_client.req(
            'delete',
            f'/api/v1/groups/{group_own_id}',
            400,
            result=error_template
        )
        # Cannot delete non empty group of other members
        test_client.req(
            'delete',
            f'/api/v1/groups/{group_others_id}',
            403,
            result=error_template
        )
        # Can delete empty group
        test_client.req('delete', f'/api/v1/groups/{group_empty_id}', 204)

    with logged_in(user_other_too):
        # Cannot delete with submission
        test_client.req(
            'delete',
            f'/api/v1/groups/{group_own_id}',
            400,
            result=error_template
        )
        # Can delete non empty group of other members
        test_client.req('delete', f'/api/v1/groups/{group_others_id}', 204)

    with logged_in(teacher_user):
        test_client.req('delete', f'/api/v1/submissions/{sub["id"]}', 204)

    with logged_in(user_only_own):
        # Can delete now submission has been deleted
        test_client.req('delete', f'/api/v1/groups/{group_own_id}', 204)
Esempio n. 24
0
def test_seeing_teacher_revision_in_group(
    test_client, session, logged_in, teacher_user, course, error_template,
    assignment, describe
):
    with describe('setup'):
        user_other_group = create_user_with_perms(
            session, [
                CPerm.can_submit_own_work, CPerm.can_view_own_teacher_files,
                CPerm.can_see_assignments
            ], course
        )
        user_with_perm = create_user_with_perms(
            session, [
                CPerm.can_submit_own_work, CPerm.can_view_own_teacher_files,
                CPerm.can_see_assignments
            ], course
        )
        user_without_perm = create_user_with_perms(
            session, [CPerm.can_submit_own_work, CPerm.can_see_assignments],
            course
        )

        with logged_in(teacher_user):
            g_set = create_group_set(test_client, course.id, 1, 2)
            test_client.req(
                'patch',
                f'/api/v1/assignments/{assignment.id}',
                200,
                data={'group_set_id': g_set['id']}
            )
            g1 = create_group(
                test_client,
                g_set['id'],
                [user_with_perm, user_without_perm],
            )

        with logged_in(user_with_perm):
            sub = create_submission(test_client, assignment.id)
            # Submission is by the group
            assert sub['user']['group']['id'] == g1['id']

            student_files = test_client.req(
                'get', f'/api/v1/submissions/{sub["id"]}/files/', 200
            )

    with describe('Cannot view teacher files while assig is open'
                  ), logged_in(user_with_perm):
        test_client.req(
            'get',
            f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher',
            403,
            result=error_template,
        )

    with describe('Can see teacher files after assig is done'
                  ), logged_in(user_with_perm):
        assignment.set_state_with_string('done')
        session.commit()

        test_client.req(
            'get',
            f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher',
            200,
            result=student_files,
        )

        # This marks all files as from the student revision, i.e. there are no
        # teacher files.
        m.File.query.filter(
            m.File.work_id == sub['id'], m.File.parent_id.isnot(None)
        ).update({'fileowner': m.FileOwner.student})

        test_client.req(
            'get',
            f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher',
            200,
            result={
                **student_files,
                'entries': [],
            }
        )

    with describe(
        'User without permission to see own teacher files gets an error'
    ), logged_in(user_without_perm):
        test_client.req(
            'get',
            f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher',
            403,
            result=error_template,
        )

    with describe('User from other group cannot see teacher files'
                  ), logged_in(user_other_group):
        test_client.req(
            'get',
            f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher',
            403,
            result=error_template,
        )
Esempio n. 25
0
def test_update_group_set(
    test_client, logged_in, bs_course, prog_course, request, error_template,
    teacher_user, ta_user, assignment, session
):
    with logged_in(teacher_user):
        group_set = test_client.req(
            'put',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            data={
                'minimum_size': 1,
                'maximum_size': 3,
            },
            status_code=200,
        )

        # Update should be done in place
        group_set = test_client.req(
            'put',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            data={
                'minimum_size': 3,
                'maximum_size': 10,
                'id': group_set['id'],
            },
            status_code=200,
            result={
                'id': group_set['id'],
                'minimum_size': 3,
                'maximum_size': 10,
                'assignment_ids': [],
            },
        )
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'group_set_id': group_set['id']}
        )
        group_set['assignment_ids'].append(assignment.id)

        test_client.req(
            'get',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            200,
            result=[group_set]
        )

        # You should not be able to change the course id
        test_client.req(
            'put',
            f'/api/v1/courses/{prog_course.id}/group_sets/',
            data={
                'minimum_size': 3,
                'maximum_size': 10,
                'id': group_set['id'],
            },
            status_code=400,
            result=error_template,
        )

        # The group set now has a group with 3 members
        user = create_user_with_perms(session, [], bs_course)
        group = test_client.req(
            'post',
            f'/api/v1/group_sets/{group_set["id"]}/group',
            data={'member_ids': [teacher_user.id, ta_user.id, user.id]},
            status_code=200
        )
        test_client.req(
            'get',
            f'/api/v1/assignments/{assignment.id}/groups/{group["id"]}/member_states/',
            200,
            result={
                str(teacher_user.id): True,
                str(ta_user.id): True,
                str(user.id): True,
            }
        )
        # You should not be able to set the max size to lower than 3
        test_client.req(
            'put',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            data={
                'minimum_size': 1,
                'maximum_size': 2,
                'id': group_set['id'],
            },
            status_code=400,
            result=error_template,
        )

        # You SHOULD be able to set the min size to higher than 3 as there was
        # no submission by any group
        test_client.req(
            'put',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            data={
                'minimum_size': 4,
                'maximum_size': 10,
                'id': group_set['id'],
            },
            status_code=200,
            result={
                'minimum_size': 4,
                'maximum_size': 10,
                'id': group_set['id'],
                'assignment_ids': [assignment.id],
            }
        )

        # Reset back to minimum size of 3
        test_client.req(
            'put',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            data={
                'minimum_size': 3,
                'maximum_size': 4,
                'id': group_set['id'],
            },
            status_code=200,
        )

        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'group_set_id': group_set['id']}
        )
        submission = create_submission(test_client, assignment.id)
        assert submission['user']['group']
        assert submission['user']['group']['id'] == group['id']

        # Now you should not be able to set the min size to higher than 3 as
        # there IS a submission by a group.
        test_client.req(
            'put',
            f'/api/v1/courses/{bs_course.id}/group_sets/',
            data={
                'minimum_size': 4,
                'maximum_size': 10,
                'id': group_set['id'],
            },
            status_code=400,
            result=error_template,
        )

        # You should also not be able to disconnect the group set for this
        # assignment
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            400,
            data={'group_set_id': None},
            result=error_template,
        )
        new_group_set = create_group_set(test_client, prog_course.id, 2, 4)
        # You should also not be able to change the group set for this
        # assignment
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            400,
            data={'group_set_id': new_group_set['id']},
            result=error_template,
        )
Esempio n. 26
0
def test_get_all_comments_of_user(logged_in, test_client, session, admin_user,
                                  describe, tomorrow, make_add_reply):
    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher1 = admin_user
        teacher2 = helpers.create_user_with_role(session, 'Teacher', course)
        student1 = helpers.create_user_with_role(session, 'Student', course)
        student2 = helpers.create_user_with_role(session, 'Student', course)

        sub1 = helpers.create_submission(test_client,
                                         assignment,
                                         for_user=student1)

        sub2 = helpers.create_submission(test_client,
                                         assignment,
                                         for_user=student2)

        add_reply1 = make_add_reply(sub1)
        add_reply2 = make_add_reply(sub2)

        with logged_in(teacher1):
            rep1_1 = add_reply1('Hello a reply', line=1)
            rep1_2 = add_reply1('Hello a reply', line=1)
            rep2 = add_reply1('Hello a reply', line=10)
            rep3 = add_reply2('Hello a reply', line=5)

        def get_url(user):
            return (f'/api/v1/assignments/{get_id(assignment)}/users'
                    f'/{get_id(user)}/comments/')

    with describe('Can get all comments by a user'), logged_in(teacher1):
        result = [
            {
                '__allow_extra__':
                True,
                'replies': [
                    dict_without(rep1_1, 'author'),
                    dict_without(rep1_2, 'author')
                ],
                'line':
                1,
            },
            {
                '__allow_extra__': True,
                'replies': [dict_without(rep2, 'author')],
                'line': 10,
            },
            {
                '__allow_extra__': True,
                'replies': [dict_without(rep3, 'author')],
                'line': 5,
            },
        ]
        test_client.req('get', get_url(teacher1), 200, result)

    with describe('Does not get threads without own reply'):
        with logged_in(teacher2):
            # rep1_3 is allowed to exist in the reply, however this is not the
            # case with the current implementation. rep5 should never be in the
            # reply.
            rep1_3 = add_reply1('A reply by somebody else', line=1)
            rep5 = add_reply1('A reply by somebody else', line=100)
            test_client.req(
                'get',
                get_url(teacher2),
                200,
                [
                    {
                        '__allow_extra__':
                        True,
                        'replies': [
                            # rep1_1 and rep1_2 are allowed to be present
                            dict_without(rep1_3, 'author')
                        ],
                        'line':
                        1,
                    },
                    {
                        '__allow_extra__': True,
                        'replies': [dict_without(rep5, 'author')],
                        'line': 100,
                    },
                ],
            )

        with logged_in(teacher1):
            test_client.req('get', get_url(teacher1), 200, result)
Esempio n. 27
0
def test_submit_with_group(
    test_client, session, logged_in, teacher_user, course, error_template,
    assignment, monkeypatch_celery
):
    user_full_group = create_user_with_perms(
        session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course
    )
    user_empty_group = create_user_with_perms(
        session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course
    )
    user_no_group = create_user_with_perms(
        session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course
    )
    user_no_perms = create_user_with_perms(
        session, [CPerm.can_see_assignments], course
    )

    with logged_in(teacher_user):
        g_set = create_group_set(test_client, course.id, 2, 4)
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'group_set_id': g_set['id']}
        )
        g1 = create_group(
            test_client,
            g_set['id'],
            [user_full_group.id, user_no_perms.id],
        )
        create_group(test_client, g_set['id'], [user_empty_group.id])

    with logged_in(user_empty_group):
        err = create_submission(test_client, assignment.id, err=400)
        assert 'enough members' in err['message']

    with logged_in(user_no_perms):
        # Can't submit even if some users in the group can
        err = create_submission(test_client, assignment.id, err=403)

    with logged_in(user_no_group):
        # Can't submit as a user without group as the minimum size is 2
        err = create_submission(test_client, assignment.id, err=404)
        assert 'group was found' in err['message']

    with logged_in(user_full_group):
        # User can submit submission
        sub = create_submission(test_client, assignment.id)
        # Make sure submission is done as the group, not as the user
        assert sub['user']['group']['id'] == g1['id']

        # Make sure the user can see the just submitted submission
        test_client.req(
            'get',
            f'/api/v1/assignments/{assignment.id}/submissions/?extended',
            200,
            result=[sub]
        )
        test_client.req(
            'get',
            f'/api/v1/submissions/{sub["id"]}?extended',
            200,
            result=sub
        )
        files = test_client.req(
            'get', f'/api/v1/submissions/{sub["id"]}/files/', 200
        )
        while 'entries' in files:
            files = files['entries'][0]
        code_id = files['id']
        response = test_client.get(f'/api/v1/code/{code_id}')
        assert response.status_code == 200
    with logged_in(teacher_user):
        test_client.req(
            'put',
            f'/api/v1/code/{code_id}/comments/3',
            204,
            data={'comment': 'Lekker gewerkt pik!'}
        )
        # Set state to done so the student can see its feedback
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'state': 'done'}
        )
    with logged_in(user_full_group):
        # Make sure we can see comments on the files
        test_client.req(
            'get',
            f'/api/v1/code/{code_id}?type=feedback',
            200,
            result={'3': {'line': 3, 'msg': 'Lekker gewerkt pik!'}}
        )
    with logged_in(teacher_user):
        # Reset state back
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'state': 'open'}
        )
Esempio n. 28
0
def test_add_feedback(request, logged_in, test_client, session, data,
                      error_template, admin_user, mail_functions, describe,
                      tomorrow):
    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher = admin_user
        student = helpers.create_user_with_perms(session,
                                                 [CPerm.can_submit_own_work],
                                                 course)
        work = helpers.create_submission(test_client,
                                         assignment,
                                         for_user=student)

        data_err = request.node.get_closest_marker('data_error') is not None

        code_id = session.query(m.File.id).filter(
            m.File.work_id == work['id'],
            m.File.parent_id.isnot(None),
            m.File.name != '__init__',
        ).first()[0]

        if data_err:
            code = 400
        else:
            code = 204

        def get_result(has_author=True):
            if data_err:
                return {}
            msg = data.get('expected_result', data['comment'])
            res = {
                '0': {
                    'line': 0,
                    'msg': msg,
                    'author': {
                        'id': teacher.id,
                        '__allow_extra__': True
                    },
                }
            }
            if not has_author:
                res['0'].pop('author')
            return res

    with describe('teacher can place comments'), logged_in(teacher):
        test_client.req('put',
                        f'/api/v1/code/{code_id}/comments/0',
                        code,
                        data=data,
                        result=None if code == 204 else error_template)
        if data_err:
            assert not mail_functions.any_mails()
            return
        assert not mail_functions.any_mails(), 'Student should not be mailed'

        res = test_client.req('get',
                              f'/api/v1/code/{code_id}',
                              200,
                              query={'type': 'feedback'},
                              result=get_result())

        data = {
            'comment': 'bye',
            'expected_result': 'bye',
        }
        test_client.req(
            'put',
            f'/api/v1/code/{code_id}/comments/0',
            code,
            data=data,
        )
        assert not mail_functions.any_mails(), 'No mails for updates'

        test_client.req('get',
                        f'/api/v1/code/{code_id}',
                        200,
                        query={'type': 'feedback'},
                        result=get_result())

    with describe(
            'Students cannot place comments if they do not have the permission'
    ), logged_in(student):
        test_client.req('put',
                        f'/api/v1/code/{code_id}/comments/1',
                        403,
                        data=data)

    with describe('Students can get comments when assignment is done'):
        with logged_in(student):
            test_client.req(
                'get',
                f'/api/v1/code/{code_id}',
                200,
                query={'type': 'feedback'},
                result={},
            )

            # We don't have the permission to see the notification
            test_client.req('get',
                            '/api/v1/notifications/',
                            200,
                            result={'notifications': []})
            test_client.req('get',
                            '/api/v1/notifications/?has_unread',
                            200,
                            result={'has_unread': False})

        with logged_in(teacher):
            test_client.req(
                'patch',
                f'/api/v1/assignments/{assignment["id"]}',
                200,
                data={'state': 'done'},
            )
            assert not mail_functions.any_mails(), (
                'Should not send old notifications when state changes')

        with logged_in(student):
            test_client.req(
                'get',
                f'/api/v1/code/{code_id}',
                200,
                query={'type': 'feedback'},
                result=get_result(has_author=False),
            )
            # Now we do have this permission
            test_client.req('get',
                            '/api/v1/notifications/',
                            200,
                            result={
                                'notifications': [{
                                    'id': int,
                                    'reasons': [['author', str]],
                                    '__allow_extra__': True,
                                    'read': False,
                                    'file_id': code_id,
                                }]
                            })
            test_client.req('get',
                            '/api/v1/notifications/?has_unread',
                            200,
                            result={'has_unread': True})

    with describe('Cannot get or update feedback after deleting submission'
                  ), logged_in(teacher):
        test_client.req('delete', f'/api/v1/submissions/{work["id"]}', 204)

        test_client.req(
            'get',
            f'/api/v1/code/{code_id}',
            404,
            query={'type': 'feedback'},
            result=error_template,
        )
        test_client.req(
            'put',
            f'/api/v1/code/{code_id}/comments/0',
            404,
            data={'comment': 'Any value'},
            result=error_template,
        )

    with describe('After delete notification should be gone'), logged_in(
            student):
        test_client.req('get',
                        '/api/v1/notifications/',
                        200,
                        result={'notifications': []})
        test_client.req('get',
                        '/api/v1/notifications/?has_unread',
                        200,
                        result={'has_unread': False})
Esempio n. 29
0
def test_remove_user_from_group(
    test_client, session, logged_in, teacher_user, prog_course, error_template,
    assignment, monkeypatch_celery
):
    def make_user():
        return create_user_with_perms(
            session, [
                CPerm.can_edit_own_groups, CPerm.can_submit_own_work,
                CPerm.can_see_assignments
            ], prog_course
        )

    u1 = make_user()
    u2 = make_user()
    u3 = make_user()
    u4 = make_user()

    with logged_in(teacher_user):
        g_set = create_group_set(test_client, prog_course.id, 2, 4)
        g1 = create_group(
            test_client,
            g_set['id'],
            [u1.id, u2.id, u3.id, u4.id],
        )
        test_client.req(
            'patch',
            f'/api/v1/assignments/{assignment.id}',
            200,
            data={'group_set_id': g_set['id']}
        )

    with logged_in(u3) as me:
        # Cannot remove other users from own group
        test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{u1.id}',
            403,
            result=error_template
        )

        # Can remove self from group
        g1 = test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{me.id}',
            200,
        )

        # Cannot remove self twice
        test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{me.id}',
            404,
        )

    with logged_in(u2) as me:
        # Make sure group has a submission
        sub = create_submission(test_client, assignment.id)
        assert sub['user']['group']['id'] == g1['id']

        # Cannot remove self from group as the group has submission
        test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{me.id}',
            403,
            result=error_template
        )

    with logged_in(teacher_user):
        # Group is has three members so this is possible
        g1 = test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{u1.id}',
            200,
        )
        # Group only has two member with and has a submission so this is not
        # possible
        test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{u2.id}',
            400,
            result=error_template
        )

        test_client.req('delete', f'/api/v1/submissions/{sub["id"]}', 204)

        # The submission is deleted so this is now possible
        g1 = test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{u4.id}',
            200,
        )
        g1 = test_client.req(
            'delete',
            f'/api/v1/groups/{g1["id"]}/members/{u2.id}',
            200,
        )
        assert g1['members'] == []
Esempio n. 30
0
def test_reply(logged_in, test_client, session, admin_user, mail_functions,
               describe, tomorrow, make_add_reply):

    with describe('setup'), logged_in(admin_user):
        assignment = helpers.create_assignment(test_client,
                                               state='open',
                                               deadline=tomorrow)
        course = assignment['course']
        teacher = admin_user
        student = helpers.create_user_with_role(session, 'Student', course)

        work_id = helpers.get_id(
            helpers.create_submission(test_client,
                                      assignment,
                                      for_user=student))

        add_reply = make_add_reply(work_id)

        feedback_url = f'/api/v1/submissions/{work_id}/feedbacks/?with_replies'

    with describe('Teachers can give feedback'), logged_in(teacher):
        _, rv = add_reply('base comment', include_response=True)
        assert 'Warning' not in rv.headers
        assert not mail_functions.any_mails(), (
            'Nobody should be emailed for the first reply')

    with describe('Students may not see feedback by default'), logged_in(
            student):
        test_client.req(
            'get',
            feedback_url,
            200,
            result={
                'general': '',
                'linter': {},
                'authors': [],
                'user': []
            },
        )

    with describe('But students can place comments'), logged_in(student):
        add_reply('A reply')
        # The teacher should be mailed as a reply was posted
        mail_functions.assert_mailed(teacher, amount=1)
        # A direct mail should be send
        assert mail_functions.direct.called
        assert not mail_functions.digest.called

    with describe('Students may see own comments'), logged_in(student):
        test_client.req(
            'get',
            feedback_url,
            200,
            result={
                'general':
                '',
                'linter': {},
                'authors': [student],
                'user': [{
                    '__allow_extra__':
                    True,
                    'replies': [{
                        'comment': 'A reply',
                        '__allow_extra__': True
                    }],
                }],
            },
        )

    with describe('Teacher may see all comments'), logged_in(teacher):
        test_client.req(
            'get',
            feedback_url,
            200,
            result={
                'general':
                '',
                'linter': {},
                'authors': [student, teacher],
                'user': [{
                    '__allow_extra__':
                    True,
                    'replies': [{
                        'comment': 'base comment',
                        '__allow_extra__': True
                    }, {
                        'comment': 'A reply',
                        '__allow_extra__': True
                    }],
                }],
            },
        )

    with describe('A teacher can reply and should get a warning'), logged_in(
            teacher):
        _, rv = add_reply('another comment', include_response=True)
        assert 'Warning' in rv.headers
        assert 'have sufficient permissions' in rv.headers['Warning']