Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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-platform
Commits
f8adfc62
Commit
f8adfc62
authored
Mar 06, 2013
by
Diana Huang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1602 from MITx/fix/will/capa_xmodule_unit_tests
Fix/will/capa xmodule unit tests
parents
cde4cdf8
c7d80a91
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
869 additions
and
165 deletions
+869
-165
common/lib/xmodule/xmodule/capa_module.py
+215
-115
common/lib/xmodule/xmodule/tests/__init__.py
+29
-15
common/lib/xmodule/xmodule/tests/test_capa_module.py
+587
-11
common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+24
-15
common/lib/xmodule/xmodule/tests/test_conditional.py
+6
-3
common/lib/xmodule/xmodule/tests/test_self_assessment.py
+8
-6
No files found.
common/lib/xmodule/xmodule/capa_module.py
View file @
f8adfc62
...
@@ -136,7 +136,7 @@ class CapaModule(XModule):
...
@@ -136,7 +136,7 @@ class CapaModule(XModule):
self
.
close_date
=
self
.
display_due_date
self
.
close_date
=
self
.
display_due_date
max_attempts
=
self
.
metadata
.
get
(
'attempts'
,
None
)
max_attempts
=
self
.
metadata
.
get
(
'attempts'
,
None
)
if
max_attempts
:
if
max_attempts
is
not
None
:
self
.
max_attempts
=
int
(
max_attempts
)
self
.
max_attempts
=
int
(
max_attempts
)
else
:
else
:
self
.
max_attempts
=
None
self
.
max_attempts
=
None
...
@@ -247,123 +247,176 @@ class CapaModule(XModule):
...
@@ -247,123 +247,176 @@ class CapaModule(XModule):
'progress'
:
Progress
.
to_js_status_str
(
self
.
get_progress
())
'progress'
:
Progress
.
to_js_status_str
(
self
.
get_progress
())
})
})
def
get_problem_html
(
self
,
encapsulate
=
True
):
def
check_button_name
(
self
):
'''Return html for the problem. Adds check, reset, save buttons
"""
as necessary based on the problem config and state.'''
Determine the name for the "check" button.
Usually it is just "Check", but if this is the student's
final attempt, change the name to "Final Check"
"""
if
self
.
max_attempts
is
not
None
:
final_check
=
(
self
.
attempts
>=
self
.
max_attempts
-
1
)
else
:
final_check
=
False
try
:
return
"Final Check"
if
final_check
else
"Check"
html
=
self
.
lcp
.
get_html
()
except
Exception
,
err
:
log
.
exception
(
err
)
# TODO (vshnayder): another switch on DEBUG.
def
should_show_check_button
(
self
):
if
self
.
system
.
DEBUG
:
"""
msg
=
(
Return True/False to indicate whether to show the "Check" button.
'[courseware.capa.capa_module] <font size="+1" color="red">'
"""
'Failed to generate HTML for problem
%
s</font>'
%
submitted_without_reset
=
(
self
.
is_completed
()
and
self
.
rerandomize
==
"always"
)
(
self
.
location
.
url
()))
msg
+=
'<p>Error:</p><p><pre>
%
s</pre></p>'
%
str
(
err
)
.
replace
(
'<'
,
'<'
)
# If the problem is closed (past due / too many attempts)
msg
+=
'<p><pre>
%
s</pre></p>'
%
traceback
.
format_exc
()
.
replace
(
'<'
,
'<'
)
# then we do NOT show the "check" button
html
=
msg
# Also, do not show the "check" button if we're waiting
# for the user to reset a randomized problem
if
self
.
closed
()
or
submitted_without_reset
:
return
False
else
:
return
True
def
should_show_reset_button
(
self
):
"""
Return True/False to indicate whether to show the "Reset" button.
"""
is_survey_question
=
(
self
.
max_attempts
==
0
)
if
self
.
rerandomize
in
[
"always"
,
"onreset"
]:
# If the problem is closed (and not a survey question with max_attempts==0),
# then do NOT show the reset button.
# If the problem hasn't been submitted yet, then do NOT show
# the reset button.
if
(
self
.
closed
()
and
not
is_survey_question
)
or
not
self
.
is_completed
():
return
False
else
:
else
:
# We're in non-debug mode, and possibly even in production. We want
return
True
# to avoid bricking of problem as much as possible
# Presumably, student submission has corrupted LoncapaProblem HTML.
# First, pull down all student answers
student_answers
=
self
.
lcp
.
student_answers
answer_ids
=
student_answers
.
keys
()
# Some inputtypes, such as dynamath, have additional "hidden" state that
# is not exposed to the student. Keep those hidden
# TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
hidden_state_keywords
=
[
'dynamath'
]
for
answer_id
in
answer_ids
:
for
hidden_state_keyword
in
hidden_state_keywords
:
if
answer_id
.
find
(
hidden_state_keyword
)
>=
0
:
student_answers
.
pop
(
answer_id
)
# Next, generate a fresh LoncapaProblem
self
.
lcp
=
LoncapaProblem
(
self
.
definition
[
'data'
],
self
.
location
.
html_id
(),
state
=
None
,
# Tabula rasa
seed
=
self
.
seed
,
system
=
self
.
system
)
# Prepend a scary warning to the student
warning
=
'<div class="capa_reset">'
\
'<h2>Warning: The problem has been reset to its initial state!</h2>'
\
'The problem
\'
s state was corrupted by an invalid submission. '
\
'The submission consisted of:'
\
'<ul>'
for
student_answer
in
student_answers
.
values
():
if
student_answer
!=
''
:
warning
+=
'<li>'
+
cgi
.
escape
(
student_answer
)
+
'</li>'
warning
+=
'</ul>'
\
'If this error persists, please contact the course staff.'
\
'</div>'
html
=
warning
try
:
html
+=
self
.
lcp
.
get_html
()
except
Exception
,
err
:
# Couldn't do it. Give up
log
.
exception
(
err
)
raise
content
=
{
'name'
:
self
.
display_name
,
# Only randomized problems need a "reset" button
'html'
:
html
,
else
:
'weight'
:
self
.
descriptor
.
weight
,
return
False
}
# We using strings as truthy values, because the terminology of the
def
should_show_save_button
(
self
):
# check button is context-specific.
"""
Return True/False to indicate whether to show the "Save" button.
"""
# Put a "Check" button if unlimited attempts or still some left
# If the user has forced the save button to display,
if
self
.
max_attempts
is
None
or
self
.
attempts
<
self
.
max_attempts
-
1
:
# then show it as long as the problem is not closed
check_button
=
"Check"
# (past due / too many attempts)
if
self
.
force_save_button
==
"true"
:
return
not
self
.
closed
()
else
:
else
:
# Will be final check so let user know that
is_survey_question
=
(
self
.
max_attempts
==
0
)
check_button
=
"Final Check"
needs_reset
=
self
.
is_completed
()
and
self
.
rerandomize
==
"always"
# If the problem is closed (and not a survey question with max_attempts==0),
# then do NOT show the reset button
# If we're waiting for the user to reset a randomized problem
# then do NOT show the reset button
if
(
self
.
closed
()
and
not
is_survey_question
)
or
needs_reset
:
return
False
else
:
return
True
reset_button
=
True
def
handle_problem_html_error
(
self
,
err
):
save_button
=
True
"""
Change our problem to a dummy problem containing
a warning message to display to users.
# If we're after deadline, or user has exhausted attempts,
Returns the HTML to show to users
# question is read-only.
if
self
.
closed
():
check_button
=
False
reset_button
=
False
save_button
=
False
# If attempts=0 then show just check and reset buttons; this is for survey questions using capa
*err* is the Exception encountered while rendering the problem HTML.
if
self
.
max_attempts
==
0
:
"""
check_button
=
False
log
.
exception
(
err
)
reset_button
=
True
save_button
=
True
# TODO (vshnayder): another switch on DEBUG.
if
self
.
system
.
DEBUG
:
msg
=
(
'[courseware.capa.capa_module] <font size="+1" color="red">'
'Failed to generate HTML for problem
%
s</font>'
%
(
self
.
location
.
url
()))
msg
+=
'<p>Error:</p><p><pre>
%
s</pre></p>'
%
str
(
err
)
.
replace
(
'<'
,
'<'
)
msg
+=
'<p><pre>
%
s</pre></p>'
%
traceback
.
format_exc
()
.
replace
(
'<'
,
'<'
)
html
=
msg
# We're in non-debug mode, and possibly even in production. We want
# to avoid bricking of problem as much as possible
else
:
# User submitted a problem, and hasn't reset. We don't want
# Presumably, student submission has corrupted LoncapaProblem HTML.
# more submissions.
# First, pull down all student answers
if
self
.
lcp
.
done
and
self
.
rerandomize
==
"always"
:
student_answers
=
self
.
lcp
.
student_answers
check_button
=
False
answer_ids
=
student_answers
.
keys
()
save_button
=
False
# Some inputtypes, such as dynamath, have additional "hidden" state that
# is not exposed to the student. Keep those hidden
# TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
hidden_state_keywords
=
[
'dynamath'
]
for
answer_id
in
answer_ids
:
for
hidden_state_keyword
in
hidden_state_keywords
:
if
answer_id
.
find
(
hidden_state_keyword
)
>=
0
:
student_answers
.
pop
(
answer_id
)
# Next, generate a fresh LoncapaProblem
self
.
lcp
=
LoncapaProblem
(
self
.
definition
[
'data'
],
self
.
location
.
html_id
(),
state
=
None
,
# Tabula rasa
seed
=
self
.
seed
,
system
=
self
.
system
)
# Prepend a scary warning to the student
warning
=
'<div class="capa_reset">'
\
'<h2>Warning: The problem has been reset to its initial state!</h2>'
\
'The problem
\'
s state was corrupted by an invalid submission. '
\
'The submission consisted of:'
\
'<ul>'
for
student_answer
in
student_answers
.
values
():
if
student_answer
!=
''
:
warning
+=
'<li>'
+
cgi
.
escape
(
student_answer
)
+
'</li>'
warning
+=
'</ul>'
\
'If this error persists, please contact the course staff.'
\
'</div>'
html
=
warning
try
:
html
+=
self
.
lcp
.
get_html
()
except
Exception
,
err
:
# Couldn't do it. Give up
log
.
exception
(
err
)
raise
# Only show the reset button if pressing it will show different values
return
html
if
self
.
rerandomize
not
in
[
"always"
,
"onreset"
]:
reset_button
=
False
# User hasn't submitted an answer yet -- we don't want resets
if
not
self
.
lcp
.
done
:
reset_button
=
False
# We may not need a "save" button if infinite number of attempts and
def
get_problem_html
(
self
,
encapsulate
=
True
):
# non-randomized. The problem author can force it. It's a bit weird for
'''Return html for the problem. Adds check, reset, save buttons
# randomization to control this; should perhaps be cleaned up.
as necessary based on the problem config and state.'''
if
(
self
.
force_save_button
==
"false"
)
and
(
self
.
max_attempts
is
None
and
self
.
rerandomize
!=
"always"
):
save_button
=
False
try
:
html
=
self
.
lcp
.
get_html
()
# If we cannot construct the problem HTML,
# then generate an error message instead.
except
Exception
,
err
:
html
=
self
.
handle_problem_html_error
(
err
)
# The convention is to pass the name of the check button
# if we want to show a check button, and False otherwise
# This works because non-empty strings evaluate to True
if
self
.
should_show_check_button
():
check_button
=
self
.
check_button_name
()
else
:
check_button
=
False
content
=
{
'name'
:
self
.
display_name
,
'html'
:
html
,
'weight'
:
self
.
descriptor
.
weight
,
}
context
=
{
'problem'
:
content
,
context
=
{
'problem'
:
content
,
'id'
:
self
.
id
,
'id'
:
self
.
id
,
'check_button'
:
check_button
,
'check_button'
:
check_button
,
'reset_button'
:
reset_button
,
'reset_button'
:
self
.
should_show_reset_button
()
,
'save_button'
:
s
ave_button
,
'save_button'
:
s
elf
.
should_show_save_button
()
,
'answer_available'
:
self
.
answer_available
(),
'answer_available'
:
self
.
answer_available
(),
'ajax_url'
:
self
.
system
.
ajax_url
,
'ajax_url'
:
self
.
system
.
ajax_url
,
'attempts_used'
:
self
.
attempts
,
'attempts_used'
:
self
.
attempts
,
...
@@ -419,7 +472,7 @@ class CapaModule(XModule):
...
@@ -419,7 +472,7 @@ class CapaModule(XModule):
def
closed
(
self
):
def
closed
(
self
):
''' Is the student still allowed to submit answers? '''
''' Is the student still allowed to submit answers? '''
if
self
.
attempts
=
=
self
.
max_attempts
:
if
self
.
max_attempts
is
not
None
and
self
.
attempts
>
=
self
.
max_attempts
:
return
True
return
True
if
self
.
is_past_due
():
if
self
.
is_past_due
():
return
True
return
True
...
@@ -528,21 +581,61 @@ class CapaModule(XModule):
...
@@ -528,21 +581,61 @@ class CapaModule(XModule):
def
make_dict_of_responses
(
get
):
def
make_dict_of_responses
(
get
):
'''Make dictionary of student responses (aka "answers")
'''Make dictionary of student responses (aka "answers")
get is POST dictionary.
get is POST dictionary.
The *get* dict has keys of the form 'x_y', which are mapped
to key 'y' in the returned dict. For example,
'input_1_2_3' would be mapped to '1_2_3' in the returned dict.
Some inputs always expect a list in the returned dict
(e.g. checkbox inputs). The convention is that
keys in the *get* dict that end with '[]' will always
have list values in the returned dict.
For example, if the *get* dict contains {'input_1[]': 'test' }
then the output dict would contain {'1': ['test'] }
(the value is a list).
Raises an exception if:
A key in the *get* dictionary does not contain >= 1 underscores
(e.g. "input" is invalid; "input_1" is valid)
Two keys end up with the same name in the returned dict.
(e.g. 'input_1' and 'input_1[]', which both get mapped
to 'input_1' in the returned dict)
'''
'''
answers
=
dict
()
answers
=
dict
()
for
key
in
get
:
for
key
in
get
:
# e.g. input_resistor_1 ==> resistor_1
# e.g. input_resistor_1 ==> resistor_1
_
,
_
,
name
=
key
.
partition
(
'_'
)
_
,
_
,
name
=
key
.
partition
(
'_'
)
#
This allows for answers which require more than one value for
#
If key has no underscores, then partition
#
the same form input (e.g. checkbox inputs). The convention is that
#
will return (key, '', '')
#
if the name ends with '[]' (which looks like an array), then the
#
We detect this and raise an error
# answer will be an array.
if
name
is
''
:
if
not
name
.
endswith
(
'[]'
):
raise
ValueError
(
"
%
s must contain at least one underscore"
%
str
(
key
))
answers
[
name
]
=
get
[
key
]
else
:
else
:
name
=
name
[:
-
2
]
# This allows for answers which require more than one value for
answers
[
name
]
=
get
.
getlist
(
key
)
# the same form input (e.g. checkbox inputs). The convention is that
# if the name ends with '[]' (which looks like an array), then the
# answer will be an array.
is_list_key
=
name
.
endswith
(
'[]'
)
name
=
name
[:
-
2
]
if
is_list_key
else
name
if
is_list_key
:
if
type
(
get
[
key
])
is
list
:
val
=
get
[
key
]
else
:
val
=
[
get
[
key
]]
else
:
val
=
get
[
key
]
# If the name already exists, then we don't want
# to override it. Raise an error instead
if
name
in
answers
:
raise
ValueError
(
"Key
%
s already exists in answers dict"
%
str
(
name
))
else
:
answers
[
name
]
=
val
return
answers
return
answers
...
@@ -550,7 +643,7 @@ class CapaModule(XModule):
...
@@ -550,7 +643,7 @@ class CapaModule(XModule):
''' Checks whether answers to a problem are correct, and
''' Checks whether answers to a problem are correct, and
returns a map of correct/incorrect answers:
returns a map of correct/incorrect answers:
{'success' :
bool
,
{'success' :
'correct' | 'incorrect' | AJAX alert msg string
,
'contents' : html}
'contents' : html}
'''
'''
event_info
=
dict
()
event_info
=
dict
()
...
@@ -609,11 +702,11 @@ class CapaModule(XModule):
...
@@ -609,11 +702,11 @@ class CapaModule(XModule):
# 'success' will always be incorrect
# 'success' will always be incorrect
event_info
[
'correct_map'
]
=
correct_map
.
get_dict
()
event_info
[
'correct_map'
]
=
correct_map
.
get_dict
()
event_info
[
'success'
]
=
success
event_info
[
'success'
]
=
success
event_info
[
'attempts'
]
=
self
.
attempts
event_info
[
'attempts'
]
=
self
.
attempts
self
.
system
.
track_function
(
'save_problem_check'
,
event_info
)
self
.
system
.
track_function
(
'save_problem_check'
,
event_info
)
if
hasattr
(
self
.
system
,
'psychometrics_handler'
):
# update PsychometricsData using callback
if
hasattr
(
self
.
system
,
'psychometrics_handler'
):
# update PsychometricsData using callback
self
.
system
.
psychometrics_handler
(
self
.
get_instance_state
())
self
.
system
.
psychometrics_handler
(
self
.
get_instance_state
())
# render problem into HTML
# render problem into HTML
html
=
self
.
get_problem_html
(
encapsulate
=
False
)
html
=
self
.
get_problem_html
(
encapsulate
=
False
)
...
@@ -663,7 +756,12 @@ class CapaModule(XModule):
...
@@ -663,7 +756,12 @@ class CapaModule(XModule):
''' Changes problem state to unfinished -- removes student answers,
''' Changes problem state to unfinished -- removes student answers,
and causes problem to rerender itself.
and causes problem to rerender itself.
Returns problem html as { 'html' : html-string }.
Returns a dictionary of the form:
{'success': True/False,
'html': Problem HTML string }
If an error occurs, the dictionary will also have an
'error' key containing an error message.
'''
'''
event_info
=
dict
()
event_info
=
dict
()
event_info
[
'old_state'
]
=
self
.
lcp
.
get_state
()
event_info
[
'old_state'
]
=
self
.
lcp
.
get_state
()
...
@@ -686,6 +784,7 @@ class CapaModule(XModule):
...
@@ -686,6 +784,7 @@ class CapaModule(XModule):
# reset random number generator seed (note the self.lcp.get_state()
# reset random number generator seed (note the self.lcp.get_state()
# in next line)
# in next line)
self
.
lcp
.
seed
=
None
self
.
lcp
.
seed
=
None
self
.
lcp
=
LoncapaProblem
(
self
.
definition
[
'data'
],
self
.
lcp
=
LoncapaProblem
(
self
.
definition
[
'data'
],
self
.
location
.
html_id
(),
self
.
lcp
.
get_state
(),
self
.
location
.
html_id
(),
self
.
lcp
.
get_state
(),
...
@@ -694,7 +793,8 @@ class CapaModule(XModule):
...
@@ -694,7 +793,8 @@ class CapaModule(XModule):
event_info
[
'new_state'
]
=
self
.
lcp
.
get_state
()
event_info
[
'new_state'
]
=
self
.
lcp
.
get_state
()
self
.
system
.
track_function
(
'reset_problem'
,
event_info
)
self
.
system
.
track_function
(
'reset_problem'
,
event_info
)
return
{
'html'
:
self
.
get_problem_html
(
encapsulate
=
False
)}
return
{
'success'
:
True
,
'html'
:
self
.
get_problem_html
(
encapsulate
=
False
)}
class
CapaDescriptor
(
RawDescriptor
):
class
CapaDescriptor
(
RawDescriptor
):
...
...
common/lib/xmodule/xmodule/tests/__init__.py
View file @
f8adfc62
...
@@ -28,21 +28,35 @@ open_ended_grading_interface = {
...
@@ -28,21 +28,35 @@ open_ended_grading_interface = {
'grading_controller'
:
'grading_controller'
'grading_controller'
:
'grading_controller'
}
}
test_system
=
ModuleSystem
(
ajax_url
=
'courses/course_id/modx/a_location'
,
def
test_system
():
track_function
=
Mock
(),
"""
get_module
=
Mock
(),
Construct a test ModuleSystem instance.
# "render" to just the context...
render_template
=
lambda
template
,
context
:
str
(
context
),
By default, the render_template() method simply returns
replace_urls
=
Mock
(),
the context it is passed as a string.
user
=
Mock
(
is_staff
=
False
),
You can override this behavior by monkey patching:
filestore
=
Mock
(),
debug
=
True
,
system = test_system()
xqueue
=
{
'interface'
:
None
,
'callback_url'
:
'/'
,
'default_queuename'
:
'testqueue'
,
'waittime'
:
10
},
system.render_template = my_render_func
node_path
=
os
.
environ
.
get
(
"NODE_PATH"
,
"/usr/local/lib/node_modules"
),
anonymous_student_id
=
'student'
,
where my_render_func is a function of the form
open_ended_grading_interface
=
open_ended_grading_interface
my_render_func(template, context)
)
"""
return
ModuleSystem
(
ajax_url
=
'courses/course_id/modx/a_location'
,
track_function
=
Mock
(),
get_module
=
Mock
(),
render_template
=
lambda
template
,
context
:
str
(
context
),
replace_urls
=
lambda
html
:
str
(
html
),
user
=
Mock
(
is_staff
=
False
),
filestore
=
Mock
(),
debug
=
True
,
xqueue
=
{
'interface'
:
None
,
'callback_url'
:
'/'
,
'default_queuename'
:
'testqueue'
,
'waittime'
:
10
},
node_path
=
os
.
environ
.
get
(
"NODE_PATH"
,
"/usr/local/lib/node_modules"
),
anonymous_student_id
=
'student'
,
open_ended_grading_interface
=
open_ended_grading_interface
)
class
ModelsTest
(
unittest
.
TestCase
):
class
ModelsTest
(
unittest
.
TestCase
):
...
...
common/lib/xmodule/xmodule/tests/test_capa_module.py
View file @
f8adfc62
import
datetime
import
datetime
import
json
import
json
from
mock
import
Mock
from
mock
import
Mock
,
MagicMock
,
patch
from
pprint
import
pprint
from
pprint
import
pprint
import
unittest
import
unittest
import
random
import
xmodule
import
capa
from
xmodule.capa_module
import
CapaModule
from
xmodule.capa_module
import
CapaModule
from
xmodule.modulestore
import
Location
from
xmodule.modulestore
import
Location
from
lxml
import
etree
from
lxml
import
etree
...
@@ -34,6 +37,18 @@ class CapaFactory(object):
...
@@ -34,6 +37,18 @@ class CapaFactory(object):
return
CapaFactory
.
num
return
CapaFactory
.
num
@staticmethod
@staticmethod
def
input_key
():
""" Return the input key to use when passing GET parameters """
return
(
"input_"
+
CapaFactory
.
answer_key
())
@staticmethod
def
answer_key
():
""" Return the key stored in the capa problem answer dict """
return
(
"-"
.
join
([
'i4x'
,
'edX'
,
'capa_test'
,
'problem'
,
'SampleProblem
%
d'
%
CapaFactory
.
num
])
+
"_2_1"
)
@staticmethod
def
create
(
graceperiod
=
None
,
def
create
(
graceperiod
=
None
,
due
=
None
,
due
=
None
,
max_attempts
=
None
,
max_attempts
=
None
,
...
@@ -59,11 +74,10 @@ class CapaFactory(object):
...
@@ -59,11 +74,10 @@ class CapaFactory(object):
module.
module.
attempts: also added to instance state. Will be converted to an int.
attempts: also added to instance state. Will be converted to an int.
correct: if True, the problem will be initialized to be answered correctly.
"""
"""
definition
=
{
'data'
:
CapaFactory
.
sample_problem_xml
,
}
definition
=
{
'data'
:
CapaFactory
.
sample_problem_xml
,
}
location
=
Location
([
"i4x"
,
"edX"
,
"capa_test"
,
"problem"
,
location
=
Location
([
"i4x"
,
"edX"
,
"capa_test"
,
"problem"
,
"SampleProblem
{0}"
.
format
(
CapaFactory
.
next_num
()
)])
"SampleProblem
%
d"
%
CapaFactory
.
next_num
(
)])
metadata
=
{}
metadata
=
{}
if
graceperiod
is
not
None
:
if
graceperiod
is
not
None
:
metadata
[
'graceperiod'
]
=
graceperiod
metadata
[
'graceperiod'
]
=
graceperiod
...
@@ -89,19 +103,12 @@ class CapaFactory(object):
...
@@ -89,19 +103,12 @@ class CapaFactory(object):
# since everything else is a string.
# since everything else is a string.
instance_state_dict
[
'attempts'
]
=
int
(
attempts
)
instance_state_dict
[
'attempts'
]
=
int
(
attempts
)
if
correct
:
# TODO: make this actually set an answer of 3.14, and mark it correct
#instance_state_dict['student_answers'] = {}
#instance_state_dict['correct_map'] = {}
pass
if
len
(
instance_state_dict
)
>
0
:
if
len
(
instance_state_dict
)
>
0
:
instance_state
=
json
.
dumps
(
instance_state_dict
)
instance_state
=
json
.
dumps
(
instance_state_dict
)
else
:
else
:
instance_state
=
None
instance_state
=
None
module
=
CapaModule
(
test_system
,
location
,
module
=
CapaModule
(
test_system
()
,
location
,
definition
,
descriptor
,
definition
,
descriptor
,
instance_state
,
None
,
metadata
=
metadata
)
instance_state
,
None
,
metadata
=
metadata
)
...
@@ -282,3 +289,572 @@ class CapaModuleTest(unittest.TestCase):
...
@@ -282,3 +289,572 @@ class CapaModuleTest(unittest.TestCase):
due
=
self
.
yesterday_str
,
due
=
self
.
yesterday_str
,
graceperiod
=
self
.
two_day_delta_str
)
graceperiod
=
self
.
two_day_delta_str
)
self
.
assertTrue
(
still_in_grace
.
answer_available
())
self
.
assertTrue
(
still_in_grace
.
answer_available
())
def
test_closed
(
self
):
# Attempts < Max attempts --> NOT closed
module
=
CapaFactory
.
create
(
max_attempts
=
"1"
,
attempts
=
"0"
)
self
.
assertFalse
(
module
.
closed
())
# Attempts < Max attempts --> NOT closed
module
=
CapaFactory
.
create
(
max_attempts
=
"2"
,
attempts
=
"1"
)
self
.
assertFalse
(
module
.
closed
())
# Attempts = Max attempts --> closed
module
=
CapaFactory
.
create
(
max_attempts
=
"1"
,
attempts
=
"1"
)
self
.
assertTrue
(
module
.
closed
())
# Attempts > Max attempts --> closed
module
=
CapaFactory
.
create
(
max_attempts
=
"1"
,
attempts
=
"2"
)
self
.
assertTrue
(
module
.
closed
())
# Max attempts = 0 --> closed
module
=
CapaFactory
.
create
(
max_attempts
=
"0"
,
attempts
=
"2"
)
self
.
assertTrue
(
module
.
closed
())
# Past due --> closed
module
=
CapaFactory
.
create
(
max_attempts
=
"1"
,
attempts
=
"0"
,
due
=
self
.
yesterday_str
)
self
.
assertTrue
(
module
.
closed
())
def
test_parse_get_params
(
self
):
# Valid GET param dict
valid_get_dict
=
{
'input_1'
:
'test'
,
'input_1_2'
:
'test'
,
'input_1_2_3'
:
'test'
,
'input_[]_3'
:
'test'
,
'input_4'
:
None
,
'input_5'
:
[],
'input_6'
:
5
}
result
=
CapaModule
.
make_dict_of_responses
(
valid_get_dict
)
# Expect that we get a dict with "input" stripped from key names
# and that we get the same values back
for
key
in
result
.
keys
():
original_key
=
"input_"
+
key
self
.
assertTrue
(
original_key
in
valid_get_dict
,
"Output dict should have key
%
s"
%
original_key
)
self
.
assertEqual
(
valid_get_dict
[
original_key
],
result
[
key
])
# Valid GET param dict with list keys
valid_get_dict
=
{
'input_2[]'
:
[
'test1'
,
'test2'
]}
result
=
CapaModule
.
make_dict_of_responses
(
valid_get_dict
)
self
.
assertTrue
(
'2'
in
result
)
self
.
assertEqual
(
valid_get_dict
[
'input_2[]'
],
result
[
'2'
])
# If we use [] at the end of a key name, we should always
# get a list, even if there's just one value
valid_get_dict
=
{
'input_1[]'
:
'test'
}
result
=
CapaModule
.
make_dict_of_responses
(
valid_get_dict
)
self
.
assertEqual
(
result
[
'1'
],
[
'test'
])
# If we have no underscores in the name, then the key is invalid
invalid_get_dict
=
{
'input'
:
'test'
}
with
self
.
assertRaises
(
ValueError
):
result
=
CapaModule
.
make_dict_of_responses
(
invalid_get_dict
)
# Two equivalent names (one list, one non-list)
# One of the values would overwrite the other, so detect this
# and raise an exception
invalid_get_dict
=
{
'input_1[]'
:
'test 1'
,
'input_1'
:
'test 2'
}
with
self
.
assertRaises
(
ValueError
):
result
=
CapaModule
.
make_dict_of_responses
(
invalid_get_dict
)
def
test_check_problem_correct
(
self
):
module
=
CapaFactory
.
create
(
attempts
=
1
)
# Simulate that all answers are marked correct, no matter
# what the input is, by patching CorrectMap.is_correct()
# Also simulate rendering the HTML
with
patch
(
'capa.correctmap.CorrectMap.is_correct'
)
as
mock_is_correct
,
\
patch
(
'xmodule.capa_module.CapaModule.get_problem_html'
)
as
mock_html
:
mock_is_correct
.
return_value
=
True
mock_html
.
return_value
=
"Test HTML"
# Check the problem
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
check_problem
(
get_request_dict
)
# Expect that the problem is marked correct
self
.
assertEqual
(
result
[
'success'
],
'correct'
)
# Expect that we get the (mocked) HTML
self
.
assertEqual
(
result
[
'contents'
],
'Test HTML'
)
# Expect that the number of attempts is incremented by 1
self
.
assertEqual
(
module
.
attempts
,
2
)
def
test_check_problem_incorrect
(
self
):
module
=
CapaFactory
.
create
(
attempts
=
0
)
# Simulate marking the input incorrect
with
patch
(
'capa.correctmap.CorrectMap.is_correct'
)
as
mock_is_correct
:
mock_is_correct
.
return_value
=
False
# Check the problem
get_request_dict
=
{
CapaFactory
.
input_key
():
'0'
}
result
=
module
.
check_problem
(
get_request_dict
)
# Expect that the problem is marked correct
self
.
assertEqual
(
result
[
'success'
],
'incorrect'
)
# Expect that the number of attempts is incremented by 1
self
.
assertEqual
(
module
.
attempts
,
1
)
def
test_check_problem_closed
(
self
):
module
=
CapaFactory
.
create
(
attempts
=
3
)
# Problem closed -- cannot submit
# Simulate that CapaModule.closed() always returns True
with
patch
(
'xmodule.capa_module.CapaModule.closed'
)
as
mock_closed
:
mock_closed
.
return_value
=
True
with
self
.
assertRaises
(
xmodule
.
exceptions
.
NotFoundError
):
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
module
.
check_problem
(
get_request_dict
)
# Expect that number of attempts NOT incremented
self
.
assertEqual
(
module
.
attempts
,
3
)
def
test_check_problem_resubmitted_with_randomize
(
self
):
# Randomize turned on
module
=
CapaFactory
.
create
(
rerandomize
=
'always'
,
attempts
=
0
)
# Simulate that the problem is completed
module
.
lcp
.
done
=
True
# Expect that we cannot submit
with
self
.
assertRaises
(
xmodule
.
exceptions
.
NotFoundError
):
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
module
.
check_problem
(
get_request_dict
)
# Expect that number of attempts NOT incremented
self
.
assertEqual
(
module
.
attempts
,
0
)
def
test_check_problem_resubmitted_no_randomize
(
self
):
# Randomize turned off
module
=
CapaFactory
.
create
(
rerandomize
=
'never'
,
attempts
=
0
)
# Simulate that the problem is completed
module
.
lcp
.
done
=
True
# Expect that we can submit successfully
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
check_problem
(
get_request_dict
)
self
.
assertEqual
(
result
[
'success'
],
'correct'
)
# Expect that number of attempts IS incremented
self
.
assertEqual
(
module
.
attempts
,
1
)
def
test_check_problem_queued
(
self
):
module
=
CapaFactory
.
create
(
attempts
=
1
)
# Simulate that the problem is queued
with
patch
(
'capa.capa_problem.LoncapaProblem.is_queued'
)
\
as
mock_is_queued
,
\
patch
(
'capa.capa_problem.LoncapaProblem.get_recentmost_queuetime'
)
\
as
mock_get_queuetime
:
mock_is_queued
.
return_value
=
True
mock_get_queuetime
.
return_value
=
datetime
.
datetime
.
now
()
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
check_problem
(
get_request_dict
)
# Expect an AJAX alert message in 'success'
self
.
assertTrue
(
'You must wait'
in
result
[
'success'
])
# Expect that the number of attempts is NOT incremented
self
.
assertEqual
(
module
.
attempts
,
1
)
def
test_check_problem_student_input_error
(
self
):
module
=
CapaFactory
.
create
(
attempts
=
1
)
# Simulate a student input exception
with
patch
(
'capa.capa_problem.LoncapaProblem.grade_answers'
)
as
mock_grade
:
mock_grade
.
side_effect
=
capa
.
responsetypes
.
StudentInputError
(
'test error'
)
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
check_problem
(
get_request_dict
)
# Expect an AJAX alert message in 'success'
self
.
assertTrue
(
'test error'
in
result
[
'success'
])
# Expect that the number of attempts is NOT incremented
self
.
assertEqual
(
module
.
attempts
,
1
)
def
test_reset_problem
(
self
):
module
=
CapaFactory
.
create
()
# Mock the module's capa problem
# to simulate that the problem is done
mock_problem
=
MagicMock
(
capa
.
capa_problem
.
LoncapaProblem
)
mock_problem
.
done
=
True
module
.
lcp
=
mock_problem
# Stub out HTML rendering
with
patch
(
'xmodule.capa_module.CapaModule.get_problem_html'
)
as
mock_html
:
mock_html
.
return_value
=
"<div>Test HTML</div>"
# Reset the problem
get_request_dict
=
{}
result
=
module
.
reset_problem
(
get_request_dict
)
# Expect that the request was successful
self
.
assertTrue
(
'success'
in
result
and
result
[
'success'
])
# Expect that the problem HTML is retrieved
self
.
assertTrue
(
'html'
in
result
)
self
.
assertEqual
(
result
[
'html'
],
"<div>Test HTML</div>"
)
# Expect that the problem was reset
mock_problem
.
do_reset
.
assert_called_once_with
()
def
test_reset_problem_closed
(
self
):
module
=
CapaFactory
.
create
()
# Simulate that the problem is closed
with
patch
(
'xmodule.capa_module.CapaModule.closed'
)
as
mock_closed
:
mock_closed
.
return_value
=
True
# Try to reset the problem
get_request_dict
=
{}
result
=
module
.
reset_problem
(
get_request_dict
)
# Expect that the problem was NOT reset
self
.
assertTrue
(
'success'
in
result
and
not
result
[
'success'
])
def
test_reset_problem_not_done
(
self
):
module
=
CapaFactory
.
create
()
# Simulate that the problem is NOT done
module
.
lcp
.
done
=
False
# Try to reset the problem
get_request_dict
=
{}
result
=
module
.
reset_problem
(
get_request_dict
)
# Expect that the problem was NOT reset
self
.
assertTrue
(
'success'
in
result
and
not
result
[
'success'
])
def
test_save_problem
(
self
):
module
=
CapaFactory
.
create
()
# Simulate that the problem is not done (not attempted or reset)
module
.
lcp
.
done
=
False
# Save the problem
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
save_problem
(
get_request_dict
)
# Expect that answers are saved to the problem
expected_answers
=
{
CapaFactory
.
answer_key
():
'3.14'
}
self
.
assertEqual
(
module
.
lcp
.
student_answers
,
expected_answers
)
# Expect that the result is success
self
.
assertTrue
(
'success'
in
result
and
result
[
'success'
])
def
test_save_problem_closed
(
self
):
module
=
CapaFactory
.
create
()
# Simulate that the problem is NOT done (not attempted or reset)
module
.
lcp
.
done
=
False
# Simulate that the problem is closed
with
patch
(
'xmodule.capa_module.CapaModule.closed'
)
as
mock_closed
:
mock_closed
.
return_value
=
True
# Try to save the problem
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
save_problem
(
get_request_dict
)
# Expect that the result is failure
self
.
assertTrue
(
'success'
in
result
and
not
result
[
'success'
])
def
test_save_problem_submitted_with_randomize
(
self
):
module
=
CapaFactory
.
create
(
rerandomize
=
'always'
)
# Simulate that the problem is completed
module
.
lcp
.
done
=
True
# Try to save
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
save_problem
(
get_request_dict
)
# Expect that we cannot save
self
.
assertTrue
(
'success'
in
result
and
not
result
[
'success'
])
def
test_save_problem_submitted_no_randomize
(
self
):
module
=
CapaFactory
.
create
(
rerandomize
=
'never'
)
# Simulate that the problem is completed
module
.
lcp
.
done
=
True
# Try to save
get_request_dict
=
{
CapaFactory
.
input_key
():
'3.14'
}
result
=
module
.
save_problem
(
get_request_dict
)
# Expect that we succeed
self
.
assertTrue
(
'success'
in
result
and
result
[
'success'
])
def
test_check_button_name
(
self
):
# If last attempt, button name changes to "Final Check"
# Just in case, we also check what happens if we have
# more attempts than allowed.
attempts
=
random
.
randint
(
1
,
10
)
module
=
CapaFactory
.
create
(
attempts
=
attempts
-
1
,
max_attempts
=
attempts
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Final Check"
)
module
=
CapaFactory
.
create
(
attempts
=
attempts
,
max_attempts
=
attempts
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Final Check"
)
module
=
CapaFactory
.
create
(
attempts
=
attempts
+
1
,
max_attempts
=
attempts
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Final Check"
)
# Otherwise, button name is "Check"
module
=
CapaFactory
.
create
(
attempts
=
attempts
-
2
,
max_attempts
=
attempts
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Check"
)
module
=
CapaFactory
.
create
(
attempts
=
attempts
-
3
,
max_attempts
=
attempts
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Check"
)
# If no limit on attempts, then always show "Check"
module
=
CapaFactory
.
create
(
attempts
=
attempts
-
3
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Check"
)
module
=
CapaFactory
.
create
(
attempts
=
0
)
self
.
assertEqual
(
module
.
check_button_name
(),
"Check"
)
def
test_should_show_check_button
(
self
):
attempts
=
random
.
randint
(
1
,
10
)
# If we're after the deadline, do NOT show check button
module
=
CapaFactory
.
create
(
due
=
self
.
yesterday_str
)
self
.
assertFalse
(
module
.
should_show_check_button
())
# If user is out of attempts, do NOT show the check button
module
=
CapaFactory
.
create
(
attempts
=
attempts
,
max_attempts
=
attempts
)
self
.
assertFalse
(
module
.
should_show_check_button
())
# If survey question (max_attempts = 0), do NOT show the check button
module
=
CapaFactory
.
create
(
max_attempts
=
0
)
self
.
assertFalse
(
module
.
should_show_check_button
())
# If user submitted a problem but hasn't reset,
# do NOT show the check button
# Note: we can only reset when rerandomize="always"
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_check_button
())
# Otherwise, DO show the check button
module
=
CapaFactory
.
create
()
self
.
assertTrue
(
module
.
should_show_check_button
())
# If the user has submitted the problem
# and we do NOT have a reset button, then we can show the check button
# Setting rerandomize to "never" ensures that the reset button
# is not shown
module
=
CapaFactory
.
create
(
rerandomize
=
"never"
)
module
.
lcp
.
done
=
True
self
.
assertTrue
(
module
.
should_show_check_button
())
def
test_should_show_reset_button
(
self
):
attempts
=
random
.
randint
(
1
,
10
)
# If we're after the deadline, do NOT show the reset button
module
=
CapaFactory
.
create
(
due
=
self
.
yesterday_str
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_reset_button
())
# If the user is out of attempts, do NOT show the reset button
module
=
CapaFactory
.
create
(
attempts
=
attempts
,
max_attempts
=
attempts
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_reset_button
())
# If we're NOT randomizing, then do NOT show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
"never"
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_reset_button
())
# If the user hasn't submitted an answer yet,
# then do NOT show the reset button
module
=
CapaFactory
.
create
()
module
.
lcp
.
done
=
False
self
.
assertFalse
(
module
.
should_show_reset_button
())
# Otherwise, DO show the reset button
module
=
CapaFactory
.
create
()
module
.
lcp
.
done
=
True
self
.
assertTrue
(
module
.
should_show_reset_button
())
# If survey question for capa (max_attempts = 0),
# DO show the reset button
module
=
CapaFactory
.
create
(
max_attempts
=
0
)
module
.
lcp
.
done
=
True
self
.
assertTrue
(
module
.
should_show_reset_button
())
def
test_should_show_save_button
(
self
):
attempts
=
random
.
randint
(
1
,
10
)
# If we're after the deadline, do NOT show the save button
module
=
CapaFactory
.
create
(
due
=
self
.
yesterday_str
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_save_button
())
# If the user is out of attempts, do NOT show the save button
module
=
CapaFactory
.
create
(
attempts
=
attempts
,
max_attempts
=
attempts
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_save_button
())
# If user submitted a problem but hasn't reset, do NOT show the save button
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_save_button
())
# Otherwise, DO show the save button
module
=
CapaFactory
.
create
()
module
.
lcp
.
done
=
False
self
.
assertTrue
(
module
.
should_show_save_button
())
# If we're not randomizing, then we can re-save
module
=
CapaFactory
.
create
(
rerandomize
=
"never"
)
module
.
lcp
.
done
=
True
self
.
assertTrue
(
module
.
should_show_save_button
())
# If survey question for capa (max_attempts = 0),
# DO show the save button
module
=
CapaFactory
.
create
(
max_attempts
=
0
)
module
.
lcp
.
done
=
False
self
.
assertTrue
(
module
.
should_show_save_button
())
def
test_should_show_save_button_force_save_button
(
self
):
# If we're after the deadline, do NOT show the save button
# even though we're forcing a save
module
=
CapaFactory
.
create
(
due
=
self
.
yesterday_str
,
force_save_button
=
"true"
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_save_button
())
# If the user is out of attempts, do NOT show the save button
attempts
=
random
.
randint
(
1
,
10
)
module
=
CapaFactory
.
create
(
attempts
=
attempts
,
max_attempts
=
attempts
,
force_save_button
=
"true"
)
module
.
lcp
.
done
=
True
self
.
assertFalse
(
module
.
should_show_save_button
())
# Otherwise, if we force the save button,
# then show it even if we would ordinarily
# require a reset first
module
=
CapaFactory
.
create
(
force_save_button
=
"true"
,
rerandomize
=
"always"
)
module
.
lcp
.
done
=
True
self
.
assertTrue
(
module
.
should_show_save_button
())
def
test_get_problem_html
(
self
):
module
=
CapaFactory
.
create
()
# We've tested the show/hide button logic in other tests,
# so here we hard-wire the values
show_check_button
=
bool
(
random
.
randint
(
0
,
1
)
%
2
)
show_reset_button
=
bool
(
random
.
randint
(
0
,
1
)
%
2
)
show_save_button
=
bool
(
random
.
randint
(
0
,
1
)
%
2
)
module
.
should_show_check_button
=
Mock
(
return_value
=
show_check_button
)
module
.
should_show_reset_button
=
Mock
(
return_value
=
show_reset_button
)
module
.
should_show_save_button
=
Mock
(
return_value
=
show_save_button
)
# Mock the system rendering function
module
.
system
.
render_template
=
Mock
(
return_value
=
"<div>Test Template HTML</div>"
)
# Patch the capa problem's HTML rendering
with
patch
(
'capa.capa_problem.LoncapaProblem.get_html'
)
as
mock_html
:
mock_html
.
return_value
=
"<div>Test Problem HTML</div>"
# Render the problem HTML
html
=
module
.
get_problem_html
(
encapsulate
=
False
)
# Also render the problem encapsulated in a <div>
html_encapsulated
=
module
.
get_problem_html
(
encapsulate
=
True
)
# Expect that we get the rendered template back
self
.
assertEqual
(
html
,
"<div>Test Template HTML</div>"
)
# Check the rendering context
render_args
,
_
=
module
.
system
.
render_template
.
call_args
self
.
assertEqual
(
len
(
render_args
),
2
)
template_name
=
render_args
[
0
]
self
.
assertEqual
(
template_name
,
"problem.html"
)
context
=
render_args
[
1
]
self
.
assertEqual
(
context
[
'problem'
][
'html'
],
"<div>Test Problem HTML</div>"
)
self
.
assertEqual
(
bool
(
context
[
'check_button'
]),
show_check_button
)
self
.
assertEqual
(
bool
(
context
[
'reset_button'
]),
show_reset_button
)
self
.
assertEqual
(
bool
(
context
[
'save_button'
]),
show_save_button
)
# Assert that the encapsulated html contains the original html
self
.
assertTrue
(
html
in
html_encapsulated
)
def
test_get_problem_html_error
(
self
):
"""
In production, when an error occurs with the problem HTML
rendering, a "dummy" problem is created with an error
message to display to the user.
"""
module
=
CapaFactory
.
create
()
# Save the original problem so we can compare it later
original_problem
=
module
.
lcp
# Simulate throwing an exception when the capa problem
# is asked to render itself as HTML
module
.
lcp
.
get_html
=
Mock
(
side_effect
=
Exception
(
"Test"
))
# Stub out the test_system rendering function
module
.
system
.
render_template
=
Mock
(
return_value
=
"<div>Test Template HTML</div>"
)
# Turn off DEBUG
module
.
system
.
DEBUG
=
False
# Try to render the module with DEBUG turned off
html
=
module
.
get_problem_html
()
# Check the rendering context
render_args
,
_
=
module
.
system
.
render_template
.
call_args
context
=
render_args
[
1
]
self
.
assertTrue
(
"error"
in
context
[
'problem'
][
'html'
])
# Expect that the module has created a new dummy problem with the error
self
.
assertNotEqual
(
original_problem
,
module
.
lcp
)
common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
View file @
f8adfc62
...
@@ -54,7 +54,8 @@ class OpenEndedChildTest(unittest.TestCase):
...
@@ -54,7 +54,8 @@ class OpenEndedChildTest(unittest.TestCase):
descriptor
=
Mock
()
descriptor
=
Mock
()
def
setUp
(
self
):
def
setUp
(
self
):
self
.
openendedchild
=
OpenEndedChild
(
test_system
,
self
.
location
,
self
.
test_system
=
test_system
()
self
.
openendedchild
=
OpenEndedChild
(
self
.
test_system
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
self
.
static_data
,
self
.
metadata
)
self
.
definition
,
self
.
descriptor
,
self
.
static_data
,
self
.
metadata
)
...
@@ -69,7 +70,7 @@ class OpenEndedChildTest(unittest.TestCase):
...
@@ -69,7 +70,7 @@ class OpenEndedChildTest(unittest.TestCase):
def
test_latest_post_assessment_empty
(
self
):
def
test_latest_post_assessment_empty
(
self
):
answer
=
self
.
openendedchild
.
latest_post_assessment
(
test_system
)
answer
=
self
.
openendedchild
.
latest_post_assessment
(
self
.
test_system
)
self
.
assertEqual
(
answer
,
""
)
self
.
assertEqual
(
answer
,
""
)
...
@@ -106,7 +107,7 @@ class OpenEndedChildTest(unittest.TestCase):
...
@@ -106,7 +107,7 @@ class OpenEndedChildTest(unittest.TestCase):
post_assessment
=
"Post assessment"
post_assessment
=
"Post assessment"
self
.
openendedchild
.
record_latest_post_assessment
(
post_assessment
)
self
.
openendedchild
.
record_latest_post_assessment
(
post_assessment
)
self
.
assertEqual
(
post_assessment
,
self
.
assertEqual
(
post_assessment
,
self
.
openendedchild
.
latest_post_assessment
(
test_system
))
self
.
openendedchild
.
latest_post_assessment
(
self
.
test_system
))
def
test_get_score
(
self
):
def
test_get_score
(
self
):
new_answer
=
"New Answer"
new_answer
=
"New Answer"
...
@@ -125,7 +126,7 @@ class OpenEndedChildTest(unittest.TestCase):
...
@@ -125,7 +126,7 @@ class OpenEndedChildTest(unittest.TestCase):
def
test_reset
(
self
):
def
test_reset
(
self
):
self
.
openendedchild
.
reset
(
test_system
)
self
.
openendedchild
.
reset
(
self
.
test_system
)
state
=
json
.
loads
(
self
.
openendedchild
.
get_instance_state
())
state
=
json
.
loads
(
self
.
openendedchild
.
get_instance_state
())
self
.
assertEqual
(
state
[
'state'
],
OpenEndedChild
.
INITIAL
)
self
.
assertEqual
(
state
[
'state'
],
OpenEndedChild
.
INITIAL
)
...
@@ -182,11 +183,13 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -182,11 +183,13 @@ class OpenEndedModuleTest(unittest.TestCase):
descriptor
=
Mock
()
descriptor
=
Mock
()
def
setUp
(
self
):
def
setUp
(
self
):
test_system
.
location
=
self
.
location
self
.
test_system
=
test_system
()
self
.
test_system
.
location
=
self
.
location
self
.
mock_xqueue
=
MagicMock
()
self
.
mock_xqueue
=
MagicMock
()
self
.
mock_xqueue
.
send_to_queue
.
return_value
=
(
None
,
"Message"
)
self
.
mock_xqueue
.
send_to_queue
.
return_value
=
(
None
,
"Message"
)
test_system
.
xqueue
=
{
'interface'
:
self
.
mock_xqueue
,
'callback_url'
:
'/'
,
'default_queuename'
:
'testqueue'
,
'waittime'
:
1
}
self
.
test_system
.
xqueue
=
{
'interface'
:
self
.
mock_xqueue
,
'callback_url'
:
'/'
,
'default_queuename'
:
'testqueue'
,
'waittime'
:
1
}
self
.
openendedmodule
=
OpenEndedModule
(
test_system
,
self
.
location
,
self
.
openendedmodule
=
OpenEndedModule
(
self
.
test_system
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
self
.
static_data
,
self
.
metadata
)
self
.
definition
,
self
.
descriptor
,
self
.
static_data
,
self
.
metadata
)
def
test_message_post
(
self
):
def
test_message_post
(
self
):
...
@@ -195,7 +198,7 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -195,7 +198,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'grader_id'
:
'1'
,
'grader_id'
:
'1'
,
'score'
:
3
}
'score'
:
3
}
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
student_info
=
{
'anonymous_student_id'
:
test_system
.
anonymous_student_id
,
student_info
=
{
'anonymous_student_id'
:
self
.
test_system
.
anonymous_student_id
,
'submission_time'
:
qtime
}
'submission_time'
:
qtime
}
contents
=
{
contents
=
{
'feedback'
:
get
[
'feedback'
],
'feedback'
:
get
[
'feedback'
],
...
@@ -205,7 +208,7 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -205,7 +208,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'student_info'
:
json
.
dumps
(
student_info
)
'student_info'
:
json
.
dumps
(
student_info
)
}
}
result
=
self
.
openendedmodule
.
message_post
(
get
,
test_system
)
result
=
self
.
openendedmodule
.
message_post
(
get
,
self
.
test_system
)
self
.
assertTrue
(
result
[
'success'
])
self
.
assertTrue
(
result
[
'success'
])
# make sure it's actually sending something we want to the queue
# make sure it's actually sending something we want to the queue
self
.
mock_xqueue
.
send_to_queue
.
assert_called_with
(
body
=
json
.
dumps
(
contents
),
header
=
ANY
)
self
.
mock_xqueue
.
send_to_queue
.
assert_called_with
(
body
=
json
.
dumps
(
contents
),
header
=
ANY
)
...
@@ -216,7 +219,7 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -216,7 +219,7 @@ class OpenEndedModuleTest(unittest.TestCase):
def
test_send_to_grader
(
self
):
def
test_send_to_grader
(
self
):
submission
=
"This is a student submission"
submission
=
"This is a student submission"
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
student_info
=
{
'anonymous_student_id'
:
test_system
.
anonymous_student_id
,
student_info
=
{
'anonymous_student_id'
:
self
.
test_system
.
anonymous_student_id
,
'submission_time'
:
qtime
}
'submission_time'
:
qtime
}
contents
=
self
.
openendedmodule
.
payload
.
copy
()
contents
=
self
.
openendedmodule
.
payload
.
copy
()
contents
.
update
({
contents
.
update
({
...
@@ -224,7 +227,7 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -224,7 +227,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'student_response'
:
submission
,
'student_response'
:
submission
,
'max_score'
:
self
.
max_score
'max_score'
:
self
.
max_score
})
})
result
=
self
.
openendedmodule
.
send_to_grader
(
submission
,
test_system
)
result
=
self
.
openendedmodule
.
send_to_grader
(
submission
,
self
.
test_system
)
self
.
assertTrue
(
result
)
self
.
assertTrue
(
result
)
self
.
mock_xqueue
.
send_to_queue
.
assert_called_with
(
body
=
json
.
dumps
(
contents
),
header
=
ANY
)
self
.
mock_xqueue
.
send_to_queue
.
assert_called_with
(
body
=
json
.
dumps
(
contents
),
header
=
ANY
)
...
@@ -238,7 +241,7 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -238,7 +241,7 @@ class OpenEndedModuleTest(unittest.TestCase):
}
}
get
=
{
'queuekey'
:
"abcd"
,
get
=
{
'queuekey'
:
"abcd"
,
'xqueue_body'
:
score_msg
}
'xqueue_body'
:
score_msg
}
self
.
openendedmodule
.
update_score
(
get
,
test_system
)
self
.
openendedmodule
.
update_score
(
get
,
self
.
test_system
)
def
update_score_single
(
self
):
def
update_score_single
(
self
):
self
.
openendedmodule
.
new_history_entry
(
"New Entry"
)
self
.
openendedmodule
.
new_history_entry
(
"New Entry"
)
...
@@ -261,11 +264,11 @@ class OpenEndedModuleTest(unittest.TestCase):
...
@@ -261,11 +264,11 @@ class OpenEndedModuleTest(unittest.TestCase):
}
}
get
=
{
'queuekey'
:
"abcd"
,
get
=
{
'queuekey'
:
"abcd"
,
'xqueue_body'
:
json
.
dumps
(
score_msg
)}
'xqueue_body'
:
json
.
dumps
(
score_msg
)}
self
.
openendedmodule
.
update_score
(
get
,
test_system
)
self
.
openendedmodule
.
update_score
(
get
,
self
.
test_system
)
def
test_latest_post_assessment
(
self
):
def
test_latest_post_assessment
(
self
):
self
.
update_score_single
()
self
.
update_score_single
()
assessment
=
self
.
openendedmodule
.
latest_post_assessment
(
test_system
)
assessment
=
self
.
openendedmodule
.
latest_post_assessment
(
self
.
test_system
)
self
.
assertFalse
(
assessment
==
''
)
self
.
assertFalse
(
assessment
==
''
)
# check for errors
# check for errors
self
.
assertFalse
(
'errors'
in
assessment
)
self
.
assertFalse
(
'errors'
in
assessment
)
...
@@ -336,7 +339,13 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
...
@@ -336,7 +339,13 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
descriptor
=
Mock
()
descriptor
=
Mock
()
def
setUp
(
self
):
def
setUp
(
self
):
self
.
combinedoe
=
CombinedOpenEndedV1Module
(
test_system
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
static_data
=
self
.
static_data
,
metadata
=
self
.
metadata
)
self
.
test_system
=
test_system
()
self
.
combinedoe
=
CombinedOpenEndedV1Module
(
self
.
test_system
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
static_data
=
self
.
static_data
,
metadata
=
self
.
metadata
)
def
test_get_tag_name
(
self
):
def
test_get_tag_name
(
self
):
name
=
self
.
combinedoe
.
get_tag_name
(
"<t>Tag</t>"
)
name
=
self
.
combinedoe
.
get_tag_name
(
"<t>Tag</t>"
)
...
...
common/lib/xmodule/xmodule/tests/test_conditional.py
View file @
f8adfc62
...
@@ -56,6 +56,9 @@ class ConditionalModuleTest(unittest.TestCase):
...
@@ -56,6 +56,9 @@ class ConditionalModuleTest(unittest.TestCase):
'''Get a dummy system'''
'''Get a dummy system'''
return
DummySystem
(
load_error_modules
)
return
DummySystem
(
load_error_modules
)
def
setUp
(
self
):
self
.
test_system
=
test_system
()
def
get_course
(
self
,
name
):
def
get_course
(
self
,
name
):
"""Get a test course by directory name. If there's more than one, error."""
"""Get a test course by directory name. If there's more than one, error."""
print
"Importing {0}"
.
format
(
name
)
print
"Importing {0}"
.
format
(
name
)
...
@@ -85,14 +88,14 @@ class ConditionalModuleTest(unittest.TestCase):
...
@@ -85,14 +88,14 @@ class ConditionalModuleTest(unittest.TestCase):
location
=
descriptor
.
location
location
=
descriptor
.
location
instance_state
=
instance_states
.
get
(
location
.
category
,
None
)
instance_state
=
instance_states
.
get
(
location
.
category
,
None
)
print
"inner_get_module, location=
%
s, inst_state=
%
s"
%
(
location
,
instance_state
)
print
"inner_get_module, location=
%
s, inst_state=
%
s"
%
(
location
,
instance_state
)
return
descriptor
.
xmodule_constructor
(
test_system
)(
instance_state
,
shared_state
)
return
descriptor
.
xmodule_constructor
(
self
.
test_system
)(
instance_state
,
shared_state
)
location
=
Location
([
"i4x"
,
"edX"
,
"cond_test"
,
"conditional"
,
"condone"
])
location
=
Location
([
"i4x"
,
"edX"
,
"cond_test"
,
"conditional"
,
"condone"
])
def
replace_urls
(
text
,
staticfiles_prefix
=
None
,
replace_prefix
=
'/static/'
,
course_namespace
=
None
):
def
replace_urls
(
text
,
staticfiles_prefix
=
None
,
replace_prefix
=
'/static/'
,
course_namespace
=
None
):
return
text
return
text
test_system
.
replace_urls
=
replace_urls
self
.
test_system
.
replace_urls
=
replace_urls
test_system
.
get_module
=
inner_get_module
self
.
test_system
.
get_module
=
inner_get_module
module
=
inner_get_module
(
location
)
module
=
inner_get_module
(
location
)
print
"module: "
,
module
print
"module: "
,
module
...
...
common/lib/xmodule/xmodule/tests/test_self_assessment.py
View file @
f8adfc62
...
@@ -53,13 +53,13 @@ class SelfAssessmentTest(unittest.TestCase):
...
@@ -53,13 +53,13 @@ class SelfAssessmentTest(unittest.TestCase):
'skip_basic_checks'
:
False
,
'skip_basic_checks'
:
False
,
}
}
self
.
module
=
SelfAssessmentModule
(
test_system
,
self
.
location
,
self
.
module
=
SelfAssessmentModule
(
test_system
()
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
self
.
definition
,
self
.
descriptor
,
static_data
,
static_data
,
state
,
metadata
=
self
.
metadata
)
state
,
metadata
=
self
.
metadata
)
def
test_get_html
(
self
):
def
test_get_html
(
self
):
html
=
self
.
module
.
get_html
(
test_
system
)
html
=
self
.
module
.
get_html
(
self
.
module
.
system
)
self
.
assertTrue
(
"This is sample prompt text"
in
html
)
self
.
assertTrue
(
"This is sample prompt text"
in
html
)
def
test_self_assessment_flow
(
self
):
def
test_self_assessment_flow
(
self
):
...
@@ -82,10 +82,11 @@ class SelfAssessmentTest(unittest.TestCase):
...
@@ -82,10 +82,11 @@ class SelfAssessmentTest(unittest.TestCase):
self
.
assertEqual
(
self
.
module
.
get_score
()[
'score'
],
0
)
self
.
assertEqual
(
self
.
module
.
get_score
()[
'score'
],
0
)
self
.
module
.
save_answer
({
'student_answer'
:
"I am an answer"
},
test_system
)
self
.
module
.
save_answer
({
'student_answer'
:
"I am an answer"
},
self
.
module
.
system
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
ASSESSING
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
ASSESSING
)
self
.
module
.
save_assessment
(
mock_query_dict
,
test_
system
)
self
.
module
.
save_assessment
(
mock_query_dict
,
self
.
module
.
system
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
DONE
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
DONE
)
...
@@ -94,7 +95,8 @@ class SelfAssessmentTest(unittest.TestCase):
...
@@ -94,7 +95,8 @@ class SelfAssessmentTest(unittest.TestCase):
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
INITIAL
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
INITIAL
)
# if we now assess as right, skip the REQUEST_HINT state
# if we now assess as right, skip the REQUEST_HINT state
self
.
module
.
save_answer
({
'student_answer'
:
'answer 4'
},
test_system
)
self
.
module
.
save_answer
({
'student_answer'
:
'answer 4'
},
self
.
module
.
system
)
responses
[
'assessment'
]
=
'1'
responses
[
'assessment'
]
=
'1'
self
.
module
.
save_assessment
(
mock_query_dict
,
test_
system
)
self
.
module
.
save_assessment
(
mock_query_dict
,
self
.
module
.
system
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
DONE
)
self
.
assertEqual
(
self
.
module
.
state
,
self
.
module
.
DONE
)
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