1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15- import asyncio
1615import time
1716import json
1817import uuid
19- import inspect
2018import random
2119import textwrap
2220from copy import deepcopy
2321from urllib .parse import urlparse
2422from dnslib import QTYPE
25- from threading import Event
2623
2724from .GenericTest import GenericTest , NMOSTestException , NMOSInitException
2825from . import Config as CONFIG
2926from .TestHelper import get_default_ip , get_mocks_hostname
3027from .TestResult import Test
3128from .NMOSUtils import NMOSUtils
29+ from .TestingFacadeUtils import TestingFacadeUtils , TestingFacadeException
3230
33- from flask import Flask , Blueprint , request
34-
35- CONTROLLER_TEST_API_KEY = "testquestion"
31+ TESTING_FACADE_API_KEY = "testquestion"
3632QUERY_API_KEY = "query"
3733CONN_API_KEY = "connection"
3834REG_API_KEY = "registration"
3935
40- CALLBACK_ENDPOINT = "/x-nmos/testanswer/<version>"
41-
42- # asyncio queue for passing Testing Façade answer responses back to tests
43- _event_loop = asyncio .new_event_loop ()
44- asyncio .set_event_loop (_event_loop )
45- _answer_response_queue = asyncio .Queue ()
46-
47- # use exit Event to quit tests early that involve waiting for senders/connections
48- exitTestEvent = Event ()
49-
50- app = Flask (__name__ )
51- TEST_API = Blueprint ('test_api' , __name__ )
52-
53-
54- class TestingFacadeException (Exception ):
55- """Exception thrown due to comms or data errors between NMOS Testing and Testing Façade"""
56- pass
57-
58-
59- @TEST_API .route (CALLBACK_ENDPOINT , methods = ['POST' ])
60- def receive_answer (version ):
61-
62- if request .method == 'POST' :
63- if 'question_id' not in request .json :
64- return 'Invalid JSON received' , 400
65-
66- answer_json = request .json
67- answer_json ['time_received' ] = time .time ()
68- _event_loop .call_soon_threadsafe (_answer_response_queue .put_nowait , answer_json )
69-
70- # Interrupt any 'sleeps' that are still active
71- exitTestEvent .set ()
72-
73- return '' , 202
74-
7536
7637class ControllerTest (GenericTest ):
7738 """
7839 Testing initial set up of new test suite for controller testing
7940 """
8041 def __init__ (self , apis , registries , node , dns_server , auths , disable_auto = True , ** kwargs ):
81- # Remove the spec_path as there are no corresponding GitHub repos for Controller Tests
82- apis [CONTROLLER_TEST_API_KEY ].pop ("spec_path" , None )
42+ # Remove the Testing Facade spec_path as there are no corresponding GitHub repos for the Testing Facade API
43+ apis [TESTING_FACADE_API_KEY ].pop ("spec_path" , None )
8344 # Ensure registration scope is added to JWT to allow authenticated
8445 # registration of mock resources with secure mock registry
8546 if CONFIG .ENABLE_AUTH :
8647 apis [REG_API_KEY ] = {}
87- if CONFIG .ENABLE_HTTPS :
88- # Comms with Testing Facade are http only
89- if apis [CONTROLLER_TEST_API_KEY ]["base_url" ] is not None :
90- apis [CONTROLLER_TEST_API_KEY ]["base_url" ] \
91- = apis [CONTROLLER_TEST_API_KEY ]["base_url" ].replace ("https" , "http" )
92- if apis [CONTROLLER_TEST_API_KEY ]["url" ] is not None :
93- apis [CONTROLLER_TEST_API_KEY ]["url" ] \
94- = apis [CONTROLLER_TEST_API_KEY ]["url" ].replace ("https" , "http" )
9548 GenericTest .__init__ (self , apis , auths = auths , disable_auto = disable_auto , ** kwargs )
49+ self .testing_facade_utils = TestingFacadeUtils (apis )
9650 self .primary_registry = registries [1 ] if registries else None
9751 self .node = node
9852 self .dns_server = dns_server
@@ -109,10 +63,6 @@ def __init__(self, apis, registries, node, dns_server, auths, disable_auto=True,
10963 if QUERY_API_KEY in apis and "version" in self .apis [QUERY_API_KEY ] else "v1.3"
11064 self .connection_api_version = self .apis [CONN_API_KEY ]["version" ] \
11165 if CONN_API_KEY in apis and "version" in self .apis [CONN_API_KEY ] else "v1.1"
112- self .qa_api_version = self .apis [CONTROLLER_TEST_API_KEY ]["version" ] \
113- if CONTROLLER_TEST_API_KEY in apis and "version" in self .apis [CONTROLLER_TEST_API_KEY ] else "v1.0"
114- self .answer_uri = "http://" + get_default_ip () + ":" + str (CONFIG .PORT_BASE ) + \
115- CALLBACK_ENDPOINT .replace ('<version>' , self .qa_api_version )
11666
11767 def set_up_tests (self ):
11868 test = Test ("Test setup" , "set_up_tests" )
@@ -150,8 +100,7 @@ def tear_down_tests(self):
150100 if self .primary_registry :
151101 self .primary_registry .disable ()
152102
153- # Reset the state of the Testing Façade
154- self .do_request ("POST" , self .apis [CONTROLLER_TEST_API_KEY ]["url" ], json = {"clear" : "True" })
103+ self .testing_facade_utils .reset ()
155104
156105 if self .dns_server :
157106 self .dns_server .reset ()
@@ -174,96 +123,7 @@ def execute_tests(self, test_names):
174123
175124 self .post_tests_message ()
176125
177- async def get_answer_response (self , timeout ):
178- # Add API processing time to specified timeout
179- # Otherwise, if timeout is None then disable timeout mechanism
180- timeout = timeout + (2 * CONFIG .API_PROCESSING_TIMEOUT ) if timeout is not None else None
181-
182- return await asyncio .wait_for (_answer_response_queue .get (), timeout = timeout )
183-
184- def _send_testing_facade_questions (
185- self ,
186- test_method_name ,
187- question ,
188- answers ,
189- test_type ,
190- multipart_test = None ,
191- metadata = None ):
192- """
193- Send question and answers to Testing Façade
194- question: text to be presented to Test User
195- answers: list of all possible answers
196- test_type: "single_choice" - one and only one answer
197- "multi_choice" - multiple answers
198- "action" - Test User asked to click button
199- multipart_test: indicates test uses multiple questions. Default None, should be increasing
200- integers with each subsequent call within the same test
201- metadata: Test details to assist fully automated testing
202- """
203-
204- method = getattr (self , test_method_name )
205-
206- timeout = CONFIG .CONTROLLER_TESTING_TIMEOUT
207-
208- question_id = test_method_name if not multipart_test else test_method_name + '_' + str (multipart_test )
209-
210- json_out = {
211- "test_type" : test_type ,
212- "question_id" : question_id ,
213- "name" : test_method_name ,
214- "description" : inspect .getdoc (method ),
215- "question" : question ,
216- "answers" : answers ,
217- "timeout" : timeout ,
218- "answer_uri" : self .answer_uri ,
219- "metadata" : metadata
220- }
221- # Send questions to Testing Façade API endpoint then wait
222- valid , response = self .do_request ("POST" , self .apis [CONTROLLER_TEST_API_KEY ]["url" ], json = json_out )
223-
224- if not valid or response .status_code != 202 :
225- raise TestingFacadeException ("Problem contacting Testing Façade: " + response .text if valid else response )
226-
227- return json_out
228-
229- def _wait_for_testing_facade (self , question_id , test_type ):
230-
231- # Wait for answer response or question timeout in seconds. A timeout of None will wait indefinitely
232- timeout = CONFIG .CONTROLLER_TESTING_TIMEOUT
233-
234- try :
235- answer_response = _event_loop .run_until_complete (self .get_answer_response (timeout ))
236- except asyncio .TimeoutError :
237- raise TestingFacadeException ("Test timed out" )
238-
239- # Basic integrity check for response json
240- if answer_response ['question_id' ] is None :
241- raise TestingFacadeException ("Integrity check failed: result format error: "
242- + json .dump (answer_response ))
243-
244- if answer_response ['question_id' ] != question_id :
245- raise TestingFacadeException (
246- "Integrity check failed: cannot compare result of " + question_id +
247- " with expected result for " + answer_response ['question_id' ])
248-
249- # Multi_choice question submitted without any answers should be an empty list
250- if test_type == 'multi_choice' and answer_response ['answer_response' ] is None :
251- answer_response ['answer_response' ] = []
252-
253- return answer_response
254-
255- def _invoke_testing_facade (self , question , answers , test_type ,
256- multipart_test = None , metadata = None , test_method_name = None ):
257- # Get the name of the calling test method to use as an identifier
258- test_method_name = test_method_name if test_method_name \
259- else inspect .currentframe ().f_back .f_code .co_name
260-
261- json_out = self ._send_testing_facade_questions (
262- test_method_name , question , answers , test_type , multipart_test , metadata )
263-
264- return self ._wait_for_testing_facade (json_out ['question_id' ], test_type )
265-
266- def _generate_random_indices (self , index_range , min_index_count = 2 , max_index_count = 4 ):
126+ def generate_random_indices (self , index_range , min_index_count = 2 , max_index_count = 4 ):
267127 """
268128 index_range: number of possible indices
269129 min_index_count, max_index_count: Minimum, maximum number of indices to be returned.
@@ -482,7 +342,7 @@ def _register_sender(self, test, sender, codes=[201], fail=Test.FAIL):
482342 sender_data = self ._create_sender_json (sender )
483343 self .post_resource (test , "sender" , sender_data , codes = codes , fail = fail )
484344
485- def _delete_sender (self , test , sender ):
345+ def delete_sender (self , test , sender ):
486346 del_url = self .mock_registry_base_url + 'x-nmos/registration/v1.3/resource/senders/' + sender ['id' ]
487347
488348 valid , r = self .do_request ("DELETE" , del_url )
@@ -588,7 +448,7 @@ def pre_tests_message(self):
588448 """ )
589449
590450 try :
591- self ._invoke_testing_facade (question , [], test_type = "action" )
451+ self .testing_facade_utils . invoke_testing_facade (question , [], test_type = "action" )
592452
593453 except TestingFacadeException :
594454 # pre_test_introducton timed out
@@ -607,7 +467,7 @@ def post_tests_message(self):
607467 """
608468
609469 try :
610- self ._invoke_testing_facade (question , [], test_type = "action" )
470+ self .testing_facade_utils . invoke_testing_facade (question , [], test_type = "action" )
611471
612472 except TestingFacadeException :
613473 # post_test_introducton timed out
0 commit comments