Repository: adlnet/adl_lrs
Issue: update doc endpoints can cause 500

ast.literal_eval unable to parse json that has true or false. Since python uses True and False, the literal_eval fails when attempting to turn json strings into objects to merge. 

work around for now is to use string representations of values, for example, 'true' instead of true


Changed files:
lrs/objects/ActivityStateManager.py, lrs/objects/AgentManager.py, lrs/tests/AgentProfileTests.py, lrs/tests/ActivityProfileTests.py, lrs/objects/ActivityProfileManager.py

diff --git a/lrs/objects/ActivityProfileManager.py b/lrs/objects/ActivityProfileManager.py
index d57c51f..0c8e300 100644
--- a/lrs/objects/ActivityProfileManager.py
+++ b/lrs/objects/ActivityProfileManager.py
@@ -32,8 +32,8 @@ class ActivityProfileManager():
                 p.updated = datetime.datetime.utcnow().replace(tzinfo=utc)
         else:
             etag.check_preconditions(request_dict,p, required=True)
-            orig_prof = ast.literal_eval(p.json_profile)
-            post_profile = ast.literal_eval(post_profile)
+            orig_prof = json.loads(p.json_profile)
+            post_profile = json.loads(post_profile)
             if not isinstance(post_profile, dict):
                 raise ParamError("The document was not able to be parsed into a JSON object.")
             else:
diff --git a/lrs/objects/ActivityStateManager.py b/lrs/objects/ActivityStateManager.py
index ec15746..78e07c4 100644
--- a/lrs/objects/ActivityStateManager.py
+++ b/lrs/objects/ActivityStateManager.py
@@ -46,8 +46,8 @@ class ActivityStateManager():
             if self.updated:
                 p.updated = self.updated
         else:
-            orig_state = ast.literal_eval(p.json_state)
-            post_state = ast.literal_eval(post_state)
+            orig_state = json.loads(p.json_state)
+            post_state = json.loads(post_state)
             if not isinstance(post_state, dict):
                 raise ParamError("The document was not able to be parsed into a JSON object.")
             else:
diff --git a/lrs/objects/AgentManager.py b/lrs/objects/AgentManager.py
index b3c439c..50d6a68 100644
--- a/lrs/objects/AgentManager.py
+++ b/lrs/objects/AgentManager.py
@@ -61,8 +61,8 @@ class AgentManager():
                 p.updated = datetime.datetime.utcnow().replace(tzinfo=utc)
         else:
             etag.check_preconditions(request_dict,p, required=True)
-            orig_prof = ast.literal_eval(p.json_profile)
-            post_profile = ast.literal_eval(post_profile)
+            orig_prof = json.loads(p.json_profile)
+            post_profile = json.loads(post_profile)
             if not isinstance(post_profile, dict):
                 raise ParamError("The document was not able to be parsed into a JSON object.")
             else:
diff --git a/lrs/tests/ActivityProfileTests.py b/lrs/tests/ActivityProfileTests.py
index 723f669..ab9bee7 100644
--- a/lrs/tests/ActivityProfileTests.py
+++ b/lrs/tests/ActivityProfileTests.py
@@ -396,3 +396,38 @@ class ActivityProfileTests(TestCase):
         
         theget = self.client.get(path, Authorization=self.auth, X_Experience_API_Version="1.0")
         self.assertEqual(theget['ETag'], '"d4827d99a5cc3510d3847baa341ba5a3b477fdfc"')
+
+    def test_json_merge(self):
+        prof = '{"test": { "goal": "ensure proper json parse", "attempt": 1, "result": null } }'
+
+        params = {"profileId": "prof:test_json_merge", "activityId": "act:test.json.merge.prof"}
+        path = '%s?%s' % (reverse(views.activity_profile), urllib.urlencode(params))
+        
+        post = self.client.post(path, prof, content_type="application/json", If_None_Match='*', Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        self.assertEqual(post.status_code, 204)
+        
+        get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        self.assertEqual(get.status_code, 200)
+        returned = json.loads(get.content)
+        sent = json.loads(prof)
+        self.assertEqual(returned['test']['goal'], sent['test']['goal'])
+        self.assertEqual(returned['test']['attempt'], sent['test']['attempt'])
+        self.assertEqual(returned['test']['result'], sent['test']['result'])
+        etag = '"%s"' % hashlib.sha1(get.content).hexdigest()
+        self.assertEqual(get.get('etag'), etag)
+
+        sent['test']['result'] = True
+        sent['test']['attempt'] = sent['test']['attempt'] + 1
+        prof = json.dumps(sent)
+        post = self.client.post(path, prof, content_type="application/json", If_Match=etag, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        self.assertEqual(post.status_code, 204)
+
+        get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        self.assertEqual(get.status_code, 200)
+        returned = json.loads(get.content)
+        sent = json.loads(prof)
+        self.assertEqual(returned['test']['goal'], sent['test']['goal'])
+        self.assertEqual(returned['test']['attempt'], sent['test']['attempt'])
+        self.assertEqual(returned['test']['result'], sent['test']['result'])
+        etag = '"%s"' % hashlib.sha1(get.content).hexdigest()
+        self.assertEqual(get.get('etag'), etag)
diff --git a/lrs/tests/AgentProfileTests.py b/lrs/tests/AgentProfileTests.py
index 613ee71..80bac65 100644
--- a/lrs/tests/AgentProfileTests.py
+++ b/lrs/tests/AgentProfileTests.py
@@ -342,12 +342,12 @@ class AgentProfileTests(TestCase):
         path = '%s?%s' % (reverse(views.agent_profile), urllib.urlencode(params))
         prof = {"test":"post updated profile","obj":{"agent":"mailto:test@example.com"}}
         
-        post = self.client.post(path, prof, content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        post = self.client.post(path, json.dumps(prof), content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(post.status_code, 204)
         
         get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(get.status_code, 200)
-        self.assertEqual(ast.literal_eval(get.content), prof)
+        self.assertEqual(json.loads(get.content), prof)
         etag = '"%s"' % hashlib.sha1(get.content).hexdigest()
         self.assertEqual(get.get('etag'), etag)
 
@@ -355,15 +355,15 @@ class AgentProfileTests(TestCase):
         path = '%s?%s' % (reverse(views.agent_profile), urllib.urlencode(params))
         prof = {"obj":{"agent":"mailto:test@example.com", "new":"thing"}, "added":"yes"}
 
-        post = self.client.post(path, prof, content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        post = self.client.post(path, json.dumps(prof), content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(post.status_code, 409)
         
-        post = self.client.post(path, prof, content_type="application/json",If_Match=etag, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        post = self.client.post(path, json.dumps(prof), content_type="application/json",If_Match=etag, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(post.status_code, 204)
 
         get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(get.status_code, 200)
-        ret_json = ast.literal_eval(get.content)
+        ret_json = json.loads(get.content)
         self.assertEqual(ret_json['added'], prof['added'])
         self.assertEqual(ret_json['test'], "post updated profile")
         self.assertEqual(ret_json['obj']['agent'], prof['obj']['agent'])
@@ -377,12 +377,12 @@ class AgentProfileTests(TestCase):
         path = '%s?%s' % (reverse(views.agent_profile), urllib.urlencode(params))
         prof = {"test":"post and put profile","obj":{"agent":"mailto:test@example.com"}}
         
-        post = self.client.post(path, prof, content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        post = self.client.post(path, json.dumps(prof), content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(post.status_code, 204)
         
         get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(get.status_code, 200)
-        self.assertEqual(ast.literal_eval(get.content), prof)
+        self.assertEqual(json.loads(get.content), prof)
         self.assertEqual(get.get('etag'), '"%s"' % hashlib.sha1(get.content).hexdigest())
 
         params = {"profileId": "prof:test_post_and_put_profile", "agent": self.testagent}
@@ -390,12 +390,12 @@ class AgentProfileTests(TestCase):
         prof = {"wipe":"new data"}
         thehash = get.get('etag')
         
-        put = self.client.put(path, prof, content_type="application/json", If_Match=thehash, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        put = self.client.put(path, json.dumps(prof), content_type="application/json", If_Match=thehash, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(put.status_code, 204)
         
         get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(get.status_code, 200)
-        self.assertEqual(ast.literal_eval(get.content), prof)
+        self.assertEqual(json.loads(get.content), prof)
         etag = '"%s"' % hashlib.sha1(get.content).hexdigest()
         self.assertEqual(get.get('etag'), etag)
 
@@ -403,15 +403,15 @@ class AgentProfileTests(TestCase):
         path = '%s?%s' % (reverse(views.agent_profile), urllib.urlencode(params))
         prof = {"test":"post updated profile","obj":{"agent":"mailto:test@example.com", "new":"thing"}, "added":"yes"}
         
-        post = self.client.post(path, prof, content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        post = self.client.post(path, json.dumps(prof), content_type="application/json", Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(post.status_code, 409)
 
-        post = self.client.post(path, prof, content_type="application/json", If_Match=etag, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
+        post = self.client.post(path, json.dumps(prof), content_type="application/json", If_Match=etag, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(post.status_code, 204)
 
         get = self.client.get(path, Authorization=self.auth,  X_Experience_API_Version="1.0.0")
         self.assertEqual(get.status_code, 200)
-        ret_json = ast.literal_eval(get.content)
+        ret_json = json.loads(get.content)
         self.assertEqual(ret_json['wipe'], "new data")
         self.assertEqual(ret_json['added'], prof['added'])
         self.assertEqual(ret_json['test'], prof['test'])