예제 #1
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]
예제 #2
0
def test_getting_inline_feedback_analytics(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']
        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)
        base_url = (
            f'/api/v1/analytics/{assignment["analytics_workspace_ids"][0]}')
        url = base_url + '/data_sources/inline_feedback'

        def test(amount):
            test_client.req(
                'get',
                url,
                200,
                result={
                    'name': 'inline_feedback',
                    'data': {
                        str(work_id): {
                            'total_amount': amount,
                        }
                    },
                },
            )

    with describe('should be includes as data source'), logged_in(teacher):
        test_client.req('get',
                        base_url,
                        200,
                        result={
                            'id': assignment['analytics_workspace_ids'][0],
                            'assignment_id': assignment['id'],
                            'student_submissions': {
                                str(get_id(student)): [{
                                    'id': work_id,
                                    'created_at': str,
                                    'grade': None,
                                    'assignee_id': None,
                                }],
                            },
                            'data_sources': lambda x: 'inline_feedback' in x,
                        })

    with describe('standard no comments'), logged_in(teacher):
        test(0)

    with describe('after adding reply it should return 1'), logged_in(teacher):
        r1 = add_reply('A reply', line=1)
        test(1)

    with describe('after two replies on 1 line should return 1'), logged_in(
            teacher):
        r2 = add_reply('A reply on the reply', line=1)
        test(1)

    with describe('a reply on another line should return more'), logged_in(
            teacher):
        r3 = add_reply('A new line reply', line=2)
        r4 = add_reply('WOW MUCH REPLIES', line=1000)
        test(3)

    with describe('Updating should not change a thing'), logged_in(teacher):
        test(3)
        r4 = r4.update('wow new text')
        test(3)

    with describe('after deleting replies it should be back at 0'), logged_in(
            teacher):
        # Should begin with 3
        test(3)

        # There is another comment on this line so it should stay the same
        r1.delete()
        test(3)
        r3.delete()
        test(2)
        r4.delete()
        test(1)
        r2.delete()
        test(0)

    with describe('students cannot access it'), logged_in(student):
        test_client.req('get', url, 403)
예제 #3
0
def test_disabling_peer_feedback(
    test_client, admin_user, session, describe, logged_in, yesterday,
    make_add_reply
):
    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, *other_users = users
        for user in users:
            helpers.create_submission(test_client, assignment, for_user=user)

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

        pf_sub = m.Work.query.filter_by(
            assignment_id=helpers.get_id(assignment),
            user_id=helpers.get_id(conns[0]),
        ).one()
        pf_url = f'{base_url}/peer_feedback_settings'

        add_reply = make_add_reply(pf_sub)
        with logged_in(user1):
            reply = add_reply(
                'A peer feedback comment', expect_peer_feedback=True
            )

    with describe('Students cannot disable peer feedback'), logged_in(user1):
        test_client.req('delete', pf_url, 403)

    with describe('Teachers can disable peer feedback'), logged_in(teacher):
        test_client.req('delete', pf_url, 204)
        get_all_connections(assignment, 0)

    with describe('Old feedback still exists after disabling'
                  ), logged_in(teacher):
        test_client.req(
            'get',
            f'/api/v1/submissions/{helpers.get_id(pf_sub)}/feedbacks/',
            200,
            query={'with_replies': '1'},
            result={
                'general': '',
                'linter': {},
                'authors': [helpers.to_db_object(user1, m.User)],
                'user': [{
                    'id': int,
                    '__allow_extra__': True,
                    'replies': [helpers.dict_without(reply, 'author')],
                }],
            }
        )

        # Students can no longer see their peer feedback subs
    with describe('Student can no longer place new comments'
                  ), logged_in(user1):
        add_reply(
            'No peer feedback',
            expect_error=403,
            base_id=reply['comment_base_id'],
        )

    with describe('deleting again does nothing'), logged_in(teacher):
        test_client.req('delete', pf_url, 204)
        get_all_connections(assignment, 0)
예제 #4
0
def test_giving_peer_feedback_comments(
    test_client, admin_user, session, describe, logged_in, yesterday, tomorrow,
    auto_approved, make_add_reply
):
    with describe('setup'), logged_in(admin_user):
        course = helpers.create_course(test_client)
        assignment = helpers.create_assignment(
            test_client, course, deadline=tomorrow, 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, *other_users = users
        subs_by_user = {}
        for user in users:
            helpers.create_submission(test_client, assignment, for_user=user)
            # Every user has two submissions
            subs_by_user[user] = helpers.create_submission(
                test_client, assignment, for_user=user
            )

        helpers.enable_peer_feedback(
            test_client, assignment, amount=1, auto_approved=auto_approved
        )
        conns = get_all_connections(assignment, 1)[user1]
        other_user = next(u for u in other_users if u not in conns)
        base_url = f'/api/v1/assignments/{helpers.get_id(assignment)}'
        pf_sub = subs_by_user[conns[0]]
        own_sub = subs_by_user[user1]
        add_reply = make_add_reply(pf_sub)

        with logged_in(teacher):
            teacher_rep = add_reply('Hello')
            base_comment_id = teacher_rep['comment_base_id']
            teacher_rep.delete()

    with describe(
        'Before deadline we have no connections (and cannot place feedback)'
    ), logged_in(user1):
        test_client.req(
            'get',
            f'{base_url}/submissions/?latest_only',
            200,
            result=[{
                'id': int,
                'user': {
                    'id': user1,
                    '__allow_extra__': True,
                },
                '__allow_extra__': True,
            }]
        )
        add_reply(
            'Too early for a reply', expect_error=403, base_id=base_comment_id
        )

    with logged_in(teacher):
        test_client.req(
            'patch', base_url, 200, data={'deadline': yesterday.isoformat()}
        )

    with describe(
        'Will receive peer feedback submissions when getting all submissions'
    ), logged_in(user1):
        test_client.req(
            'get',
            f'{base_url}/submissions/?latest_only&extended',
            200,
            result=[pf_sub, own_sub],
        )
        # Can also get older subs of a assigned user
        test_client.req(
            'get',
            f'{base_url}/users/{conns[0]}/submissions/?extended',
            200,
            result=[
                pf_sub,
                {
                    '__allow_extra__': True,
                    'user': {'id': conns[0], '__allow_extra__': True},
                }
            ],
        )

    with describe('Can comment on other submission'), logged_in(user1):
        reply = add_reply('A peer feedback comment', expect_peer_feedback=True)
        assert reply['approved'] == auto_approved

    with describe('Cannot change approval status yourself'), logged_in(user1):
        reply.set_approval(not auto_approved, err=403)

    with describe('Teacher can change approval status'), logged_in(teacher):
        reply = reply.set_approval(not auto_approved)

    with describe('Editing feedback resets approval status'):
        with logged_in(user1):
            # We don't reset it back to ``True`` if ``auto_approved`` is
            # ``True`` but we do set it back to ``False``.
            reply = reply.update('New content', approved=False)

        if auto_approved:
            # If reply is approved an ``auto_approved`` is ``True`` the
            # approval state should not change.
            with logged_in(teacher):
                reply.set_approval(True)
            with logged_in(user1):
                reply = reply.update('New content2', approved=True)

    with describe(
        'Cannot place or edit peer feedback after pf deadline has expired'
    ):
        with logged_in(teacher):
            helpers.enable_peer_feedback(
                test_client,
                assignment,
                amount=1,
                auto_approved=auto_approved,
                days=0.5
            )
            assert get_all_connections(assignment, 1)[user1] == conns
        with logged_in(user1):
            add_reply('Another peer feedback comment', expect_error=403)

            reply.update('Cannot update!', err=403)

    with describe('Can always place peer feedback if pf time is None'):
        with logged_in(teacher):
            helpers.enable_peer_feedback(
                test_client,
                assignment,
                amount=1,
                auto_approved=auto_approved,
                days=None,
            )

        with freezegun.freeze_time(
            DatetimeWithTimezone.utcnow() + timedelta(days=365 * 20)
        ), logged_in(user1):
            reply = reply.update('Can update way after the deadline!')

    with describe('Cannot add peer feedback to a non assigned sub'
                  ), logged_in(other_user):
        add_reply(
            'Not possible to add this',
            expect_error=403,
            base_id=reply['comment_base_id']
        )

    with describe('Student can see approved peer feedback after done'):
        with logged_in(teacher):
            reply = reply.set_approval(not auto_approved)
            test_client.req(
                'get',
                f'/api/v1/submissions/{helpers.get_id(pf_sub)}/feedbacks/',
                200,
                query={'with_replies': '1'},
                result={
                    'general': '',
                    'linter': {},
                    'user': [{
                        'id': int,
                        '__allow_extra__': True,
                        'replies': [helpers.dict_without(reply, 'author'), ],
                    }],
                    'authors': [helpers.to_db_object(user1, m.User)],
                },
            )
            test_client.req('patch', base_url, 200, data={'state': 'done'})

        with logged_in(conns[0]):
            test_client.req(
                'get',
                f'/api/v1/submissions/{helpers.get_id(pf_sub)}/feedbacks/',
                200,
                query={'with_replies': '1'},
                result={
                    'general': '',
                    'linter': {},
                    'authors': ([helpers.to_db_object(user1, m.User)]
                                if reply['approved'] else []),
                    'user': ([{
                        'id': int,
                        '__allow_extra__': True,
                        'replies': [helpers.dict_without(reply, 'author')],
                    }] if reply['approved'] else []),
                },
            )
예제 #5
0
def test_send_mails(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))

        url = '/api/v1/settings/notification_settings/'
        add_reply = make_add_reply(work_id)
        add_reply('base comment', include_response=True)

    with describe('default should send mail'):
        with logged_in(teacher):
            test_client.req('get',
                            url,
                            200,
                            result={
                                'options': [
                                    {
                                        'reason': 'assignee',
                                        'explanation': str,
                                        'value': 'direct',
                                    },
                                    {
                                        'reason': 'author',
                                        'explanation': str,
                                        'value': 'off',
                                    },
                                    {
                                        'reason': 'replied',
                                        'explanation': str,
                                        'value': 'direct',
                                    },
                                ],
                                'possible_values':
                                ['direct', 'daily', 'weekly', 'off'],
                            })

        with logged_in(student):
            add_reply('first reply')
            msg, = mail_functions.assert_mailed(teacher)

    with describe('Preferences can be changed as logged in user'):
        with logged_in(teacher):
            test_client.req('patch',
                            url,
                            204,
                            data={
                                'reason': 'replied',
                                'value': 'daily'
                            })

        with logged_in(student):
            add_reply('first reply')
            mail_functions.assert_mailed(teacher, amount=0)

            psef.tasks._send_daily_notifications()
            mail_functions.assert_mailed(teacher, amount=1)
            assert mail_functions.digest.called
            assert not mail_functions.direct.called

    with describe('Mails should not be send multiple times'):
        psef.tasks._send_daily_notifications()
        mail_functions.assert_mailed(teacher, amount=0)

    with describe('Fastest setting should be used'):
        m.Work.query.filter_by(id=work_id).update({
            'assigned_to': teacher.id,
        })
        session.commit()
        with logged_in(teacher):
            for reason, value in [('replied', 'daily'),
                                  ('assignee', 'weekly')]:
                test_client.req('patch',
                                url,
                                204,
                                data={
                                    'reason': reason,
                                    'value': value
                                })

        with logged_in(student):
            add_reply('first reply')
            psef.tasks._send_weekly_notifications()
            # Should not be send as a more specific is available
            mail_functions.assert_mailed(teacher, amount=0)

            # Should be send as this is the most specific enabled setting
            psef.tasks._send_daily_notifications()
            mail_functions.assert_mailed(teacher, amount=1)

    with describe('should not send when comment is deleted'):
        with logged_in(student):
            reply = add_reply('reply that will be deleted')
        with logged_in(teacher):
            reply.delete()

        psef.tasks._send_daily_notifications()
        mail_functions.assert_mailed(teacher, amount=0)

    with describe('Preferences can be changed using the mailed token'):
        token = re.search(r'unsubscribe/email_notifications/\?token=([^"]+)"',
                          msg.msg).group(1)
        assert '"' not in token
        test_client.req('patch',
                        url + '?token=' + token,
                        204,
                        data={
                            'reason': 'replied',
                            'value': 'direct'
                        })

        with logged_in(student):
            add_reply('third reply')
            mail_functions.assert_mailed(teacher, amount=1)

    with describe('Tokens can be reused for a few days'):
        test_client.req('patch',
                        url + '?token=' + token,
                        204,
                        data={
                            'reason': 'assignee',
                            'value': 'direct'
                        })

        with freeze_time(datetime.datetime.utcnow() +
                         datetime.timedelta(days=10)):
            test_client.req('patch',
                            url + '?token=' + token,
                            403,
                            data={
                                'reason': 'assignee',
                                'value': 'direct'
                            })

    with describe('Tokens should not be random garbage'):
        test_client.req('patch',
                        url + '?token=' + 'random_garbage',
                        403,
                        data={
                            'reason': 'assignee',
                            'value': 'direct'
                        })
예제 #6
0
def test_updating_notifications(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 logged_in(student):
            r1 = add_reply('first reply')
            r2 = add_reply('second reply')
            r3 = add_reply('third reply')
            r4 = add_reply('fourth reply')

    with describe('Can get all notifications'), logged_in(teacher):
        notis = test_client.req('get',
                                '/api/v1/notifications/?has_unread',
                                200,
                                result={'has_unread': True})
        notis = test_client.req(
            'get', '/api/v1/notifications/', 200, {
                'notifications': [
                    {
                        'read': False,
                        '__allow_extra__': True,
                        'comment_reply': r4,
                    },
                    {
                        'read': False,
                        '__allow_extra__': True,
                        'comment_reply': r3,
                    },
                    {
                        'read': False,
                        '__allow_extra__': True,
                        'comment_reply': r2,
                    },
                    {
                        'read': False,
                        '__allow_extra__': True,
                        'comment_reply': r1,
                    },
                ]
            })['notifications']

    with describe('Can update single notification'), logged_in(teacher):
        noti = notis.pop(0)
        noti = test_client.req('patch',
                               f'/api/v1/notifications/{get_id(noti)}',
                               200,
                               data={
                                   'read': True,
                               })
        notis.append(noti)
        print(notis)
        notis = test_client.req('get', '/api/v1/notifications/', 200,
                                {'notifications': notis})['notifications']

    with describe('Can update bulk notifications'), logged_in(teacher):
        # Update the first two notifications (those sould now become the last
        # two)
        notis += test_client.req(
            'patch',
            f'/api/v1/notifications/',
            200,
            data={
                'notifications': [{
                    'id': n['id'],
                    'read': True,
                } for n in [notis.pop(0), notis.pop(0)]],
            })['notifications']

        notis = test_client.req('get', '/api/v1/notifications/', 200,
                                {'notifications': notis})['notifications']

    with describe('cannot update notifications for deleted replies'
                  ), logged_in(teacher):
        # Make sure the notification we are going to update is that of the
        # deleted reply
        assert notis[0]['comment_reply']['id'] == r1['id']
        r1.delete()

        test_client.req('patch',
                        f'/api/v1/notifications/',
                        404,
                        data={
                            'notifications': [{
                                'id': notis[0]['id'],
                                'read': True
                            }],
                        })
        test_client.req('patch',
                        f'/api/v1/notifications/{notis[0]["id"]}',
                        404,
                        data={'read': True})

    with describe('Cannot update notifications of others'), logged_in(student):
        # Make sure this not the notification of the deleted reply
        assert notis[-1]['comment_reply']['id'] != r1['id']
        test_client.req('patch',
                        f'/api/v1/notifications/',
                        403,
                        data={
                            'notifications': [{
                                'id': notis[-1]['id'],
                                'read': True
                            }],
                        })
        test_client.req('patch',
                        f'/api/v1/notifications/{notis[-1]["id"]}',
                        403,
                        data={'read': True})