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
edx
edx-proctoring
Commits
f2fd74c3
Commit
f2fd74c3
authored
Jan 04, 2018
by
rabiaiftikhar
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
invalidate certificate when marked proctored exam as suspicious
parent
5199cccf
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
132 additions
and
5 deletions
+132
-5
edx_proctoring/api.py
+16
-0
edx_proctoring/backends/tests/test_software_secure.py
+8
-1
edx_proctoring/management/commands/tests/test_set_attempt_status.py
+3
-1
edx_proctoring/tests/test_api.py
+40
-3
edx_proctoring/tests/test_email.py
+19
-0
edx_proctoring/tests/test_services.py
+46
-0
No files found.
edx_proctoring/api.py
View file @
f2fd74c3
...
...
@@ -936,6 +936,22 @@ def update_attempt_status(exam_id, user_id, to_status,
earned_graded
=
REJECTED_GRADE_OVERRIDE_EARNED
)
certificates_service
=
get_runtime_service
(
'certificates'
)
log
.
info
(
'Invalidating certificate for user_id {user_id} in course {course_id} whose '
'grade dropped below passing threshold due to suspicious proctored exam'
.
format
(
user_id
=
exam_attempt_obj
.
user_id
,
course_id
=
exam
[
'course_id'
]
)
)
# invalidate certificate after overriding subsection grade
certificates_service
.
invalidate_certificate
(
user_id
=
exam_attempt_obj
.
user_id
,
course_key_or_id
=
exam
[
'course_id'
]
)
if
(
to_status
==
ProctoredExamStudentAttemptStatus
.
verified
and
ProctoredExamStudentAttemptStatus
.
needs_grade_override
(
from_status
)):
grades_service
=
get_runtime_service
(
'grades'
)
...
...
edx_proctoring/backends/tests/test_software_secure.py
View file @
f2fd74c3
...
...
@@ -43,7 +43,12 @@ from edx_proctoring.models import (
ProctoredExamStudentAllowance
)
from
edx_proctoring.backends.tests.test_review_payload
import
create_test_review_payload
from
edx_proctoring.tests.test_services
import
MockCreditService
,
MockInstructorService
,
MockGradesService
from
edx_proctoring.tests.test_services
import
(
MockCreditService
,
MockInstructorService
,
MockGradesService
,
MockCertificateService
)
from
edx_proctoring.backends.software_secure
import
SOFTWARE_SECURE_INVALID_CHARS
...
...
@@ -105,6 +110,7 @@ class SoftwareSecureTests(TestCase):
set_runtime_service
(
'credit'
,
MockCreditService
())
set_runtime_service
(
'instructor'
,
MockInstructorService
())
set_runtime_service
(
'grades'
,
MockGradesService
())
set_runtime_service
(
'certificates'
,
MockCertificateService
())
def
tearDown
(
self
):
"""
...
...
@@ -113,6 +119,7 @@ class SoftwareSecureTests(TestCase):
super
(
SoftwareSecureTests
,
self
)
.
tearDown
()
set_runtime_service
(
'credit'
,
None
)
set_runtime_service
(
'grades'
,
None
)
set_runtime_service
(
'certificates'
,
None
)
def
test_provider_instance
(
self
):
"""
...
...
edx_proctoring/management/commands/tests/test_set_attempt_status.py
View file @
f2fd74c3
...
...
@@ -15,7 +15,8 @@ from edx_proctoring.management.commands import set_attempt_status
from
edx_proctoring.models
import
ProctoredExamStudentAttemptStatus
,
ProctoredExamStudentAttempt
from
edx_proctoring.tests.test_services
import
(
MockCreditService
,
MockGradesService
MockGradesService
,
MockCertificateService
)
from
edx_proctoring.runtime
import
set_runtime_service
...
...
@@ -33,6 +34,7 @@ class SetAttemptStatusTests(LoggedInTestCase):
super
(
SetAttemptStatusTests
,
self
)
.
setUp
()
set_runtime_service
(
'credit'
,
MockCreditService
())
set_runtime_service
(
'grades'
,
MockGradesService
())
set_runtime_service
(
'certificates'
,
MockCertificateService
())
self
.
exam_id
=
create_exam
(
course_id
=
'foo'
,
content_id
=
'bar'
,
...
...
edx_proctoring/tests/test_api.py
View file @
f2fd74c3
...
...
@@ -76,7 +76,8 @@ from .test_services import (
MockCreditService
,
MockCreditServiceNone
,
MockCreditServiceWithCourseEndDate
,
MockGradesService
MockGradesService
,
MockCertificateService
)
from
.utils
import
ProctoredExamTestCase
...
...
@@ -88,6 +89,20 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
All tests for the models.py
"""
def
setUp
(
self
):
"""
Initialize
"""
super
(
ProctoredExamApiTests
,
self
)
.
setUp
()
set_runtime_service
(
'certificates'
,
MockCertificateService
())
def
tearDown
(
self
):
"""
When tests are done
"""
super
(
ProctoredExamApiTests
,
self
)
.
tearDown
()
set_runtime_service
(
'certificates'
,
None
)
def
_add_allowance_for_user
(
self
):
"""
creates allowance for user.
...
...
@@ -938,12 +953,14 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
def
test_grade_override
(
self
):
"""
Verify that putting an attempt into the rejected state will also override
the learner's subsection grade for the exam
Verify that putting an attempt into the rejected state will override
the learner's subsection grade for the exam and also invalidate the
learner's certificate
"""
set_runtime_service
(
'grades'
,
MockGradesService
())
grades_service
=
get_runtime_service
(
'grades'
)
certificates_service
=
get_runtime_service
(
'certificates'
)
exam_attempt
=
self
.
_create_started_exam_attempt
()
# Pretend learner answered 5 graded questions in the exam correctly
grades_service
.
init_grade
(
...
...
@@ -975,6 +992,26 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
'earned_graded'
:
0.0
})
# Rejected exam attempt should invalidate learner's certificate
invalid_generated_certificate
=
certificates_service
.
get_invalidated_certificate
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
exam_attempt
.
proctored_exam
.
course_id
)
self
.
assertDictEqual
({
'verify_uuid'
:
invalid_generated_certificate
.
verify_uuid
,
'download_uuid'
:
invalid_generated_certificate
.
download_uuid
,
'download_url'
:
invalid_generated_certificate
.
download_url
,
'grade'
:
invalid_generated_certificate
.
grade
,
'status'
:
invalid_generated_certificate
.
status
},
{
'verify_uuid'
:
''
,
'download_uuid'
:
''
,
'download_url'
:
''
,
'grade'
:
''
,
'status'
:
'unavailable'
})
# The MockGradeService updates the PersistentSubsectionGrade synchronously, but in the real GradesService, this
# would be updated by an asynchronous recalculation celery task.
...
...
edx_proctoring/tests/test_email.py
View file @
f2fd74c3
...
...
@@ -19,6 +19,8 @@ from edx_proctoring.runtime import set_runtime_service, get_runtime_service
from
.test_services
import
(
MockCreditService
,
MockGradesService
,
MockCertificateService
)
from
.utils
import
(
ProctoredExamTestCase
,
...
...
@@ -32,6 +34,23 @@ class ProctoredExamEmailTests(ProctoredExamTestCase):
All tests for proctored exam emails.
"""
def
setUp
(
self
):
"""
Initialize
"""
super
(
ProctoredExamEmailTests
,
self
)
.
setUp
()
set_runtime_service
(
'grades'
,
MockGradesService
())
set_runtime_service
(
'certificates'
,
MockCertificateService
())
def
tearDown
(
self
):
"""
When tests are done
"""
super
(
ProctoredExamEmailTests
,
self
)
.
tearDown
()
set_runtime_service
(
'grades'
,
None
)
set_runtime_service
(
'certificates'
,
None
)
@ddt.data
(
[
ProctoredExamStudentAttemptStatus
.
submitted
,
...
...
edx_proctoring/tests/test_services.py
View file @
f2fd74c3
...
...
@@ -189,6 +189,26 @@ class MockGradeOverride(object):
self
.
earned_graded_override
=
earned_graded
class
MockGeneratedCertificate
(
object
):
"""Fake GeneratedCertificate instance."""
def
__init__
(
self
):
self
.
verify_uuid
=
'test_verify_uuid'
self
.
download_uuid
=
'test_download_uuid'
self
.
download_url
=
'test_download_url'
self
.
grade
=
1.0
self
.
status
=
'downloadable'
def
mock_invalidate
(
self
):
"""
Invalidate Generated Certificate by marking it 'unavailable'.
"""
self
.
verify_uuid
=
''
self
.
download_uuid
=
''
self
.
download_url
=
''
self
.
grade
=
''
self
.
status
=
'unavailable'
class
MockGradesService
(
object
):
"""
Simple mock of the Grades Service
...
...
@@ -239,3 +259,29 @@ class MockGradesService(object):
def
should_override_grade_on_rejected_exam
(
self
,
course_key
):
"""Mock will always return instance variable: rejected_exam_overrides_grade"""
return
self
.
rejected_exam_overrides_grade
class
MockCertificateService
(
object
):
"""
mock Certificate Service
"""
def
__init__
(
self
):
"""
Initialize empty data stores for generated certificate
"""
self
.
generated_certificate
=
{}
def
invalidate_certificate
(
self
,
user_id
,
course_key_or_id
):
"""
Get the generated certificate for key (user_id + course_key) and invalidate certificate
whose grade dropped below passing threshold due to suspicious proctored exam
"""
key
=
str
(
user_id
)
+
str
(
course_key_or_id
)
self
.
generated_certificate
[
key
]
=
MockGeneratedCertificate
()
self
.
generated_certificate
[
key
]
.
mock_invalidate
()
def
get_invalidated_certificate
(
self
,
user_id
,
course_key_or_id
):
"""
Returns invalidated certificate for key (user_id + course_key)
"""
return
self
.
generated_certificate
.
get
(
str
(
user_id
)
+
str
(
course_key_or_id
))
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