SEBA-394 More unit tests for ORM; Refactor grouping of tests

Change-Id: I5ab93dbb8a9be27829ee1febb6e4b3d7eb4a12a4
diff --git a/VERSION b/VERSION
index f092941..a880003 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.2.8
+3.2.9-dev
diff --git a/lib/xos-api/tests/test_config.yaml b/lib/xos-api/tests/test_config.yaml
index ae389f4..b13128e 100644
--- a/lib/xos-api/tests/test_config.yaml
+++ b/lib/xos-api/tests/test_config.yaml
@@ -20,6 +20,7 @@
   handlers:
     console:
       class: logging.StreamHandler
+      level: WARNING
   loggers:
     'multistructlog':
       handlers:
diff --git a/lib/xos-api/tests/test_orm.py b/lib/xos-api/tests/test_orm.py
index 86be969..6a59713 100644
--- a/lib/xos-api/tests/test_orm.py
+++ b/lib/xos-api/tests/test_orm.py
@@ -22,10 +22,10 @@
 
 try:  # python 3
     from io import StringIO
-    from unittest.mock import patch
+    from unittest.mock import patch, MagicMock
 except ImportError:  # python 2
     from StringIO import StringIO
-    from mock import patch
+    from mock import patch, MagicMock
 
 
 # by default, use fake stub rather than real core
@@ -34,7 +34,7 @@
 PARENT_DIR = os.path.join(os.path.dirname(__file__), "..")
 
 
-class TestORM(unittest.TestCase):
+class ORMTestBaseClass(unittest.TestCase):
     def setUp(self):
         from xosconfig import Config
 
@@ -51,6 +51,8 @@
         self.ORMQuerySet = ORMQuerySet
         self.ORMLocalObjectManager = ORMLocalObjectManager
 
+        self.orm = self.make_coreapi()
+
     def tearDown(self):
         if USE_FAKE_STUB:
             sys.path.remove(PARENT_DIR)
@@ -74,576 +76,22 @@
         else:
             return xos_grpc_client.coreapi
 
-    def test_repr_name(self):
-        orm = self.make_coreapi()
-        s = orm.Slice(name="foo")
-        self.assertNotEqual(s, None)
-        self.assertEqual(repr(s), "<Slice: foo>")
 
-    def test_str_name(self):
-        orm = self.make_coreapi()
-        s = orm.Slice(name="foo")
-        self.assertNotEqual(s, None)
-        self.assertEqual(str(s), "foo")
+class TestORMWrapper(ORMTestBaseClass):
+    """ Tests for the ORMWrapper class """
 
-    def test_dumpstr_name(self):
-        orm = self.make_coreapi()
-        s = orm.Slice(name="foo")
-        self.assertNotEqual(s, None)
-        self.assertEqual(s.dumpstr(), 'name: "foo"\n')
-
-    def test_repr_noname(self):
-        orm = self.make_coreapi()
-        s = orm.Slice()
-        self.assertNotEqual(s, None)
-        self.assertEqual(repr(s), "<Slice: id-0>")
-
-    def test_str_noname(self):
-        orm = self.make_coreapi()
-        s = orm.Slice()
-        self.assertNotEqual(s, None)
-        self.assertEqual(str(s), "Slice-0")
-
-    def test_dumpstr_noname(self):
-        orm = self.make_coreapi()
-        s = orm.Slice()
-        self.assertNotEqual(s, None)
-        self.assertEqual(s.dumpstr(), "")
-
-    def test_dump(self):
-        """ dump() is like dumpstr() but prints to stdout. Mock stdout by using a stringIO. """
-
-        orm = self.make_coreapi()
-        s = orm.Slice(name="foo")
-        self.assertNotEqual(s, None)
-        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
-            s.dump()
-            self.assertEqual(mock_stdout.getvalue(), 'name: "foo"\n\n')
-
-    def test_create(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-
-    def test_get(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        got_site = orm.Site.objects.get(id=site.id)
-        self.assertNotEqual(got_site, None)
-        self.assertEqual(got_site.id, site.id)
-
-    def test_invalidate_cache(self):
-        orm = self.make_coreapi()
-        testModel = orm.TestModel()
-
-        # populate the caches with some placeholders we can test for
-        testModel.cache = {"a": 1}
-        testModel.reverse_cache = {"b": 2}
-
-        testModel.invalidate_cache()
-
-        self.assertEqual(testModel.cache, {})
-        self.assertEqual(testModel.reverse_cache, {})
-
-    def test_save_new(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-
-    def test_save_existing(self):
-        orm = self.make_coreapi()
-        orig_len_sites = len(orm.Site.objects.all())
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-
-        # there should be one new site
-        self.assertEqual(len(orm.Site.objects.all()), orig_len_sites + 1)
-
-        # retrieve the site, and update it
-        created_site_id = site.id
-        site = orm.Site.objects.get(id=created_site_id)
-        site.name = "mysitetwo"
-        site.save()
-
-        # the site_id should not have changed
-        self.assertEqual(site.id, created_site_id)
-
-        # there should still be only one new site
-        self.assertEqual(len(orm.Site.objects.all()), orig_len_sites + 1)
-
-        # the name should have changed
-        self.assertEqual(orm.Site.objects.get(id=created_site_id).name, "mysitetwo")
-
-    def test_delete(self):
-        orm = self.make_coreapi()
-        orig_len_sites = len(orm.Site.objects.all())
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        site.delete()
-        sites = orm.Site.objects.all()
-        self.assertEqual(len(sites), orig_len_sites)
-
-    def test_objects_all(self):
-        orm = self.make_coreapi()
-        orig_len_sites = len(orm.Site.objects.all())
-        site = orm.Site(name="mysite")
-        site.save()
-        sites = orm.Site.objects.all()
-        self.assertEqual(len(sites), orig_len_sites + 1)
-
-    def test_objects_first(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        site = orm.Site.objects.first()
-        self.assertNotEqual(site, None)
-
-    def test_content_type_map(self):
-        orm = self.make_coreapi()
-        self.assertTrue("Slice" in orm.content_type_map.values())
-        self.assertTrue("Site" in orm.content_type_map.values())
-        self.assertTrue("Tag" in orm.content_type_map.values())
-
-    def test_foreign_key_get(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(name="mysite_foo", site_id=site.id, creator_id=user.id)
-        slice.save()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-
-    def test_foreign_key_set_with_invalidate(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
-        slice.save()
-        slice.invalidate_cache()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id in slice.site.slices_ids)
-
-    def test_foreign_key_set_without_invalidate(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
-        slice.save()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id in slice.site.slices_ids)
-            ids_from_models = [x.id for x in slice.site.slices.all()]
-            self.assertTrue(slice.id in ids_from_models)
-
-    def test_foreign_key_reset(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
-        slice.save()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id in site.slices_ids)
-            self.assertTrue(slice.id in slice.site.slices_ids)
-
-        site2 = orm.Site(name="mysite2")
-        site2.save()
-        slice.name = "mysite2_foo"
-        slice.site = site2
-        slice.save()
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site2.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id not in site.slices_ids)
-            self.assertTrue(slice.id in site2.slices_ids)
-            self.assertTrue(slice.id in slice.site.slices_ids)
-            ids_from_models1 = [x.id for x in site.slices.all()]
-            self.assertTrue(slice.id not in ids_from_models1)
-            ids_from_models2 = [x.id for x in site2.slices.all()]
-            self.assertTrue(slice.id in ids_from_models2)
-
-    def test_foreign_key_back_and_forth_even(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
-        slice.save()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id in site.slices_ids)
-            self.assertTrue(slice.id in slice.site.slices_ids)
-
-        site2 = orm.Site(name="mysite2")
-        site2.save()
-        slice.name = "mysite2_foo"
-        slice.site = site2
-        slice.site = site
-        slice.site = site2
-        slice.site = site
-        slice.save()
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id not in site2.slices_ids)
-            self.assertTrue(slice.id in site.slices_ids)
-            self.assertTrue(slice.id in slice.site.slices_ids)
-
-    def test_foreign_key_back_and_forth_odd(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
-        slice.save()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id in site.slices_ids)
-            self.assertTrue(slice.id in slice.site.slices_ids)
-
-        site2 = orm.Site(name="mysite2")
-        site2.save()
-        slice.name = "mysite2_foo"
-        slice.site = site2
-        slice.site = site
-        slice.site = site2
-        slice.site = site
-        slice.site = site2
-        slice.save()
-        self.assertNotEqual(slice.site, None)
-        self.assertEqual(slice.site.id, site2.id)
-        if not USE_FAKE_STUB:
-            self.assertTrue(slice.id not in site.slices_ids)
-            self.assertTrue(slice.id in site2.slices_ids)
-            self.assertTrue(slice.id in slice.site.slices_ids)
-
-    def test_foreign_key_create_null(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        slice = orm.Slice(
-            name="mysite_foo", site=site, service=None, creator_id=user.id
-        )
-        slice.save()
-        slice.invalidate_cache()
-        self.assertTrue(slice.id > 0)
-        self.assertEqual(slice.service, None)
-
-    def test_foreign_key_set_null(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        user = orm.User(
-            email="fake_"
-            + "".join(
-                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
-            ),
-            site_id=site.id,
-        )
-        user.save()
-        self.assertTrue(user.id > 0)
-        service = orm.Service(name="myservice")
-        service.save()
-        self.assertTrue(service.id > 0)
-        # start out slice.service is non-None
-        slice = orm.Slice(
-            name="mysite_foo", site=site, service=service, creator_id=user.id
-        )
-        slice.save()
-        slice.invalidate_cache()
-        self.assertTrue(slice.id > 0)
-        self.assertNotEqual(slice.service, None)
-        self.assertEqual(slice.service.id, service.id)
-        # now set it to None
-        slice.service = None
-        slice.save()
-        slice.invalidate_cache()
-        self.assertEqual(slice.service, None)
-
-    def test_generic_foreign_key_get(self):
-        orm = self.make_coreapi()
-        service = orm.Service(name="myservice")
-        service.save()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        tag = orm.Tag(
-            service=service,
-            name="mytag",
-            value="somevalue",
-            content_type=site.self_content_type_id,
-            object_id=site.id,
-        )
-        tag.save()
-        self.assertTrue(tag.id > 0)
-        self.assertNotEqual(tag.content_object, None)
-        self.assertEqual(tag.content_object.id, site.id)
-
-    def test_generic_foreign_key_get_decl(self):
-        orm = self.make_coreapi()
-        service = orm.Service(name="myservice")
-        service.save()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        tag = orm.Tag(
-            service=service,
-            name="mytag",
-            value="somevalue",
-            content_type=site.self_content_type_id + "_decl",
-            object_id=site.id,
-        )
-        tag.save()
-        self.assertTrue(tag.id > 0)
-        self.assertNotEqual(tag.content_object, None)
-        self.assertEqual(tag.content_object.id, site.id)
-
-    def test_generic_foreign_key_get_bad_contenttype(self):
-        orm = self.make_coreapi()
-        service = orm.Service(name="myservice")
-        service.save()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        tag = orm.Tag(
-            service=service,
-            name="mytag",
-            value="somevalue",
-            content_type="does_not_exist",
-            object_id=site.id,
-        )
-        tag.save()
-        self.assertTrue(tag.id > 0)
-        with self.assertRaises(Exception) as e:
-
-            self.assertEqual(
-                str(e.exception),
-                "Content_type does_not_exist not found in self.content_type_map",
-            )
-
-    def test_generic_foreign_key_get_bad_id(self):
-        orm = self.make_coreapi()
-        service = orm.Service(name="myservice")
-        service.save()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        tag = orm.Tag(
-            service=service,
-            name="mytag",
-            value="somevalue",
-            content_type=site.self_content_type_id,
-            object_id=4567,
-        )
-        tag.save()
-        self.assertTrue(tag.id > 0)
-        with self.assertRaises(Exception) as e:
-            self.assertEqual(
-                str(e.exception), "Object 4567 of model Site was not found"
-            )
-
-    def test_generic_foreign_key_set(self):
-        orm = self.make_coreapi()
-        service = orm.Service(name="myservice")
-        service.save()
-        site = orm.Site(name="mysite")
-        site.save()
-        self.assertTrue(site.id > 0)
-        tag = orm.Tag(service=service, name="mytag", value="somevalue")
-        tag.content_object = site
-        tag.invalidate_cache()
-        self.assertEqual(tag.content_type, site.self_content_type_id)
-        self.assertEqual(tag.object_id, site.id)
-        tag.save()
-        self.assertTrue(tag.id > 0)
-        self.assertNotEqual(tag.content_object, None)
-        self.assertEqual(tag.content_object.id, site.id)
-
-    def test_leaf_model_trivial(self):
-        orm = self.make_coreapi()
-        service = orm.Service(name="myservice")
-        service.save()
-        self.assertEqual(service.leaf_model_name, "Service")
-
-    def test_leaf_model_descendant(self):
-        orm = self.make_coreapi()
-        onos_service = orm.ONOSService(name="myservice")
-        onos_service.save()
-        self.assertEqual(onos_service.model_name, "ONOSService")
-        self.assertEqual(onos_service.leaf_model_name, "ONOSService")
-
-        service = orm.Service.objects.get(id=onos_service.id)
-        self.assertEqual(service.id, onos_service.id)
-        self.assertEqual(service.model_name, "Service")
-        self.assertEqual(service.leaf_model_name, "ONOSService")
-
-        onos_service_cast = service.leaf_model
-        self.assertEqual(onos_service_cast.model_name, "ONOSService")
-        self.assertEqual(onos_service_cast.leaf_model_name, "ONOSService")
-        self.assertEqual(onos_service_cast.id, onos_service.id)
-
-    def test_field_null(self):
-        """ In a saved object, if a nullable field is left set to None, make sure the ORM returns None """
-
-        orm = self.make_coreapi()
-        tm = orm.TestModel()
-        tm.save()
-
-        tm = orm.TestModel.objects.all()[0]
-        self.assertFalse(tm._wrapped_class.HasField("intfield"))
-        self.assertEqual(tm.intfield, None)
-
-    def test_field_null_new(self):
-        """ For models that haven't been saved yet, reading the field should return the gRPC default """
-
-        orm = self.make_coreapi()
-        tm = orm.TestModel()
-
-        self.assertEqual(tm.intfield, 0)
-
-    def test_field_non_null(self):
-        """ In a saved object, if a nullable field is set to a value, then make sure the ORM returns the value """
-
-        orm = self.make_coreapi()
-        tm = orm.TestModel(intfield=3)
-        tm.save()
-
-        tm = orm.TestModel.objects.all()[0]
-        self.assertEqual(tm.intfield, 3)
-
-    def test_field_set_null(self):
-        """ Setting a field to None is not allowed """
-
-        orm = self.make_coreapi()
-        tm = orm.TestModel()
-        with self.assertRaises(Exception) as e:
-            tm.intfile = None
-        self.assertEqual(
-            str(e.exception),
-            "Setting a non-foreignkey field to None is not supported",
-        )
-
-    def test_query_iexact(self):
-        orm = self.make_coreapi()
-        with patch.object(orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
-            orm.TestModel.objects.filter(name__iexact="foo")
-            self.assertEqual(filter.call_count, 1)
-            q = filter.call_args[0][0]
-
-            self.assertEqual(q.kind, q.DEFAULT)
-            self.assertEqual(len(q.elements), 1)
-            self.assertEqual(q.elements[0].operator, q.elements[0].IEXACT)
-            self.assertEqual(q.elements[0].sValue, "foo")
-
-    def test_query_equal(self):
-        orm = self.make_coreapi()
-        with patch.object(orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
-            orm.TestModel.objects.filter(name="foo")
-            self.assertEqual(filter.call_count, 1)
-            q = filter.call_args[0][0]
-
-            self.assertEqual(q.kind, q.DEFAULT)
-            self.assertEqual(len(q.elements), 1)
-            self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
-            self.assertEqual(q.elements[0].sValue, "foo")
+    def test_ORMWrapper_fields_differ(self):
+        testModel = self.orm.TestModel(intfield=7, stringfield="foo")
+        self.assertTrue(testModel.fields_differ("a", "b"))
+        self.assertFalse(testModel.fields_differ("a", "a"))
 
     def test_ORMWrapper_dict(self):
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel(intfield=7, stringfield="foo")
+        testModel = self.orm.TestModel(intfield=7, stringfield="foo")
 
         self.assertDictEqual(testModel._dict, {"intfield": 7, "stringfield": "foo"})
 
     def test_ORMWrapper_new_diff(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite")
+        site = self.orm.Site(name="mysite")
 
         self.assertEqual(site.is_new, True)
         self.assertEqual(site._dict, {"name": "mysite"})
@@ -651,6 +99,7 @@
         self.assertEqual(site.changed_fields, ["name"])
         self.assertEqual(site.has_field_changed("name"), False)
         self.assertEqual(site.has_field_changed("login_base"), False)
+        self.assertEqual(site.has_changed, False)
 
         site.login_base = "bar"
 
@@ -660,13 +109,13 @@
         self.assertIn("login_base", site.changed_fields)
         self.assertEqual(site.has_field_changed("name"), False)
         self.assertEqual(site.has_field_changed("login_base"), True)
+        self.assertEqual(site.has_changed, True)
         self.assertEqual(site.get_field_diff("login_base"), (None, "bar"))
 
     def test_ORMWrapper_existing_diff(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite", login_base="foo")
+        site = self.orm.Site(name="mysite", login_base="foo")
         site.save()
-        site = orm.Site.objects.first()
+        site = self.orm.Site.objects.first()
 
         self.assertEqual(site.is_new, False)
         self.assertEqual(site._dict, {"id": 1, "name": "mysite", "login_base": "foo"})
@@ -674,6 +123,7 @@
         self.assertEqual(site.changed_fields, [])
         self.assertEqual(site.has_field_changed("name"), False)
         self.assertEqual(site.has_field_changed("login_base"), False)
+        self.assertEqual(site.has_changed, False)
 
         site.login_base = "bar"
 
@@ -682,12 +132,12 @@
         self.assertIn("login_base", site.changed_fields)
         self.assertEqual(site.has_field_changed("name"), False)
         self.assertEqual(site.has_field_changed("login_base"), True)
+        self.assertEqual(site.has_changed, True)
 
     def test_ORMWrapper_diff_after_save(self):
-        orm = self.make_coreapi()
-        site = orm.Site(name="mysite", login_base="foo")
+        site = self.orm.Site(name="mysite", login_base="foo")
         site.save()
-        site = orm.Site.objects.first()
+        site = self.orm.Site.objects.first()
 
         self.assertEqual(site.diff, {})
 
@@ -704,9 +154,7 @@
             from the diff set.
         """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         testModel.save()
 
         testModel.intfield = 9
@@ -715,23 +163,14 @@
         testModel.recompute_initial()
         self.assertEqual(testModel.changed_fields, [])
 
-    def test_ORMWrapper_create_attr(self):
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
-        testModel.create_attr("some_new_attribute", "foo")
-        self.assertEqual(testModel.some_new_attribute, "foo")
-
     def test_ORMWrapper_save_changed_fields(self):
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel(intfield=7, stringfield="foo")
+        testModel = self.orm.TestModel(intfield=7, stringfield="foo")
         testModel.save()
 
         testModel.intfield = 9
 
         with patch.object(
-            orm.grpc_stub, "UpdateTestModel", wraps=orm.grpc_stub.UpdateTestModel
+            self.orm.grpc_stub, "UpdateTestModel", wraps=self.orm.grpc_stub.UpdateTestModel
         ) as update:
             testModel.save_changed_fields()
 
@@ -742,20 +181,21 @@
             ]
             self.assertEqual(update_fields_arg, ["intfield"])
 
+    def test_ORMWrapper_create_attr(self):
+        testModel = self.orm.TestModel()
+        testModel.create_attr("some_new_attribute", "foo")
+        self.assertEqual(testModel.some_new_attribute, "foo")
+
     def test_ORMWrapper_get_generic_foreignkeys(self):
         """ Currently this is a placeholder that returns an empty list """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         self.assertEqual(testModel.get_generic_foreignkeys(), [])
 
     def test_ORMWrapper_gen_fkmap(self):
         """ TestModelTwo includes a foreignkey relation to TestModel, and the fkmap should contain that relation """
 
-        orm = self.make_coreapi()
-
-        testModelTwo = orm.TestModelTwo()
+        testModelTwo = self.orm.TestModelTwo()
 
         self.assertDictEqual(
             testModelTwo.gen_fkmap(),
@@ -774,9 +214,7 @@
             relation.
         """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
 
         self.assertDictEqual(
             testModel.gen_reverse_fkmap(),
@@ -794,12 +232,10 @@
             return that model.
         """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         testModel.save()
 
-        testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+        testModelTwo = self.orm.TestModelTwo(testmodel_id=testModel.id)
 
         testModel_resolved = testModelTwo.fk_resolve("testmodel")
         self.assertEqual(testModel_resolved.id, testModel.id)
@@ -812,12 +248,10 @@
             of TestModelTwo objects.
         """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         testModel.save()
 
-        testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+        testModelTwo = self.orm.TestModelTwo(testmodel_id=testModel.id)
         testModelTwo.save()
 
         # fake_stub.py doesn't populate the reverse relations for us, so force what the server would have done...
@@ -834,12 +268,10 @@
     def test_ORMWrapper_fk_set(self):
         """ fk_set will set the testmodel field on TesTModelTwo to point to the TestModel. """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         testModel.save()
 
-        testModelTwo = orm.TestModelTwo()
+        testModelTwo = self.orm.TestModelTwo()
 
         testModelTwo.fk_set("testmodel", testModel)
 
@@ -848,12 +280,10 @@
     def test_ORMWrapper_post_save_fixups_remove(self):
         """ Apply a post_save_fixup that removes a reverse foreign key """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         testModel.save()
 
-        testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+        testModelTwo = self.orm.TestModelTwo(testmodel_id=testModel.id)
 
         # fake_stub.py doesn't populate the reverse relations for us, so force what the server would have done...
         testModel._wrapped_class.testmodeltwos_ids = [testModelTwo.id]
@@ -876,12 +306,10 @@
     def test_ORMWrapper_post_save_fixups_add(self):
         """ Apply a post_save_fixup that adds a reverse foreign key """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel()
+        testModel = self.orm.TestModel()
         testModel.save()
 
-        testModelTwo = orm.TestModelTwo(testmodel_id=testModel.id)
+        testModelTwo = self.orm.TestModelTwo(testmodel_id=testModel.id)
         testModelTwo.save()
 
         # Make sure the reverse_relation is unpopulated. This should be the case, as fake_stub.py() doesn't populate
@@ -905,61 +333,98 @@
 
     def test_ORMWrapper_tologdict(self):
         """ Tologdict contains the model name and id, used for structured logging """
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel(intfield=7, stringfile="foo")
+        testModel = self.orm.TestModel(intfield=7, stringfile="foo")
 
         self.assertDictEqual(
             testModel.tologdict(), {"model_name": "TestModel", "pk": 0}
         )
 
+    def test_repr_name(self):
+        s = self.orm.Slice(name="foo")
+        self.assertNotEqual(s, None)
+        self.assertEqual(repr(s), "<Slice: foo>")
+
+    def test_repr_noname(self):
+        s = self.orm.Slice()
+        self.assertNotEqual(s, None)
+        self.assertEqual(repr(s), "<Slice: id-0>")
+
+    def test_str_name(self):
+        s = self.orm.Slice(name="foo")
+        self.assertNotEqual(s, None)
+        self.assertEqual(str(s), "foo")
+
+    def test_str_noname(self):
+        s = self.orm.Slice()
+        self.assertNotEqual(s, None)
+        self.assertEqual(str(s), "Slice-0")
+
+    def test_dumpstr_name(self):
+        s = self.orm.Slice(name="foo")
+        self.assertNotEqual(s, None)
+        self.assertEqual(s.dumpstr(), 'name: "foo"\n')
+
+    def test_dumpstr_noname(self):
+        s = self.orm.Slice()
+        self.assertNotEqual(s, None)
+        self.assertEqual(s.dumpstr(), "")
+
+    def test_dump(self):
+        """ dump() is like dumpstr() but prints to stdout. Mock stdout by using a stringIO. """
+
+        s = self.orm.Slice(name="foo")
+        self.assertNotEqual(s, None)
+        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
+            s.dump()
+            self.assertEqual(mock_stdout.getvalue(), 'name: "foo"\n\n')
+
+    def test_invalidate_cache(self):
+        testModel = self.orm.TestModel()
+
+        # populate the caches with some placeholders we can test for
+        testModel.cache = {"a": 1}
+        testModel.reverse_cache = {"b": 2}
+
+        testModel.invalidate_cache()
+
+        self.assertEqual(testModel.cache, {})
+        self.assertEqual(testModel.reverse_cache, {})
+
+    def test_leaf_model_trivial(self):
+        service = self.orm.Service(name="myservice")
+        service.save()
+        self.assertEqual(service.leaf_model_name, "Service")
+
+    def test_leaf_model_descendant(self):
+        onos_service = self.orm.ONOSService(name="myservice")
+        onos_service.save()
+        self.assertEqual(onos_service.model_name, "ONOSService")
+        self.assertEqual(onos_service.leaf_model_name, "ONOSService")
+
+        service = self.orm.Service.objects.get(id=onos_service.id)
+        self.assertEqual(service.id, onos_service.id)
+        self.assertEqual(service.model_name, "Service")
+        self.assertEqual(service.leaf_model_name, "ONOSService")
+
+        onos_service_cast = service.leaf_model
+        self.assertEqual(onos_service_cast.model_name, "ONOSService")
+        self.assertEqual(onos_service_cast.leaf_model_name, "ONOSService")
+        self.assertEqual(onos_service_cast.id, onos_service.id)
+
+    def test_ORMWrapper_model_name(self):
+        testModel = self.orm.TestModel()
+        self.assertEqual(testModel.model_name, "TestModel")
+
     def test_ORMWrapper_ansible_tag(self):
         """ Ansible_tag is used by old-style synchronizers. Deprecated. """
 
-        orm = self.make_coreapi()
-
-        testModel = orm.TestModel(id=7)
+        testModel = self.orm.TestModel(id=7)
 
         self.assertEqual(testModel.ansible_tag, "TestModel_7")
 
-    def test_deleted_objects_all(self):
-        orm = self.make_coreapi()
-        orig_len_sites = len(orm.Site.objects.all())
-        orig_len_deleted_sites = len(orm.Site.deleted_objects.all())
-        site = orm.Site(name="mysite")
-        site.save()
-        site.delete()
-        sites = orm.Site.objects.all()
-        self.assertEqual(len(sites), orig_len_sites)
-        deleted_sites = orm.Site.deleted_objects.all()
-        self.assertEqual(len(deleted_sites), orig_len_deleted_sites + 1)
 
-    def test_deleted_objects_filter(self):
-        orm = self.make_coreapi()
-        with patch.object(
-            orm.grpc_stub, "FilterTestModel", wraps=orm.grpc_stub.FilterTestModel
-        ) as filter:
-            foo = orm.TestModel(name="foo")
-            foo.save()
-            foo.delete()
-
-            # There should be no live objects
-            objs = orm.TestModel.objects.filter(name="foo")
-            self.assertEqual(len(objs), 0)
-
-            # There should be one deleted object
-            deleted_objs = orm.TestModel.deleted_objects.filter(name="foo")
-            self.assertEqual(len(deleted_objs), 1)
-
-            # Two calls, one for when we checked live objects, the other for when we checked deleted objects
-            self.assertEqual(filter.call_count, 2)
-            q = filter.call_args[0][0]
-
-            # Now spy on the query that was generated, to make sure it looks like we expect
-            self.assertEqual(q.kind, q.SYNCHRONIZER_DELETED_OBJECTS)
-            self.assertEqual(len(q.elements), 1)
-            self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
-            self.assertEqual(q.elements[0].sValue, "foo")
+class TestORMQuerySet(ORMTestBaseClass):
+    """ Tests for high-level ORMQuerySet class """
 
     def test_ORMQuerySet_first_nonempty(self):
         qs = self.ORMQuerySet([1, 2, 3])
@@ -977,13 +442,15 @@
         qs = self.ORMQuerySet()
         self.assertEqual(qs.exists(), False)
 
+
+class TestORMLocalObjectManager(ORMTestBaseClass):
+    """ Tests for high-level ORMLocalObjectManager class """
+
     def test_ORMLocalObjectManager_nonempty(self):
         """ Test all(), first(), exists(), and count() together since they're all closely related. Use a nonempty
             list.
         """
-        orm = self.make_coreapi()
-
-        t = orm.TestModel()
+        t = self.orm.TestModel()
         t.save()
 
         lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [t.id], False)
@@ -997,9 +464,7 @@
         """ Test all(), first(), exists(), and count() together since they're all closely related. Use an empty
             list.
         """
-        orm = self.make_coreapi()
-
-        t = orm.TestModel()
+        t = self.orm.TestModel()
         t.save()
 
         lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [], False)
@@ -1010,9 +475,7 @@
 
     def test_ORMLocalObjectManager_not_writeable(self):
         """ An ORMLocalObjectManager that is not writeable should throw exceptions on add() and remove() """
-        orm = self.make_coreapi()
-
-        t = orm.TestModel()
+        t = self.orm.TestModel()
         t.save()
 
         lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [t.id], False)
@@ -1026,9 +489,7 @@
         self.assertEqual(str(e.exception), "Only ManyToMany lists are writeable")
 
     def test_ORMLocalObjectManager_add(self):
-        orm = self.make_coreapi()
-
-        t = orm.TestModel()
+        t = self.orm.TestModel()
         t.save()
 
         lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [], True)
@@ -1037,9 +498,7 @@
         self.assertEqual(lobjs.first().id, t.id)
 
     def test_ORMLocalObjectManager_remove(self):
-        orm = self.make_coreapi()
-
-        t = orm.TestModel()
+        t = self.orm.TestModel()
         t.save()
 
         lobjs = self.ORMLocalObjectManager(t.stub, "TestModel", [t.id], True)
@@ -1047,6 +506,558 @@
         self.assertEqual(lobjs.count(), 0)
 
 
+class TestORMObjectManager(ORMTestBaseClass):
+    """ Tests for ORMObjectManager class """
+
+    def test_wrap_single(self):
+        wrapped_obj = self.orm.all_grpc_classes["Site"](name="mysite")
+        orm_obj = self.orm.Site.objects.wrap_single(wrapped_obj)
+        self.assertEqual(orm_obj._wrapped_class, wrapped_obj)
+
+    def test_wrap_list(self):
+        wrapped_obj = self.orm.all_grpc_classes["Site"](name="mysite")
+        wrapped_list = MagicMock(items=[wrapped_obj])
+        orm_objs = self.orm.Site.objects.wrap_list(wrapped_list)
+        self.assertEqual(len(orm_objs), 1)
+        self.assertEqual(orm_objs[0]._wrapped_class, wrapped_obj)
+
+    def test_objects_all(self):
+        orig_len_sites = len(self.orm.Site.objects.all())
+        site = self.orm.Site(name="mysite")
+        site.save()
+        sites = self.orm.Site.objects.all()
+        self.assertEqual(len(sites), orig_len_sites + 1)
+
+    def test_objects_first(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        site = self.orm.Site.objects.first()
+        self.assertNotEqual(site, None)
+
+    def test_filter_query_iexact(self):
+        with patch.object(self.orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+            self.orm.TestModel.objects.filter(name__iexact="foo")
+            self.assertEqual(filter.call_count, 1)
+            q = filter.call_args[0][0]
+
+            self.assertEqual(q.kind, q.DEFAULT)
+            self.assertEqual(len(q.elements), 1)
+            self.assertEqual(q.elements[0].operator, q.elements[0].IEXACT)
+            self.assertEqual(q.elements[0].sValue, "foo")
+
+    def test_filter_query_equal(self):
+        with patch.object(self.orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+            self.orm.TestModel.objects.filter(name="foo")
+            self.assertEqual(filter.call_count, 1)
+            q = filter.call_args[0][0]
+
+            self.assertEqual(q.kind, q.DEFAULT)
+            self.assertEqual(len(q.elements), 1)
+            self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
+            self.assertEqual(q.elements[0].sValue, "foo")
+
+    def test_filter_special(self):
+        with patch.object(self.orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+            self.orm.TestModel.objects.filter_special(kind=self.orm.TestModel.objects.SYNCHRONIZER_DIRTY_OBJECTS)
+            self.assertEqual(filter.call_count, 1)
+            q = filter.call_args[0][0]
+            self.assertEqual(q.kind, q.SYNCHRONIZER_DIRTY_OBJECTS)
+
+    def test_get(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        got_site = self.orm.Site.objects.get(id=site.id)
+        self.assertNotEqual(got_site, None)
+        self.assertEqual(got_site.id, site.id)
+
+    def test_new(self):
+        site = self.orm.Site.objects.new(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+
+
+class TestORMModelClass(ORMTestBaseClass):
+    """ Tests for ORMModelClass class """
+
+    def test_name(self):
+        self.assertEqual(self.orm.Site.__name__, "Site")
+
+    def test_content_type_id(self):
+        self.assertNotEqual(self.orm.Site.content_type_id, None)
+
+    def test_call(self):
+        new_obj = self.orm.Site(name="mysite")
+        self.assertNotEqual(new_obj, None)
+        self.assertEqual(new_obj.name, "mysite")
+
+
+class TestORM(ORMTestBaseClass):
+    """ Tests for OMRStub and misc high-level ORM operations """
+
+    def test_add_default_metadata(self):
+        md = []
+        self.orm.add_default_metadata(md)
+        self.assertListEqual(md, [('caller_kind', 'grpcapi')])
+
+    def test_make_ID(self):
+        id_proto = self.orm.make_ID(123)
+        self.assertEqual(id_proto.id, 123)
+
+    def test_make_empty(self):
+        empty_proto = self.orm.make_empty()
+        self.assertNotEqual(empty_proto, None)
+
+    def test_make_Query(self):
+        query_proto = self.orm.make_Query()
+        self.assertNotEqual(query_proto, None)
+        self.assertTrue(hasattr(query_proto, "elements"))
+
+    def test_listObjects(self):
+        objs = self.orm.listObjects()
+        self.assertIn("Slice", objs)
+
+    def test_create(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+
+    def test_save_existing(self):
+        orig_len_sites = len(self.orm.Site.objects.all())
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+
+        # there should be one new site
+        self.assertEqual(len(self.orm.Site.objects.all()), orig_len_sites + 1)
+
+        # retrieve the site, and update it
+        created_site_id = site.id
+        site = self.orm.Site.objects.get(id=created_site_id)
+        site.name = "mysitetwo"
+        site.save()
+
+        # the site_id should not have changed
+        self.assertEqual(site.id, created_site_id)
+
+        # there should still be only one new site
+        self.assertEqual(len(self.orm.Site.objects.all()), orig_len_sites + 1)
+
+        # the name should have changed
+        self.assertEqual(self.orm.Site.objects.get(id=created_site_id).name, "mysitetwo")
+
+    def test_delete(self):
+        orig_len_sites = len(self.orm.Site.objects.all())
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        site.delete()
+        sites = self.orm.Site.objects.all()
+        self.assertEqual(len(sites), orig_len_sites)
+
+    def test_content_type_map(self):
+        self.assertTrue("Slice" in self.orm.content_type_map.values())
+        self.assertTrue("Site" in self.orm.content_type_map.values())
+        self.assertTrue("Tag" in self.orm.content_type_map.values())
+
+    def test_foreign_key_get(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(name="mysite_foo", site_id=site.id, creator_id=user.id)
+        slice.save()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+
+    def test_foreign_key_set_with_invalidate(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+        slice.save()
+        slice.invalidate_cache()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id in slice.site.slices_ids)
+
+    def test_foreign_key_set_without_invalidate(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+        slice.save()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id in slice.site.slices_ids)
+            ids_from_models = [x.id for x in slice.site.slices.all()]
+            self.assertTrue(slice.id in ids_from_models)
+
+    def test_foreign_key_reset(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+        slice.save()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id in site.slices_ids)
+            self.assertTrue(slice.id in slice.site.slices_ids)
+
+        site2 = self.orm.Site(name="mysite2")
+        site2.save()
+        slice.name = "mysite2_foo"
+        slice.site = site2
+        slice.save()
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site2.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id not in site.slices_ids)
+            self.assertTrue(slice.id in site2.slices_ids)
+            self.assertTrue(slice.id in slice.site.slices_ids)
+            ids_from_models1 = [x.id for x in site.slices.all()]
+            self.assertTrue(slice.id not in ids_from_models1)
+            ids_from_models2 = [x.id for x in site2.slices.all()]
+            self.assertTrue(slice.id in ids_from_models2)
+
+    def test_foreign_key_back_and_forth_even(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+        slice.save()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id in site.slices_ids)
+            self.assertTrue(slice.id in slice.site.slices_ids)
+
+        site2 = self.orm.Site(name="mysite2")
+        site2.save()
+        slice.name = "mysite2_foo"
+        slice.site = site2
+        slice.site = site
+        slice.site = site2
+        slice.site = site
+        slice.save()
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id not in site2.slices_ids)
+            self.assertTrue(slice.id in site.slices_ids)
+            self.assertTrue(slice.id in slice.site.slices_ids)
+
+    def test_foreign_key_back_and_forth_odd(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(name="mysite_foo", site=site, creator_id=user.id)
+        slice.save()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id in site.slices_ids)
+            self.assertTrue(slice.id in slice.site.slices_ids)
+
+        site2 = self.orm.Site(name="mysite2")
+        site2.save()
+        slice.name = "mysite2_foo"
+        slice.site = site2
+        slice.site = site
+        slice.site = site2
+        slice.site = site
+        slice.site = site2
+        slice.save()
+        self.assertNotEqual(slice.site, None)
+        self.assertEqual(slice.site.id, site2.id)
+        if not USE_FAKE_STUB:
+            self.assertTrue(slice.id not in site.slices_ids)
+            self.assertTrue(slice.id in site2.slices_ids)
+            self.assertTrue(slice.id in slice.site.slices_ids)
+
+    def test_foreign_key_create_null(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        slice = self.orm.Slice(
+            name="mysite_foo", site=site, service=None, creator_id=user.id
+        )
+        slice.save()
+        slice.invalidate_cache()
+        self.assertTrue(slice.id > 0)
+        self.assertEqual(slice.service, None)
+
+    def test_foreign_key_set_null(self):
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        user = self.orm.User(
+            email="fake_"
+            + "".join(
+                random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
+            ),
+            site_id=site.id,
+        )
+        user.save()
+        self.assertTrue(user.id > 0)
+        service = self.orm.Service(name="myservice")
+        service.save()
+        self.assertTrue(service.id > 0)
+        # start out slice.service is non-None
+        slice = self.orm.Slice(
+            name="mysite_foo", site=site, service=service, creator_id=user.id
+        )
+        slice.save()
+        slice.invalidate_cache()
+        self.assertTrue(slice.id > 0)
+        self.assertNotEqual(slice.service, None)
+        self.assertEqual(slice.service.id, service.id)
+        # now set it to None
+        slice.service = None
+        slice.save()
+        slice.invalidate_cache()
+        self.assertEqual(slice.service, None)
+
+    def test_generic_foreign_key_get(self):
+        service = self.orm.Service(name="myservice")
+        service.save()
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        tag = self.orm.Tag(
+            service=service,
+            name="mytag",
+            value="somevalue",
+            content_type=site.self_content_type_id,
+            object_id=site.id,
+        )
+        tag.save()
+        self.assertTrue(tag.id > 0)
+        self.assertNotEqual(tag.content_object, None)
+        self.assertEqual(tag.content_object.id, site.id)
+
+    def test_generic_foreign_key_get_decl(self):
+        service = self.orm.Service(name="myservice")
+        service.save()
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        tag = self.orm.Tag(
+            service=service,
+            name="mytag",
+            value="somevalue",
+            content_type=site.self_content_type_id + "_decl",
+            object_id=site.id,
+        )
+        tag.save()
+        self.assertTrue(tag.id > 0)
+        self.assertNotEqual(tag.content_object, None)
+        self.assertEqual(tag.content_object.id, site.id)
+
+    def test_generic_foreign_key_get_bad_contenttype(self):
+        service = self.orm.Service(name="myservice")
+        service.save()
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        tag = self.orm.Tag(
+            service=service,
+            name="mytag",
+            value="somevalue",
+            content_type="does_not_exist",
+            object_id=site.id,
+        )
+        tag.save()
+        self.assertTrue(tag.id > 0)
+        with self.assertRaises(Exception) as e:
+
+            self.assertEqual(
+                str(e.exception),
+                "Content_type does_not_exist not found in self.content_type_map",
+            )
+
+    def test_generic_foreign_key_get_bad_id(self):
+        service = self.orm.Service(name="myservice")
+        service.save()
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        tag = self.orm.Tag(
+            service=service,
+            name="mytag",
+            value="somevalue",
+            content_type=site.self_content_type_id,
+            object_id=4567,
+        )
+        tag.save()
+        self.assertTrue(tag.id > 0)
+        with self.assertRaises(Exception) as e:
+            self.assertEqual(
+                str(e.exception), "Object 4567 of model Site was not found"
+            )
+
+    def test_generic_foreign_key_set(self):
+        service = self.orm.Service(name="myservice")
+        service.save()
+        site = self.orm.Site(name="mysite")
+        site.save()
+        self.assertTrue(site.id > 0)
+        tag = self.orm.Tag(service=service, name="mytag", value="somevalue")
+        tag.content_object = site
+        tag.invalidate_cache()
+        self.assertEqual(tag.content_type, site.self_content_type_id)
+        self.assertEqual(tag.object_id, site.id)
+        tag.save()
+        self.assertTrue(tag.id > 0)
+        self.assertNotEqual(tag.content_object, None)
+        self.assertEqual(tag.content_object.id, site.id)
+
+    def test_field_null(self):
+        """ In a saved object, if a nullable field is left set to None, make sure the ORM returns None """
+
+        tm = self.orm.TestModel()
+        tm.save()
+
+        tm = self.orm.TestModel.objects.all()[0]
+        self.assertFalse(tm._wrapped_class.HasField("intfield"))
+        self.assertEqual(tm.intfield, None)
+
+    def test_field_null_new(self):
+        """ For models that haven't been saved yet, reading the field should return the gRPC default """
+
+        tm = self.orm.TestModel()
+
+        self.assertEqual(tm.intfield, 0)
+
+    def test_field_non_null(self):
+        """ In a saved object, if a nullable field is set to a value, then make sure the ORM returns the value """
+
+        tm = self.orm.TestModel(intfield=3)
+        tm.save()
+
+        tm = self.orm.TestModel.objects.all()[0]
+        self.assertEqual(tm.intfield, 3)
+
+    def test_field_set_null(self):
+        """ Setting a field to None is not allowed """
+
+        tm = self.orm.TestModel()
+        with self.assertRaises(Exception) as e:
+            tm.intfile = None
+        self.assertEqual(
+            str(e.exception),
+            "Setting a non-foreignkey field to None is not supported",
+        )
+
+    def test_deleted_objects_all(self):
+        orig_len_sites = len(self.orm.Site.objects.all())
+        orig_len_deleted_sites = len(self.orm.Site.deleted_objects.all())
+        site = self.orm.Site(name="mysite")
+        site.save()
+        site.delete()
+        sites = self.orm.Site.objects.all()
+        self.assertEqual(len(sites), orig_len_sites)
+        deleted_sites = self.orm.Site.deleted_objects.all()
+        self.assertEqual(len(deleted_sites), orig_len_deleted_sites + 1)
+
+    def test_deleted_objects_filter(self):
+        with patch.object(
+            self.orm.grpc_stub, "FilterTestModel", wraps=self.orm.grpc_stub.FilterTestModel
+        ) as filter:
+            foo = self.orm.TestModel(name="foo")
+            foo.save()
+            foo.delete()
+
+            # There should be no live objects
+            objs = self.orm.TestModel.objects.filter(name="foo")
+            self.assertEqual(len(objs), 0)
+
+            # There should be one deleted object
+            deleted_objs = self.orm.TestModel.deleted_objects.filter(name="foo")
+            self.assertEqual(len(deleted_objs), 1)
+
+            # Two calls, one for when we checked live objects, the other for when we checked deleted objects
+            self.assertEqual(filter.call_count, 2)
+            q = filter.call_args[0][0]
+
+            # Now spy on the query that was generated, to make sure it looks like we expect
+            self.assertEqual(q.kind, q.SYNCHRONIZER_DELETED_OBJECTS)
+            self.assertEqual(len(q.elements), 1)
+            self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
+            self.assertEqual(q.elements[0].sValue, "foo")
+
+
 def main():
     global USE_FAKE_STUB
     global xos_grpc_client