def test_service_get_command_conf_events(story): chain = deque( [Service('service'), Command('cmd'), Event('foo'), Command('bar')]) story.app.services = { 'service': { 'configuration': { 'actions': { 'cmd': { 'events': { 'foo': { 'output': { 'actions': { 'bar': { 'a': 'b' } } } } } } } } } } assert Services.get_command_conf(story, chain) == {'a': 'b'}
async def test_services_execute_external_http(patch, story, async_mock): line = { Line.service: 'cups', Line.command: 'print', Line.method: 'execute' } story.app.services = { 'cups': { ServiceConstants.config: { 'actions': { 'print': { 'http': {} } } } } } patch.object(Services, 'execute_http', new=async_mock()) patch.object(Services, 'start_container', new=async_mock()) ret = await Services.execute_external(story, line) Services.execute_http.mock.assert_called_with( story, line, deque([Service(name='cups'), Command(name='print')]), {'http': {}}) assert ret == await Services.execute_http() Services.start_container.mock.assert_called()
async def test_execute_inline(patch, story, command): chain = deque([Service('http'), Event('server'), Command(command)]) req = MagicMock() io_loop = MagicMock() story.context = { ContextConstants.server_request: req, ContextConstants.server_io_loop: io_loop } command_conf = { 'arguments': { 'content': { 'type': 'string', 'in': 'responseBody', 'required': True } } } patch.object(story, 'argument_by_name', return_value='hello world!') expected_body = {'command': command, 'data': {'content': 'hello world!'}} line = {} await Services.execute_inline(story, line, chain, command_conf) req.write.assert_called_with(ujson.dumps(expected_body) + '\n') if command == 'finish': io_loop.add_callback.assert_called_with(req.finish) else: io_loop.add_callback.assert_not_called()
async def test_execute_inline(patch, story, command, simulate_finished, bin_content): # Not a valid combination. if bin_content and command != 'write': return chain = deque([Service('http'), Event('server'), Command(command)]) req = MagicMock() req._finished = simulate_finished def is_finished(): return req._finished req.is_finished = is_finished io_loop = MagicMock() story.context = { ContextConstants.server_request: req, ContextConstants.server_io_loop: io_loop } command_conf = { 'arguments': { 'content': { 'type': 'string', 'in': 'responseBody', 'required': True } } } if bin_content: patch.object(story, 'argument_by_name', return_value=b'bin world!') else: patch.object(story, 'argument_by_name', return_value='hello world!') expected_body = { 'command': command, 'data': { 'content': 'hello world!' } } line = {} if simulate_finished: with pytest.raises(StoryscriptError): await Services.execute_inline(story, line, chain, command_conf) return else: await Services.execute_inline(story, line, chain, command_conf) if bin_content: req.write.assert_called_with(b'bin world!') else: req.write.assert_called_with(ujson.dumps(expected_body) + '\n') if command == 'finish' or bin_content: io_loop.add_callback.assert_called_with(req.finish) else: io_loop.add_callback.assert_not_called()
def test_service_get_command_conf_simple(story): chain = deque([Service('service'), Command('cmd')]) story.app.services = { 'service': { 'configuration': { 'actions': { 'cmd': {'x': 'y'} } } } } assert Services.get_command_conf(story, chain) == {'x': 'y'}
async def test_start_services(patch, app, async_mock, magic, reusable, internal): app.stories = { 'a.story': { 'tree': { '1': { 'method': 'execute', 'next': '2' }, '2': { 'method': 'execute', 'next': '3' }, '3': { 'method': 'not_execute' } }, 'entrypoint': '1' } } chain = deque() chain.append(Service(name='cold_service')) chain.append(Command(name='cold_command')) start_container_result = magic() patch.object(Services, 'resolve_chain', return_value=chain) patch.object(Services, 'is_internal', return_value=internal) patch.object(Services, 'start_container', return_value=start_container_result) patch.object(Containers, 'is_service_reusable', return_value=reusable) patch.object(asyncio, 'wait', new=async_mock(return_value=([], []))) await app.start_services() tasks = [start_container_result] if not reusable: tasks = [start_container_result, start_container_result] if internal: tasks = [] asyncio.wait.mock.assert_called_with(tasks)
async def test_start_services_completed_exc(patch, app, async_mock, magic): app.stories = { 'a': { 'tree': { '1': { 'method': 'execute' } }, 'entrypoint': '1' } } patch.object(Containers, 'is_service_reusable', return_value=True) patch.object(Services, 'is_internal', return_value=False) chain = deque() chain.append(Service(name='foo')) chain.append(Command(name='foo')) patch.object(Services, 'resolve_chain', return_value=chain) task = magic() task.exception.return_value = Exception() patch.object(asyncio, 'wait', new=async_mock(return_value=([task], []))) with pytest.raises(Exception): await app.start_services()
async def test_services_execute_http(patch, story, async_mock, locations): chain = deque([Service(name='service'), Command(name='cmd')]) patch.object(Containers, 'get_hostname', new=async_mock(return_value='container_host')) command_conf = { 'http': { 'method': 'post', 'port': 2771, 'path': '/invoke/{path_param}' }, 'arguments': { 'foo': { 'in': 'requestBody' }, 'query_param': { 'type': 'string', 'in': 'query' }, 'path_param': { 'type': 'string', 'in': 'path' }, 'default_case': { 'type': 'string', 'in': 'invalid' } } } expected_url = ('http://container_host:2771/invoke/' f'{locations["path_param"]}?' f'query_param={locations["query_param"]}') def argument_by_name(line, arg): return locations[arg] patch.object(story, 'argument_by_name', side_effect=argument_by_name) expected_kwargs = { 'method': 'POST', 'body': json.dumps({'foo': locations['foo']}), 'headers': {'Content-Type': 'application/json; charset=utf-8'} } line = { 'ln': '1' } patch.init(AsyncHTTPClient) client = AsyncHTTPClient() response = HTTPResponse(HTTPRequest(url=expected_url), 200, buffer=StringIO('{"foo": "\U0001f44d"}'), headers={'Content-Type': 'application/json'}) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) ret = await Services.execute_http(story, line, chain, command_conf) assert ret == {'foo': '\U0001f44d'} HttpUtils.fetch_with_retry.mock.assert_called_with( 3, story.logger, expected_url, client, expected_kwargs) # Additionally, test for other scenarios. response = HTTPResponse(HTTPRequest(url=expected_url), 200, buffer=StringIO('foo'), headers={}) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) ret = await Services.execute_http(story, line, chain, command_conf) assert ret == 'foo' response = HTTPResponse(HTTPRequest(url=expected_url), 500) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) with pytest.raises(AsyncyError): await Services.execute_http(story, line, chain, command_conf)
def test_resolve_chain(story): """ The story tested here is: alpine echo as client when client foo as echo_helper alpine echo echo_helper sonar # This isn't possible, but OK. echo_helper sonar """ story.app.services = { 'alpine': {} } story.tree = { '1': { Line.method: 'execute', Line.service: 'alpine', Line.command: 'echo', Line.enter: '2', Line.output: ['client'] }, '2': { Line.method: 'when', Line.service: 'client', Line.command: 'foo', Line.parent: '1', Line.output: ['echo_helper'] }, '3': { Line.method: 'execute', Line.service: 'alpine', Line.command: 'echo', Line.parent: '2', Line.enter: '4' }, '4': { Line.method: 'execute', Line.service: 'echo_helper', Line.command: 'sonar', Line.parent: '3' }, '5': { Line.method: 'execute', Line.service: 'echo_helper', Line.command: 'sonar', Line.parent: '2' } } assert Services.resolve_chain(story, story.tree['1']) \ == deque([Service(name='alpine'), Command(name='echo')]) assert Services.resolve_chain(story, story.tree['2']) \ == deque([Service(name='alpine'), Command(name='echo'), Event(name='foo')]) assert Services.resolve_chain(story, story.tree['3']) \ == deque([Service(name='alpine'), Command(name='echo')]) assert Services.resolve_chain(story, story.tree['4']) \ == deque([Service(name='alpine'), Command(name='echo'), Event(name='foo'), Command(name='sonar')]) assert Services.resolve_chain(story, story.tree['5']) \ == deque([Service(name='alpine'), Command(name='echo'), Event(name='foo'), Command(name='sonar')])
async def test_services_execute_http(patch, story, async_mock, location, method, service_output): if location == 'formBody' and method == 'GET': return # Invalid case. chain = deque([Service(name='service'), Command(name='cmd')]) patch.object(Containers, 'get_hostname', new=async_mock(return_value='container_host')) patch.object(uuid, 'uuid4') patch.object(ServiceOutputValidator, 'raise_if_invalid') command_conf = { 'http': { 'method': method.lower(), 'port': 2771, 'path': '/invoke' }, 'arguments': { 'foo': { 'in': location } } } if service_output is not None: command_conf['output'] = service_output if location is None: del command_conf['arguments'] if location == 'formBody': command_conf['http']['contentType'] = 'multipart/form-data' patch.object(story, 'argument_by_name', return_value='bar') if location == 'path': command_conf['http']['path'] = '/invoke/{foo}' expected_url = 'http://container_host:2771/invoke/bar' elif location == 'query': expected_url = 'http://container_host:2771/invoke?foo=bar' else: # requestBody expected_url = 'http://container_host:2771/invoke' expected_kwargs = {'method': method} if method == 'POST': expected_kwargs['headers'] = { 'Content-Type': 'application/json; charset=utf-8' } if location == 'requestBody': expected_kwargs['body'] = '{"foo": "bar"}' elif location == 'formBody': expected_kwargs['headers']['Content-Type'] = \ f'multipart/form-data; boundary={uuid.uuid4().hex}' else: expected_kwargs['body'] = '{}' line = {'ln': '1'} patch.init(AsyncHTTPClient) client = AsyncHTTPClient() response = HTTPResponse(HTTPRequest(url=expected_url), 200, buffer=StringIO('{"foo": "\U0001f44d"}'), headers={'Content-Type': 'application/json'}) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) if location == 'invalid_loc' or \ (location == 'requestBody' and method == 'GET'): with pytest.raises(StoryscriptError): await Services.execute_http(story, line, chain, command_conf) return else: ret = await Services.execute_http(story, line, chain, command_conf) assert ret == {'foo': '\U0001f44d'} if location == 'formBody': call = HttpUtils.fetch_with_retry.mock.mock_calls[0][1] assert call[0] == 3 assert call[1] == story.logger assert call[2] == expected_url assert call[3] == client # Since we can't mock the partial, we must inspect it. actual_body_producer = call[4].pop('body_producer') assert call[4] == expected_kwargs assert actual_body_producer.func == Services._multipart_producer else: HttpUtils.fetch_with_retry.mock.assert_called_with( 3, story.logger, expected_url, client, expected_kwargs) if service_output is not None: ServiceOutputValidator.raise_if_invalid.assert_called_with( command_conf['output'], ret, chain) else: ServiceOutputValidator.raise_if_invalid.assert_not_called() # Additionally, test for other scenarios. response = HTTPResponse(HTTPRequest(url=expected_url), 200, buffer=StringIO('foo'), headers={}) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) ret = await Services.execute_http(story, line, chain, command_conf) assert ret == 'foo' response = HTTPResponse(HTTPRequest(url=expected_url), 500) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) with pytest.raises(StoryscriptError): await Services.execute_http(story, line, chain, command_conf)
async def test_services_execute_http(patch, story, async_mock, location, method): chain = deque([Service(name='service'), Command(name='cmd')]) patch.object(Containers, 'get_hostname', new=async_mock(return_value='container_host')) command_conf = { 'http': { 'method': method.lower(), 'port': 2771, 'path': '/invoke' }, 'arguments': { 'foo': { 'in': location } } } patch.object(story, 'argument_by_name', return_value='bar') if location == 'path': command_conf['http']['path'] = '/invoke/{foo}' expected_url = 'http://container_host:2771/invoke/bar' elif location == 'query': expected_url = 'http://container_host:2771/invoke?foo=bar' else: # requestBody expected_url = 'http://container_host:2771/invoke' expected_kwargs = {'method': method} if method == 'POST': if location == 'requestBody': expected_kwargs['body'] = '{"foo": "bar"}' else: expected_kwargs['body'] = '{}' expected_kwargs['headers'] = { 'Content-Type': 'application/json; charset=utf-8' } line = {'ln': '1'} patch.init(AsyncHTTPClient) client = AsyncHTTPClient() response = HTTPResponse(HTTPRequest(url=expected_url), 200, buffer=StringIO('{"foo": "\U0001f44d"}'), headers={'Content-Type': 'application/json'}) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) if location == 'invalid_loc' or \ (location == 'requestBody' and method == 'GET'): with pytest.raises(AsyncyError): await Services.execute_http(story, line, chain, command_conf) return else: ret = await Services.execute_http(story, line, chain, command_conf) assert ret == {'foo': '\U0001f44d'} HttpUtils.fetch_with_retry.mock.assert_called_with(3, story.logger, expected_url, client, expected_kwargs) # Additionally, test for other scenarios. response = HTTPResponse(HTTPRequest(url=expected_url), 200, buffer=StringIO('foo'), headers={}) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) ret = await Services.execute_http(story, line, chain, command_conf) assert ret == 'foo' response = HTTPResponse(HTTPRequest(url=expected_url), 500) patch.object(HttpUtils, 'fetch_with_retry', new=async_mock(return_value=response)) with pytest.raises(AsyncyError): await Services.execute_http(story, line, chain, command_conf)