diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a786a1b6..4239cb5d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,31 +13,47 @@ Change Log Unreleased ~~~~~~~~~~ + +[7.0.2] +~~~~~~~ + +* Ensure lists of answers in problem_check are properly serialized to JSON so they + can be parsed downstream + + **Note: Old events cannot be updated, the log must be replayed (if possible).** + [7.0.1] +~~~~~~~ * Do not send events for unknown courses [7.0.0] +~~~~~~~ * Multi-question problem_check tracking log statements will now be split into one xAPI statement for each question [6.2.0] +~~~~~~~ * Add support for completion events [6.1.0] +~~~~~~~ * Add support for exam attempts events [6.0.0] +~~~~~~~ * Do not send events for unknown users [5.5.6] +~~~~~~~ * upgrading deprecated `djfernet` with `django-fernet-fields-v2` [5.4.0] +~~~~~~~ * Add support for the ``edx.course.enrollment.mode_changed`` event diff --git a/event_routing_backends/__init__.py b/event_routing_backends/__init__.py index 8bbadc57..91d6684f 100644 --- a/event_routing_backends/__init__.py +++ b/event_routing_backends/__init__.py @@ -2,4 +2,4 @@ Various backends for receiving edX LMS events.. """ -__version__ = '7.0.1' +__version__ = '7.0.2' diff --git a/event_routing_backends/processors/tests/fixtures/current/problem_check(server).list_answers.json b/event_routing_backends/processors/tests/fixtures/current/problem_check(server).list_answers.json new file mode 100644 index 00000000..8f0debb2 --- /dev/null +++ b/event_routing_backends/processors/tests/fixtures/current/problem_check(server).list_answers.json @@ -0,0 +1,187 @@ +{ + "name": "problem_check", + "context": { + "course_id": "course-v1:edX+DemoX+Demo_Course", + "course_user_tags": {}, + "user_id": 6, + "path": "/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4/handler/xmodule_handler/problem_check", + "org_id": "edX", + "enterprise_uuid": "", + "module": { + "display_name": "Multiple Choice Questions", + "usage_key": "block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4" + }, + "asides": {} + }, + "username": "tacotuesday", + "session": "9885dd163f65eed1fe88759c186b4ac3", + "ip": "192.168.1.1", + "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/119.0", + "host": "local.overhang.io", + "referer": "http://local.overhang.io/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@54bb9b142c6c4c22afc62bcb628f0e68?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=Homework", + "accept_language": "en-US,en;q=0.5", + "event": { + "state": { + "seed": 1, + "student_answers": { + "a0effb954cca4759994f1ac9e9434bf4_4_1": [ + "choice_0", + "choice_1", + "choice_2" + ], + "a0effb954cca4759994f1ac9e9434bf4_2_1": "blue", + "a0effb954cca4759994f1ac9e9434bf4_3_1": "choice_2" + }, + "has_saved_answers": false, + "correct_map": { + "a0effb954cca4759994f1ac9e9434bf4_2_1": { + "correctness": "correct", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1": { + "correctness": "correct", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1": { + "correctness": "incorrect", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + } + }, + "input_state": { + "a0effb954cca4759994f1ac9e9434bf4_2_1": {}, + "a0effb954cca4759994f1ac9e9434bf4_3_1": {}, + "a0effb954cca4759994f1ac9e9434bf4_4_1": {}, + "a0effb954cca4759994f1ac9e9434bf4_5_1": {} + }, + "done": true + }, + "problem_id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "answers": { + "a0effb954cca4759994f1ac9e9434bf4_4_1": [ + "choice_0", + "choice_1", + "choice_2" + ], + "a0effb954cca4759994f1ac9e9434bf4_5_1": [ + "choice_0", + "choice_1", + "choice_2", + "choice_3", + "choice_4", + "choice_5" + ], + "a0effb954cca4759994f1ac9e9434bf4_2_1": "blue", + "a0effb954cca4759994f1ac9e9434bf4_3_1": "choice_2" + }, + "grade": 2, + "max_grade": 4, + "correct_map": { + "a0effb954cca4759994f1ac9e9434bf4_2_1": { + "correctness": "correct", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1": { + "correctness": "correct", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1": { + "correctness": "incorrect", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + }, + "a0effb954cca4759994f1ac9e9434bf4_5_1": { + "correctness": "incorrect", + "npoints": null, + "msg": "", + "hint": "", + "hintmode": null, + "queuestate": null, + "answervariable": null + } + }, + "success": "incorrect", + "attempts": 9, + "submission": { + "a0effb954cca4759994f1ac9e9434bf4_4_1": { + "question": "", + "answer": [ + "a piano", + "a tree", + "a guitar" + ], + "response_type": "choiceresponse", + "input_type": "checkboxgroup", + "correct": false, + "variant": "", + "group_label": "" + }, + "a0effb954cca4759994f1ac9e9434bf4_5_1": { + "question": "", + "answer": [ + "Un emprunt \u00e0 l'anglais ou anglicisme", + "Un type de demande", + "Quelque chose de rapide, d'instantan\u00e9", + "Une question structur\u00e9e et pr\u00e9cise", + "Un type de poisson", + "I\"M SWP' \"SDF\"" + ], + "response_type": "choiceresponse", + "input_type": "checkboxgroup", + "correct": false, + "variant": "", + "group_label": "" + }, + "a0effb954cca4759994f1ac9e9434bf4_2_1": { + "question": "", + "answer": "blue", + "response_type": "optionresponse", + "input_type": "optioninput", + "correct": true, + "variant": "", + "group_label": "" + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1": { + "question": "", + "answer": "a chair", + "response_type": "multiplechoiceresponse", + "input_type": "choicegroup", + "correct": true, + "variant": "", + "group_label": "" + } + } + }, + "time": "2023-11-03T16:31:50.023274+00:00", + "event_type": "problem_check", + "event_source": "server", + "page": "x_module" +} diff --git a/event_routing_backends/processors/tests/transformers_test_mixin.py b/event_routing_backends/processors/tests/transformers_test_mixin.py index a5db646e..0ca28e4e 100644 --- a/event_routing_backends/processors/tests/transformers_test_mixin.py +++ b/event_routing_backends/processors/tests/transformers_test_mixin.py @@ -119,6 +119,7 @@ def test_event_transformer(self, event_filename, mocked_uuid4): try: self.compare_events(actual_transformed_event, expected_event) except Exception as e: # pragma: no cover + print("Comparison failed, writing output to test_output for debugging") with open(f"test_output/generated.{event_filename}.json", "w") as actual_transformed_event_file: try: actual_transformed_event_file.write(actual_transformed_event.to_json()) diff --git a/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py b/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py index 3f9d2806..7db335bd 100644 --- a/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py @@ -1,6 +1,8 @@ """ Transformers for problem interaction events. """ +import json + from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result from event_routing_backends.helpers import get_problem_block_id @@ -315,6 +317,15 @@ def get_result(self): response = submission["answer"] correct = submission.get("correct") else: + # The submission key didn't exist until March 2014, prior to that + # there was usually a version of the answer in the "answers" key, + # but it is very flaky (sometimes containing xml, and without the + # appended variant identifier that we get for free in the + # submission. We don't attempt to work around those issues here due + # to how few and how old those events are and how complicated the + # parsing is. Should we ever find it necessary to make a better + # parser for them, Insights had a good effort here: + # https://github.com/openedx/edx-analytics-pipeline/blob/8d96f93/edx/analytics/tasks/insights/answer_dist.py#L260C36-L260C36 response = event_data.get('answers', None) correct = self.get_data('success') == 'correct' @@ -328,7 +339,17 @@ def get_result(self): else: scaled = 0 - return Result( + # Some problems can provide a list of responses answers, but + # the Result type wants a string for "response". So we dump those + # to JSON here to provide a parsable version of the response instead + # of getting the __repr__ of the list, which is what Result will + # generate by default. + if isinstance(response, list): + cls = JSONEncodedResult + else: + cls = Result + + return cls( success=correct, score={ 'min': 0, @@ -455,3 +476,30 @@ def get_result(self): submission = self._get_submission() or {} result.response = submission.get('answer') return result + + +class JSONEncodedResult(Result): + """ + This is a workaround for a TinCan issue where it will coerce a value passed + in for a `response` to str. This breaks our ability to serialize list + responses into JSON, so we override it here. + """ + @property + def response(self): + """Response for Result + + :setter: Tries to JSON dump the list value. + :setter type: list + :rtype: str + """ + return self._response + + @response.setter + def response(self, value): + """ + Ensures the list is serialized as JSON. + """ + if not isinstance(value, list): + raise ValueError(f"JSONEncodedResult only accepts lists, {type(value)} given.") + + self._response = json.dumps(value) diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json index 3fb71d5a..c3fe1111 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json @@ -39,7 +39,7 @@ "objectType": "Activity" }, "result": { - "response": "['a correct answer', 'an incorrect answer']", + "response": "[\"a correct answer\", \"an incorrect answer\"]", "score": { "max": 1, "min": 0, diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).list_answers.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).list_answers.json new file mode 100644 index 00000000..549e99c0 --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).list_answers.json @@ -0,0 +1,340 @@ +[ + { + "id": "3744fa95-27e3-5c44-9573-98db43406f0a", + "result": { + "score": { + "scaled": 0.5, + "raw": 2.0, + "min": 0.0, + "max": 4.0 + }, + "success": false + }, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "objectType": "GroupActivity", + "definition": { + "name": { + "en-US": "Multiple Choice Questions" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "other" + } + }, + "timestamp": "2023-11-03T16:31:50.023274+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "9885dd163f65eed1fe88759c186b4ac3" + } + } + }, + { + "id": "10cf9cbf-1437-5f1e-9d32-b96ee8596ec0", + "result": { + "success": false, + "response": "[\"a piano\", \"a tree\", \"a guitar\"]" + }, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_4_1", + "objectType": "Activity", + "definition": { + "description": { + "en-US": "" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "timestamp": "2023-11-03T16:31:50.023274+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "objectType": "GroupActivity", + "definition": { + "name": { + "en-US": "Multiple Choice Questions" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "other" + } + } + ], + "grouping": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "statement": { + "objectType": "StatementRef", + "id": "3744fa95-27e3-5c44-9573-98db43406f0a" + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "9885dd163f65eed1fe88759c186b4ac3" + } + } + }, + { + "id": "707a78e0-c017-5a78-9581-df5bcca031c9", + "result": { + "success": false, + "response": "[\"Un emprunt \\u00e0 l'anglais ou anglicisme\", \"Un type de demande\", \"Quelque chose de rapide, d'instantan\\u00e9\", \"Une question structur\\u00e9e et pr\\u00e9cise\", \"Un type de poisson\", \"I\\\"M SWP' \\\"SDF\\\"\"]" + }, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_5_1", + "objectType": "Activity", + "definition": { + "description": { + "en-US": "" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "timestamp": "2023-11-03T16:31:50.023274+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "objectType": "GroupActivity", + "definition": { + "name": { + "en-US": "Multiple Choice Questions" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "other" + } + } + ], + "grouping": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "statement": { + "objectType": "StatementRef", + "id": "3744fa95-27e3-5c44-9573-98db43406f0a" + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "9885dd163f65eed1fe88759c186b4ac3" + } + } + }, + { + "id": "7d7147aa-fa7a-56b3-a5e0-965f25b098da", + "result": { + "success": true, + "response": "blue" + }, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_2_1", + "objectType": "Activity", + "definition": { + "description": { + "en-US": "" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "timestamp": "2023-11-03T16:31:50.023274+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "objectType": "GroupActivity", + "definition": { + "name": { + "en-US": "Multiple Choice Questions" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "other" + } + } + ], + "grouping": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "statement": { + "objectType": "StatementRef", + "id": "3744fa95-27e3-5c44-9573-98db43406f0a" + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "9885dd163f65eed1fe88759c186b4ac3" + } + } + }, + { + "id": "a50c26d4-b7f5-50f8-9686-63727d2f0018", + "result": { + "success": true, + "response": "a chair" + }, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_3_1", + "objectType": "Activity", + "definition": { + "description": { + "en-US": "" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "timestamp": "2023-11-03T16:31:50.023274+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "objectType": "GroupActivity", + "definition": { + "name": { + "en-US": "Multiple Choice Questions" + }, + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "other" + } + } + ], + "grouping": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "statement": { + "objectType": "StatementRef", + "id": "3744fa95-27e3-5c44-9573-98db43406f0a" + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "9885dd163f65eed1fe88759c186b4ac3" + } + } + } +] diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json index 5cf96cc3..2d2fc08d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json @@ -39,7 +39,7 @@ "objectType": "Activity" }, "result": { - "response": "['a correct answer', 'an incorrect answer']", + "response": "[\"a correct answer\", \"an incorrect answer\"]", "score": { "max": 0, "min": 0, diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json index 265dd612..747d662d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json @@ -116,7 +116,7 @@ }, { "id": "19ff71b6-3e82-5dda-886d-f98182d61b90", - "result": {"response": "['a piano', 'a guitar']", "success": true}, + "result": {"response": "[\"a piano\", \"a guitar\"]", "success": true}, "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json index c3d7c7be..57317b82 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json @@ -118,7 +118,7 @@ }, { "id": "fd898be3-1b7e-5273-a83f-02cb95cbb91d", - "result": {"response": "['a window']", "success": false}, + "result": {"response": "[\"a window\"]", "success": false}, "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json index 0e9e6319..bbc2eddc 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json @@ -116,7 +116,7 @@ }, { "id": "8a30b6a8-b9a3-5246-8a23-7bed05cfc031", - "result": {"response": "['a piano', 'a guitar']", "success": true}, + "result": {"response": "[\"a piano\", \"a guitar\"]", "success": true}, "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/test_xapi_event_transformers.py b/event_routing_backends/processors/xapi/tests/test_xapi_event_transformers.py new file mode 100644 index 00000000..3ac03db3 --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/test_xapi_event_transformers.py @@ -0,0 +1,27 @@ +""" +Test functions in event transformers not necessarily covered by the fixtures. +""" + +import json + +from django.test import SimpleTestCase + +from event_routing_backends.processors.xapi.event_transformers.problem_interaction_events import JSONEncodedResult + + +class TestXAPIEventTransformers(SimpleTestCase): + """ + These cases are covered by the fixtures, but coverage doesn't think so. + """ + def test_jsonencodedresult_list(self): + test_data = ["foo", "b'ar", 'test"ing', 2] + result = JSONEncodedResult() + result.response = test_data + self.assertEqual(test_data, json.loads(result.response)) + + def test_jsonencodedresult_not_list(self): + test_data = "this is not a list and should fail" + result = JSONEncodedResult() + + with self.assertRaises(ValueError): + result.response = test_data