Adding unit tests for TOSCA parser

Change-Id: Ia2384ab325d02f4cc1bd3c73087a4a0cbfa4d71a
diff --git a/Makefile b/Makefile
index 190d0e1..5be709c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
 help:
 	@echo "tests: Run unit tests (if you're running local, you'll need to have virtual-env activated)"
+	@echo "tosca: Generate tosca definition from core.xproto"
 	@echo "test-call: Send a sample tosca recipe"
-	@echo "sample-tosca: Generate tosca definition from core.xproto"
 
-tests:
+tests: tosca
 	nosetests -s -v --with-id --with-coverage --cover-html --cover-erase --cover-xml --cover-package="grpc_client, tosca"
 
 build:
@@ -15,5 +15,5 @@
 test-call:
 	curl -H "xos-username: xosadmin@opencord.org" -H "xos-password: rk1UYDHZXbu6KVCMkhmV" -X POST --data-binary @test/tosca/test.yaml 127.0.0.1:9200
 
-sample-tosca:
+tosca:
 	xosgenx --target=src/tosca/xtarget/tosca.xtarget --output=src/tosca/custom_types --write-to-file=model --dest-extension=yaml ../xos/xos/core/models/core.xproto
\ No newline at end of file
diff --git a/src/tosca/parser.py b/src/tosca/parser.py
index 22e26a0..029b6ed 100644
--- a/src/tosca/parser.py
+++ b/src/tosca/parser.py
@@ -20,6 +20,7 @@
                         nodetemplate.dependencies_names.append(name)
 
                     # go another level deep, as our requirements can have requirements...
+                    # NOTE do we still need to go deep?
                     for sd_req in v.get("requirements",[]):
                         for (sd_req_k, sd_req_v) in sd_req.items():
                             name = sd_req_v["node"]
@@ -86,7 +87,6 @@
     def _translate_exception(msg):
         readable = []
         for line in msg.splitlines():
-            print line
             if line.strip().startswith('MissingRequiredFieldError'):
                 readable.append(line)
 
@@ -130,16 +130,19 @@
         # dictionary containing the models in the recipe and their template
         self.templates_by_model_name = None
         # list of models ordered by requirements
-        self.ordered_models_name = None
+        self.ordered_models_name = []
         # dictionary containing the saved model
         self.saved_model_by_name = {}
 
         self.ordered_models_template = []
         self.recipe_file = TOSCA_RECIPES_DIR + '/tmp.yaml'
 
+        self.recipe = recipe
+
+    def execute(self):
         try:
             # [] save the recipe to a tmp file
-            self.save_recipe_to_tmp_file(recipe)
+            self.save_recipe_to_tmp_file(self.recipe)
             # [] parse the recipe with TOSCA Parse
             self.template = ToscaTemplate(self.recipe_file)
             # [] get all models in the recipe
diff --git a/src/web_server/main.py b/src/web_server/main.py
index 6995b12..4896a71 100644
--- a/src/web_server/main.py
+++ b/src/web_server/main.py
@@ -21,8 +21,9 @@
         else:
             try:
                 # print request.headers['xos-password']
-                parsed = TOSCA_Parser(request.get_data())
-                response_text = "Created models: %s" % str(parsed.ordered_models_name)
+                parser = TOSCA_Parser(request.get_data())
+                parser.execute()
+                response_text = "Created models: %s" % str(parser.ordered_models_name)
                 return make_response(response_text, 201)
             except Exception, e:
                 return make_response(e.message, 400)
diff --git a/test/test_tosca_parser.py b/test/test_tosca_parser.py
index 6c0a14c..4fb2f2b 100644
--- a/test/test_tosca_parser.py
+++ b/test/test_tosca_parser.py
@@ -1,4 +1,5 @@
 import unittest
+import os
 from tosca.parser import TOSCA_Parser
 
 class TOSCA_Parser_Test(unittest.TestCase):
@@ -16,13 +17,15 @@
                 FakeNode('model1'),
                 FakeNode('model2')
             ]
-            pass
 
 
         res = TOSCA_Parser.get_tosca_models_by_name(FakeTemplate)
         self.assertIsInstance(res['model1'], FakeNode)
         self.assertIsInstance(res['model2'], FakeNode)
 
+        self.assertEqual(res['model1'].name, 'model1')
+        self.assertEqual(res['model2'].name, 'model2')
+
     def test_populate_dependencies(self):
         """
         [TOSCA_Parser] populate_dependencies: if a recipe has dependencies, it should find the ID of the requirements and add it to the model
@@ -49,4 +52,129 @@
         }
 
         model = TOSCA_Parser.populate_dependencies(FakeModel, FakeRecipe.requirements, saved_models)
-        self.assertEqual(model.site_id, 1)
\ No newline at end of file
+        self.assertEqual(model.site_id, 1)
+
+    def test_get_ordered_models_template(self):
+        """
+        [TOSCA_Parser] get_ordered_models_template: Create a list of templates based on topsorted models
+        """
+        ordered_models = ['foo', 'bar']
+
+        templates = {
+            'foo': 'foo_template',
+            'bar': 'bar_template'
+        }
+
+        ordered_templates = TOSCA_Parser.get_ordered_models_template(ordered_models, templates)
+
+        self.assertEqual(ordered_templates[0], 'foo_template')
+        self.assertEqual(ordered_templates[1], 'bar_template')
+
+    def test_topsort_dependencies(self):
+        """
+        [TOSCA_Parser] topsort_dependencies: Create a list of models based on dependencies
+        """
+        class FakeTemplate:
+            def __init__(self, name, deps):
+                self.name = name
+                self.dependencies_names =  deps
+
+
+        templates = {
+            'deps': FakeTemplate('deps', ['main']),
+            'main': FakeTemplate('main', []),
+        }
+
+        sorted = TOSCA_Parser.topsort_dependencies(templates)
+
+        self.assertEqual(sorted[0], 'main')
+        self.assertEqual(sorted[1], 'deps')
+
+    def test_compute_dependencies(self):
+        """
+        [TOSCA_Parser] compute_dependencies: augment the TOSCA nodetemplate with information on requirements (aka related models)
+        """
+
+        parser = TOSCA_Parser('')
+
+        class FakeNode:
+            def __init__(self, name, requirements):
+                self.name = name
+                self.requirements = requirements
+
+        main = FakeNode('main', [])
+        dep = FakeNode('dep', [{'relation': {'node': 'main'}}])
+
+        models_by_name = {
+            'main': main,
+            'dep': dep
+        }
+
+        class FakeTemplate:
+            nodetemplates = [dep, main]
+
+        parser.compute_dependencies(FakeTemplate, models_by_name)
+
+        templates = FakeTemplate.nodetemplates
+        augmented_dep = templates[0]
+        augmented_main = templates[1]
+
+        self.assertIsInstance(augmented_dep.dependencies[0], FakeNode)
+        self.assertEqual(augmented_dep.dependencies[0].name, 'main')
+        self.assertEqual(augmented_dep.dependencies_names[0], 'main')
+
+        self.assertEqual(len(augmented_main.dependencies), 0)
+        self.assertEqual(len(augmented_main.dependencies_names), 0)
+
+    def test_populate_model(self):
+        """
+        [TOSCA_Parser] populate_model: augment the GRPC model with data from TOSCA
+        """
+        class FakeModel:
+            pass
+
+        data = {
+            'name': 'test',
+            'foo': 'bar',
+            'number': 1
+        }
+
+        model = TOSCA_Parser.populate_model(FakeModel, data)
+
+        self.assertEqual(model.name, 'test')
+        self.assertEqual(model.foo, 'bar')
+        self.assertEqual(model.number, 1)
+
+    def test_translate_exception(self):
+        """
+        [TOSCA_Parser] translate_exception: convert a TOSCA Parser exception in a user readable string
+        """
+        e = TOSCA_Parser._translate_exception("Non tosca exception")
+        self.assertEqual(e, "Non tosca exception")
+
+        e = TOSCA_Parser._translate_exception("""        
+MissingRequiredFieldError: some message
+    followed by unreadable
+    and mystic
+        python error
+        starting at line
+            38209834 of some file
+        """)
+        self.assertEqual(e, "MissingRequiredFieldError: some message")
+
+    def test_save_recipe_to_tmp_file(self):
+        """
+        [TOSCA_Parser] save_recipe_to_tmp_file: should save a TOSCA recipe to a tmp file
+        """
+        parser = TOSCA_Parser('')
+        parser.recipe_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_tmp.yaml')
+
+        parser.save_recipe_to_tmp_file('my tosca')
+
+        self.assertTrue(os.path.exists(parser.recipe_file))
+
+        content = open(parser.recipe_file).read()
+
+        self.assertEqual(content, 'my tosca')
+
+        os.remove(parser.recipe_file)
\ No newline at end of file
diff --git a/test/test_tosca_parser_e2e.py b/test/test_tosca_parser_e2e.py
new file mode 100644
index 0000000..78276f2
--- /dev/null
+++ b/test/test_tosca_parser_e2e.py
@@ -0,0 +1,133 @@
+import unittest
+from mock import patch, MagicMock
+from tosca.parser import TOSCA_Parser
+from grpc_client.resources import RESOURCES
+
+class FakeObj:
+    new = None
+    filter = None
+
+class FakeModel:
+    save = None
+    id = 1
+
+class FakeGuiExt:
+    objects = FakeObj
+
+class FakeSite:
+    objects = FakeObj
+
+class FakeUser:
+    objects = FakeObj
+
+mock_resources = {
+    'XOSGuiExtension': FakeGuiExt,
+    'Site': FakeSite,
+    'User': FakeUser
+}
+
+class TOSCA_Parser_E2E(unittest.TestCase):
+
+    @patch.dict(RESOURCES, mock_resources, clear=True)
+    @patch.object(FakeGuiExt.objects, 'filter', MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, 'save')
+    def test_basic_creation(self, mock_save):
+        """
+        [TOSCA_Parser] Should save models defined in a TOSCA recipe
+        """
+        recipe = """
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Persist xos-sample-gui-extension
+
+imports:
+   - custom_types/xosguiextension.yaml
+
+topology_template:
+  node_templates:
+
+    # UI Extension
+    test:
+      type: tosca.nodes.XOSGuiExtension
+      properties:
+        name: test
+        files: /spa/extensions/test/vendor.js, /spa/extensions/test/app.js
+"""
+
+        parser = TOSCA_Parser(recipe)
+
+        parser.execute()
+
+        # checking that the model has been saved
+        mock_save.assert_called()
+
+        self.assertIsNotNone(parser.templates_by_model_name['test'])
+        self.assertEqual(parser.ordered_models_name, ['test'])
+
+        # check that the model was saved with the expected values
+        saved_model = parser.saved_model_by_name['test']
+        self.assertEqual(saved_model.name, 'test')
+        self.assertEqual(saved_model.files, '/spa/extensions/test/vendor.js, /spa/extensions/test/app.js')
+
+    @patch.dict(RESOURCES, mock_resources, clear=True)
+    @patch.object(FakeSite.objects, 'filter', MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeUser.objects, 'filter', MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, 'save')
+    def test_related_models_creation(self, mock_save):
+        """
+        [TOSCA_Parser] Should save related models defined in a TOSCA recipe
+        """
+
+        recipe = """
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Create a new site with one user
+
+imports:
+   - custom_types/user.yaml
+   - custom_types/site.yaml
+
+topology_template:
+  node_templates:
+
+    # Site
+    site_onlab:
+      type: tosca.nodes.Site
+      properties:
+        name: Open Networking Lab
+        site_url: http://onlab.us/
+        hosts_nodes: True
+
+    # User
+    user_test:
+      type: tosca.nodes.User
+      properties:
+        username: test@opencord.org
+        email: test@opencord.org
+        password: mypwd
+        firstname: User
+        lastname: Test
+        is_admin: True
+      requirements:
+        - site:
+            node: site_onlab
+            relationship: tosca.relationships.BelongsToOne
+"""
+
+        parser = TOSCA_Parser(recipe)
+
+        parser.execute()
+
+        self.assertEqual(mock_save.call_count, 2)
+
+        self.assertIsNotNone(parser.templates_by_model_name['site_onlab'])
+        self.assertIsNotNone(parser.templates_by_model_name['user_test'])
+        self.assertEqual(parser.ordered_models_name, ['site_onlab', 'user_test'])
+
+        # check that the model was saved with the expected values
+        saved_site = parser.saved_model_by_name['site_onlab']
+        self.assertEqual(saved_site.name, 'Open Networking Lab')
+
+        saved_user = parser.saved_model_by_name['user_test']
+        self.assertEqual(saved_user.firstname, 'User')
+        self.assertEqual(saved_user.site_id, 1)
\ No newline at end of file