Commit c967a786 by John Lee

Add a simple unit test for SchoolYourselfReviewXBlock and handle an extra…

Add a simple unit test for SchoolYourselfReviewXBlock and handle an extra (however unlikely) case where inputs have a valid signature but are still malformed somehow
parent f2bc9ed3
...@@ -87,9 +87,19 @@ class SchoolYourselfReviewXBlock(SchoolYourselfXBlock): ...@@ -87,9 +87,19 @@ class SchoolYourselfReviewXBlock(SchoolYourselfXBlock):
We will verify the message to make sure that it is signed and We will verify the message to make sure that it is signed and
that the signature is valid. If everything is good, then we'll that the signature is valid. If everything is good, then we'll
publish a "grade" event for this module. publish a "grade" event for this module.
The actual work is done in handle_grade_json(), and this method
just calls that. This method is just here so that it can be wrapped
by XBlock.json_handler, but the unit test covers the code in
handle_grade_json() to avoid having to wrap everything around a
Request/Response object.
""" """
return self.handle_grade_json(data)
def handle_grade_json(self, data):
if not isinstance(data, dict): if not isinstance(data, dict):
return "bad request" return "bad_request"
mastery = data.get("mastery", None) mastery = data.get("mastery", None)
user_id = data.get("user_id", None) user_id = data.get("user_id", None)
...@@ -104,12 +114,27 @@ class SchoolYourselfReviewXBlock(SchoolYourselfXBlock): ...@@ -104,12 +114,27 @@ class SchoolYourselfReviewXBlock(SchoolYourselfXBlock):
if mastery_level is None: if mastery_level is None:
return "bad_request" return "bad_request"
try:
# The mastery level being passed in should be a number, otherwise
# things later on in this method will choke.
mastery_level = float(mastery_level)
except:
return "bad_request"
# Verify the signature. # Verify the signature.
verifier = hmac.new(str(self.shared_key), user_id) verifier = hmac.new(str(self.shared_key), user_id)
for key in sorted(mastery): for key in sorted(mastery):
verifier.update(key) verifier.update(key)
# Every entry should be a number.
try:
mastery[key] = float(mastery[key])
except:
return "bad_request"
verifier.update("%.2f" % mastery[key]) verifier.update("%.2f" % mastery[key])
# If the signature is invalid, do nothing. # If the signature is invalid, do nothing.
if signature != verifier.hexdigest(): if signature != verifier.hexdigest():
return "invalid_signature" return "invalid_signature"
......
"""This file contains a unit test for the SchoolYourselfReviewXBlock."""
import unittest
from schoolyourself_review import SchoolYourselfReviewXBlock
from mock import Mock
from xblock.fields import ScopeIds
from xblock.field_data import DictFieldData
class FakeXModuleRuntime(object):
"""
Depending on whether we're running in the LMS or in the XBlock
workbench, the "xmodule_runtime" attr may or may not be set (in
the LMS, it's set, and that's what production uses). The only field
we ever look at in our XBlock code is "anonymous_student_id",
so this is a dummy object that holds that.
"""
def __init__(self, anonymous_student_id):
self.anonymous_student_id = anonymous_student_id
class SchoolYourselfReviewXBlockTest(unittest.TestCase):
def setUp(self):
self.mock_runtime = Mock()
self.block = SchoolYourselfReviewXBlock(self.mock_runtime,
DictFieldData({}),
ScopeIds("foo", "bar", "baz", "x"))
# This is a fake shared key and a manually computed signature for use
# in this test.
self.block.shared_key = "key"
self.canned_signature = "f0cc345470c322e0c6f41d541fe2b736"
def test_default_params(self):
self.assertFalse(SchoolYourselfReviewXBlock.has_children)
self.assertTrue(SchoolYourselfReviewXBlock.has_score)
self.assertAlmostEqual(SchoolYourselfReviewXBlock.weight, 1.0)
def test_display_name(self):
"""
Make sure we are correctly overriding the get_display_name() of
the base class.
"""
self.assertEqual(self.block.get_display_name("blah"), "Review: blah")
def test_student_id(self):
self.assertEqual(self.block.get_student_id(), "debug")
self.block.xmodule_runtime = FakeXModuleRuntime("abc123")
self.assertEqual(self.block.get_student_id(), "abc123")
def test_handle_grade_malformed_input(self):
self.block.module_id = "algebra/multiplication"
self.assertEqual(self.block.handle_grade_json("foo"), "bad_request")
self.assertEqual(self.block.handle_grade_json(["foo"]), "bad_request")
self.assertEqual(self.block.handle_grade_json({}), "forbidden")
self.assertEqual(self.block.handle_grade_json(
{"mastery": {"invalid_module_id": 1.0},
"user_id": "foo",
"signature": "asdf"}), "bad_request")
# Make sure we never publish any grades for situations like this.
self.assertEqual(len(self.mock_runtime.publish.method_calls), 0)
def test_handle_grade_malformed_signed_input(self):
"""
This is a test for an unlikely situation where the input is malformed
but the signature is somehow correct. We should at least not start
throwing errors.
"""
self.block.module_id = "algebra/multiplication"
self.assertEqual(self.block.handle_grade_json(
{"mastery": {"algebra/multiplication": "hello"}, # A non-number!
"user_id": "foo",
"signature": self.canned_signature}), "bad_request")
def test_handle_grade(self):
self.block.module_id = "algebra/multiplication"
self.assertEqual(self.block.handle_grade_json(
{"mastery": {"algebra/multiplication": 0.7},
"user_id": "foo",
"signature": "asdf"}), "invalid_signature")
# Invalid signatures should never publish grades.
self.assertEqual(len(self.mock_runtime.publish.method_calls), 0)
self.assertEqual(self.block.handle_grade_json(
{"mastery": {"algebra/multiplication": 0.7},
"user_id": "foo",
"signature": self.canned_signature}), 1.0)
self.mock_runtime.publish.assert_called_with(self.block, "grade",
{ "value": 1.0,
"max_value": 1.0 })
if __name__ == "__main__":
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment