SEBA-463 LoadModels unit test

Change-Id: I66f411f6cc5c2a81abf37c9604f05f9f31c972a1
diff --git a/VERSION b/VERSION
index c95a9d4..4739442 100644
--- a/VERSION
+++ b/VERSION
@@ -1,2 +1,2 @@
-2.1.44
+2.1.45
 
diff --git a/containers/chameleon/Dockerfile.chameleon b/containers/chameleon/Dockerfile.chameleon
index 05df0d9..300ee40 100644
--- a/containers/chameleon/Dockerfile.chameleon
+++ b/containers/chameleon/Dockerfile.chameleon
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/chameleon
-FROM xosproject/xos-base:2.1.44
+FROM xosproject/xos-base:2.1.45
 
 # xos-base already has protoc and dependencies installed
 
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index bfc7649..b3793ca 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-client
-FROM xosproject/xos-libraries:2.1.44
+FROM xosproject/xos-libraries:2.1.45
 
 # Install XOS client
 COPY lib/xos-api /tmp/xos-api
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index ae8000c..67a7948 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-libraries
-FROM xosproject/xos-base:2.1.44
+FROM xosproject/xos-base:2.1.45
 
 # Add libraries
 COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index b0f2da1..738c070 100644
--- a/containers/xos/Dockerfile.synchronizer-base
+++ b/containers/xos/Dockerfile.synchronizer-base
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-synchronizer-base
-FROM xosproject/xos-client:2.1.44
+FROM xosproject/xos-client:2.1.45
 
 COPY xos/synchronizers/new_base /opt/xos/synchronizers/new_base
 COPY xos/xos/logger.py /opt/xos/xos/logger.py
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index 632347f..0ccfc89 100644
--- a/containers/xos/Dockerfile.xos-core
+++ b/containers/xos/Dockerfile.xos-core
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-core
-FROM xosproject/xos-libraries:2.1.44
+FROM xosproject/xos-libraries:2.1.45
 
 # Install XOS
 ADD xos /opt/xos
diff --git a/lib/xos-synchronizer/xos-synchronizer-tests/test_loadmodels.py b/lib/xos-synchronizer/xos-synchronizer-tests/test_loadmodels.py
new file mode 100644
index 0000000..6353104
--- /dev/null
+++ b/lib/xos-synchronizer/xos-synchronizer-tests/test_loadmodels.py
@@ -0,0 +1,135 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+from mock import patch, Mock, MagicMock
+
+from io import StringIO
+import functools
+import os
+import sys
+
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+
+def mock_listdir(dir_map, dir):
+    """ mock os.listdir() """
+    return dir_map.get(dir, [])
+
+
+def mock_exists(file_map, fn):
+    """ mock os.path.exists() """
+    return (fn in file_map)
+
+
+def mock_open(orig_open, file_map, fn, *args, **kwargs):
+    """ mock file open() """
+    if fn in file_map:
+        return StringIO(file_map[fn])
+    else:
+        return orig_open(fn, *args, **kwargs)
+
+
+class ItemList(object):
+    """ mock the various items within a LoadModelsRequest protobuf """
+
+    def __init__(self):
+        self.items = []
+
+    def add(self):
+        item = Mock()
+        self.items.append(item)
+        return item
+
+
+class MockLoadModelsRequest(object):
+    """ mock a LoadModelsRequest protobuf """
+
+    def __init__(self, *args, **kwargs):
+        for (k, v) in kwargs.items():
+            setattr(self, k, v)
+        self.xprotos = ItemList()
+        self.decls = ItemList()
+        self.attics = ItemList()
+        self.convenience_methods = ItemList()
+        self.migrations = ItemList()
+
+
+class TestLoadModels(unittest.TestCase):
+    def setUp(self):
+        self.sys_path_save = sys.path
+        self.cwd_save = os.getcwd()
+
+        config = os.path.join(test_path, "test_config.yaml")
+        from xosconfig import Config
+
+        Config.clear()
+        Config.init(config, "synchronizer-config-schema.yaml")
+
+        from xossynchronizer import loadmodels
+        from xossynchronizer.loadmodels import ModelLoadClient
+        self.loadmodels = loadmodels
+
+        self.api = MagicMock()
+        self.api.dynamicload_pb2.LoadModelsRequest = MockLoadModelsRequest
+        self.loader = ModelLoadClient(self.api)
+
+    def tearDown(self):
+        sys.path = self.sys_path_save
+        os.chdir(self.cwd_save)
+
+    def test_upload_models(self):
+        dir_map = {"models_dir": ["models.xproto", "models.py"],
+                   "models_dir/convenience": ["convenience1.py"],
+                   "models_dir/../migrations": ["migration1.py", "migration2.py"]}
+
+        file_map = {"models_dir/models.xproto": u"some xproto",
+                    "models_dir/models.py": u"print `python models file`",
+                    "models_dir/convenience": u"directory",
+                    "models_dir/convenience/convenience1.py": u"print `python convenience file`",
+                    "models_dir/../migrations": u"directory",
+                    "models_dir/../migrations/migration1.py": u"print `first migration`",
+                    "models_dir/../migrations/migration2.py": u"print `second migration`"}
+
+        orig_open = open
+        with patch("os.listdir", side_effect=functools.partial(mock_listdir, dir_map)), \
+                patch("os.path.exists", side_effect=functools.partial(mock_exists, file_map)), \
+                patch("__builtin__.open", side_effect=functools.partial(mock_open, orig_open, file_map)):
+            self.loader.upload_models("myservice", "models_dir", "1.2")
+
+            request = self.api.dynamicload.LoadModels.call_args[0][0]
+            self.assertEqual(request.name, "myservice")
+            self.assertEqual(request.version, "1.2")
+
+            self.assertEqual(len(request.xprotos.items), 1)
+            self.assertEqual(request.xprotos.items[0].filename, "models.xproto")
+            self.assertEqual(request.xprotos.items[0].contents, u"some xproto")
+
+            self.assertEqual(len(request.decls.items), 1)
+            self.assertEqual(request.decls.items[0].filename, "models.py")
+            self.assertEqual(request.decls.items[0].contents, u"print `python models file`")
+
+            self.assertEqual(len(request.convenience_methods.items), 1)
+            self.assertEqual(request.convenience_methods.items[0].filename, "convenience1.py")
+            self.assertEqual(request.convenience_methods.items[0].contents, u"print `python convenience file`")
+
+            self.assertEqual(len(request.migrations.items), 2)
+            self.assertEqual(request.migrations.items[0].filename, "migration1.py")
+            self.assertEqual(request.migrations.items[0].contents, u"print `first migration`")
+            self.assertEqual(request.migrations.items[1].filename, "migration2.py")
+            self.assertEqual(request.migrations.items[1].contents, u"print `second migration`")
+
+
+if __name__ == "__main__":
+    unittest.main()