def test_submit_ora_test_data(self):
        """ Test for behavior of the submit step """
        self.cmd.submit_ora_test_data(COURSE_ID, CONFIG_1)

        # User 1 should have a submission, their workflow should be 'done', they should have a staff grade
        # and they should be locked.
        user_1_submission = sub_api.get_submissions(
            student_item(USERNAME_1, self.mock_block.location))[0]
        user_1_workflow = workflow_api.get_workflow_for_submission(
            user_1_submission['uuid'], None)
        assert user_1_workflow['status'] == 'done'
        user_1_assessment = staff_api.get_latest_staff_assessment(
            user_1_submission['uuid'])
        assert user_1_assessment['points_earned'] == 1
        assert user_1_assessment['scorer_id'] == anonymous_user_id(
            STAFF_USER_2)
        assert user_1_assessment['feedback'] == SUBMISSION_CONFIG_1[
            'gradeData']['overallFeedback']
        user_1_lock_owner = SubmissionGradingLock.get_submission_lock(
            user_1_submission['uuid']).owner_id
        assert user_1_lock_owner == anonymous_user_id(STAFF_USER_1)

        # User 2 should have a submission, their workflow should be 'waiting', they should not have a
        # staff grade and they should not be locked
        user_2_submission = sub_api.get_submissions(
            student_item(USERNAME_2, self.mock_block.location))[0]
        user_2_workflow = workflow_api.get_workflow_for_submission(
            user_2_submission['uuid'], None)
        assert user_2_workflow['status'] == 'waiting'
        user_2_assessment = staff_api.get_latest_staff_assessment(
            user_2_submission['uuid'])
        assert user_2_assessment is None
        assert SubmissionGradingLock.get_submission_lock(
            user_2_submission['uuid']) is None
Exemple #2
0
 def test_clear_submission_lock_contested(self):
     # clear_submission_lock blocks you from clearing a lock owned by another user
     assert SubmissionGradingLock.get_submission_lock(
         self.locked_submission_uuid) == self.existing_submission_lock
     with self.assertRaises(SubmissionLockContestedError):
         SubmissionGradingLock.clear_submission_lock(
             self.locked_submission_uuid, self.other_user_id)
Exemple #3
0
 def test_claim_submission_lock_contested(self):
     # Trying to claim a lock when someone else has a lock raises a SubmissionLockContestedError
     assert SubmissionGradingLock.get_submission_lock(
         self.locked_submission_uuid) == self.existing_submission_lock
     with self.assertRaises(SubmissionLockContestedError):
         SubmissionGradingLock.claim_submission_lock(
             self.locked_submission_uuid, self.other_user_id)
Exemple #4
0
 def test_clear_submission_lock(self):
     # clear_submission_lock removes the existing lock
     assert SubmissionGradingLock.get_submission_lock(
         self.locked_submission_uuid) == self.existing_submission_lock
     SubmissionGradingLock.clear_submission_lock(
         self.locked_submission_uuid, self.user_id)
     assert SubmissionGradingLock.get_submission_lock(
         self.locked_submission_uuid) is None
Exemple #5
0
    def test_claim_submission_lock_stale(self):
        # When a submission lock has become inactive (older than TIMEOUT), it can be claimed by a new user
        new_lock = SubmissionGradingLock.claim_submission_lock(
            self.expired_locked_submission_uuid, self.other_user_id)

        assert new_lock is not None
        assert SubmissionGradingLock.get_submission_lock(
            self.expired_locked_submission_uuid) == new_lock
Exemple #6
0
    def test_claim_submission_lock(self):
        # Can claim a lock on a submission without an existing lock
        assert SubmissionGradingLock.get_submission_lock(
            self.unlocked_submission_uuid) is None
        new_lock = SubmissionGradingLock.claim_submission_lock(
            self.unlocked_submission_uuid, self.user_id)

        assert new_lock is not None
        assert SubmissionGradingLock.get_submission_lock(
            self.unlocked_submission_uuid) == new_lock
Exemple #7
0
    def test_batch_clear_submission_lock(self):
        # batch_clear_submission_lock removes requested locks that the user owns

        # Create a bunch of submisison IDs
        submission_ids = [uuid4() for _ in range(4)]

        # Create lock owned by user to be cleared
        SubmissionGradingLock.objects.create(
            submission_uuid=submission_ids[0],
            owner_id=self.user_id,
            created_at=datetime.now(tz=timezone.utc),
        )

        # Create a second lock owned by user to be cleared
        SubmissionGradingLock.objects.create(
            submission_uuid=submission_ids[1],
            owner_id=self.user_id,
            created_at=datetime.now(tz=timezone.utc),
        )

        # Create lock owned by someone else to not be cleared
        SubmissionGradingLock.objects.create(
            submission_uuid=submission_ids[2],
            owner_id=self.other_user_id,
            created_at=datetime.now(tz=timezone.utc),
        )

        # Create lock owned by user to not be cleared
        SubmissionGradingLock.objects.create(
            submission_uuid=submission_ids[3],
            owner_id=self.user_id,
            created_at=datetime.now(tz=timezone.utc),
        )

        submission_ids_to_clear = [
            str(submission) for submission in submission_ids[0:3]
        ]
        count_cleared = SubmissionGradingLock.batch_clear_submission_locks(
            submission_ids_to_clear, self.user_id)

        # API returns the number of records cleared
        assert count_cleared == 2

        # Assert that the requested and allowed locks got cleared and returned
        for submission_id in submission_ids_to_clear[0:2]:
            assert SubmissionGradingLock.get_submission_lock(
                submission_id) is None

        # Assert that the remaining locks are untouched
        for submission_id in submission_ids_to_clear[2:4]:
            assert SubmissionGradingLock.get_submission_lock(
                submission_id) is not None
Exemple #8
0
    def setUp(self):
        super().setUp()

        # create a test lock for test_submission_id by test_user_id_1
        self.test_submission_lock = SubmissionGradingLock.claim_submission_lock(
            self.test_submission_id,
            self.test_user_id_1,
        )
Exemple #9
0
    def delete_submission_lock(self, submission_uuid, data, suffix=''):  # pylint: disable=unused-argument
        """
        Attempt to clear a submission lock.

        Returns:
        - Serialized submission lock info (which in this case would just be {'lock_status': 'unlocked'})

        Raises:
        - 403 in the case of a contested lock
        """
        anonymous_user_id = self.get_anonymous_user_id_from_xmodule_runtime()
        context = {'user_id': anonymous_user_id}

        try:
            SubmissionGradingLock.clear_submission_lock(
                submission_uuid, anonymous_user_id)
            return SubmissionLockSerializer({}, context=context).data
        except SubmissionLockContestedError as err:
            raise JsonHandlerError(403, err.get_error_code()) from err
Exemple #10
0
    def check_submission_lock(self, submission_uuid, data, suffix=""):  # pylint: disable=unused-argument
        """
        Get info about a submission lock. Does not verify that the ID is a valid submission.

        Returns:
        - Serialized submission lock info.
        """
        anonymous_user_id = self.get_anonymous_user_id_from_xmodule_runtime()
        context = {'user_id': anonymous_user_id}

        submission_lock = SubmissionGradingLock.get_submission_lock(
            submission_uuid) or {}
        return SubmissionLockSerializer(submission_lock, context=context).data
Exemple #11
0
    def _bulk_fetch_annotated_staff_workflows(self, is_team_assignment=False):
        """
        Returns: QuerySet of StaffWorkflows, filtered by the current course and item, with the following annotations:
         - current_lock_user: The "owner_id" of the most recent active (created less than TIME_LIMIT ago) lock
         - grading_status: one of
                              * "graded"   - the StaffWorkflow has an associated Assessment
                              * "ungraded" - the StaffWorkflow has no asociated Assessment
        - lock_status: one of
                              * "in-progress" - current_lock_user is the current user's anonymous id.
                                                The current user has an active lock on this submission.
                              * "locked"      - current_lock_user is non-null and not the current user's anonymous id.
                                                Another user has an active lock on this submission.
                              * "unlocked"    - current_lock_user is null
                                                There is no active lock on this submission.
        """
        # Create an unevaluated QuerySet of "active" SubmissionLock objects that refer to the same submission as the
        # "current" workflow
        student_item_dict = self.get_student_item_dict()

        # Return TeamStaffWorkflows for teams, StaffWorkflows for individual
        if is_team_assignment:
            workflow_type = TeamStaffWorkflow
            identifying_uuid = 'team_submission_uuid'
        else:
            workflow_type = StaffWorkflow
            identifying_uuid = 'submission_uuid'

        newest_lock = SubmissionGradingLock.currently_active().filter(
            submission_uuid=OuterRef(identifying_uuid)).order_by('-created_at')

        staff_workflows = workflow_type.objects.filter(
            course_id=student_item_dict['course_id'],
            item_id=student_item_dict['item_id'],
            cancelled_at=None,
        ).annotate(current_lock_user=Subquery(
            newest_lock.values('owner_id')), ).annotate(
                grading_status=Case(When(assessment__isnull=False,
                                         then=Value("graded",
                                                    output_field=CharField())),
                                    default=Value("ungraded",
                                                  output_field=CharField())),
                lock_status=Case(When(
                    current_lock_user=student_item_dict['student_id'],
                    then=Value("in-progress", output_field=CharField())),
                                 When(current_lock_user__isnull=False,
                                      then=Value("locked",
                                                 output_field=CharField())),
                                 default=Value("unlocked",
                                               output_field=CharField())))
        return staff_workflows
Exemple #12
0
    def batch_delete_submission_lock(self, data, suffix=''):  # pylint: disable=unused-argument
        """
        Given a list of submission UUIDs, clear those that we currently have locks for.

        Returns: None, no errors is implicit success

        Raises:
        - 400 in the case of bad params/data
        - 500 for generic errors
        """
        submission_uuids = data.get("submission_uuids")
        if not isinstance(submission_uuids, list):
            raise JsonHandlerError(
                400, "Body must contain a submission_uuids list")

        anonymous_user_id = self.get_anonymous_user_id_from_xmodule_runtime()
        if not anonymous_user_id:
            raise JsonHandlerError(500, "Failed to get anonymous user ID")

        try:
            SubmissionGradingLock.batch_clear_submission_locks(
                submission_uuids, anonymous_user_id)
        except Exception as err:
            raise JsonHandlerError(500, str(err)) from err
Exemple #13
0
    def claim_submission_lock(self, submission_uuid, data, suffix=''):  # pylint: disable=unused-argument
        """
        Attempt to claim or reclaim a submission lock

        Returns:
        - Serialized submission lock info.

        Raises:
        - 403 in the case of a contested lock
        """
        anonymous_user_id = self.get_anonymous_user_id_from_xmodule_runtime()
        context = {'user_id': anonymous_user_id}

        try:
            submission_lock = SubmissionGradingLock.claim_submission_lock(
                submission_uuid, anonymous_user_id)
            return SubmissionLockSerializer(submission_lock,
                                            context=context).data
        except SubmissionLockContestedError as err:
            raise JsonHandlerError(403, err.get_error_code()) from err
    def assert_submission_created(self, user, expected_graded_by,
                                  expected_locked_by):
        submission = sub_api.get_submissions(
            student_item(user, self.mock_block.location))[0]
        workflow = workflow_api.get_workflow_for_submission(
            submission['uuid'], None)
        assessment = staff_api.get_latest_staff_assessment(submission['uuid'])
        lock = SubmissionGradingLock.get_submission_lock(submission['uuid'])

        if expected_graded_by:
            assert workflow['status'] == 'done'
            assert assessment['scorer_id'] == anonymous_user_id(
                expected_graded_by)
        else:
            assert workflow['status'] == 'waiting'
            assert assessment is None

        if expected_locked_by:
            assert lock is not None
            assert lock.owner_id == anonymous_user_id(STAFF_USER_1)
Exemple #15
0
 def test_get_submisison_lock_none(self):
     # Getting info about a non-existing submission lock returns None
     assert SubmissionGradingLock.get_submission_lock(
         self.unlocked_submission_uuid) is None
Exemple #16
0
 def test_get_submission_lock(self):
     # Can get an existing submission lock by submission ID
     assert SubmissionGradingLock.get_submission_lock(
         self.locked_submission_uuid) == self.existing_submission_lock