diff --git a/report_coverage.sh b/report_coverage_all.sh
similarity index 100%
rename from report_coverage.sh
rename to report_coverage_all.sh
diff --git a/report_coverage_context.sh b/report_coverage_context.sh
index 15a7712a0724bd0b86f7ccd2a3490abe85994f05..f2f71fa744b5d8209589b283c7a375b4f25be0c8 100755
--- a/report_coverage_context.sh
+++ b/report_coverage_context.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 
-./report_coverage.sh | grep --color -E -i "^.*context.*$|$"
+./report_coverage_all.sh | grep --color -E -i "^.*context.*$|$"
diff --git a/report_coverage_device.sh b/report_coverage_device.sh
index f884fb1c7806069412e29fcb11ba278974520c35..b4215cd30141bb524a7d99717841de127d7cda15 100755
--- a/report_coverage_device.sh
+++ b/report_coverage_device.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 
-./report_coverage.sh | grep --color -E -i "^.*device.*$|$"
+./report_coverage_all.sh | grep --color -E -i "^.*device.*$|$"
diff --git a/run_unitary_tests.sh b/run_unitary_tests.sh
index 2f18b7285c44456a400c9b229c2e4b442256af86..84b8010341b9fa70275cd2c1039c8c0cca3d2fdc 100755
--- a/run_unitary_tests.sh
+++ b/run_unitary_tests.sh
@@ -5,7 +5,8 @@ RCFILE=~/teraflow/controller/coverage/.coveragerc
 
 # Run unitary tests and analyze coverage of code at same time
 coverage run --rcfile=$RCFILE -m pytest --log-level=DEBUG --verbose \
-    common/database/tests/test_unitary_inmemory.py \
+    common/database/tests/test_unitary.py \
+    common/database/tests/test_engine_inmemory.py \
     context/tests/test_unitary.py \
     device/tests/test_unitary.py
 
diff --git a/src/common/database/api/Database.py b/src/common/database/api/Database.py
index 3ce5d8dfbaf7a46e77ce14a5e2165e68023d067e..c3aeaf628339f8ba58e3c616b7eb6a501cad9278 100644
--- a/src/common/database/api/Database.py
+++ b/src/common/database/api/Database.py
@@ -2,20 +2,21 @@ import logging
 from typing import List
 from ..engines._DatabaseEngine import _DatabaseEngine
 from .context.Context import Context
+from .Exceptions import WrongDatabaseEngine, MutexException
 
 LOGGER = logging.getLogger(__name__)
 
 class Database:
     def __init__(self, database_engine : _DatabaseEngine):
-        if not isinstance(database_engine, _DatabaseEngine):
-            raise Exception('database_engine must inherit from _DatabaseEngine')
+        if not isinstance(database_engine, _DatabaseEngine):    
+            raise WrongDatabaseEngine('database_engine must inherit from _DatabaseEngine')
         self._database_engine = database_engine
         self._acquired = False
         self._owner_key = None
 
     def __enter__(self) -> '_DatabaseEngine':
         self._acquired, self._owner_key = self._database_engine.lock()
-        if not self._acquired: raise Exception('Unable to acquire database lock')
+        if not self._acquired: raise MutexException('Unable to acquire database lock')
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
diff --git a/src/common/database/api/Exceptions.py b/src/common/database/api/Exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef60ac5f5ab9c3ec3de6cbf4c1af54dcc4389470
--- /dev/null
+++ b/src/common/database/api/Exceptions.py
@@ -0,0 +1,5 @@
+class WrongDatabaseEngine(Exception):
+    pass
+
+class MutexException(Exception):
+    pass
diff --git a/src/common/database/api/context/Context.py b/src/common/database/api/context/Context.py
index f913becbf01f6efb04c77bf4300db0b7c1a206bc..32991cc5ad2b29fe8492d42539edb7cccad7c5f1 100644
--- a/src/common/database/api/context/Context.py
+++ b/src/common/database/api/context/Context.py
@@ -1,23 +1,18 @@
 from typing import Dict
 from ...engines._DatabaseEngine import _DatabaseEngine
-from ..entity._Entity import _Entity
-from ..entity.EntityAttributes import EntityAttributes
+from ..entity._RootEntity import _RootEntity
 from ..entity.EntityCollection import EntityCollection
 from .Keys import KEY_CONTEXT, KEY_TOPOLOGIES
 from .Topology import Topology
 
-VALIDATORS = {}
+VALIDATORS = {}  # no attributes accepted
+TRANSCODERS = {} # no transcoding applied to attributes
 
-class Context(_Entity):
+class Context(_RootEntity):
     def __init__(self, context_uuid : str, database_engine : _DatabaseEngine):
-        self._database_engine = database_engine
-        super().__init__(context_uuid, parent=self)
-        self._attributes = EntityAttributes(self, KEY_CONTEXT, validators=VALIDATORS)
+        super().__init__(database_engine, context_uuid, KEY_CONTEXT, VALIDATORS, TRANSCODERS)
         self._topologies = EntityCollection(self, KEY_TOPOLOGIES)
 
-    @property
-    def database_engine(self) -> _DatabaseEngine: return self._database_engine
-
     @property
     def parent(self) -> 'Context': return self
 
@@ -27,9 +22,6 @@ class Context(_Entity):
     @property
     def context_uuid(self) -> str: return self._entity_uuid
 
-    @property
-    def attributes(self) -> EntityAttributes: return self._attributes
-
     @property
     def topologies(self) -> EntityCollection: return self._topologies
 
diff --git a/src/common/database/api/context/Device.py b/src/common/database/api/context/Device.py
index 54ba02f6bbd385916c36410928eae7f35dba5653..fb4b5becb6de1f158447b3e7630ff6f87fbbdf7d 100644
--- a/src/common/database/api/context/Device.py
+++ b/src/common/database/api/context/Device.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 from typing import TYPE_CHECKING, Dict
 from ..entity._Entity import _Entity
-from ..entity.EntityAttributes import EntityAttributes
 from ..entity.EntityCollection import EntityCollection
 from .Endpoint import Endpoint
 from .Keys import KEY_DEVICE, KEY_DEVICE_ENDPOINTS
@@ -12,9 +11,9 @@ if TYPE_CHECKING:
     from .Topology import Topology
 
 VALIDATORS = {
-    'device_type': lambda v: v is None or isinstance(v, str),
-    'device_config': lambda v: v is None or isinstance(v, str),
-    'device_operational_status': lambda v: v is None or isinstance(v, OperationalStatus),
+    'device_type': lambda v: v is not None and isinstance(v, str) and (len(v) > 0),
+    'device_config': lambda v: v is not None and isinstance(v, str) and (len(v) > 0),
+    'device_operational_status': lambda v: v is not None and isinstance(v, OperationalStatus),
 }
 
 TRANSCODERS = {
@@ -27,8 +26,7 @@ TRANSCODERS = {
 
 class Device(_Entity):
     def __init__(self, device_uuid : str, parent : 'Topology'):
-        super().__init__(device_uuid, parent=parent)
-        self._attributes = EntityAttributes(self, KEY_DEVICE, VALIDATORS, transcoders=TRANSCODERS)
+        super().__init__(parent, device_uuid, KEY_DEVICE, VALIDATORS, TRANSCODERS)
         self._endpoints = EntityCollection(self, KEY_DEVICE_ENDPOINTS)
 
     @property
@@ -49,9 +47,6 @@ class Device(_Entity):
     @property
     def device_uuid(self) -> str: return self._entity_uuid
 
-    @property
-    def attributes(self) -> EntityAttributes: return self._attributes
-
     @property
     def endpoints(self) -> EntityCollection: return self._endpoints
 
diff --git a/src/common/database/api/context/Endpoint.py b/src/common/database/api/context/Endpoint.py
index c9364847c86a281bfe4c90ebcf8fdbdf60f30ace..413a680a8e8cdb13e8df120a2514c46497d7a071 100644
--- a/src/common/database/api/context/Endpoint.py
+++ b/src/common/database/api/context/Endpoint.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 from typing import TYPE_CHECKING, Dict
 from ..entity._Entity import _Entity
-from ..entity.EntityAttributes import EntityAttributes
 from .Keys import KEY_ENDPOINT
 
 if TYPE_CHECKING:
@@ -10,13 +9,14 @@ if TYPE_CHECKING:
     from .Device import Device
 
 VALIDATORS = {
-    'port_type': lambda v: v is None or isinstance(v, str),
+    'port_type': lambda v: v is not None and isinstance(v, str) and (len(v) > 0),
 }
 
+TRANSCODERS = {} # no transcoding applied to attributes
+
 class Endpoint(_Entity):
     def __init__(self, endpoint_uuid : str, parent : 'Device'):
-        super().__init__(endpoint_uuid, parent=parent)
-        self._attributes = EntityAttributes(self, KEY_ENDPOINT, VALIDATORS)
+        super().__init__(parent, endpoint_uuid, KEY_ENDPOINT, VALIDATORS, TRANSCODERS)
 
     @property
     def parent(self) -> 'Device': return self._parent
@@ -42,9 +42,6 @@ class Endpoint(_Entity):
     @property
     def endpoint_uuid(self) -> str: return self._entity_uuid
 
-    @property
-    def attributes(self) -> EntityAttributes: return self._attributes
-
     def create(self, port_type : str) -> 'Endpoint':
         self.update(update_attributes={
             'port_type': port_type
diff --git a/src/common/database/api/context/Link.py b/src/common/database/api/context/Link.py
index 8dfffe3d29c63718bd75a15ede52112bea6497d5..bf661dbb2897822a45071c157619b97c1ebca1d9 100644
--- a/src/common/database/api/context/Link.py
+++ b/src/common/database/api/context/Link.py
@@ -3,15 +3,18 @@ from typing import TYPE_CHECKING, Dict
 from ..entity._Entity import _Entity
 from ..entity.EntityCollection import EntityCollection
 from .LinkEndpoint import LinkEndpoint
-from .Keys import KEY_LINK_ENDPOINTS
+from .Keys import KEY_LINK, KEY_LINK_ENDPOINTS
 
 if TYPE_CHECKING:
     from .Context import Context
     from .Topology import Topology
 
+VALIDATORS = {}  # no attributes accepted
+TRANSCODERS = {} # no transcoding applied to attributes
+
 class Link(_Entity):
     def __init__(self, link_uuid : str, parent : 'Topology'):
-        super().__init__(link_uuid, parent=parent)
+        super().__init__(parent, link_uuid, KEY_LINK, VALIDATORS, TRANSCODERS)
         self._endpoints = EntityCollection(self, KEY_LINK_ENDPOINTS)
 
     @property
@@ -43,6 +46,7 @@ class Link(_Entity):
 
     def delete(self) -> None:
         for endpoint_uuid in self.endpoints.get(): self.endpoint(endpoint_uuid).delete()
+        self.attributes.delete()
         self.parent.links.delete(self.link_uuid)
 
     def dump_id(self) -> Dict:
diff --git a/src/common/database/api/context/LinkEndpoint.py b/src/common/database/api/context/LinkEndpoint.py
index 741946ba36428930dc0408694b6e52f416065c57..4acb62fdb36fe78f65710d361370f41962312859 100644
--- a/src/common/database/api/context/LinkEndpoint.py
+++ b/src/common/database/api/context/LinkEndpoint.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 from typing import TYPE_CHECKING, Dict
 from ..entity._Entity import _Entity
-from ..entity.EntityAttributes import EntityAttributes
 from .Endpoint import Endpoint
 from .Keys import KEY_LINK_ENDPOINT
 
@@ -11,14 +10,15 @@ if TYPE_CHECKING:
     from .Link import Link
 
 VALIDATORS = {
-    'device_uuid': lambda v: v is None or isinstance(v, str),
-    'endpoint_uuid': lambda v: v is None or isinstance(v, str),
+    'device_uuid': lambda v: v is not None and isinstance(v, str) and (len(v) > 0),
+    'endpoint_uuid': lambda v: v is not None and isinstance(v, str) and (len(v) > 0),
 }
 
+TRANSCODERS = {} # no transcoding applied to attributes
+
 class LinkEndpoint(_Entity):
     def __init__(self, link_endpoint_uuid : str, parent : 'Link'):
-        super().__init__(link_endpoint_uuid, parent=parent)
-        self._attributes = EntityAttributes(self, KEY_LINK_ENDPOINT, VALIDATORS)
+        super().__init__(parent, link_endpoint_uuid, KEY_LINK_ENDPOINT, VALIDATORS, TRANSCODERS)
 
     @property
     def parent(self) -> 'Link': return self._parent
@@ -44,9 +44,6 @@ class LinkEndpoint(_Entity):
     @property
     def link_endpoint_uuid(self) -> str: return self._entity_uuid
 
-    @property
-    def attributes(self) -> EntityAttributes: return self._attributes
-
     def create(self, endpoint : Endpoint) -> 'LinkEndpoint':
         self.update(update_attributes={
             'device_uuid': endpoint.device_uuid,
diff --git a/src/common/database/api/context/Topology.py b/src/common/database/api/context/Topology.py
index 82680f8b20f908a403c07ae09e95e19cd00220bc..2fc36ed3c46a64dfb92337c2f62608529d2e65d9 100644
--- a/src/common/database/api/context/Topology.py
+++ b/src/common/database/api/context/Topology.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 from typing import TYPE_CHECKING, Dict
 from ..entity._Entity import _Entity
-from ..entity.EntityAttributes import EntityAttributes
 from ..entity.EntityCollection import EntityCollection
 from .Keys import KEY_TOPOLOGY, KEY_DEVICES, KEY_LINKS
 from .Device import Device
@@ -10,12 +9,12 @@ from .Link import Link
 if TYPE_CHECKING:
     from .Context import Context
 
-VALIDATORS = {}
+VALIDATORS = {}  # no attributes accepted
+TRANSCODERS = {} # no transcoding applied to attributes
 
 class Topology(_Entity):
     def __init__(self, topology_uuid : str, parent : 'Context'):
-        super().__init__(topology_uuid, parent=parent)
-        self._attributes = EntityAttributes(self, KEY_TOPOLOGY, validators=VALIDATORS)
+        super().__init__(parent, topology_uuid, KEY_TOPOLOGY, VALIDATORS, TRANSCODERS)
         self._devices = EntityCollection(self, KEY_DEVICES)
         self._links = EntityCollection(self, KEY_LINKS)
 
@@ -31,17 +30,14 @@ class Topology(_Entity):
     @property
     def topology_uuid(self) -> str: return self._entity_uuid
 
-    @property
-    def attributes(self) -> EntityAttributes: return self._attributes
-
     @property
     def devices(self) -> EntityCollection: return self._devices
 
-    def device(self, device_uuid : str) -> Device: return Device(device_uuid, self)
-
     @property
     def links(self) -> EntityCollection: return self._links
 
+    def device(self, device_uuid : str) -> Device: return Device(device_uuid, self)
+
     def link(self, link_uuid : str) -> Link: return Link(link_uuid, self)
 
     def create(self) -> 'Topology':
diff --git a/src/common/database/api/entity/EntityAttributes.py b/src/common/database/api/entity/EntityAttributes.py
index 244b43bc711cfa67067de47866cee302cfc9138e..47642b823de3164db18667aa474a95aed84e730c 100644
--- a/src/common/database/api/entity/EntityAttributes.py
+++ b/src/common/database/api/entity/EntityAttributes.py
@@ -8,7 +8,7 @@ if TYPE_CHECKING:
     from ._Entity import _Entity
 
 class EntityAttributes:
-    def __init__(self, parent : '_Entity', entity_key : str, validators : Dict, transcoders : Dict={}):
+    def __init__(self, parent : '_Entity', entity_key : str, validators : Dict, transcoders : Dict = {}):
         self._parent = parent
         self._database_engine : _DatabaseEngine = self._parent.database_engine
         self._entity_key = format_key(entity_key, self._parent)
@@ -18,6 +18,7 @@ class EntityAttributes:
     def validate(self, update_attributes, remove_attributes, attribute_name):
         remove_attributes.discard(attribute_name)
         value = update_attributes.pop(attribute_name, None)
+        if value is None: return
         validator = self._validators.get(attribute_name)
         if validator is None: return
         if not validator(value): raise AttributeError('{} is invalid'.format(attribute_name))
diff --git a/src/common/database/api/entity/_Entity.py b/src/common/database/api/entity/_Entity.py
index 2d8fbdfcd0500648eec80ba0ad20a20e489ff654..9d0b3dfb05f58e9af1a595e7527f3e48b5bb7a5d 100644
--- a/src/common/database/api/entity/_Entity.py
+++ b/src/common/database/api/entity/_Entity.py
@@ -1,19 +1,35 @@
-from typing import Dict
+from typing import Any, Callable, Dict
+from ...engines._DatabaseEngine import _DatabaseEngine
+from .EntityAttributes import EntityAttributes
 
 class _Entity:
-    def __init__(self, entity_uuid : str, parent=None):
-        if entity_uuid is None:
-            raise AttributeError('entity_uuid is None')
-        if (parent is None) or (not isinstance(parent, _Entity)):
+    def __init__(self, parent, entity_uuid : str, attributes_key : str,
+                 attribute_validators : Dict[str, Callable[[Any], bool]],
+                 attribute_transcoders : Dict[str, Dict[Any, Callable[[Any], Any]]]):
+        if not isinstance(parent, _Entity):
             raise AttributeError('parent must be an instance of _Entity')
+        if (not isinstance(entity_uuid, str)) or (len(entity_uuid) == 0):
+            raise AttributeError('entity_uuid must be a non-empty instance of str')
+        if (not isinstance(attributes_key, str)) or (len(attributes_key) == 0):
+            raise AttributeError('attributes_key must be a non-empty instance of str')
+        if not isinstance(attribute_validators, dict):
+            raise AttributeError('attribute_validators must be an instance of dict')
+        if not isinstance(attribute_transcoders, dict):
+            raise AttributeError('attribute_transcoders must be an instance of dict')
+
         self._entity_uuid = entity_uuid
         self._parent = parent
+        self._attributes = EntityAttributes(self, attributes_key, attribute_validators,
+                                            transcoders=attribute_transcoders)
 
     @property
     def parent(self) -> '_Entity': return self._parent
 
     @property
-    def database_engine(self) -> object: return self._parent.database_engine
+    def database_engine(self) -> _DatabaseEngine: return self._parent.database_engine
+
+    @property
+    def attributes(self) -> EntityAttributes: return self._attributes
 
     def load(self):
         raise NotImplementedError()
diff --git a/src/common/database/api/entity/_RootEntity.py b/src/common/database/api/entity/_RootEntity.py
new file mode 100644
index 0000000000000000000000000000000000000000..6047bec8b268d4e412161d4515b2c3c7c56d800a
--- /dev/null
+++ b/src/common/database/api/entity/_RootEntity.py
@@ -0,0 +1,16 @@
+from typing import Any, Callable, Dict
+from ._Entity import _Entity
+from ...engines._DatabaseEngine import _DatabaseEngine
+
+class _RootEntity(_Entity):
+    def __init__(self, database_engine : _DatabaseEngine, entity_uuid: str, attributes_key: str,
+                 attributes_validators: Dict[str, Callable[[Any], bool]],
+                 attribute_transcoders: Dict[str, Dict[Any, Callable[[Any], Any]]]):
+        self._database_engine = database_engine
+        super().__init__(self, entity_uuid, attributes_key, attributes_validators, attribute_transcoders)
+
+    @property
+    def parent(self) -> '_RootEntity': return self
+
+    @property
+    def database_engine(self) -> _DatabaseEngine: return self._database_engine
diff --git a/src/common/database/tests/test_unitary_inmemory.py b/src/common/database/tests/test_engine_inmemory.py
similarity index 100%
rename from src/common/database/tests/test_unitary_inmemory.py
rename to src/common/database/tests/test_engine_inmemory.py
diff --git a/src/common/database/tests/test_integration_redis.py b/src/common/database/tests/test_engine_redis.py
similarity index 100%
rename from src/common/database/tests/test_integration_redis.py
rename to src/common/database/tests/test_engine_redis.py
diff --git a/src/common/database/tests/test_unitary.py b/src/common/database/tests/test_unitary.py
new file mode 100644
index 0000000000000000000000000000000000000000..c00e2f7c9165ee84bccf4a34f06c0915bf4726c9
--- /dev/null
+++ b/src/common/database/tests/test_unitary.py
@@ -0,0 +1,84 @@
+import logging, pytest
+from ..api.Database import Database
+from ..api.entity._Entity import _Entity
+from ..api.entity._RootEntity import _RootEntity
+from ..api.entity.EntityAttributes import EntityAttributes
+from ..api.Exceptions import WrongDatabaseEngine
+from ..engines._DatabaseEngine import _DatabaseEngine
+from ..engines.inmemory.InMemoryDatabaseEngine import InMemoryDatabaseEngine
+
+logging.basicConfig(level=logging.INFO)
+
+def test_database_gets_none_database_engine():
+    # should fail with invalid database engine
+    with pytest.raises(WrongDatabaseEngine) as e:
+        Database(None)
+    assert str(e.value) == 'database_engine must inherit from _DatabaseEngine'
+
+def test_database_gets_correct_database_engine():
+    # should work
+    assert Database(InMemoryDatabaseEngine()) is not None
+
+def test_entity_gets_invalid_parameters():
+
+    class RootMockEntity(_RootEntity):
+        def __init__(self, database_engine : _DatabaseEngine):
+            super().__init__(database_engine, 'valid-uuid', 'valid-key', {}, {})
+
+    # should fail with invalid parent
+    with pytest.raises(AttributeError) as e:
+        _Entity(None, 'valid-uuid', 'valid-attributes-key', {}, {})
+    assert str(e.value) == 'parent must be an instance of _Entity'
+
+    # should fail with invalid entity uuid
+    with pytest.raises(AttributeError) as e:
+        _Entity(RootMockEntity(InMemoryDatabaseEngine()), None, 'valid-attributes-key', {}, {})
+    assert str(e.value) == 'entity_uuid must be a non-empty instance of str'
+
+    # should fail with invalid entity uuid
+    with pytest.raises(AttributeError) as e:
+        _Entity(RootMockEntity(InMemoryDatabaseEngine()), '', 'valid-attributes-key', {}, {})
+    assert str(e.value) == 'entity_uuid must be a non-empty instance of str'
+
+    # should fail with invalid attribute key
+    with pytest.raises(AttributeError) as e:
+        _Entity(RootMockEntity(InMemoryDatabaseEngine()), 'valid-uuid', None, {}, {})
+    assert str(e.value) == 'attributes_key must be a non-empty instance of str'
+
+    # should fail with invalid attribute key
+    with pytest.raises(AttributeError) as e:
+        _Entity(RootMockEntity(InMemoryDatabaseEngine()), 'valid-uuid', '', {}, {})
+    assert str(e.value) == 'attributes_key must be a non-empty instance of str'
+
+    # should fail with invalid attribute validators
+    with pytest.raises(AttributeError) as e:
+        _Entity(RootMockEntity(InMemoryDatabaseEngine()), 'valid-uuid', 'valid-attributes-key', [], {})
+    assert str(e.value) == 'attribute_validators must be an instance of dict'
+
+    # should fail with invalid attribute transcoders
+    with pytest.raises(AttributeError) as e:
+        _Entity(RootMockEntity(InMemoryDatabaseEngine()), 'valid-uuid', 'valid-attributes-key', {}, [])
+    assert str(e.value) == 'attribute_transcoders must be an instance of dict'
+
+    # should work
+    assert _Entity(RootMockEntity(InMemoryDatabaseEngine()), 'valid-uuid', 'valid-attributes-key', {}, {}) is not None
+
+def test_entity_attributes_gets_invalid_parameters():
+
+    class RootMockEntity(_RootEntity):
+        def __init__(self, database_engine : _DatabaseEngine):
+            super().__init__(database_engine, 'valid-uuid', 'valid-key', {}, {})
+
+    # should work
+    root_entity = RootMockEntity(InMemoryDatabaseEngine())
+    validators = {'attr': lambda v: True}
+    entity_attrs = EntityAttributes(root_entity, 'valid-attributes-key', validators, {})
+    assert entity_attrs is not None
+
+    with pytest.raises(AttributeError) as e:
+        entity_attrs.update(update_attributes={'non-defined-attr': 'random-value'})
+    assert str(e.value) == "Unexpected update_attributes: {'non-defined-attr': 'random-value'}"
+
+    with pytest.raises(AttributeError) as e:
+        entity_attrs.update(remove_attributes=['non-defined-attr'])
+    assert str(e.value) == "Unexpected remove_attributes: {'non-defined-attr'}"
diff --git a/src/common/tests/Assertions.py b/src/common/tests/Assertions.py
index f4f88e4aa2aac645f8cd84a7ee3912071cd2606b..6cf2f757bb87174882344ff4c762716e217accb3 100644
--- a/src/common/tests/Assertions.py
+++ b/src/common/tests/Assertions.py
@@ -16,6 +16,12 @@ def validate_device_id(message):
     assert 'device_id' in message
     validate_uuid(message['device_id'])
 
+def validate_link_id(message):
+    assert type(message) is dict
+    assert len(message.keys()) == 1
+    assert 'link_id' in message
+    validate_uuid(message['link_id'])
+
 def validate_topology(message):
     assert type(message) is dict
     assert len(message.keys()) > 0
diff --git a/src/context/client/ContextClient.py b/src/context/client/ContextClient.py
index d868b5694abf69e076ca9d8f314cbde16fc0d42d..64bd0010b3d1d57a90023b5473d8d89b8a69823a 100644
--- a/src/context/client/ContextClient.py
+++ b/src/context/client/ContextClient.py
@@ -1,5 +1,6 @@
 import grpc, logging
 from common.tools.RetryDecorator import retry, delay_exponential
+from context.proto.context_pb2 import Link, LinkId, Empty, Topology
 from context.proto.context_pb2_grpc import ContextServiceStub
 
 LOGGER = logging.getLogger(__name__)
@@ -25,8 +26,22 @@ class ContextClient:
         self.stub = None
 
     @retry(exceptions=set(), max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
-    def GetTopology(self, request):
+    def GetTopology(self, request : Empty) -> Topology:
         LOGGER.debug('GetTopology request: {}'.format(request))
         response = self.stub.GetTopology(request)
         LOGGER.debug('GetTopology result: {}'.format(response))
         return response
+
+    @retry(exceptions=set(), max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
+    def AddLink(self, request : Link) -> LinkId:
+        LOGGER.debug('AddLink request: {}'.format(request))
+        response = self.stub.AddLink(request)
+        LOGGER.debug('AddLink result: {}'.format(response))
+        return response
+
+    @retry(exceptions=set(), max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
+    def DeleteLink(self, request : LinkId) -> Empty:
+        LOGGER.debug('DeleteLink request: {}'.format(request))
+        response = self.stub.DeleteLink(request)
+        LOGGER.debug('DeleteLink result: {}'.format(response))
+        return response
diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py
index 543adf7dfac9356e91d43ea38ef47f6fe8d26bfc..765a825081985f8f973ef9e71f6a5639780f444b 100644
--- a/src/context/service/ContextServiceServicerImpl.py
+++ b/src/context/service/ContextServiceServicerImpl.py
@@ -1,8 +1,10 @@
+from typing import Dict, List, Set, Tuple
 import grpc, logging
 from prometheus_client import Counter, Histogram
+from common.Checkers import chk_string
 from common.database.api.Database import Database
 from common.exceptions.ServiceException import ServiceException
-from context.proto.context_pb2 import Empty, Topology
+from context.proto.context_pb2 import Empty, Link, LinkId, Topology
 from context.proto.context_pb2_grpc import ContextServiceServicer
 
 LOGGER = logging.getLogger(__name__)
@@ -19,6 +21,24 @@ GETTOPOLOGY_COUNTER_FAILED     = Counter  ('context_gettopology_counter_failed',
 GETTOPOLOGY_HISTOGRAM_DURATION = Histogram('context_gettopology_histogram_duration',
                                           'Context:GetTopology histogram of request duration')
 
+ADDLINK_COUNTER_STARTED    = Counter  ('context_addlink_counter_started',
+                                       'Context:AddLink counter of requests started'  )
+ADDLINK_COUNTER_COMPLETED  = Counter  ('context_addlink_counter_completed',
+                                       'Context:AddLink counter of requests completed')
+ADDLINK_COUNTER_FAILED     = Counter  ('context_addlink_counter_failed',
+                                       'Context:AddLink counter of requests failed'   )
+ADDLINK_HISTOGRAM_DURATION = Histogram('context_addlink_histogram_duration',
+                                       'Context:AddLink histogram of request duration')
+
+DELETELINK_COUNTER_STARTED    = Counter  ('context_deletelink_counter_started',
+                                          'Context:DeleteLink counter of requests started'  )
+DELETELINK_COUNTER_COMPLETED  = Counter  ('context_deletelink_counter_completed',
+                                          'Context:DeleteLink counter of requests completed')
+DELETELINK_COUNTER_FAILED     = Counter  ('context_deletelink_counter_failed',
+                                          'Context:DeleteLink counter of requests failed'   )
+DELETELINK_HISTOGRAM_DURATION = Histogram('context_deletelink_histogram_duration',
+                                          'Context:DeleteLink histogram of request duration')
+
 class ContextServiceServicerImpl(ContextServiceServicer):
     def __init__(self, database : Database):
         LOGGER.debug('Creating Servicer...')
@@ -43,9 +63,157 @@ class ContextServiceServicerImpl(ContextServiceServicer):
             LOGGER.debug('GetTopology reply: {}'.format(str(reply)))
             GETTOPOLOGY_COUNTER_COMPLETED.inc()
             return reply
-        except ServiceException as e:
-            grpc_context.abort(e.code, e.details)
+        except ServiceException as e:                               # pragma: no cover (ServiceException not thrown)
+            grpc_context.abort(e.code, e.details)                   # pragma: no cover (ServiceException not thrown)
         except Exception as e:                                      # pragma: no cover
             LOGGER.exception('GetTopology exception')               # pragma: no cover
             GETTOPOLOGY_COUNTER_FAILED.inc()                        # pragma: no cover
             grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))    # pragma: no cover
+
+    @ADDLINK_HISTOGRAM_DURATION.time()
+    def AddLink(self, request : Link, grpc_context : grpc.ServicerContext) -> LinkId:
+        ADDLINK_COUNTER_STARTED.inc()
+        try:
+            LOGGER.debug('AddLink request: {}'.format(str(request)))
+
+            # ----- Validate request data and pre-conditions -----------------------------------------------------------
+            try:
+                link_id = chk_string('link.link_id.link_id.uuid',
+                                     request.link_id.link_id.uuid,
+                                     allow_empty=False)
+            except Exception as e:
+                LOGGER.exception('Invalid arguments:')
+                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
+
+            db_context = self.database.context(DEFAULT_CONTEXT_ID).create()
+            db_topology = db_context.topology(DEFAULT_TOPOLOGY_ID).create()
+
+            if db_topology.links.contains(link_id):
+                msg = 'Link({}) already exists in the database.'
+                msg = msg.format(link_id)
+                raise ServiceException(grpc.StatusCode.ALREADY_EXISTS, msg)
+
+            added_devices_and_endpoints : Dict[str, Set[str]] = {}
+            device_endpoint_pairs : List[Tuple[str, str]] = []
+            for i,endpoint in enumerate(request.endpointList):
+                try:
+                    ep_context_id  = chk_string('endpoint[#{}].topoId.contextId.contextUuid.uuid'.format(i),
+                                                endpoint.topoId.contextId.contextUuid.uuid,
+                                                allow_empty=True)
+                    ep_topology_id = chk_string('endpoint[#{}].topoId.topoId.uuid'.format(i),
+                                                endpoint.topoId.topoId.uuid,
+                                                allow_empty=True)
+                    ep_device_id   = chk_string('endpoint[#{}].dev_id.device_id.uuid'.format(i),
+                                                endpoint.dev_id.device_id.uuid,
+                                                allow_empty=False)
+                    ep_port_id     = chk_string('endpoint[#{}].port_id.uuid'.format(i),
+                                                endpoint.port_id.uuid,
+                                                allow_empty=False)
+                except Exception as e:
+                    LOGGER.exception('Invalid arguments:')
+                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
+
+                if (len(ep_context_id) > 0) and (ep_context_id != DEFAULT_CONTEXT_ID):
+                    msg = ' '.join([
+                        'Unsupported Context({}) in Endpoint(#{}) of Link({}).',
+                        'Only default Context({}) is currently supported.',
+                        'Optionally, leave field empty to use default Context.',
+                    ])
+                    msg = msg.format(ep_context_id, i, link_id, DEFAULT_CONTEXT_ID)
+                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
+                elif len(ep_context_id) == 0:
+                    ep_context_id = DEFAULT_CONTEXT_ID
+
+                if (len(ep_topology_id) > 0) and (ep_topology_id != DEFAULT_TOPOLOGY_ID):
+                    msg = ' '.join([
+                        'Unsupported Topology({}) in Endpoint(#{}) of Link({}).',
+                        'Only default Topology({}) is currently supported.',
+                        'Optionally, leave field empty to use default Topology.',
+                    ])
+                    msg = msg.format(ep_topology_id, i, link_id, DEFAULT_TOPOLOGY_ID)
+                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
+                elif len(ep_topology_id) == 0:
+                    ep_topology_id = DEFAULT_TOPOLOGY_ID
+
+                if ep_device_id in added_devices_and_endpoints:
+                    msg = 'Duplicated Device({}) in Endpoint(#{}) of Link({}).'
+                    msg = msg.format(ep_device_id, i, link_id)
+                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
+
+                if not db_topology.devices.contains(ep_device_id):
+                    msg = 'Device({}) in Endpoint(#{}) of Link({}) does not exist in the database.'
+                    msg = msg.format(ep_device_id, i, link_id)
+                    raise ServiceException(grpc.StatusCode.NOT_FOUND, msg)
+
+                added_device_and_endpoints = added_devices_and_endpoints.setdefault(ep_device_id, set())
+
+                # should never happen since same device cannot appear 2 times in the link
+                if ep_port_id in added_device_and_endpoints:                                # pragma: no cover
+                    msg = 'Duplicated Device({})/Port({}) in Endpoint(#{}) of Link({}).'    # pragma: no cover
+                    msg = msg.format(ep_device_id, ep_port_id, i, link_id)                  # pragma: no cover
+                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)           # pragma: no cover
+
+                if not db_topology.device(ep_device_id).endpoints.contains(ep_port_id):
+                    msg = 'Device({})/Port({}) in Endpoint(#{}) of Link({}) does not exist in the database.'
+                    msg = msg.format(ep_device_id, ep_port_id, i, link_id)
+                    raise ServiceException(grpc.StatusCode.NOT_FOUND, msg)
+
+                added_device_and_endpoints.add(ep_port_id)
+                device_endpoint_pairs.append((ep_device_id, ep_port_id))
+
+            # ----- Implement changes in the database ------------------------------------------------------------------
+            db_link = db_topology.link(link_id).create()
+            for device_id,endpoint_id in device_endpoint_pairs:
+                link_endpoint_id = '{}/{}'.format(device_id, endpoint_id)
+                db_endpoint = db_topology.device(ep_device_id).endpoint(ep_port_id)
+                db_link.endpoint(link_endpoint_id).create(db_endpoint)
+
+            # ----- Compose reply --------------------------------------------------------------------------------------
+            reply = LinkId(**db_link.dump_id())
+            LOGGER.debug('AddLink reply: {}'.format(str(reply)))
+            ADDLINK_COUNTER_COMPLETED.inc()
+            return reply
+        except ServiceException as e:
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:                                      # pragma: no cover
+            LOGGER.exception('AddLink exception')                   # pragma: no cover
+            ADDLINK_COUNTER_FAILED.inc()                            # pragma: no cover
+            grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))    # pragma: no cover
+
+    @DELETELINK_HISTOGRAM_DURATION.time()
+    def DeleteLink(self, request : LinkId, grpc_context : grpc.ServicerContext) -> Empty:
+        DELETELINK_COUNTER_STARTED.inc()
+        try:
+            LOGGER.debug('DeleteLink request: {}'.format(str(request)))
+
+            # ----- Validate request data and pre-conditions -----------------------------------------------------------
+            try:
+                link_id = chk_string('link_id.link_id.uuid',
+                                     request.link_id.uuid,
+                                     allow_empty=False)
+            except Exception as e:
+                LOGGER.exception('Invalid arguments:')
+                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
+
+            db_context = self.database.context(DEFAULT_CONTEXT_ID).create()
+            db_topology = db_context.topology(DEFAULT_TOPOLOGY_ID).create()
+
+            if not db_topology.links.contains(link_id):
+                msg = 'Link({}) does not exist in the database.'
+                msg = msg.format(link_id)
+                raise ServiceException(grpc.StatusCode.NOT_FOUND, msg)
+
+            # ----- Implement changes in the database ------------------------------------------------------------------
+            db_topology.link(link_id).delete()
+
+            # ----- Compose reply --------------------------------------------------------------------------------------
+            reply = Empty()
+            LOGGER.debug('DeleteLink reply: {}'.format(str(reply)))
+            DELETELINK_COUNTER_COMPLETED.inc()
+            return reply
+        except ServiceException as e:
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:                                      # pragma: no cover
+            LOGGER.exception('DeleteLink exception')                # pragma: no cover
+            DELETELINK_COUNTER_FAILED.inc()                         # pragma: no cover
+            grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))    # pragma: no cover
diff --git a/src/context/tests/test_integration.py b/src/context/tests/test_integration.py
index eab068b493a06754ec335ea118fa60e671fddec7..b8d5de420bb9ead65b9fdf7dab3cd5147765b8cc 100644
--- a/src/context/tests/test_integration.py
+++ b/src/context/tests/test_integration.py
@@ -1,24 +1,24 @@
-import logging, os, pytest, sys
-
-from pathlib import Path
-sys.path.append(__file__.split('src')[0] + 'src')
-print(sys.path)
-
-from context.client.ContextClient import ContextClient
-from context.proto.context_pb2 import Empty
-from .tools.ValidateTopology import validate_topology_dict
-
-LOGGER = logging.getLogger(__name__)
-LOGGER.setLevel(logging.DEBUG)
-
-@pytest.fixture(scope='session')
-def remote_context_client():
-    address = os.environ.get('TEST_TARGET_ADDRESS')
-    if(address is None): raise Exception('EnvironmentVariable(TEST_TARGET_ADDRESS) not specified')
-    port = os.environ.get('TEST_TARGET_PORT')
-    if(port is None): raise Exception('EnvironmentVariable(TEST_TARGET_PORT) not specified')
-    return ContextClient(address=address, port=port)
-
-def test_remote_get_topology(remote_context_client):
-    response = remote_context_client.GetTopology(Empty())
-    validate_topology_dict(response)
+#import logging, os, pytest, sys
+#
+#from pathlib import Path
+#sys.path.append(__file__.split('src')[0] + 'src')
+#print(sys.path)
+#
+#from context.client.ContextClient import ContextClient
+#from context.proto.context_pb2 import Empty
+#from .tools.ValidateTopology import validate_topology_dict
+#
+#LOGGER = logging.getLogger(__name__)
+#LOGGER.setLevel(logging.DEBUG)
+#
+#@pytest.fixture(scope='session')
+#def remote_context_client():
+#    address = os.environ.get('TEST_TARGET_ADDRESS')
+#    if(address is None): raise Exception('EnvironmentVariable(TEST_TARGET_ADDRESS) not specified')
+#    port = os.environ.get('TEST_TARGET_PORT')
+#    if(port is None): raise Exception('EnvironmentVariable(TEST_TARGET_PORT) not specified')
+#    return ContextClient(address=address, port=port)
+#
+#def test_remote_get_topology(remote_context_client):
+#    response = remote_context_client.GetTopology(Empty())
+#    validate_topology_dict(response)
diff --git a/src/context/tests/test_unitary.py b/src/context/tests/test_unitary.py
index c05aa930c9c5a713b7ba246f7b0a342c38c13611..104736af195f0eb0643563f816bdaeefa5b0743b 100644
--- a/src/context/tests/test_unitary.py
+++ b/src/context/tests/test_unitary.py
@@ -1,11 +1,11 @@
-import logging, pytest
+import copy, grpc, logging, pytest
 from google.protobuf.json_format import MessageToDict
 from common.database.Factory import get_database, DatabaseEngineEnum
 from common.database.api.Database import Database
 from common.database.tests.script import populate_example
-from common.tests.Assertions import validate_topology
+from common.tests.Assertions import validate_empty, validate_link_id, validate_topology
 from context.client.ContextClient import ContextClient
-from context.proto.context_pb2 import Empty
+from context.proto.context_pb2 import Empty, Link, LinkId
 from context.service.ContextService import ContextService
 from context.Config import GRPC_SERVICE_PORT, GRPC_MAX_WORKERS, GRPC_GRACE_PERIOD
 
@@ -14,35 +14,206 @@ port = 10000 + GRPC_SERVICE_PORT # avoid first 1024 privileged ports to avoid ev
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.DEBUG)
 
+LINK_ID = {'link_id': {'uuid': 'dev1/to-dev2 ==> dev2/to-dev1'}}
+LINK = {
+    'link_id': {'link_id': {'uuid': 'dev1/to-dev2 ==> dev2/to-dev1'}},
+    'endpointList' : [
+        {
+            'topoId': {
+                'contextId': {'contextUuid': {'uuid': 'admin'}},
+                'topoId': {'uuid': 'admin'}
+            },
+            'dev_id': {'device_id': {'uuid': 'dev1'}},
+            'port_id': {'uuid' : 'to-dev2'}
+        },
+        {
+            'topoId': {
+                'contextId': {'contextUuid': {'uuid': 'admin'}},
+                'topoId': {'uuid': 'admin'}
+            },
+            'dev_id': {'device_id': {'uuid': 'dev2'}},
+            'port_id': {'uuid' : 'to-dev1'}
+        },
+    ]
+}
+
 @pytest.fixture(scope='session')
-def database():
+def context_database():
     _database = get_database(engine=DatabaseEngineEnum.INMEMORY)
     return _database
 
 @pytest.fixture(scope='session')
-def service(database : Database):
+def context_service(context_database : Database):
     _service = ContextService(
-        database, port=port, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD)
+        context_database, port=port, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD)
     _service.start()
     yield _service
     _service.stop()
 
 @pytest.fixture(scope='session')
-def client(service):
+def context_client(context_service):
     _client = ContextClient(address='127.0.0.1', port=port)
     yield _client
     _client.close()
 
-def test_get_topology_empty(client : ContextClient, database : Database):
-    database.clear_all()
+def test_get_topology_empty(context_client : ContextClient, context_database : Database):
+    # should work
+    context_database.clear_all()
     validate_topology(MessageToDict(
-        client.GetTopology(Empty()),
+        context_client.GetTopology(Empty()),
+        including_default_value_fields=True, preserving_proto_field_name=True,
+        use_integers_for_enums=False))
+
+def test_get_topology_completed(context_client : ContextClient, context_database : Database):
+    # should work
+    populate_example(context_database)
+    validate_topology(MessageToDict(
+        context_client.GetTopology(Empty()),
+        including_default_value_fields=True, preserving_proto_field_name=True,
+        use_integers_for_enums=False))
+
+def test_delete_link_empty_uuid(context_client : ContextClient):
+    # should fail with link not found
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link_id = copy.deepcopy(LINK_ID)
+        copy_link_id['link_id']['uuid'] = ''
+        context_client.DeleteLink(LinkId(**copy_link_id))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    assert e.value.details() == 'link_id.link_id.uuid() string is empty.'
+
+def test_add_link_already_exists(context_client : ContextClient):
+    # should fail with link already exists
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        context_client.AddLink(Link(**LINK))
+    assert e.value.code() == grpc.StatusCode.ALREADY_EXISTS
+    assert e.value.details() == 'Link(dev1/to-dev2 ==> dev2/to-dev1) already exists in the database.'
+
+def test_delete_link(context_client : ContextClient):
+    # should work
+    validate_empty(MessageToDict(
+        context_client.DeleteLink(LinkId(**LINK_ID)),
+        including_default_value_fields=True, preserving_proto_field_name=True,
+        use_integers_for_enums=False))
+
+def test_delete_link_not_existing(context_client : ContextClient):
+    # should fail with link not found
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        context_client.DeleteLink(LinkId(**LINK_ID))
+    assert e.value.code() == grpc.StatusCode.NOT_FOUND
+    assert e.value.details() == 'Link(dev1/to-dev2 ==> dev2/to-dev1) does not exist in the database.'
+
+def test_add_link_uuid_empty(context_client : ContextClient):
+    # should fail with link uuid empty
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['link_id']['link_id']['uuid'] = ''
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    assert e.value.details() == 'link.link_id.link_id.uuid() string is empty.'
+
+def test_add_link_endpoint_wrong_context(context_client : ContextClient):
+    # should fail with unsupported context
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][0]['topoId']['contextId']['contextUuid']['uuid'] = 'wrong-context'
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    msg = ' '.join([
+        'Unsupported Context(wrong-context) in Endpoint(#0) of Link(dev1/to-dev2 ==> dev2/to-dev1).',
+        'Only default Context(admin) is currently supported.',
+        'Optionally, leave field empty to use default Context.',
+    ])
+    assert e.value.details() == msg
+
+def test_add_link_endpoint_wrong_topology(context_client : ContextClient):
+    # should fail with unsupported topology
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][0]['topoId']['topoId']['uuid'] = 'wrong-topo'
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    msg = ' '.join([
+        'Unsupported Topology(wrong-topo) in Endpoint(#0) of Link(dev1/to-dev2 ==> dev2/to-dev1).',
+        'Only default Topology(admin) is currently supported.',
+        'Optionally, leave field empty to use default Topology.',
+    ])
+    assert e.value.details() == msg
+
+def test_add_link_empty_device_uuid(context_client : ContextClient):
+    # should fail with port uuid is empty
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][0]['dev_id']['device_id']['uuid'] = ''
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    assert e.value.details() == 'endpoint[#0].dev_id.device_id.uuid() string is empty.'
+
+def test_add_link_endpoint_wrong_device(context_client : ContextClient):
+    # should fail with wrong endpoint device
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][0]['dev_id']['device_id']['uuid'] = 'wrong-device'
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.NOT_FOUND
+    msg = 'Device(wrong-device) in Endpoint(#0) of Link(dev1/to-dev2 ==> dev2/to-dev1) does not exist in the database.'
+    assert e.value.details() == msg
+
+def test_add_link_endpoint_wrong_port(context_client : ContextClient):
+    # should fail with wrong endpoint port
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][0]['port_id']['uuid'] = 'wrong-port'
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.NOT_FOUND
+    msg = 'Device(dev1)/Port(wrong-port) in Endpoint(#0) of Link(dev1/to-dev2 ==> dev2/to-dev1) does not exist in the database.'
+    assert e.value.details() == msg
+
+def test_add_link_endpoint_duplicated_device(context_client : ContextClient):
+    # should fail with duplicated endpoint device
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][1]['dev_id']['device_id']['uuid'] = 'dev1'
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    msg = 'Duplicated Device(dev1) in Endpoint(#1) of Link(dev1/to-dev2 ==> dev2/to-dev1).'
+    assert e.value.details() == msg
+
+def test_add_link_empty_port_uuid(context_client : ContextClient):
+    # should fail with port uuid is empty
+    with pytest.raises(grpc._channel._InactiveRpcError) as e:
+        copy_link = copy.deepcopy(LINK)
+        copy_link['endpointList'][0]['port_id']['uuid'] = ''
+        context_client.AddLink(Link(**copy_link))
+    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+    assert e.value.details() == 'endpoint[#0].port_id.uuid() string is empty.'
+
+def test_add_link(context_client : ContextClient):
+    # should work
+    validate_link_id(MessageToDict(
+        context_client.AddLink(Link(**LINK)),
+        including_default_value_fields=True, preserving_proto_field_name=True,
+        use_integers_for_enums=False))
+
+def test_delete_link_2(context_client : ContextClient):
+    # should work
+    validate_empty(MessageToDict(
+        context_client.DeleteLink(LinkId(**LINK_ID)),
         including_default_value_fields=True, preserving_proto_field_name=True,
         use_integers_for_enums=False))
 
-def test_get_topology_completed(client : ContextClient, database : Database):
-    populate_example(database)
+def test_add_link_default_endpoint_context_topology(context_client : ContextClient):
+    # should work
+    copy_link = copy.deepcopy(LINK)
+    copy_link['endpointList'][0]['topoId']['contextId']['contextUuid']['uuid'] = ''
+    copy_link['endpointList'][0]['topoId']['topoId']['uuid'] = ''
+    validate_link_id(MessageToDict(
+            context_client.AddLink(Link(**copy_link)),
+            including_default_value_fields=True, preserving_proto_field_name=True,
+            use_integers_for_enums=False))
+
+def test_get_topology_completed_2(context_client : ContextClient):
+    # should work
     validate_topology(MessageToDict(
-        client.GetTopology(Empty()),
+        context_client.GetTopology(Empty()),
         including_default_value_fields=True, preserving_proto_field_name=True,
         use_integers_for_enums=False))
diff --git a/src/context/tests/tools/ValidateTopology.py b/src/context/tests/tools/ValidateTopology.py
deleted file mode 100644
index b52546e39c27292bec4f11755dade987929e5e71..0000000000000000000000000000000000000000
--- a/src/context/tests/tools/ValidateTopology.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def validate_topology_dict(topology):
-    assert type(topology) is dict
-    assert len(topology.keys()) > 0
-    assert 'topoId' in topology
-    assert 'device' in topology
-    assert 'link' in topology
diff --git a/src/context/tests/tools/__init__.py b/src/context/tests/tools/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/device/client/DeviceClient.py b/src/device/client/DeviceClient.py
index 3e9f83f3f5459a738f7863955b78164c81ec21a3..a517ebdd1551465f9404714ec07bd7326cad7c2d 100644
--- a/src/device/client/DeviceClient.py
+++ b/src/device/client/DeviceClient.py
@@ -1,5 +1,6 @@
 import grpc, logging
 from common.tools.RetryDecorator import retry, delay_exponential
+from device.proto.context_pb2 import Device, DeviceId, Empty
 from device.proto.device_pb2_grpc import DeviceServiceStub
 
 LOGGER = logging.getLogger(__name__)
@@ -25,21 +26,21 @@ class DeviceClient:
         self.stub = None
 
     @retry(exceptions=set(), max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
-    def AddDevice(self, request):
+    def AddDevice(self, request : Device) -> DeviceId:
         LOGGER.debug('AddDevice request: {}'.format(request))
         response = self.stub.AddDevice(request)
         LOGGER.debug('AddDevice result: {}'.format(response))
         return response
 
     @retry(exceptions=set(), max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
-    def ConfigureDevice(self, request):
+    def ConfigureDevice(self, request : Device) -> DeviceId:
         LOGGER.debug('ConfigureDevice request: {}'.format(request))
         response = self.stub.ConfigureDevice(request)
         LOGGER.debug('ConfigureDevice result: {}'.format(response))
         return response
 
     @retry(exceptions=set(), max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
-    def DeleteDevice(self, request):
+    def DeleteDevice(self, request : DeviceId) -> Empty:
         LOGGER.debug('DeleteDevice request: {}'.format(request))
         response = self.stub.DeleteDevice(request)
         LOGGER.debug('DeleteDevice result: {}'.format(response))
diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index afacfc27881fcad524e8218e88d8b36a3ec4ed11..2aa135c287e153031b2b5651af19f41112416d4e 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -55,16 +55,29 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
 
             # ----- Validate request data and pre-conditions -----------------------------------------------------------
             try:
-                device_uuid = chk_string('device_uuid', request.device_id.device_id.uuid, allow_empty=False)
-                device_type = chk_string('device_type', request.device_type, allow_empty=False)
-                device_config = chk_string('device_config', request.device_config.device_config, allow_empty=True)
-                device_opstat = chk_options('devOperationalStatus', request.devOperationalStatus,
+                device_id     = chk_string ('device.device_id.device_id.uuid',
+                                            request.device_id.device_id.uuid,
+                                            allow_empty=False)
+                device_type   = chk_string ('device.device_type',
+                                            request.device_type,
+                                            allow_empty=False)
+                device_config = chk_string ('device.device_config.device_config',
+                                            request.device_config.device_config,
+                                            allow_empty=True)
+                device_opstat = chk_options('device.devOperationalStatus',
+                                            request.devOperationalStatus,
                                             operationalstatus_enum_values())
             except Exception as e:
                 LOGGER.exception('Invalid arguments:')
                 raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
 
             device_opstat = to_operationalstatus_enum(device_opstat)
+            # should not happen because gRPC limits accepted values in enums
+            if device_opstat is None:                                           # pragma: no cover
+                msg = 'Unsupported OperationalStatus({}).'                      # pragma: no cover
+                msg = msg.format(request.devOperationalStatus)                  # pragma: no cover
+                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)   # pragma: no cover
+
             if device_opstat == OperationalStatus.KEEP_STATE:
                 msg = ' '.join([
                     'Device has to be created with either ENABLED/DISABLED Operational State.',
@@ -75,71 +88,81 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             db_context = self.database.context(DEFAULT_CONTEXT_ID).create()
             db_topology = db_context.topology(DEFAULT_TOPOLOGY_ID).create()
 
-            if db_topology.devices.contains(device_uuid):
-                msg = 'device_uuid({}) already exists.'
-                msg = msg.format(device_uuid)
+            if db_topology.devices.contains(device_id):
+                msg = 'Device({}) already exists in the database.'
+                msg = msg.format(device_id)
                 raise ServiceException(grpc.StatusCode.ALREADY_EXISTS, msg)
 
             added_endpoint_uuids = set()
             endpoint_pairs : List[Tuple[str, str]] = []
             for i,endpoint in enumerate(request.endpointList):
-                contextId = endpoint.port_id.topoId.contextId.contextUuid.uuid
-                if (len(contextId) > 0) and (contextId != DEFAULT_CONTEXT_ID):
+                try:
+                    ep_context_id  = chk_string('endpoint[#{}].port_id.topoId.contextId.contextUuid.uuid'.format(i),
+                                                endpoint.port_id.topoId.contextId.contextUuid.uuid,
+                                                allow_empty=True)
+                    ep_topology_id = chk_string('endpoint[#{}].port_id.topoId.topoId.uuid'.format(i),
+                                                endpoint.port_id.topoId.topoId.uuid,
+                                                allow_empty=True)
+                    ep_device_id   = chk_string('endpoint[#{}].port_id.dev_id.device_id.uuid'.format(i),
+                                                endpoint.port_id.dev_id.device_id.uuid,
+                                                allow_empty=True)
+                    ep_port_id     = chk_string('endpoint[#{}].port_id.port_id.uuid'.format(i),
+                                                endpoint.port_id.port_id.uuid,
+                                                allow_empty=False)
+                    ep_port_type   = chk_string('endpoint[#{}].port_type'.format(i),
+                                                endpoint.port_type,
+                                                allow_empty=False)
+                except Exception as e:
+                    LOGGER.exception('Invalid arguments:')
+                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
+
+                if (len(ep_context_id) > 0) and (ep_context_id != DEFAULT_CONTEXT_ID):
                     msg = ' '.join([
-                        'Unsupported context_id({}) in endpoint #{}.',
-                        'Only default context_id({}) is currently supported.',
-                        'Optionally, leave field empty to use default context_id.',
+                        'Unsupported Context({}) in Endpoint(#{}) of Device({}).',
+                        'Only default Context({}) is currently supported.',
+                        'Optionally, leave field empty to use default Context.',
                     ])
-                    msg = msg.format(contextId, i, DEFAULT_CONTEXT_ID)
+                    msg = msg.format(ep_context_id, i, device_id, DEFAULT_CONTEXT_ID)
                     raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
-                elif len(contextId) == 0:
-                    contextId = DEFAULT_CONTEXT_ID
+                elif len(ep_context_id) == 0:
+                    ep_context_id = DEFAULT_CONTEXT_ID
 
-                topoId = endpoint.port_id.topoId.topoId.uuid
-                if (len(topoId) > 0) and (topoId != DEFAULT_TOPOLOGY_ID):
+                if (len(ep_topology_id) > 0) and (ep_topology_id != DEFAULT_TOPOLOGY_ID):
                     msg = ' '.join([
-                        'Unsupported topology_id({}) in endpoint #{}.',
-                        'Only default topology_id({}) is currently supported.',
-                        'Optionally, leave field empty to use default topology_id.',
+                        'Unsupported Topology({}) in Endpoint(#{}) of Device({}).',
+                        'Only default Topology({}) is currently supported.',
+                        'Optionally, leave field empty to use default Topology.',
                     ])
-                    msg = msg.format(topoId, i, DEFAULT_TOPOLOGY_ID)
+                    msg = msg.format(ep_topology_id, i, device_id, DEFAULT_TOPOLOGY_ID)
                     raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
-                elif len(topoId) == 0:
-                    topoId = DEFAULT_TOPOLOGY_ID
+                elif len(ep_topology_id) == 0:
+                    ep_topology_id = DEFAULT_TOPOLOGY_ID
 
-                dev_id = endpoint.port_id.dev_id.device_id.uuid
-                if (len(dev_id) > 0) and (dev_id != device_uuid):
+                if (len(ep_device_id) > 0) and (ep_device_id != device_id):
                     msg = ' '.join([
-                        'Wrong device_id({}) in endpoint #{}.',
-                        'Parent specified in message is device_id({}).',
-                        'Optionally, leave field empty to use parent device_id.',
+                        'Wrong Device({}) in Endpoint(#{}).',
+                        'Parent specified in message is Device({}).',
+                        'Optionally, leave field empty to use parent Device.',
                     ])
-                    msg = msg.format(dev_id, i, device_uuid)
+                    msg = msg.format(ep_device_id, i, device_id)
                     raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
-                elif len(dev_id) == 0:
-                    dev_id = device_uuid
+                elif len(ep_device_id) == 0:
+                    ep_device_id = device_id
 
-                try:
-                    port_id = chk_string('port_uuid', endpoint.port_id.port_id.uuid, allow_empty=False)
-                    port_type = chk_string('port_type', endpoint.port_type, allow_empty=False)
-                except Exception as e:
-                    LOGGER.exception('Invalid arguments:')
-                    raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
-
-                if port_id in added_endpoint_uuids:
-                    msg = 'Duplicated port_id({}) in device_id({}).'
-                    msg = msg.format(port_id, device_uuid)
+                if ep_port_id in added_endpoint_uuids:
+                    msg = 'Duplicated Port({}) in Endpoint(#{}) of Device({}).'
+                    msg = msg.format(ep_port_id, i, device_id)
                     raise ServiceException(grpc.StatusCode.ALREADY_EXISTS, msg)
 
-                added_endpoint_uuids.add(port_id)
-                endpoint_pairs.append((port_id, port_type))
+                added_endpoint_uuids.add(ep_port_id)
+                endpoint_pairs.append((ep_port_id, ep_port_type))
 
-            # ----- Implement changes in database ----------------------------------------------------------------------
-            db_device = db_topology.device(device_uuid).create(device_type, device_config, device_opstat)
+            # ----- Implement changes in the database ------------------------------------------------------------------
+            db_device = db_topology.device(device_id).create(device_type, device_config, device_opstat)
             for port_id,port_type in endpoint_pairs: db_device.endpoint(port_id).create(port_type)
 
             # ----- Compose reply --------------------------------------------------------------------------------------
-            reply = DeviceId(device_id=dict(uuid=device_uuid))
+            reply = DeviceId(**db_device.dump_id())
             LOGGER.debug('AddDevice reply: {}'.format(str(reply)))
             ADDDEVICE_COUNTER_COMPLETED.inc()
             return reply
@@ -154,54 +177,64 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
     def ConfigureDevice(self, request : Device, grpc_context : grpc.ServicerContext) -> DeviceId:
         CONFIGUREDEVICE_COUNTER_STARTED.inc()
         try:
-            LOGGER.info('ConfigureDevice request: {}'.format(str(request)))
+            LOGGER.debug('ConfigureDevice request: {}'.format(str(request)))
 
             # ----- Validate request data and pre-conditions -----------------------------------------------------------
             try:
-                device_uuid = chk_string('device_uuid', request.device_id.device_id.uuid, allow_empty=False)
-                device_type = chk_string('device_type', request.device_type, allow_empty=True)
-                device_config = chk_string('device_config', request.device_config.device_config, allow_empty=True)
-                device_opstat = chk_options('devOperationalStatus', request.devOperationalStatus,
+                device_id     = chk_string ('device.device_id.device_id.uuid',
+                                            request.device_id.device_id.uuid,
+                                            allow_empty=False)
+                device_type   = chk_string ('device.device_type',
+                                            request.device_type,
+                                            allow_empty=True)
+                device_config = chk_string ('device.device_config.device_config',
+                                            request.device_config.device_config,
+                                            allow_empty=True)
+                device_opstat = chk_options('device.devOperationalStatus',
+                                            request.devOperationalStatus,
                                             operationalstatus_enum_values())
             except Exception as e:
                 LOGGER.exception('Invalid arguments:')
                 raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
 
             device_opstat = to_operationalstatus_enum(device_opstat)
-            if device_opstat is None:
-                msg = 'Unsupported OperationalStatus({}).'
-                msg = msg.format(request.devOperationalStatus)
-                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
+            # should not happen because gRPC limits accepted values in enums
+            if device_opstat is None:                                           # pragma: no cover
+                msg = 'Unsupported OperationalStatus({}).'                      # pragma: no cover
+                msg = msg.format(request.devOperationalStatus)                  # pragma: no cover
+                raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)   # pragma: no cover
 
             db_context = self.database.context(DEFAULT_CONTEXT_ID).create()
             db_topology = db_context.topology(DEFAULT_TOPOLOGY_ID).create()
 
-            if not db_topology.devices.contains(device_uuid):
-                msg = 'device_uuid({}) does not exist.'
-                msg = msg.format(device_uuid)
+            if not db_topology.devices.contains(device_id):
+                msg = 'Device({}) does not exist in the database.'
+                msg = msg.format(device_id)
                 raise ServiceException(grpc.StatusCode.NOT_FOUND, msg)
 
-            db_device = db_topology.device(device_uuid)
+            db_device = db_topology.device(device_id)
             db_device_attributes = db_device.attributes.get(attributes=['device_type'])
-            if len(db_device_attributes) == 0:
-                msg = 'attribute device_type for device_uuid({}) does not exist.'
-                msg = msg.format(device_uuid)
-                raise ServiceException(grpc.StatusCode.FAILED_PRECONDITION, msg)
+            # should not happen, device creation through Database API ensures all fields are always present
+            if len(db_device_attributes) == 0:                                                  # pragma: no cover
+                msg = 'Attribute device_type for Device({}) does not exist in the database.'    # pragma: no cover
+                msg = msg.format(device_id)                                                     # pragma: no cover
+                raise ServiceException(grpc.StatusCode.FAILED_PRECONDITION, msg)                # pragma: no cover
 
             db_device_type = db_device_attributes.get('device_type')
-            if len(db_device_type) == 0:
-                msg = 'attribute device_type for device_uuid({}) is empty.'
-                msg = msg.format(device_uuid)
-                raise ServiceException(grpc.StatusCode.FAILED_PRECONDITION, msg)
+            # should not happen, device creation through Database API ensures all fields are always present
+            if len(db_device_type) == 0:                                                # pragma: no cover
+                msg = 'Attribute device_type for Device({}) is empty in the database.'  # pragma: no cover
+                msg = msg.format(device_id)                                             # pragma: no cover
+                raise ServiceException(grpc.StatusCode.FAILED_PRECONDITION, msg)        # pragma: no cover
 
             if db_device_type != device_type:
-                msg = 'Device({}) has Type({}). Cannot be changed to Type({}).'
-                msg = msg.format(device_uuid, db_device_type, device_type)
+                msg = 'Device({}) has Type({}) in the database. Cannot be changed to Type({}).'
+                msg = msg.format(device_id, db_device_type, device_type)
                 raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
 
             if len(request.endpointList) > 0:
                 msg = 'Endpoints belonging to Device({}) cannot be modified.'
-                msg = msg.format(device_uuid)
+                msg = msg.format(device_id)
                 raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, msg)
 
             update_attributes = {}
@@ -212,22 +245,20 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             if device_opstat != OperationalStatus.KEEP_STATE:
                 update_attributes['device_operational_status'] = device_opstat
 
-            LOGGER.info('update_attributes={}'.format(str(update_attributes)))
-
             if len(update_attributes) == 0:
                 msg = ' '.join([
                     'Any change has been requested for Device({}).',
-                    'Either specify a new configuration or a new device state.',
+                    'Either specify a new configuration or a new device operational status.',
                 ])
-                msg = msg.format(device_uuid)
+                msg = msg.format(device_id)
                 raise ServiceException(grpc.StatusCode.ABORTED, msg)
 
-            # ----- Implement changes in database ----------------------------------------------------------------------
+            # ----- Implement changes in the database ------------------------------------------------------------------
             db_device.update(update_attributes=update_attributes)
 
             # ----- Compose reply --------------------------------------------------------------------------------------
-            reply = DeviceId(device_id=dict(uuid=device_uuid))
-            LOGGER.info('ConfigureDevice reply: {}'.format(str(reply)))
+            reply = DeviceId(**db_device.dump_id())
+            LOGGER.debug('ConfigureDevice reply: {}'.format(str(reply)))
             CONFIGUREDEVICE_COUNTER_COMPLETED.inc()
             return reply
         except ServiceException as e:
@@ -245,7 +276,9 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
 
             # ----- Validate request data and pre-conditions -----------------------------------------------------------
             try:
-                device_uuid = chk_string('device_uuid', request.device_id.uuid, allow_empty=False)
+                device_id = chk_string('device_id.device_id.uuid',
+                                       request.device_id.uuid,
+                                       allow_empty=False)
             except Exception as e:
                 LOGGER.exception('Invalid arguments:')
                 raise ServiceException(grpc.StatusCode.INVALID_ARGUMENT, str(e))
@@ -253,13 +286,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             db_context = self.database.context(DEFAULT_CONTEXT_ID).create()
             db_topology = db_context.topology(DEFAULT_TOPOLOGY_ID).create()
 
-            if not db_topology.devices.contains(device_uuid):
-                msg = 'device_uuid({}) does not exist.'
-                msg = msg.format(device_uuid)
+            if not db_topology.devices.contains(device_id):
+                msg = 'Device({}) does not exist in the database.'
+                msg = msg.format(device_id)
                 raise ServiceException(grpc.StatusCode.NOT_FOUND, msg)
 
-            # ----- Implement changes in database ----------------------------------------------------------------------
-            db_topology.device(device_uuid).delete()
+            # ----- Implement changes in the database ------------------------------------------------------------------
+            db_topology.device(device_id).delete()
 
             # ----- Compose reply --------------------------------------------------------------------------------------
             reply = Empty()
diff --git a/src/device/tests/test_unitary.py b/src/device/tests/test_unitary.py
index 43e1c45aec638b93dbf0a9bcd5888c7ccf3d9302..9834c5c39761997b336ed9feda11d6899dde19c0 100644
--- a/src/device/tests/test_unitary.py
+++ b/src/device/tests/test_unitary.py
@@ -11,12 +11,12 @@ from device.Config import GRPC_SERVICE_PORT, GRPC_MAX_WORKERS, GRPC_GRACE_PERIOD
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.DEBUG)
 
-DEVICE_ID = {'device_id': {'uuid': 'test-device-001'}}
+DEVICE_ID = {'device_id': {'uuid': 'dev1'}}
 DEVICE = {
-    'device_id': {'device_id': {'uuid': 'test-device-001'}},
+    'device_id': {'device_id': {'uuid': 'dev1'}},
     'device_type': 'ROADM',
-    'device_config': {'device_config': ''},
-    'devOperationalStatus': 1,
+    'device_config': {'device_config': '<config/>'},
+    'devOperationalStatus': OperationalStatus.ENABLED.value,
     'endpointList' : [
         {
             'port_id': {
@@ -24,10 +24,10 @@ DEVICE = {
                     'contextId': {'contextUuid': {'uuid': 'admin'}},
                     'topoId': {'uuid': 'admin'}
                 },
-                'dev_id': {'device_id': {'uuid': 'test-device-001'}},
-                'port_id': {'uuid' : 'port-101'}
+                'dev_id': {'device_id': {'uuid': 'dev1'}},
+                'port_id': {'uuid' : 'to-dev2'}
             },
-            'port_type': 'LINE'
+            'port_type': 'WDM'
         },
         {
             'port_id': {
@@ -35,53 +35,68 @@ DEVICE = {
                     'contextId': {'contextUuid': {'uuid': 'admin'}},
                     'topoId': {'uuid': 'admin'}
                 },
-                'dev_id': {'device_id': {'uuid': 'test-device-001'}},
-                'port_id': {'uuid' : 'port-102'}
+                'dev_id': {'device_id': {'uuid': 'dev1'}},
+                'port_id': {'uuid' : 'to-dev3'}
             },
-            'port_type': 'LINE'
+            'port_type': 'WDM'
+        },
+        {
+            'port_id': {
+                'topoId': {
+                    'contextId': {'contextUuid': {'uuid': 'admin'}},
+                    'topoId': {'uuid': 'admin'}
+                },
+                'dev_id': {'device_id': {'uuid': 'dev1'}},
+                'port_id': {'uuid' : 'to-dev4'}
+            },
+            'port_type': 'WDM'
         },
     ]
 }
 
 @pytest.fixture(scope='session')
-def service():
-    database = get_database(engine=DatabaseEngineEnum.INMEMORY)
+def device_database():
+    _database = get_database(engine=DatabaseEngineEnum.INMEMORY)
+    return _database
+
+@pytest.fixture(scope='session')
+def device_service(device_database):
     _service = DeviceService(
-        database, port=GRPC_SERVICE_PORT, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD)
+        device_database, port=GRPC_SERVICE_PORT, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD)
     _service.start()
     yield _service
     _service.stop()
 
 @pytest.fixture(scope='session')
-def client(service):
+def device_client(device_service):
     _client = DeviceClient(address='127.0.0.1', port=GRPC_SERVICE_PORT)
     yield _client
     _client.close()
 
-def test_create_empty_device_uuid(client : DeviceClient):
+def test_add_device_empty_device_uuid(device_client : DeviceClient):
     # should fail with device uuid is empty
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['device_id']['device_id']['uuid'] = ''
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'device_uuid() string is empty.'
+    assert e.value.details() == 'device.device_id.device_id.uuid() string is empty.'
 
-def test_create_empty_device_type(client : DeviceClient):
+def test_add_device_empty_device_type(device_client : DeviceClient):
     # should fail with device type is empty
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['device_type'] = ''
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'device_type() string is empty.'
+    assert e.value.details() == 'device.device_type() string is empty.'
 
-def test_create_wrong_device_operational_status(client : DeviceClient):
+def test_add_device_wrong_device_operational_status(device_client : DeviceClient):
     # should fail with wrong device operational status
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['devOperationalStatus'] = OperationalStatus.KEEP_STATE.value
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
     msg = ' '.join([
         'Device has to be created with either ENABLED/DISABLED Operational State.',
@@ -89,183 +104,183 @@ def test_create_wrong_device_operational_status(client : DeviceClient):
     ])
     assert e.value.details() == msg
 
-def test_create_endpoint_wrong_context(client : DeviceClient):
+def test_add_device_endpoint_wrong_context(device_client : DeviceClient):
     # should fail with unsupported context
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['endpointList'][0]['port_id']['topoId']['contextId']['contextUuid']['uuid'] = 'wrong-context'
         request = Device(**copy_device)
-        LOGGER.warning('request = {}'.format(request))
-        client.AddDevice(request)
+        device_client.AddDevice(request)
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
     msg = ' '.join([
-        'Unsupported context_id(wrong-context) in endpoint #0.',
-        'Only default context_id(admin) is currently supported.',
-        'Optionally, leave field empty to use default context_id.',
+        'Unsupported Context(wrong-context) in Endpoint(#0) of Device(dev1).',
+        'Only default Context(admin) is currently supported.',
+        'Optionally, leave field empty to use default Context.',
     ])
     assert e.value.details() == msg
 
-def test_create_endpoint_wrong_topology(client : DeviceClient):
+def test_add_device_endpoint_wrong_topology(device_client : DeviceClient):
     # should fail with unsupported topology
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['endpointList'][0]['port_id']['topoId']['topoId']['uuid'] = 'wrong-topo'
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
     msg = ' '.join([
-        'Unsupported topology_id(wrong-topo) in endpoint #0.',
-        'Only default topology_id(admin) is currently supported.',
-        'Optionally, leave field empty to use default topology_id.',
+        'Unsupported Topology(wrong-topo) in Endpoint(#0) of Device(dev1).',
+        'Only default Topology(admin) is currently supported.',
+        'Optionally, leave field empty to use default Topology.',
     ])
     assert e.value.details() == msg
 
-def test_create_endpoint_wrong_device(client : DeviceClient):
+def test_add_device_endpoint_wrong_device(device_client : DeviceClient):
     # should fail with wrong endpoint device
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['endpointList'][0]['port_id']['dev_id']['device_id']['uuid'] = 'wrong-device'
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
     msg = ' '.join([
-        'Wrong device_id(wrong-device) in endpoint #0.',
-        'Parent specified in message is device_id(test-device-001).',
-        'Optionally, leave field empty to use parent device_id.',
+        'Wrong Device(wrong-device) in Endpoint(#0).',
+        'Parent specified in message is Device(dev1).',
+        'Optionally, leave field empty to use parent Device.',
     ])
     assert e.value.details() == msg
 
-def test_create_empty_port_uuid(client : DeviceClient):
+def test_add_device_empty_port_uuid(device_client : DeviceClient):
     # should fail with port uuid is empty
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['endpointList'][0]['port_id']['port_id']['uuid'] = ''
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'port_uuid() string is empty.'
+    assert e.value.details() == 'endpoint[#0].port_id.port_id.uuid() string is empty.'
 
-def test_create_empty_port_type(client : DeviceClient):
+def test_add_device_empty_port_type(device_client : DeviceClient):
     # should fail with port type is empty
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['endpointList'][0]['port_type'] = ''
-        client.AddDevice(Device(**copy_device))
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'port_type() string is empty.'
+    assert e.value.details() == 'endpoint[#0].port_type() string is empty.'
 
-def test_create_duplicate_port(client : DeviceClient):
+def test_add_device_duplicate_port(device_client : DeviceClient):
     # should fail with uplicate port in device
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
-        copy_device['endpointList'][1]['port_id']['port_id']['uuid'] = 'port-101'
-        client.AddDevice(Device(**copy_device))
+        copy_device['endpointList'][1]['port_id']['port_id']['uuid'] = 'to-dev2'
+        device_client.AddDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.ALREADY_EXISTS
-    assert e.value.details() == 'Duplicated port_id(port-101) in device_id(test-device-001).'
+    assert e.value.details() == 'Duplicated Port(to-dev2) in Endpoint(#1) of Device(dev1).'
 
-def test_create(client : DeviceClient):
+def test_add_device(device_client : DeviceClient):
     # should work
     validate_device_id(MessageToDict(
-            client.AddDevice(Device(**DEVICE)),
+            device_client.AddDevice(Device(**DEVICE)),
             including_default_value_fields=True, preserving_proto_field_name=True,
             use_integers_for_enums=False))
 
-def test_create_duplicate(client : DeviceClient):
+def test_add_device_duplicate(device_client : DeviceClient):
     # should fail with device already exists
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
-        client.AddDevice(Device(**DEVICE))
+        device_client.AddDevice(Device(**DEVICE))
     assert e.value.code() == grpc.StatusCode.ALREADY_EXISTS
-    assert e.value.details() == 'device_uuid(test-device-001) already exists.'
+    assert e.value.details() == 'Device(dev1) already exists in the database.'
 
-def test_delete_empty_uuid(client : DeviceClient):
+def test_delete_device_empty_uuid(device_client : DeviceClient):
     # should fail with device uuid is empty
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device_id = copy.deepcopy(DEVICE_ID)
         copy_device_id['device_id']['uuid'] = ''
-        client.DeleteDevice(DeviceId(**copy_device_id))
+        device_client.DeleteDevice(DeviceId(**copy_device_id))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'device_uuid() string is empty.'
+    assert e.value.details() == 'device_id.device_id.uuid() string is empty.'
 
-def test_delete_device_not_found(client : DeviceClient):
+def test_delete_device_not_found(device_client : DeviceClient):
     # should fail with device not found
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device_id = copy.deepcopy(DEVICE_ID)
         copy_device_id['device_id']['uuid'] = 'wrong-device-id'
-        client.DeleteDevice(DeviceId(**copy_device_id))
+        device_client.DeleteDevice(DeviceId(**copy_device_id))
     assert e.value.code() == grpc.StatusCode.NOT_FOUND
-    assert e.value.details() == 'device_uuid(wrong-device-id) does not exist.'
+    assert e.value.details() == 'Device(wrong-device-id) does not exist in the database.'
 
-def test_delete(client : DeviceClient):
+def test_delete_device(device_client : DeviceClient):
     # should work
     validate_empty(MessageToDict(
-            client.DeleteDevice(DeviceId(**DEVICE_ID)),
+            device_client.DeleteDevice(DeviceId(**DEVICE_ID)),
             including_default_value_fields=True, preserving_proto_field_name=True,
             use_integers_for_enums=False))
 
-def test_configure_empty_device_uuid(client : DeviceClient):
+def test_configure_device_empty_device_uuid(device_client : DeviceClient):
     # should fail with device uuid is empty
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['device_id']['device_id']['uuid'] = ''
-        client.ConfigureDevice(Device(**copy_device))
+        device_client.ConfigureDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'device_uuid() string is empty.'
+    assert e.value.details() == 'device.device_id.device_id.uuid() string is empty.'
 
-def test_configure_device_not_found(client : DeviceClient):
+def test_configure_device_not_found(device_client : DeviceClient):
     # should fail with device not found
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['device_id']['device_id']['uuid'] = 'wrong-device-id'
-        client.ConfigureDevice(Device(**copy_device))
+        device_client.ConfigureDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.NOT_FOUND
-    assert e.value.details() == 'device_uuid(wrong-device-id) does not exist.'
+    assert e.value.details() == 'Device(wrong-device-id) does not exist in the database.'
 
-def test_create_device_default_endpoint_context_topology(client : DeviceClient):
+def test_add_device_default_endpoint_context_topology_device(device_client : DeviceClient):
     # should work
     copy_device = copy.deepcopy(DEVICE)
     copy_device['endpointList'][0]['port_id']['topoId']['contextId']['contextUuid']['uuid'] = ''
     copy_device['endpointList'][0]['port_id']['topoId']['topoId']['uuid'] = ''
     copy_device['endpointList'][0]['port_id']['dev_id']['device_id']['uuid'] = ''
     validate_device_id(MessageToDict(
-            client.AddDevice(Device(**copy_device)),
+            device_client.AddDevice(Device(**copy_device)),
             including_default_value_fields=True, preserving_proto_field_name=True,
             use_integers_for_enums=False))
 
-def test_configure_wrong_device_type(client : DeviceClient):
+def test_configure_device_wrong_device_type(device_client : DeviceClient):
     # should fail with device type is wrong
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
         copy_device['device_type'] = 'wrong-type'
-        client.ConfigureDevice(Device(**copy_device))
+        device_client.ConfigureDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'Device(test-device-001) has Type(ROADM). Cannot be changed to Type(wrong-type).'
+    assert e.value.details() == 'Device(dev1) has Type(ROADM) in the database. Cannot be changed to Type(wrong-type).'
 
-def test_configure_with_endpoints(client : DeviceClient):
+def test_configure_device_with_endpoints(device_client : DeviceClient):
     # should fail with endpoints cannot be modified
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
-        client.ConfigureDevice(Device(**copy_device))
+        device_client.ConfigureDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-    assert e.value.details() == 'Endpoints belonging to Device(test-device-001) cannot be modified.'
+    assert e.value.details() == 'Endpoints belonging to Device(dev1) cannot be modified.'
 
-def test_configure_no_change(client : DeviceClient):
+def test_configure_device_no_change(device_client : DeviceClient):
     # should fail with any change detected
     with pytest.raises(grpc._channel._InactiveRpcError) as e:
         copy_device = copy.deepcopy(DEVICE)
+        copy_device['device_config']['device_config'] = ''
         copy_device['devOperationalStatus'] = OperationalStatus.KEEP_STATE.value
         copy_device['endpointList'].clear()
-        client.ConfigureDevice(Device(**copy_device))
+        device_client.ConfigureDevice(Device(**copy_device))
     assert e.value.code() == grpc.StatusCode.ABORTED
     msg = ' '.join([
-        'Any change has been requested for Device(test-device-001).',
-        'Either specify a new configuration or a new device state.',
+        'Any change has been requested for Device(dev1).',
+        'Either specify a new configuration or a new device operational status.',
     ])
     assert e.value.details() == msg
 
-def test_configure(client : DeviceClient):
+def test_configure_device(device_client : DeviceClient):
     # should work
     copy_device = copy.deepcopy(DEVICE)
     copy_device['device_config']['device_config'] = '<new_config/>'
     copy_device['devOperationalStatus'] = OperationalStatus.DISABLED.value
     copy_device['endpointList'].clear()
     validate_device_id(MessageToDict(
-            client.ConfigureDevice(Device(**copy_device)),
+            device_client.ConfigureDevice(Device(**copy_device)),
             including_default_value_fields=True, preserving_proto_field_name=True,
             use_integers_for_enums=False))