Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-proctoring
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
edx-proctoring
Commits
390bae54
Commit
390bae54
authored
Sep 18, 2015
by
Chris Dodge
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add some protection about not transition from a completed state to an error state
parent
da5746f5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
102 additions
and
9 deletions
+102
-9
edx_proctoring/api.py
+22
-0
edx_proctoring/tests/test_api.py
+1
-0
edx_proctoring/tests/test_views.py
+67
-2
edx_proctoring/views.py
+11
-6
setup.py
+1
-1
No files found.
edx_proctoring/api.py
View file @
390bae54
...
...
@@ -552,6 +552,21 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
)
raise
ProctoredExamIllegalStatusTransition
(
err_msg
)
# special case logic, if we are in a completed status we shouldn't allow
# for a transition to 'Error' state
if
in_completed_status
and
to_status
==
ProctoredExamStudentAttemptStatus
.
error
:
err_msg
=
(
'A status transition from {from_status} to {to_status} was attempted '
'on exam_id {exam_id} for user_id {user_id}. This is not '
'allowed!'
.
format
(
from_status
=
exam_attempt_obj
.
status
,
to_status
=
to_status
,
exam_id
=
exam_id
,
user_id
=
user_id
)
)
raise
ProctoredExamIllegalStatusTransition
(
err_msg
)
# OK, state transition is fine, we can proceed
exam_attempt_obj
.
status
=
to_status
exam_attempt_obj
.
save
()
...
...
@@ -591,6 +606,13 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
)
if
cascade_effects
and
ProctoredExamStudentAttemptStatus
.
is_a_cascadable_failure
(
to_status
):
if
to_status
==
ProctoredExamStudentAttemptStatus
.
declined
:
# if user declines attempt, make sure we clear out the external_id and
# taking_as_proctored fields
exam_attempt_obj
.
taking_as_proctored
=
False
exam_attempt_obj
.
external_id
=
None
exam_attempt_obj
.
save
()
# some state transitions (namely to a rejected or declined status)
# will mark other exams as declined because once we fail or decline
# one exam all other (un-completed) proctored exams will be likewise
...
...
edx_proctoring/tests/test_api.py
View file @
390bae54
...
...
@@ -1230,6 +1230,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
(
ProctoredExamStudentAttemptStatus
.
rejected
,
ProctoredExamStudentAttemptStatus
.
started
),
(
ProctoredExamStudentAttemptStatus
.
not_reviewed
,
ProctoredExamStudentAttemptStatus
.
started
),
(
ProctoredExamStudentAttemptStatus
.
error
,
ProctoredExamStudentAttemptStatus
.
started
),
(
ProctoredExamStudentAttemptStatus
.
submitted
,
ProctoredExamStudentAttemptStatus
.
error
),
)
@ddt.unpack
@patch.dict
(
'django.conf.settings.PROCTORING_SETTINGS'
,
{
'ALLOW_TIMED_OUT_STATE'
:
True
})
...
...
edx_proctoring/tests/test_views.py
View file @
390bae54
...
...
@@ -580,7 +580,61 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
response_data
[
'status'
],
'started'
)
self
.
assertEqual
(
response_data
[
'status'
],
ProctoredExamStudentAttemptStatus
.
started
)
attempt_code
=
response_data
[
'attempt_code'
]
# test the polling callback point
response
=
self
.
client
.
get
(
reverse
(
'edx_proctoring.anonymous.proctoring_poll_status'
,
args
=
[
attempt_code
]
)
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# now reset the time to 2 minutes in the future.
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
freeze_time
(
reset_time
):
response
=
self
.
client
.
get
(
reverse
(
'edx_proctoring.proctored_exam.attempt'
,
args
=
[
attempt_id
])
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
response_data
[
'status'
],
ProctoredExamStudentAttemptStatus
.
error
)
def
test_attempt_status_stickiness
(
self
):
"""
Test to confirm that a status timeout error will not alter a completed state
"""
# Create an exam.
proctored_exam
=
ProctoredExam
.
objects
.
create
(
course_id
=
'a/b/c'
,
content_id
=
'test_content'
,
exam_name
=
'Test Exam'
,
external_id
=
'123aXqe3'
,
time_limit_mins
=
90
)
attempt_data
=
{
'exam_id'
:
proctored_exam
.
id
,
'external_id'
:
proctored_exam
.
external_id
,
'start_clock'
:
True
,
}
response
=
self
.
client
.
post
(
reverse
(
'edx_proctoring.proctored_exam.attempt.collection'
),
attempt_data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
attempt_id
=
response_data
[
'exam_attempt_id'
]
self
.
assertEqual
(
attempt_id
,
1
)
response
=
self
.
client
.
get
(
reverse
(
'edx_proctoring.proctored_exam.attempt'
,
args
=
[
attempt_id
])
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
response_data
[
'status'
],
ProctoredExamStudentAttemptStatus
.
started
)
attempt_code
=
response_data
[
'attempt_code'
]
# test the polling callback point
...
...
@@ -592,6 +646,13 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# now switched to a submitted state
update_attempt_status
(
proctored_exam
.
id
,
self
.
user
.
id
,
ProctoredExamStudentAttemptStatus
.
submitted
)
# now reset the time to 2 minutes in the future.
reset_time
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
minutes
=
2
)
with
freeze_time
(
reset_time
):
...
...
@@ -600,7 +661,11 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
response_data
[
'status'
],
'error'
)
# make sure the submitted status is sticky
self
.
assertEqual
(
response_data
[
'status'
],
ProctoredExamStudentAttemptStatus
.
submitted
)
@ddt.data
(
ProctoredExamStudentAttemptStatus
.
created
,
...
...
edx_proctoring/views.py
View file @
390bae54
...
...
@@ -37,6 +37,7 @@ from edx_proctoring.exceptions import (
UserNotFoundException
,
ProctoredExamPermissionDenied
,
StudentExamAttemptDoesNotExistsException
,
ProctoredExamIllegalStatusTransition
,
)
from
edx_proctoring.serializers
import
ProctoredExamSerializer
,
ProctoredExamStudentAttemptSerializer
from
edx_proctoring.models
import
ProctoredExamStudentAttemptStatus
,
ProctoredExamStudentAttempt
...
...
@@ -285,12 +286,16 @@ class StudentProctoredExamAttempt(AuthenticatedAPIView):
last_poll_timestamp
=
attempt
[
'last_poll_timestamp'
]
if
last_poll_timestamp
is
not
None
\
and
(
datetime
.
now
(
pytz
.
UTC
)
-
last_poll_timestamp
)
.
total_seconds
()
>
SOFTWARE_SECURE_CLIENT_TIMEOUT
:
attempt
[
'status'
]
=
'error'
update_attempt_status
(
attempt
[
'proctored_exam'
][
'id'
],
attempt
[
'user'
][
'id'
],
ProctoredExamStudentAttemptStatus
.
error
)
try
:
update_attempt_status
(
attempt
[
'proctored_exam'
][
'id'
],
attempt
[
'user'
][
'id'
],
ProctoredExamStudentAttemptStatus
.
error
)
attempt
[
'status'
]
=
ProctoredExamStudentAttemptStatus
.
error
except
ProctoredExamIllegalStatusTransition
:
# don't transition a completed state to an error state
pass
# add in the computed time remaining as a helper to a client app
time_remaining_seconds
=
get_time_remaining_for_attempt
(
attempt
)
...
...
setup.py
View file @
390bae54
...
...
@@ -34,7 +34,7 @@ def load_requirements(*requirements_paths):
setup
(
name
=
'edx-proctoring'
,
version
=
'0.9.6
b
'
,
version
=
'0.9.6
c
'
,
description
=
'Proctoring subsystem for Open edX'
,
long_description
=
open
(
'README.md'
)
.
read
(),
author
=
'edX'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment