Repository: amwa-tv/nmos-testing
Issue: IS0501: Testing mqtt receiver results in KeyError

Testing a mqtt receiver against the IS0501 testsuite throws a bunch of `Uncaught exception. Please report the traceback from the terminal to https://github.com/amwa-tv/nmos-testing/issues. 'destination_port' `. 

Traceback: 
```
 * Running test_37
Traceback (most recent call last):
  File "/home/jasper/git/github/nmos-testing/nmostesting/GenericTest.py", line 140, in execute_test
    self.result.append(method(test))
  File "/home/jasper/git/github/nmos-testing/nmostesting/suites/IS0501Test.py", line 770, in test_37
    valid, response = self.check_bulk_stage("receiver", self.receivers)
  File "/home/jasper/git/github/nmos-testing/nmostesting/suites/IS0501Test.py", line 980, in check_bulk_stage
    valid, response = self.is05_utils.generate_changeable_param(port, portInst, self.transport_types[portInst])
  File "/home/jasper/git/github/nmos-testing/nmostesting/IS05Utils.py", line 427, in generate_changeable_param
    return self.generate_destination_ports(port, portId)
  File "/home/jasper/git/github/nmos-testing/nmostesting/IS05Utils.py", line 445, in generate_destination_ports
    if "enum" in entry['destination_port']:
KeyError: 'destination_port'
```

I'm really not sure why this did not pop up earlier on our side..

During a couple tests (test_24, test_26, test_28, test_30 and test_37) at some point a changeable parameter is chosen depending on the transport type. 

Until now this is the corresponding piece of code: 
```
    def generate_changeable_param(self, port, portId, transportType):
        """Use a port's constraints to generate a changeable parameter"""
        if transportType == "urn:x-nmos:transport:websocket":
            return self.generate_connection_uris(port, portId)
        else:
            return self.generate_destination_ports(port, portId)
```

For MQTT receivers there is no `destination_port`, neither in the constraints nor in the `/staged` or `/active` params.

Side issue here: the corresponding piece of code did not catch `KeyErrors` in such cases.


Changed files:
nmostesting/IS05Utils.py

diff --git a/nmostesting/IS05Utils.py b/nmostesting/IS05Utils.py
index 402e3e9..b56fc97 100644
--- a/nmostesting/IS05Utils.py
+++ b/nmostesting/IS05Utils.py
@@ -423,6 +423,8 @@ class IS05Utils(NMOSUtils):
         """Use a port's constraints to generate a changeable parameter"""
         if transportType == "urn:x-nmos:transport:websocket":
             return self.generate_connection_uris(port, portId)
+        elif transportType == "urn:x-nmos:transport:mqtt":
+            return self.generate_broker_topics(port, portId)
         else:
             return self.generate_destination_ports(port, portId)
 
@@ -430,6 +432,8 @@ class IS05Utils(NMOSUtils):
         """Identify the parameter name which will be used to change IS-05 configuration"""
         if transportType == "urn:x-nmos:transport:websocket":
             return "connection_uri"
+        elif transportType == "urn:x-nmos:transport:mqtt":
+            return "broker_topic"
         else:
             return "destination_port"
 
@@ -459,6 +463,8 @@ class IS05Utils(NMOSUtils):
             except TypeError:
                 return False, "Expected a dict to be returned from {}, got a {}: {}".format(url, type(constraints),
                                                                                             constraints)
+            except KeyError as e:
+                return False, "Expected key '{}' not found in response from {}".format(str(e), url)
         else:
             return False, constraints
 
@@ -482,6 +488,30 @@ class IS05Utils(NMOSUtils):
             except TypeError:
                 return False, "Expected a dict to be returned from {}, got a {}: {}".format(url, type(constraints),
                                                                                             constraints)
+            except KeyError as e:
+                return False, "Expected key '{}' not found in response from {}".format(str(e), url)
+        else:
+            return False, constraints
+
+    def generate_broker_topics(self, port, portId):
+        """Generates a fake broker topic, or re-uses one from the advertised constraints"""
+        url = "single/" + port + "s/" + portId + "/constraints/"
+        valid, constraints = self.checkCleanRequestJSON("GET", url)
+        if valid:
+            toReturn = []
+            try:
+                for entry in constraints:
+                    if "enum" in entry['broker_topic']:
+                        values = entry['broker_topic']['enum']
+                        toReturn.append(values[randint(0, len(values) - 1)])
+                    else:
+                        toReturn.append("test_broker_topic")
+                return True, toReturn
+            except TypeError:
+                return False, "Expected a dict to be returned from {}, got a {}: {}".format(url, type(constraints),
+                                                                                            constraints)
+            except KeyError as e:
+                return False, "Expected key '{}' not found in response from {}".format(str(e), url)
         else:
             return False, constraints