Repository: xlab-si/xopera-opera

Issue:
Duplicate node_template names
## Description
If a service template has 2 node template with same node template names defined, one of them is omitted, and template execution proceeds without error. According to section **3.1.3.1 Additional Requirements**
>  Duplicate Template names within a Service Template’s Topology Template SHALL be considered an error.
## Prerequisites
- `opera` installed
- `service.yaml` TOSCA Service Template containing 2 node templates with same name.
```
tosca_definitions_version: tosca_simple_yaml_1_3
topology_template:
  node_templates:
    node_1:
      type: tosca.nodes.SoftwareComponent   
    node_1:
      type: tosca.nodes.SoftwareComponent 
```

## Steps
- User runs deploy `opera deploy service.yaml`
## Current behavior
 - xOpera proceeds with deployment.
## Expected behavior
- xOpera returns a parser error.

Discussion:

Patch:
diff --git a/src/opera/parser/yaml/constructor.py b/src/opera/parser/yaml/constructor.py
index 464b4fa..154526a 100644
--- a/src/opera/parser/yaml/constructor.py
+++ b/src/opera/parser/yaml/constructor.py
@@ -1,4 +1,5 @@
 from yaml.constructor import BaseConstructor, ConstructorError
+from collections import Counter
 
 from opera.parser.utils.location import Location
 
@@ -56,6 +57,14 @@ class Constructor(BaseConstructor):
         data = Node({}, self._pos(node))
         yield data
         data.value.update(self.construct_mapping(node))
+        counts = Counter(n.bare for n in data.value)
+        duplicates = [k for k, v in counts.items() if v > 1]
+        if duplicates:
+            raise ConstructorError(
+                None, None,
+                "Duplicate map names: {}".format(', '.join(duplicates)),
+                node.start_mark,
+            )
 
     def construct_undefined(self, node):
         raise ConstructorError(


Test patch:
diff --git a/tests/unit/opera/parser/yaml/test_constructor.py b/tests/unit/opera/parser/yaml/test_constructor.py
index cec2c01..ee88892 100644
--- a/tests/unit/opera/parser/yaml/test_constructor.py
+++ b/tests/unit/opera/parser/yaml/test_constructor.py
@@ -3,6 +3,7 @@ import math
 import pytest
 from yaml.error import Mark
 from yaml.nodes import MappingNode, ScalarNode, SequenceNode
+from yaml.constructor import ConstructorError
 
 from opera.parser.yaml.constructor import Constructor
 
@@ -138,3 +139,19 @@ class TestNull:
         assert res.loc.line == 9
         assert res.loc.column == 9
         assert res.loc.stream_name == "map"
+
+    def test_construct_map_duplicate(self):
+        mark = Mark(None, None, 8, 8, None, None)
+        children = [
+            (
+                ScalarNode("tag:yaml.org,2002:str", "node1", start_mark=mark),
+                ScalarNode("tag:yaml.org,2002:str", "node1", start_mark=mark),
+            ),
+            (
+                ScalarNode("tag:yaml.org,2002:str", "node1", start_mark=mark),
+                ScalarNode("tag:yaml.org,2002:str", "node1", start_mark=mark),
+            )
+        ]
+        node = MappingNode("tag:yaml.org,2002:map", children, start_mark=mark)
+        with pytest.raises(ConstructorError):
+            res, = Constructor("map").construct_yaml_map(node)