diff --git a/libraries/robotframework-httpctrl/CHANGES b/libraries/robotframework-httpctrl/CHANGES new file mode 100755 index 0000000000000000000000000000000000000000..76f2b198d0349f9d4422712ba58d010f3b903fd0 --- /dev/null +++ b/libraries/robotframework-httpctrl/CHANGES @@ -0,0 +1,262 @@ +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.3.1 (RELEASED: Dec 3, 2022) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Fixed bug in function `Send HTTPS Request Async` when HTTP protocol was used instead of HTTPS. + See: https://github.com/annoviko/robotframework-httpctrl/pull/39 + +- Fixed bug with response header types when `HTTPMessage` object was returned instead of `dict`. + See: https://github.com/annoviko/robotframework-httpctrl/issues/40 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.3.0 (RELEASED: Jul 4, 2022) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Introduce new argument `resp_body_to_file` for functions `Send HTTP Request`, `Send HTTP Request Async`, + `Send Https Request`, `Send Https Request Async` to write response body to a file that is specified + by the argument. + See: https://github.com/annoviko/robotframework-httpctrl/issues/36 + +- Changed return type for `Get Body From Response` and `Get Response Body` from `string` to `bytes`. + See: https://github.com/annoviko/robotframework-httpctrl/issues/37 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.7 (RELEASED: Jun 30, 2022) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Introduce the limit for body size to log and corresponding library `HttpCtrl.Logging` to configure it. + See: https://github.com/annoviko/robotframework-httpctrl/issues/34 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.6 (RELEASED: Apr 26, 2022) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Fixed bug with Flask based server that reply by 404. + See: https://github.com/annoviko/robotframework-httpctrl/issues/33 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.5 (RELEASED: Dec 24, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Update meta-information (CHANGE file). + See: no reference + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.4 (RELEASED: Dec 24, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Introduced new keyword `Set Stub Reply` to create server stub functions. + See: https://github.com/annoviko/robotframework-httpctrl/issues/30 + +- Introduced new keyword `Get Stub Count` to get server stub statistic. + See: https://github.com/annoviko/robotframework-httpctrl/issues/30 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.2 (RELEASED: Oct 18, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Fixes bug with pypi installer 0.2.1. + See: https://github.com/annoviko/robotframework-httpctrl/issues/27 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.1 (RELEASED: Oct 8, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Change license to The 3-Clause BSD License. + See: https://github.com/annoviko/robotframework-httpctrl/issues/26 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.2.0 (RELEASED: Oct 8, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES + +- Change license to The 3-Clause BSD License. + See: https://github.com/annoviko/robotframework-httpctrl/issues/26 + +- Feature to release the library automatically. + See: https://github.com/annoviko/robotframework-httpctrl/issues/14 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.10 (RELEASED: Oct 7, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Fixed bug where body was not provided for HEAD, OPTION, DELETE and GET methods (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/24 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.9 (RELEASED: Sep 29, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Support bytes body type for `Reply By` (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/17 + +CORRECTED MAJOR BUGS: + +- Fixed bug where response server body was used as a response reason (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/19 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.8 (RELEASED: Sep 28, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Option to bind client to the specific address only without port (HttpCtrl.Client). + See: https://github.com/annoviko/robotframework-httpctrl/issues/21 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.7 (RELEASED: Sep 27, 2021) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Option to bind client to the specific address and port (HttpCtrl.Client). + See: https://github.com/annoviko/robotframework-httpctrl/issues/21 + +- Supported IPv6 address (HttpCtrl.Client, HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/15 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.6 (RELEASED: Jan 13, 2020) + +------------------------------------------------------------------------ + +CORRECTED MAJOR BUGS: + +- Fixed bug where function `Get Async Response` hangs when timeout is specified and message is not received (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/13 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.5 (RELEASED: Jan 13, 2020) + +------------------------------------------------------------------------ + +CORRECTED MAJOR BUGS: + +- Missing package in setup.py. + See: https://github.com/annoviko/robotframework-httpctrl/issues/10 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.4 (RELEASED: Jan 13, 2020) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Introduced argument `timeout` for function `Wait For Request` to specify waiting time (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/11 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.3 (RELEASED: Oct 25, 2019) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Function `Wait And Ingore Request` to ignore incoming request by closing connection (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/8 + +- Optimized termination of the HTTP(S) server - problem with up-to 5 seconds delay (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/7 + +- Functions to work with async. received responses are introduced (HttpCtrl.Client). + See: https://github.com/annoviko/robotframework-httpctrl/issues/5 + +- Functions `Send HTTPS Request Async` and `Send HTTP Request Async` must returns connection (HttpCtrl.Client). + See: https://github.com/annoviko/robotframework-httpctrl/issues/5 + +CORRECTED MAJOR BUGS: + +- Fixed bug where function `Wait For No Requests` ignored input timeout and always uses 5 seconds (HttpCtrl.Server). + See: https://github.com/annoviko/robotframework-httpctrl/issues/6 + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.2 (RELEASED: Sep 10, 2019) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Built-in documentation is updated. + See: no reference + + +------------------------------------------------------------------------ + +CHANGE NOTES FOR 0.1.1 (RELEASED: Sep 9, 2019) + +------------------------------------------------------------------------ + +GENERAL CHANGES: + +- Bug correction for HttpCtrl.Server when client close connection after sending request. + See: no reference diff --git a/libraries/robotframework-httpctrl/LICENSE b/libraries/robotframework-httpctrl/LICENSE new file mode 100755 index 0000000000000000000000000000000000000000..34a4d3f623ec152104229bf15d54d9b785c33c05 --- /dev/null +++ b/libraries/robotframework-httpctrl/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2018-2022 Andrei Novikov + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/PKG-INFO.rst b/libraries/robotframework-httpctrl/PKG-INFO.rst new file mode 100755 index 0000000000000000000000000000000000000000..1e62cc2029db5720496acdfcf7e4f37637d20c6c --- /dev/null +++ b/libraries/robotframework-httpctrl/PKG-INFO.rst @@ -0,0 +1,111 @@ +HttpCtrl library for Robot Framework +==================================== + +**RobotFramework-HttpCtrl** is a library for Robot Framework that provides HTTP/HTTPS client and HTTP (IPv4 and IPv6) server services +to make REST API testing easy. + +**License**: The 3-Clause BSD License + +**Documentation**: https://annoviko.github.io/robotframework-httpctrl/ + + +Dependencies +============ + +**Python version**: >=3.8 + + +Setup +============ + +Make sure to add the src/ directory to your PYTHONPATH. This allows Python and Robot Framework to correctly locate the HttpCtrl modules. + + +Brief Overview of the Library Content +===================================== + +**HttpCtrl** contains following general libraries: + +- **HttpCtrl.Client** - provides API to work with HTTP/HTTPS client [`link client documentation`_]. + +- **HttpCtrl.Server** - provides API to work with HTTP server [`link server documentation`_]. + +- **HttpCtrl.Json** - provides API to work Json messages [`link json documentation`_]. + +- **HttpCtrl.Logging** - provides API to configure the logging system that is used by `HttpCtrl` library [`link logging documentation`_]. + +.. _link client documentation: https://annoviko.github.io/robotframework-httpctrl/client.html +.. _link server documentation: https://annoviko.github.io/robotframework-httpctrl/server.html +.. _link json documentation: https://annoviko.github.io/robotframework-httpctrl/json.html +.. _link logging documentation: https://annoviko.github.io/robotframework-httpctrl/logging.html + + +Examples +======== + +Send GET request to obtain origin IP address and check that is not empty: + +.. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Get Origin Address + Initialize Client www.httpbin.org + Send HTTP Request GET /ip + + ${response status}= Get Response Status + ${response body}= Get Response Body + ${response body}= Decode Bytes To String ${response body} UTF-8 + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${origin}= Get Json Value From String ${response body} origin + Should Not Be Empty ${origin} + + +In this example HTTP client sends POST request to HTTP server. HTTP server receives it and checks incoming +request for correctness. + +.. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Server + + Test Setup Initialize HTTP Client And Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + Receive And Reply To POST + ${request body}= Set Variable { "message": "Hello!" } + Send HTTP Request Async POST /post ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${request body} + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/README.rst b/libraries/robotframework-httpctrl/README.rst new file mode 100755 index 0000000000000000000000000000000000000000..d65b9b63a38e0476dd359d74e9961e9fa60e04e6 --- /dev/null +++ b/libraries/robotframework-httpctrl/README.rst @@ -0,0 +1,199 @@ +|Build Status Linux| |PyPi| + +HttpCtrl library for Robot Framework +==================================== + +**RobotFramework-HttpCtrl** is a library for Robot Framework that provides HTTP/HTTPS client and HTTP server (IPv4 and IPv6) services +to make REST API testing easy. + +**Author**: Andrei Novikov + +**License**: The 3-Clause BSD License + +**Documentation**: https://annoviko.github.io/robotframework-httpctrl/ + + +Request Feature +=============== + +New features are implemented by request that can be created here using issues [`Press Me to Create a Feature Request`_]. Please, do not hesitate to create feature requests. + +.. _Press Me to Create a Feature Request: https://github.com/annoviko/robotframework-httpctrl/issues/new?assignees=&labels=&template=feature_request.md&title= + + +Bug Reporting +============= + +Please, do not hesitate to report about bugs using issues here [`Press Me to Report a Bug`_]. + +.. _Press Me to Report a Bug: https://github.com/annoviko/robotframework-httpctrl/issues/new?assignees=&labels=bug&template=bug_report.md&title= + + +Dependencies +============ + +**Python version**: >=3.8 + + +Setup +============ + +Make sure to add the src/ directory to your PYTHONPATH. This allows Python and Robot Framework to correctly locate the HttpCtrl modules. + + +Brief Overview of the Library Content +===================================== + +**HttpCtrl** contains following general libraries: + +- **HttpCtrl.Client** - provides API to work with HTTP/HTTPS client [`link client documentation`_]. + +- **HttpCtrl.Server** - provides API to work with HTTP server [`link server documentation`_]. + +- **HttpCtrl.Json** - provides API to work Json messages [`link json documentation`_]. + +- **HttpCtrl.Logging** - provides API to configure the logging system that is used by `HttpCtrl` library [`link logging documentation`_]. + +.. _link client documentation: https://annoviko.github.io/robotframework-httpctrl/client.html +.. _link server documentation: https://annoviko.github.io/robotframework-httpctrl/server.html +.. _link json documentation: https://annoviko.github.io/robotframework-httpctrl/json.html +.. _link logging documentation: https://annoviko.github.io/robotframework-httpctrl/logging.html + + +Examples +======== + +Send GET request to obtain origin IP address and check that is not empty: + +.. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Get Origin Address + Initialize Client www.httpbin.org + Send HTTP Request GET /ip + + ${response status}= Get Response Status + ${response body}= Get Response Body + ${response body}= Decode Bytes To String ${response body} UTF-8 + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${origin}= Get Json Value From String ${response body} origin + Should Not Be Empty ${origin} + + +Send POST request and extract required information from response: + +.. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Send POST Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "message": "Hello World!" } + Send HTTP Request POST /post ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + ${response body}= Decode Bytes To String ${response body} UTF-8 + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${message}= Get Json Value From String ${response body} data + Should Be Equal ${message} ${body} + + +Send PATCH request using HTTPS protocol: + +.. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Send HTTPS PATCH Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "volume": 77, "mute": false } + Send HTTPS Request PATCH /patch ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + ${response body}= Decode Bytes To String ${response body} UTF-8 + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${volume}= Get Json Value From String ${response body} json/volume + Should Be Equal ${volume} ${77} + + ${mute}= Get Json Value From String ${response body} json/mute + Should Be Equal ${mute} ${False} + + +In this example HTTP client sends POST request to HTTP server. HTTP server receives it and checks incoming +request for correctness. + +.. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Server + + Test Setup Initialize HTTP Client And Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + Receive And Reply To POST + ${request body}= Set Variable { "message": "Hello!" } + Send HTTP Request Async POST /post ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${request body} + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server + + +.. |Build Status Linux| image:: https://github.com/annoviko/robotframework-httpctrl/actions/workflows/build-httpctrl.yml/badge.svg + :target: https://github.com/annoviko/robotframework-httpctrl/actions +.. |PyPi| image:: https://badge.fury.io/py/robotframework-httpctrl.svg + :target: https://badge.fury.io/py/robotframework-httpctrl \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/VERSION b/libraries/robotframework-httpctrl/VERSION new file mode 100755 index 0000000000000000000000000000000000000000..9e11b32fcaa96816319e5d0dcff9fb2873f04061 --- /dev/null +++ b/libraries/robotframework-httpctrl/VERSION @@ -0,0 +1 @@ +0.3.1 diff --git a/libraries/robotframework-httpctrl/setup.py b/libraries/robotframework-httpctrl/setup.py new file mode 100755 index 0000000000000000000000000000000000000000..a8973d7675860204477402b3acda7d38a2c8d93e --- /dev/null +++ b/libraries/robotframework-httpctrl/setup.py @@ -0,0 +1,72 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +import os + +from setuptools import setup + + +def load_readme(): + readme_file = 'PKG-INFO.rst' + if os.path.isfile(readme_file): + with open(readme_file) as file_descr: + return file_descr.read() + + return "robotframework-httpctrl is a library for Robot Framework that provides HTTP/HTTPS client and HTTP server services." + + +def load_version(): + version_file = 'VERSION' + if os.path.isfile(version_file): + with open(version_file) as file_descr: + return file_descr.read() + + return 'unknown' + + +setup( + name='robotframework-httpctrl', + packages=['HttpCtrl', 'HttpCtrl.utils'], + version=load_version(), + description='robotframework-httpctrl is a library for Robot Framework that provides HTTP/HTTPS client and HTTP server services', + platforms='any', + long_description=load_readme(), + url='https://github.com/annoviko/robotframework-httpctrl', + project_urls={ + 'Homepage': 'https://annoviko.github.io/robotframework-httpctrl/', + 'Repository': 'https://github.com/annoviko/robotframework-httpctrl', + 'Documentation': 'https://annoviko.github.io/robotframework-httpctrl/', + 'Bug Tracker': 'https://github.com/annoviko/robotframework-httpctrl/issues' + }, + license='BSD-3-Clause', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Telecommunications Industry', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Education', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: HTTP Servers', + 'Topic :: Software Development :: Testing', + 'Framework :: Robot Framework :: Library' + ], + keywords='httpctrl http https robotframework client server json test testing', + author='Andrei Novikov', + author_email='spb.andr@yandex.ru', + + python_requires='>=3.8', + install_requires=['robotframework'], + + package_dir={'': 'src'}, + data_files=[('', ['LICENSE', 'CHANGES', 'README.rst', 'PKG-INFO.rst', 'VERSION'])] +) + diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/__init__.py b/libraries/robotframework-httpctrl/src/HttpCtrl/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..571984816661a9cfe9782b4fb6ed925609f0c74e --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/__init__.py @@ -0,0 +1,1615 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +import datetime +import http.client +import json +import threading + +from robot.api import logger + +from HttpCtrl.utils.logger import LoggerAssistant + +from HttpCtrl.internal_messages import IgnoreRequest +from HttpCtrl.http_server import HttpServer +from HttpCtrl.http_stub import HttpStubContainer, HttpStubCriteria +from HttpCtrl.request_storage import RequestStorage +from HttpCtrl.response_storage import ResponseStorage +from HttpCtrl.response import Response + + +class Client: + """ + + HTTP/HTTPS Client library that provides comprehensive interface to Robot Framework to control HTTP/HTTPS client. + + See other HttpCtrl libraries: + + - HttpCtrl.Server_ - HTTP Server API for testing where easy-controlled HTTP server is required. + + - HttpCtrl.Json_ - Json related API for testing where work with Json message is required. + + - HttpCtrl.Logging_ - Logging related API to configure the logging system that is used by HttpCtrl library. + + .. _HttpCtrl.Server: server.html + .. _HttpCtrl.Json: json.html + .. _HttpCtrl.Logging: logging.html + + Example how to send GET request to obtain origin IP address and check that response is not empty: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Get Origin Address + Initialize Client www.httpbin.org + Send HTTP Request GET /ip + + ${response status}= Get Response Status + ${response body}= Get Response Body + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${origin}= Get Json Value From String ${response body} origin + Should Not Be Empty ${origin} + + Example how to sent PATCH request using HTTPS: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Send HTTPS PATCH Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "volume": 77, "mute": false } + Send HTTPS Request PATCH /patch ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + ${response body}= Decode Bytes To String ${response body} UTF-8 + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${volume}= Get Json Value From String ${response body} json/volume + Should Be Equal ${volume} ${77} + + ${mute}= Get Json Value From String ${response body} json/mute + Should Be Equal ${mute} ${False} + + """ + + def __init__(self): + self.__server_host = None + self.__server_port = None + self.__client_host = None + self.__client_port = None + + self.__request_headers = {} + + self.__response_guard = threading.Lock() + self.__response_status = None + self.__response_message = None + self.__response_body_filename = None + self.__response_body = None + self.__response_headers = None + + self.__event_queue = threading.Condition() + self.__async_queue = {} + + + def initialize_client(self, server_host, server_port=None, client_host=None, client_port=0): + """ + + Initialize client using host and port of a server which will be used for communication. + + `server_host` [in] (string): Host of a server that is going to be used for communication by a client. + + `server_port` [in] (string|integer): Port of a server that is going to be used for communication by a client. Optional argument. + + `client_host` [in] (string): Host of a client (source) that is used to bind. Optional argument. + + `client_port` [in] (string|integer): Port of a client (source) that is used to bind. Optional argument. + + Example when server is located on a machine with address 192.168.0.1 and port 8000: + + +-------------------+-------------+------+ + | Initialize Client | 192.168.0.1 | 8000 | + +-------------------+-------------+------+ + + Example when server is located on a machine with IPv6 address 0000:0000:0000:0000:0000:0000:0000:0001 and port 8000: + + +-------------------+-----------------------------------------+------+ + | Initialize Client | 0000:0000:0000:0000:0000:0000:0000:0001 | 8000 | + +-------------------+-----------------------------------------+------+ + + .. code:: text + + Initialize Client 192.168.0.1 8000 + + Example when your server has name: + + +-------------------+-----------------+ + | Initialize Client | www.httpbin.org | + +-------------------+-----------------+ + + .. code:: text + + Initialize Client www.httpbin.org + + Example when a client is bind to the specific address 192.168.0.1 and port 8001: + + +-------------------+-------------+------+-------------+------+ + | Initialize Client | 192.168.0.5 | 8000 | 192.168.0.1 | 8001 | + +-------------------+-------------+------+-------------+------+ + + .. code:: text + + Initialize Client 192.168.0.5 8000 192.168.0.1 8001 + + Example when a client is bind to the specific address only 192.168.0.1 (without port): + + +-------------------+-------------+------+-------------+ + | Initialize Client | 192.168.0.5 | 8000 | 192.168.0.1 | + +-------------------+-------------+------+-------------+ + + .. code:: text + + Initialize Client 192.168.0.5 8000 192.168.0.1 + + """ + self.__server_host = server_host + self.__server_port = server_port or "" + + self.__client_host = client_host + self.__client_port = client_port + + + def __get_source_address(self): + if self.__client_host is None: + return None + + return self.__client_host, int(self.__client_port) + + + def __send(self, connection_type, method, url, body): + if self.__server_host is None or self.__server_port is None: + raise AssertionError("Client is not initialized (host and port are empty).") + + endpoint = "%s:%s" % (self.__server_host, str(self.__server_port)) + source_address = self.__get_source_address() + + logger.info("Connect to the server (type: '%s', endpoint: '%s')" % (connection_type, endpoint)) + + if connection_type == 'http': + connection = http.client.HTTPConnection(endpoint, source_address=source_address) + elif connection_type == 'https': + connection = http.client.HTTPSConnection(endpoint, source_address=source_address) + else: + raise AssertionError("Internal error of the client, please report to " + "'https://github.com/annoviko/robotframework-httpctrl/issues'.") + + logger.info("Send request to the server (method: '%s', url: '%s')." % (method, url)) + try: + connection.request(method, url, body, self.__request_headers) + except Exception as exception: + logger.info("Impossible to send request to the server (reason: '%s')." % str(exception)) + + self.__request_headers = {} + + logger.info("Request (type: '%s', method '%s') was sent to '%s'." % (connection_type, method, endpoint)) + logger.info("%s %s" % (method, url)) + if body is not None: + body_to_log = LoggerAssistant.get_body(body) + logger.info("%s" % body_to_log) + + return connection + + + def __read_body_to_file(self, server_response, filename): + logger.info("Write body to file '%s'." % filename) + + default_chunk_size = 10000000 # 10 MByte + with open(filename, "wb") as file_stream: + while True: + obtained_chunk = server_response.read(default_chunk_size) + if not obtained_chunk: + break + + file_stream.write(obtained_chunk) + + + def __wait_response(self, connection, read_body_to_file): + try: + server_response = connection.getresponse() + + with self.__response_guard: + self.__response_status = server_response.status + self.__response_message = server_response.msg + self.__response_headers = server_response.headers + + if read_body_to_file is None: + self.__response_body_filename = None + self.__response_body = server_response.read() + else: + self.__response_body_filename = read_body_to_file + self.__response_body = None + + self.__read_body_to_file(server_response, read_body_to_file) + + + except Exception as exception: + logger.info("Server has not provided response to the request (reason: %s)." % str(exception)) + + finally: + connection.close() + + + def __wait_response_async(self, connection, read_body_to_file): + try: + server_response = connection.getresponse() + + with self.__event_queue: + if read_body_to_file is None: + response_body_file = None + response_body = server_response.read() + + else: + response_body_file = read_body_to_file + response_body = None + + self.__read_body_to_file(server_response, read_body_to_file) + + headers = server_response.getheaders() + headers_dict = {} + if headers is not None: + for key, value in headers: + headers_dict[key] = value + + response_instance = Response(server_response.status, server_response.reason, + response_body, response_body_file, + headers_dict) + + self.__async_queue[connection] = response_instance + self.__event_queue.notify_all() + + except Exception as exception: + logger.info("Server has not provided response to the request (reason: %s)." % str(exception)) + + finally: + connection.close() + + + def __send_request(self, connection_type, method, url, body, read_body_to_file): + connection = self.__send(connection_type, method, url, body) + self.__wait_response(connection, read_body_to_file) + + + def __sent_request_async(self, connection_type, method, url, body, read_body_to_file): + connection = self.__send(connection_type, method, url, body) + + wait_thread = threading.Thread(target=self.__wait_response_async, args=(connection, read_body_to_file)) + wait_thread.daemon = True + wait_thread.start() + + return connection + + + def send_http_request(self, method, url, body=None, resp_body_to_file=None): + """ + + Send HTTP request with specified parameters. This function is blocked until server replies or + timeout connection. + + `method` [in] (string): Method that is used to send request (GET, POST, PUT, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + `resp_body_to_file` [in] (string): Path to file where response body should be written. By default is `None` - response + body is writing in RAM. It is useful to write response body into a file when it is expected to be big enough to keep + it in the memory. + + Example where GET request is sent to server: + + +-------------------+-----+-----+ + | Send HTTP Request | GET | /ip | + +-------------------+-----+-----+ + + .. code:: text + + Send HTTP Request GET /ip + + Example where POST request is sent with specific body: + + +-------------------+------+-------+-------------------------------+ + | Send HTTP Request | POST | /post | { "message": "Hello World!" } | + +-------------------+------+-------+-------------------------------+ + + .. code:: text + + ${body}= Set Variable { "message": "Hello World!" } + Send HTTP Request POST /post ${body} + + Example where GET request is sent and where response body is written into a file: + + +-------------------+-----+--------------------+-----------------------------------+ + | Send HTTP Request | GET | /download_big_file | resp_body_to_file=big_archive.tar | + +-------------------+-----+--------------------+-----------------------------------+ + + .. code:: text + + Send HTTP Request GET /download_big_file resp_body_to_file=big_archive.tar + + """ + self.__send_request('http', method, url, body, resp_body_to_file) + + + def send_http_request_async(self, method, url, body=None, resp_body_to_file=None): + """ + + Send HTTP request with specified parameters asynchronously. Non-blocking function to send request that waits + for reply using separate thread. Return connection object that is used as a key to get asynchronous response + using function 'Get Async Response'. + + `method` [in] (string): Method that is used to send request (GET, POST, PUT, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + `resp_body_to_file` [in] (string): Path to file where response body should be written. By default is `None` - response + body is writing in RAM. It is useful to write response body into a file when it is expected to be big enough to keep + it in the memory. + + Example where PUT request is sent with specific body: + + +----------------+-------------------------+-----+------+---------------+ + | ${connection}= | Send HTTP Request Async | PUT | /put | Hello Server! | + +----------------+-------------------------+-----+------+---------------+ + + .. code:: text + + ${connection}= Send HTTP Request Async PUT /put Hello Server! + + Example where GET request is sent and where response body is written into a file: + + +----------------+-------------------------+-----+--------------------+-----------------------------------+ + | ${connection}= | Send HTTP Request Async | GET | /download_big_file | resp_body_to_file=big_archive.tar | + +----------------+-------------------------+-----+--------------------+-----------------------------------+ + + .. code:: text + + ${connection}= Send HTTP Request Async GET /download_big_file resp_body_to_file=big_archive.tar + + """ + return self.__sent_request_async('http', method, url, body, resp_body_to_file) + + + def send_https_request(self, method, url, body=None, resp_body_to_file=None): + """ + + Send HTTPS request with specified parameters. + + `method` [in] (string): Method that is used to send request (GET, POST, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + `resp_body_to_file` [in] (string): Path to file where response body should be written. By default is `None` - response + body is writing in RAM. It is useful to write response body into a file when it is expected to be big enough to keep + it in the memory. + + Example where PATCH request to update parameters: + + +--------------------+-------+--------+---------------------------------+ + | Send HTTPS Request | PATCH | /patch | { "volume": 77, "mute": false } | + +--------------------+-------+--------+---------------------------------+ + + .. code:: text + + ${body}= Set Variable { "volume": 77, "mute": false } + Send HTTPS Request PATCH /patch ${body} + + """ + self.__send_request('https', method, url, body, resp_body_to_file) + + + def send_https_request_async(self, method, url, body=None, resp_body_to_file=None): + """ + + Send HTTPS request with specified parameters asynchronously. Non-blocking function to send request that waits + for reply using separate thread. Return connection object that is used as a key to get asynchronous response + using function 'Get Async Response'. + + `method` [in] (string): Method that is used to send request (GET, POST, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + `resp_body_to_file` [in] (string): Path to file where response body should be written. By default is `None` - response + body is writing in RAM. It is useful to write response body into a file when it is expected to be big enough to keep + it in the memory. + + Example where DELETE request is sent with specific body: + + +----------------+--------------------------+--------+---------+ + | ${connection}= | Send HTTPS Request Async | DELETE | /delete | + +----------------+--------------------------+--------+---------+ + + .. code:: text + + ${connection}= Send HTTPS Request Async DELETE /delete + + """ + return self.__sent_request_async('https', method, url, body, resp_body_to_file) + + + def set_request_header(self, key, value): + """ + + Set HTTP header for request that is going to be sent. Should be called before 'Send HTTP Request' or + 'Send HTTPS Request'. + + `key` [in] (string): Header name that should be used in the request (be aware of case-sensitive headers). + + `value` [in] (string): Value that corresponds to specified header. + + Example where several specific headers 'Content-Type' and 'Some Header' are set to request: + + +--------------------+------------------+-------------------+ + | Set Request Header | Important-Header | important-value | + +--------------------+------------------+-------------------+ + | Set Request Header | Some-Header | some-value-for-it | + +--------------------+------------------+-------------------+ + + .. code:: text + + Set Request Header Important-Header important-value + Set Request Header Some-Header some-value-for-it + + """ + self.__request_headers[key] = value + + + def get_response_status(self): + """ + + Return response code as an integer value. This method should be called once after 'Send HTTP Request' or + 'Send HTTPS Request'. It returns None, in case of attempt to get response code more then once or if + 'Send HTTP Request' or 'Send HTTPS Request' is not called before. + + Example how to get response code: + + +---------------------+---------------------+ + | ${response status}= | Get Response Status | + +---------------------+---------------------+ + + .. code:: text + + ${response status}= Get Response Status + + """ + with self.__response_guard: + status = self.__response_status + self.__response_status = None + return status + + + def get_response_message(self): + with self.__response_guard: + message = self.__response_message + self.__response_message = None + return message + + + def get_response_headers(self): + """ + + Return response headers as a dictionary. This method should be called once after 'Send HTTP Request' or + 'Send HTTPS Request'. It returns None, in case of attempt to get response code more then once or if + 'Send HTTP Request' or 'Send HTTPS Request' is not called before. + + Example how to get response code: + + +----------------------+----------------------+ + | ${response headers}= | Get Response Headers | + +----------------------+----------------------+ + + .. code:: text + + ${response headers}= Get Response Headers + + """ + with self.__response_guard: + headers = self.__response_headers + self.__response_headers = None + return headers + + + def get_response_body(self): + """ + + Return response body as a byte array. This method should be called once after 'Send HTTP Request' or + 'Send HTTPS Request'. It returns None, in case of attempt to get response code more then once or if + 'Send HTTP Request' or 'Send HTTPS Request' is not called before. + + Example how to get response code: + + +-------------------+-------------------+ + | ${response body}= | Get Response Body | + +-------------------+-------------------+ + + .. code:: text + + ${response body}= Get Response Body + + """ + with self.__response_guard: + body = None + + if self.__response_body is not None: + body = self.__response_body + + elif self.__response_body_filename is not None: + with open(self.__response_body_filename) as file_stream: + body = file_stream.read() + + self.__response_body_filename = None + self.__response_body = None + + return body + + + def get_async_response(self, connection, timeout=0): + """ + + Return response as an object for the specified connection. This method should be called once after + 'Send HTTP Request Async' or 'Send HTTPS Request Async'. It returns None if there is no response for the + specified connection. + + `connection` [in] (object): Connection for that response should be obtained. + + `timeout` [in] (int): Period of time in seconds to obtain response (by default is 0). + + Example how to get response object: + + +--------------+--------------------+ + | ${response}= | Get Async Response | + +--------------+--------------------+ + + Example how to try to get response object during 10 seconds: + + +--------------+--------------------+----+ + | ${response}= | Get Async Response | 10 | + +--------------+--------------------+----+ + + .. code:: text + + ${connection}= Send HTTP Async Request POST /post Hello Server! + ${response}= Get Async Response ${connection} 5 + + """ + with self.__event_queue: + start_time = datetime.datetime.now() + end_time = start_time + + while (end_time - start_time).total_seconds() < int(timeout): + if connection in self.__async_queue: + return self.__async_queue.pop(connection) + + def predicate(): + return connection in self.__async_queue + + self.__event_queue.wait_for(predicate, int(timeout)) + end_time = datetime.datetime.now() + + return self.__async_queue.pop(connection, None) + + + def get_status_from_response(self, response : Response): + """ + + Return response status as an integer value from the specified response object that was obtained by function + 'Get Async Response'. Return 'None' if response object is None. + + Example how to get response status from a response object: + + +---------------------+--------------------------+-------------+ + | ${response status}= | Get Status From Response | ${response} | + +---------------------+--------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response status}= Get Status From Response ${response} + + """ + if response is None: + logger.error("Impossible to get status from 'None' response object.") + return None + + return response.get_status() + + + def get_reason_from_response(self, response : Response): + """ + + Return response reason as a string from the specified response object that was obtained by function + 'Get Async Response'. For example, response code and reason are '200 OK', in this case 'OK' is going + to be returned by this function. + + Example how to get response reason from a response object: + + +---------------------+--------------------------+-------------+ + | ${response reason}= | Get Reason From Response | ${response} | + +---------------------+--------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response reason}= Get Reason From Response ${response} + + """ + if response is None: + logger.error("Impossible to get reason from 'None' response object.") + return None + + return response.get_reason() + + + def get_headers_from_response(self, response : Response) -> dict: + """ + + Return response headers as a dictionary from the specified response object that was obtained by function + 'Get Async Response'. Return 'None' if response object is None. + + Example how to get response headers from a response object: + + +----------------------+---------------------------+-------------+ + | ${response headers}= | Get Headers From Response | ${response} | + +----------------------+---------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response headers}= Get Headers From Response ${response} + + """ + if response is None: + logger.error("Impossible to get headers from 'None' response object.") + return None + + return response.get_headers() + + + def get_body_from_response(self, response : Response): + """ + + Return response body as a byte array from the specified response object that was obtained by function + 'Get Async Response'. Return 'None' if response object is None. + + Example how to get response code from a response object: + + +-------------------+------------------------+-------------+ + | ${response body}= | Get Body From Response | ${response} | + +-------------------+------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response body}= Get Body From Response ${response} + + """ + if response is None: + logger.error("Impossible to get body from 'None' response object.") + return None + + return response.get_body() + + + +class Server: + """ + + HTTP Server library that provides comprehensive interface to Robot Framework to control HTTP server. + + See other HttpCtrl libraries: + + - HttpCtrl.Client_ - HTTP/HTTP Client API for testing where easy-controlled HTTP/HTTPS client is required. + + - HttpCtrl.Json_ - Json related API for testing where work with Json message is required. + + - HttpCtrl.Logging_ - Logging related API to configure the logging system that is used by HttpCtrl library. + + .. _HttpCtrl.Client: client.html + .. _HttpCtrl.Json: json.html + .. _HttpCtrl.Logging: logging.html + + Here is an example of receiving POST request. In this example HTTP client sends POST request to HTTP server. HTTP + server receives it and checks incoming request for correctness. + + .. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Server + + Test Setup Initialize HTTP Client And Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + Receive And Reply To POST + ${request body}= Set Variable { "method": "POST" } + Send HTTP Request Async POST /post ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${request body} + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server + + In case of requirement to use IPv6 the keyword `Initialize HTTP Client And Server` might be the following: + + .. code:: robotframework + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 0000:0000:0000:0000:0000:0000:0000:0001 8000 + Start Server 0000:0000:0000:0000:0000:0000:0000:0001 8000 + + There is an example where server stubs are using. Sever stub is a pre-defined function that is used by the server to + reply automatically to a request that satisfies a user specific HTTP criteria. + + .. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Server + + Test Setup Initialize HTTP Client And Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + Set Signle Stub And Send Request + # Set server stub to reply automatically to POST /api/v1/post + Set Stub Reply POST /api/v1/post 200 Post Message + + # Send HTTP request to the server + Send HTTP Request POST /api/v1/post + + # Check that the client receives pre-defined values by the stub + ${status}= Get Response Status + ${body}= Get Response Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${status} ${200} + Should Be Equal ${body} Post Message + + # Check that the server receives a single request + ${count}= Get Stub Count POST /api/v1/post + Should Be Equal ${count} ${1} + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server + + """ + + def __init__(self): + self.__response_headers = {} + self.__request = None + + self.__server = None + self.__thread = None + + + def __del__(self): + self.stop_server() + + + def start_server(self, host, port): + """ + + Start HTTP server on specific address and port. Server should be closed when it is not required, for example, + when test is over. In case of double call of \`Start Server\`, the previous will be stopped and only then the + next one HTTP server will be started. + + `host` [in] (string): Address that will be used by HTTP server to listen. + + `port` [in] (string): Port that will be used by HTTP server to listen. + + Example how to initialize server: + + +--------------+-----------+------+ + | Start Server | 127.0.0.1 | 8000 | + +--------------+-----------+------+ + + .. code:: text + + Start Server 127.0.0.1 8000 + + It is a good practice to start server and stop it using 'Test Setup' and 'Test Teardown', for example: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Server + + Test Setup Initialize HTTP Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + HTTP Server Based Test + Wait For Request + Reply By 200 + + # Check incoming request + + *** Keywords *** + + Initialize HTTP Server + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server + + """ + + self.stop_server() + + logger.info("Prepare HTTP server '%s:%s' and thread to serve it." % (host, port)) + + self.__server = HttpServer(host, int(port)) + self.__thread = threading.Thread(target=self.__server.start, args=()) + self.__thread.start() + + # In case of start-stop, stop may be finished before server start and ir will be impossible to join thread. + self.__server.wait_run_state() + + + def stop_server(self): + """ + + Stop HTTP server if it has been started. This function should be called if server has been started. + + Example how to stop server: + + +-------------+ + | Stop Server | + +-------------+ + + .. code:: text + + Stop Server + + It is a good practice to start server and stop it using `Test Setup` and `Test Teardown` - see example for + \`Start Server\`. + + """ + if self.__server is not None: + self.__server.stop() + self.__thread.join() + + self.__response_headers = {} + self.__request = None + + ResponseStorage().clear() + RequestStorage().clear() + HttpStubContainer().clear() + + self.__server = None + self.__thread = None + + logger.info("HTTP server is stopped.") + + + def wait_for_request(self, timeout=5): + """ + + Command to server to wait incoming request. This call is blocked until HTTP request arrives. Basically server + receives all requests after \`Start Server\` and places them to internal queue. When test call function + \`Wait For Request\` it checks the queue and if it is not empty returns the first request in the queue. If the + queue is empty then function waits when the server receives request and place it to the queue. There is + default time period '5 seconds' to wait request and this waiting time can be changed. If during wait time the + request is not received then timeout error occurs. + + `timeout` [in] (int): Period of time in seconds when a request should be received by HTTP server. + + Example how to wait request. + + +------------------+ + | Wait For Request | + +------------------+ + + .. code:: text + + Wait For Request + + Example how to wait request during 2 seconds. + + +------------------+---+ + | Wait For Request | 2 | + +------------------+---+ + + .. code:: text + + Wait For Request 2 + + """ + self.__request = RequestStorage().pop(int(timeout)) + if self.__request is None: + raise AssertionError("Timeout: request was not received.") + + logger.info("Request is received: %s" % self.__request) + + + def wait_for_no_request(self, timeout=5.0): + """ + + Command to server to wait for no incoming request during specific time. This call is blocked until HTTP request + arrives or timeout. Basically server receives all requests after \`Start Server\` and places them to internal + queue. When test call function \`Wait For No Request\` it checks the queue and if it is not empty returns throws + exception. Otherwise it waits for request during 'timeout' seconds. If during this time request is received then + exception is thrown. + + `timeout` [in] (int): Period of time in seconds when requests should not be received by HTTP server. + + Example how to wait for lack of requests. + + +---------------------+ + | Wait For No Request | + +---------------------+ + + .. code:: text + + Wait For No Request + + Example how to wait for lack of requests during 10 seconds. + + +---------------------+----+ + | Wait For No Request | 10 | + +---------------------+----+ + + .. code:: text + + Wait For No Request 10 + + """ + self.__request = RequestStorage().pop(int(timeout)) + if self.__request is not None: + raise AssertionError("Request was received: %s." % self.__request) + + logger.info("Request is not received.") + + + def wait_and_ignore_request(self): + """ + + Command to server to wait incoming request and ignore it by closing connection. This call is blocked until HTTP + request arrives. Basically server receives all requests after \`Start Server\` and places them to internal + queue. When test call function \`Wait And Ignore Request\` it checks the queue and if it is not empty returns + the first request in the queue is ignore and connection is closed. If the queue is empty then function waits + when the server receives request and place it to the queue. + + Example how to wait and ignore request. + + +-------------------------+ + | Wait And Ignore Request | + +-------------------------+ + + .. code:: text + + Wait And Ignore Request + + """ + self.wait_for_request() + ResponseStorage().push(IgnoreRequest()) + logger.info("Request is ignored by closing connection.") + + + def set_stub_reply(self, method, url, status, body=None): + """ + + Sets stub reply for HTTP(S) server. This function sets a server stub to reply automatically by a specific + response to a specific request. When the stub is used to reply, then corresponding statistic is incremented + (see \`Get Stub Count\`). + + `method` [in] (string): Request method that is used to handle by server stub (GET, POST, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource that is used by server stub, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `status` [in] (int|string): HTTP status code for response that is used by server stub. + + `body` [in] (string|bytes): Response body that is used by server stub. + + Example how to set stub to reply automatically to request `POST` `/api/v1/request` by status `200`. + + +----------------+------+-----------------+-----+ + | Set Stub Reply | POST | /api/v1/request | 200 | + +----------------+------+-----------------+-----+ + + .. code:: text + + Set Stub Reply POST /api/v1/request 200 + + Example how to set stub to reply automatically using a specific body. + + +----------------+------+-----------------+-----+--------------------------+ + | Set Stub Reply | POST | /api/v1/request | 202 | Request has been handled | + +----------------+------+-----------------+-----+--------------------------+ + + .. code:: text + + Set Stub Reply POST /api/v1/request 200 Request has been handled + + """ + if self.__server is None: + message_error = "Impossible to set server stub reply (reason: 'server is not created')." + raise AssertionError(message_error) + + criteria = HttpStubCriteria(method=method, url=url) + response = Response(int(status), None, json.dumps(body), None, None) + HttpStubContainer().add(criteria, response) + + + def get_stub_count(self, method, url): + """ + + Returns server stub statistic that defines how many time the stub was used by server to reply. + + `method` [in] (string): Request method that is used to handle by server stub (GET, POST, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource that is used by server stub, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + Example how to get server stub statistic for request with `POST` method and URL `/api/v2/request`. + + +----------------+------+-----------------+ + | Get Stub Count | POST | /api/v2/request | + +----------------+------+-----------------+ + + .. code:: text + + Get Stub Count POST /api/v2/request + + Example how to get server stub statistic for request with `GET` method and URL `/get` + + +----------------+------+-----+ + | Get Stub Count | GET | /get | + +----------------+------+-----+ + + .. code:: text + + Get Stub Count GET /get + + """ + if self.__server is None: + message_error = "Impossible to get server stub statistic (reason: 'server is not created')." + raise AssertionError(message_error) + + criteria = HttpStubCriteria(method=method, url=url) + return HttpStubContainer().count(criteria) + + + def get_request_source_address(self): + """ + + Returns source address (client address) of received request as string value. This function should be called + after \`Wait For Request\`, otherwise None is returned. + + Example how to obtain source address of incoming request: + + +----------------------------+ + | Get Request Source Address | + +----------------------------+ + + .. code:: text + + ${source address}= Get Request Source Address + + """ + return self.__request.get_source_address() + + + def get_request_source_port(self): + """ + + Returns source port (client port) of received request as string value. This function should be called + after \`Wait For Request\`, otherwise None is returned. + + Example how to obtain source port of incoming request: + + +-------------------------+ + | Get Request Source Port | + +-------------------------+ + + .. code:: text + + ${source port}= Get Request Source Port + + """ + return str(self.__request.get_source_port()) + + + def get_request_source_port_as_integer(self): + """ + + Returns source port (client port) of received request as integer value. This function should be called + after \`Wait For Request\`, otherwise None is returned. + + Example how to obtain source port of incoming request: + + +------------------------------------+ + | Get Request Source Port As Integer | + +------------------------------------+ + + .. code:: text + + ${source port}= Get Request Source Port As Integer + + """ + return self.__request.get_source_port() + + + def get_request_method(self): + """ + + Returns method of received request as a string. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain method of incoming request: + + +--------------------+ + | Get Request Method | + +--------------------+ + + .. code:: text + + Get Request Method + + """ + return self.__request.get_method() + + + def get_request_body(self): + """ + + Returns body of received request as a string. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain body of incoming request: + + +------------------+ + | Get Request Body | + +------------------+ + + .. code:: text + + Get Request Body + + """ + return self.__request.get_body() + + + def get_request_headers(self): + """ + + Returns headers of received request as a dictionary. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain headers of incoming request: + + +---------------------+ + | Get Request Headers | + +---------------------+ + + .. code:: text + + Get Request Headers + + """ + return self.__request.get_headers() + + + def get_request_url(self): + """ + + Returns URL of received request as a string. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain URL of incoming request: + + +-----------------+ + | Get Request Url | + +-----------------+ + + .. code:: text + + Get Request Url + + """ + return self.__request.get_url() + + + def set_reply_header(self, key, value): + """ + + Set or insert new (if it does not exist yet) header to HTTP response. To send response itself function + \`Reply By\` is used. + + `key` [in] (string): HTTP header name. + + `value` [in] (string): HTTP header value. + + Example how to set header for HTTP response: + + +------------------+--------+----------------+ + | Set Reply Header | Origin | 127.0.0.1:8000 | + +------------------+--------+----------------+ + + .. code:: text + + Set Reply Header Origin 127.0.0.1:8000 + + Example how to set several headers for HTTP response: + + +------------------+-------------+----------------+ + | Set Reply Header | Origin | 127.0.0.1:8001 | + +------------------+-------------+----------------+ + | Set Reply Header | City-Source | St.-Petersburg | + +------------------+-------------+----------------+ + + .. code:: text + + Set Reply Header Origin 127.0.0.1:8000 + Set Reply Header City-Source St.-Petersburg + + """ + self.__response_headers[key] = value + + + def reply_by(self, status, body=None): + """ + + Send response using specified HTTP code and body. This function should be called after \`Wait For Request\`. + + `status` [in] (string): HTTP status code for response. + + `body` [in] (string|bytes): Body that should contain response. + + Example how to reply by 204 (No Content) to incoming request: + + +----------+-----+ + | Reply By | 204 | + +----------+-----+ + + .. code:: text + + Reply By 204 + + Example how to reply 200 (OK) with body to incoming request: + + +----------+-----+--------------------------+ + | Reply By | 200 | { "status": "accepted" } | + +----------+-----+--------------------------+ + + .. code:: text + + ${response body}= Set Variable { "status": "accepted" } + Reply By 204 ${response body} + + Example how to reply with a body represented by a sequence of bytes: + + ..code:: text + + Wait For Request + + ${body bytes}= Evaluate bytes((0x0a, 0x12, 0x0a)) + Reply By 200 ${body bytes} + + """ + response = Response(int(status), None, body, None, self.__response_headers) + ResponseStorage().push(response) + + +class Json: + """ + + Json library provide comprehensive interface to Robot Framework to work with JSON structures that are actively + used for Internet communication nowadays. + + See other HttpCtrl libraries: + + - HttpCtrl.Client_ - HTTP/HTTP Client API for testing where easy-controlled HTTP/HTTPS client is required. + + - HttpCtrl.Server_ - HTTP Server API for testing where easy-controlled HTTP server is required. + + - HttpCtrl.Logging_ - Logging related API to configure the logging system that is used by HttpCtrl library. + + .. _HttpCtrl.Client: client.html + .. _HttpCtrl.Server: server.html + .. _HttpCtrl.Logging: logging.html + + Example where Json values are updated in a string and then read from it: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Write And Read Json Nesting Value + ${json template}= Catenate + ... { + ... "book": { + ... "title": "St Petersburg: A Cultural History", + ... "author": "Solomon Volkov", + ... "price": 0, + ... "currency": "" + ... } + ... } + + ${catalog}= Set Json Value In String ${json template} book/price ${500} + ${catalog}= Set Json Value In String ${catalog} book/currency RUB + + ${title}= Get Json Value From String ${catalog} book/title + ${price}= Get Json Value From String ${catalog} book/price + ${currency}= Get Json Value From String ${catalog} book/currency + + + Here is an another example where Json array's values are updated and then read from it: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Write And Read Json Array Value + ${json template}= Catenate + ... { + ... "array": [ + ... "red", "green", "blue", "yellow" + ... ] + ... } + + ${colors}= Set Json Value In String ${json template} array/3 white + + ${red}= Get Json Value From String ${colors} array/0 + ${green}= Get Json Value From String ${colors} array/1 + ${blue}= Get Json Value From String ${colors} array/2 + ${white}= Get Json Value From String ${colors} array/3 + + """ + + @staticmethod + def get_json_value_from_string(json_string, path): + """ + + Return value from Json that is represented by string. Be aware, returned value's type corresponds to its + type in Json. + + `json_string` [in] (string): Json that is represented by string. + + `path` [in] (string): Path to Json node value. + + Example how to obtain Json value: + + +-----------+----------------------------+------------------------------------+------------+ + | ${value}= | Get Json Value From String | { "book": { "title": "Unknown" } } | book/title | + +-----------+----------------------------+------------------------------------+------------+ + + .. code:: text + + ${json}= Catenate + ... { + ... "book": { + ... "title": "Unknown" + ... } + ... } + ${value}= Get Json Value From String ${json} book/title + + """ + json_content = json.loads(json_string) + keys = path.split('/') + + current_element = json_content + for key in keys: + if isinstance(current_element, list): + key = int(key) + + current_element = current_element[key] + + return current_element + + + @staticmethod + def set_json_value_in_string(json_string, path, value): + """ + + Set value in Json that is represented by string. Be aware type of updated value should correspond to its + type in Json, otherwise type will be changed with new value. + + `json_string` [in] (string): Json that is represented by string. + + `path` [in] (string): Path to Json node value. + + `value` [in] (any): New value for the Json node. + + Example how to set Json value: + + +-----------+--------------------------+------------------------------------+------------+--------+ + | ${value}= | Set Json Value In String | { "book": { "title": "Unknown" } } | book/title | "Math" | + +-----------+--------------------------+------------------------------------+------------+--------+ + + .. code:: text + + ${json}= Catenate + ... { + ... "book": { + ... "title": "Unknown" + ... } + ... } + ${value}= Set Json Value In String ${json} book/title "Math" + + Example how to set value in Json array node: + + +-----------+--------------------------+--------------------------------------+---------+---------+ + | ${value}= | Set Json Value In String | { "array": [ "one", "two", "ten" ] } | array/2 | "three" | + +-----------+--------------------------+--------------------------------------+---------+---------+ + + .. code:: text + + ${json}= Catenate + ... { + ... "array": [ + ... "one", "two", "ten" + ... ] + ... } + ${value}= Set Json Value In String ${json} array/2 "three" + + """ + json_content = json.loads(json_string) + keys = path.split('/') + + current_element = json_content + for key in keys: + if key == keys[-1]: + if isinstance(current_element, list): + key = int(key) + + current_element[key] = value + else: + if isinstance(current_element, list): + key = int(key) + + current_element = current_element[key] + + return json.dumps(json_content) + + + +class Logging: + """ + + Logging library provide functionality to configure the logging system that is used by HttpCtrl library. + + See other HttpCtrl libraries: + + - HttpCtrl.Client_ - HTTP/HTTP Client API for testing where easy-controlled HTTP/HTTPS client is required. + + - HttpCtrl.Server_ - HTTP Server API for testing where easy-controlled HTTP server is required. + + - HttpCtrl.Json_ - Json related API for testing where work with Json message is required. + + .. _HttpCtrl.Client: client.html + .. _HttpCtrl.Server: server.html + .. _HttpCtrl.Json: json.html + + """ + + @staticmethod + def set_body_size_limit_to_log(body_size): + """ + + Set body (HTTP request/response) size that is allowed to log. By default the library logs `512` symbols of the body. If the + limit should be removed then `${None}` value can be provided to the function. + + The logging body limit protects test logs to be too large if tests use big data for testing. + + Example how to set the logging body limit to 1024 symbols: + + +----------------------------+------+ + | Set Body Size Limit To Log | 1024 | + +----------------------------+------+ + + .. code:: text + + Set Body Size Limit To Log 1024 + + Example how to remove the logging body limit: + + +----------------------------+---------+ + | Set Body Size Limit To Log | ${None} | + +----------------------------+---------+ + + .. code:: text + + Set Body Size Limit To Log ${None} + + """ + + LoggerAssistant.set_body_size(body_size) diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/http_handler.py b/libraries/robotframework-httpctrl/src/HttpCtrl/http_handler.py new file mode 100755 index 0000000000000000000000000000000000000000..cc7450d0319285441ea56faf1970540b0469058c --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/http_handler.py @@ -0,0 +1,125 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +from http.server import SimpleHTTPRequestHandler +from robot.api import logger + +from HttpCtrl.internal_messages import TerminationRequest, IgnoreRequest +from HttpCtrl.request import Request +from HttpCtrl.request_storage import RequestStorage +from HttpCtrl.response_storage import ResponseStorage +from HttpCtrl.http_stub import HttpStubContainer, HttpStubCriteria + + +class HttpHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + self.server_version = "HttpCtrl.Server/" + self.sys_version = "" + + SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) + + + def do_GET(self): + self.__default_handler('GET') + + + def do_POST(self): + self.__default_handler('POST') + + + def do_PUT(self): + self.__default_handler('PUT') + + + def do_OPTIONS(self): + self.__default_handler('OPTIONS') + + + def do_HEAD(self): + self.__default_handler('HEAD') + + + def do_PATCH(self): + self.__default_handler('PATCH') + + + def do_DELETE(self): + self.__default_handler('DELETE') + + + def log_message(self, format, *args): + return + + + def log_error(self, format, *args): + return + + + def log_request(self, code='-', size='-'): + return + + + def __extract_body(self): + body_length = int(self.headers.get('Content-Length', 0)) + if body_length > 0: + return self.rfile.read(body_length) + + return None + + + def __default_handler(self, method): + host, port = self.client_address[:2] + body = self.__extract_body() + + logger.info("'%s' request is received from '%s:%s'." % (method, host, port)) + + stub = HttpStubContainer().get(HttpStubCriteria(method=method, url=self.path), body) + if stub is not None: + response = stub.response + request = Request(host, port, method, self.path, self.headers, body) + RequestStorage().push(request) + else: + request = Request(host, port, method, self.path, self.headers, body) + RequestStorage().push(request) + + response = ResponseStorage().pop() + if isinstance(response, TerminationRequest) or isinstance(response, IgnoreRequest): + return + + try: + self.__send_response(response) + except Exception as exception: + logger.info("Response was not sent to client due to reason: '%s'." % str(exception)) + + + def __send_response(self, response): + if response is None: + logger.error("Response is not provided for incoming request.") + return + + self.send_response(response.get_status()) + + headers = response.get_headers() + if headers is not None: + for key, value in headers.items(): + self.send_header(key, value) + + body = response.get_body() + + if body is not None: + if isinstance(body, str): + body = body.encode("utf-8") + + + self.send_header('Content-Length', str(len(body))) + self.end_headers() + + if body is not None: + self.wfile.write(body) \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/http_server.py b/libraries/robotframework-httpctrl/src/HttpCtrl/http_server.py new file mode 100755 index 0000000000000000000000000000000000000000..d75b154f46575b70c4c9ea66b23c20edabe80757 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/http_server.py @@ -0,0 +1,108 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +import ipaddress +import socket +import threading + +from socketserver import TCPServer + +from robot.api import logger + +from HttpCtrl.http_handler import HttpHandler +from HttpCtrl.internal_messages import TerminationRequest +from HttpCtrl.response_storage import ResponseStorage + + +class TCPServerIPv6(TCPServer): + address_family = socket.AF_INET6 + + +class HttpServer: + def __init__(self, host, port): + self.__host = host + self.__port = port + + self.__handler = None + self.__server = None + + self.__is_run_state = False + self.__cv_run = threading.Condition() + + + def __del__(self): + if self.__server is not None: + self.stop() + + + def start(self): + self.__handler = HttpHandler + self.__server = self.__create_tcp_server() + self.__server.allow_reuse_address = True + + try: + with self.__cv_run: + self.__is_run_state = True + self.__cv_run.notify() + + self.__server.serve_forever() + + except Exception as exception: + self.stop() + raise exception + + + def wait_run_state(self): + with self.__cv_run: + while not self.__is_run_state: + self.__cv_run.wait() + + + def stop(self): + if self.__server is not None: + ResponseStorage().push(TerminationRequest()) + + self.__server.shutdown() + self.__server.server_close() + self.__server = None + + with self.__cv_run: + self.__is_run_state = False + + + def __create_tcp_server(self): + tcp_server = self.__create_ipv6_tcp_server() + if tcp_server is not None: + return tcp_server + + return self.__create_ipv4_tcp_server() + + + def __create_ipv6_tcp_server(self): + try: + TCPServerIPv6.allow_reuse_address = True + ipaddress.IPv6Address(self.__host) # if throws exception then address is not IPv6 + + tcp_server = TCPServerIPv6((self.__host, self.__port), self.__handler) + + logger.info("IPv6 TCP server '%s:%s' is created for HTTP." % (self.__host, str(self.__port))) + + return tcp_server + except: + return None + + + def __create_ipv4_tcp_server(self): + TCPServer.allow_reuse_address = True + tcp_server = TCPServer((self.__host, self.__port), self.__handler) + + logger.info("IPv4 TCP server '%s:%s' is created for HTTP." % (self.__host, str(self.__port))) + + return tcp_server diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/http_stub.py b/libraries/robotframework-httpctrl/src/HttpCtrl/http_stub.py new file mode 100755 index 0000000000000000000000000000000000000000..96039a7eb502e0dea0398091e9ea1c57c145e144 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/http_stub.py @@ -0,0 +1,157 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +from threading import Lock +import json +from urllib.parse import parse_qs +from HttpCtrl.utils.singleton import Singleton + + +class HttpStubCriteria: + def __init__(self, **kwargs): + self.method = kwargs.get('method', None) + if self.method is not None: + self.method = self.method.upper() + + self.url = kwargs.get('url', None) + if self.url is not None: + self.url = self.url.lower() + + + def __eq__(self, other): + return (self.method == other.method) and (self.url == other.url) + + +class HttpStub: + def __init__(self, criteria, response): + self.criteria = criteria + self.response = response + self.count = 0 + + +class HttpStubContainer(metaclass=Singleton): + def __init__(self): + self.__stubs = [] + self.__lock = Lock() + + + def add(self, criteria, response): + with self.__lock: + self.__stubs.append(HttpStub(criteria, response)) + + + def count(self, criteria): + with self.__lock: + for stub in self.__stubs: + if self.__is_satisfy(stub, criteria) is True: + return stub.count + + return 0 + + + def get(self, criteria, body): + with self.__lock: + for stub in self.__stubs: + if self.__is_satisfy(stub, criteria, body) is True: + stub.count += 1 + return stub + + return None + + + def clear(self): + with self.__lock: + self.__stubs.clear() + + def __is_satisfy(self, stub, criteria, body=None): + + if stub.criteria.method != criteria.method: + return False + + + if '?' in criteria.url: + # url should be divided into two parts, the former is the endpoint, while the latter refers to the parameters + criteria_url_components = criteria.url.split('?') + + # distribution brokers may add optional parameters to the url, so we should check if the url is valid even though it is not the same as the stub's url + if criteria.method == "GET": + if '?' in stub.criteria.url: + stub_url_components = stub.criteria.url.split('?') + + # the last slash should be removed + criteria_url = criteria_url_components[0].rstrip("/") + if criteria_url != stub_url_components[0]: + return False + + # extraction of attributes from the response body + attributes = [key.lower() for key in json.loads(stub.response.get_body())] + # criteria parameters should be separated to check if attributes are into the response body + if ('attrs' in parse_qs(criteria_url_components[1])): + criteria_parse = parse_qs(criteria_url_components[1])['attrs'] + criteria_attributes = [element for attr in criteria_parse for element in attr.split(",")] + for attr in criteria_attributes: + if attr not in attributes: + return False + + # stub's parameters should be separated to check if they are into the received request + stub_params = stub_url_components[1].split("&") + # Flatten the list to get a list of key-value elements + stub_params_components = [element for param in stub_params for element in param.split("=")] + for param in stub_params_components: + if param not in criteria_url_components[1]: + return False + return True + + # we should check if an id is into the url + if "urn" in stub.criteria.url: + stub_url = stub.criteria.url.split("/") + id = stub_url[-1].split(":") + for elements in id: + if elements not in (criteria.url): + return False + return True + else: + return False + + # if the method is not GET, we should ignore parameters and check if the url is the same + else: + if stub.criteria.url == criteria_url_components[0].rstrip("/"): + # if the request is a query via POST, we should have a specific check + if stub.criteria.url == "/ngsi-ld/v1/entityoperations/query": + response_body = json.loads(stub.response.get_body()) + body_str = body.decode('utf-8') + request_body = json.loads(body_str) + # check if the request body contains the same type as the response body + if "type" in request_body["entities"][0] or "id" in request_body["entities"][0]: + if request_body["entities"][0]["type"] != response_body["type"]: + return False + if request_body["entities"][0]["id"] != response_body["id"]: + return False + else: + return False + return True + + if stub.criteria.url == criteria.url.rstrip("/"): + # if the request is a query via POST, we should have a specific check + if stub.criteria.url == "/ngsi-ld/v1/entityoperations/query": + response_body = json.loads(stub.response.get_body()) + body_str = body.decode('utf-8') + request_body = json.loads(body_str) + # check if the request body contains the same type as the response body + if "type" in request_body["entities"][0] or "id" in request_body["entities"][0]: + if request_body["entities"][0]["type"] != response_body["type"]: + return False + if request_body["entities"][0]["id"] != response_body["id"]: + return False + else: + return False + return True + + return False \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/internal_messages.py b/libraries/robotframework-httpctrl/src/HttpCtrl/internal_messages.py new file mode 100755 index 0000000000000000000000000000000000000000..367aabe058355084f420f773b7eb6a00ebdd2ca4 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/internal_messages.py @@ -0,0 +1,17 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + + +class TerminationRequest: + pass + + +class IgnoreRequest: + pass diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/request.py b/libraries/robotframework-httpctrl/src/HttpCtrl/request.py new file mode 100755 index 0000000000000000000000000000000000000000..ec199b00c9533c152f36a676ff5bb997bd520ca1 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/request.py @@ -0,0 +1,47 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + + +from HttpCtrl.utils.logger import LoggerAssistant + + +class Request: + def __init__(self, host, port, method, url, headers, body=None): + self.__source_host = host + self.__source_port = port + self.__method = method + self.__url = url + self.__body = body + self.__headers = headers + + def __copy__(self): + return Request(self.__source_host, self.__source_port, self.__method, self.__url, self.__headers, self.__body) + + def __str__(self): + body_to_log = LoggerAssistant.get_body(self.__body) + return "%s %s\n%s" % (self.__method, self.__url, body_to_log) + + def get_source_address(self): + return self.__source_host + + def get_source_port(self): + return self.__source_port + + def get_method(self): + return self.__method + + def get_headers(self): + return self.__headers + + def get_url(self): + return self.__url + + def get_body(self): + return self.__body diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/request_storage.py b/libraries/robotframework-httpctrl/src/HttpCtrl/request_storage.py new file mode 100755 index 0000000000000000000000000000000000000000..4efd30c3bdfde89fc9507f4fb7dbd80aee90e085 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/request_storage.py @@ -0,0 +1,48 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +import threading + +from robot.api import logger + +from HttpCtrl.utils.singleton import Singleton + + +class RequestStorage(metaclass=Singleton): + def __init__(self): + self.__request = [] + self.__event_incoming = threading.Condition() + + + def __ready(self): + return len(self.__request) != 0 + + + def push(self, request): + with self.__event_incoming: + logger.info("Push request to the Request Storage: %s" % request) + self.__request.append(request) + self.__event_incoming.notify() + + + def pop(self, timeout=5.0): + with self.__event_incoming: + if not self.__ready(): + result = self.__event_incoming.wait_for(self.__ready, timeout) + if result is False: + return None + + request = self.__request.pop() + return request + + + def clear(self): + with self.__event_incoming: + self.__request.clear() diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/response.py b/libraries/robotframework-httpctrl/src/HttpCtrl/response.py new file mode 100755 index 0000000000000000000000000000000000000000..57eb94c500dfb2427fc6c4ea7f962170a1e362e9 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/response.py @@ -0,0 +1,53 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + + +from HttpCtrl.utils.logger import LoggerAssistant + + +class Response: + def __init__(self, status, reason, body, body_file, headers : dict): + self.__status = status + self.__reason = reason + self.__body = body + self.__body_file = body_file + self.__headers = headers + + def __str__(self): + if (self.__body is None) or (len(self.__body) == 0): + if self.__body_file is None: + return str(self.__status) + else: + return "%s\n" % (self.__status, self.__body_file) + + body_to_log = LoggerAssistant.get_body(self.__body) + return "%s\n%s" % (str(self.__status), body_to_log) + + def __copy__(self): + return Response(self.__status, self.__reason, self.__body, self.__body_file, self.__headers) + + def get_status(self): + return self.__status + + def get_reason(self): + return self.__reason + + def get_body_file(self): + return self.__body_file + + def get_body(self): + if (self.__body is None) and (self.__body_file is not None): + with open(self.__body_file) as file_stream: + return file_stream.read() + + return self.__body + + def get_headers(self) -> dict: + return self.__headers diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/response_storage.py b/libraries/robotframework-httpctrl/src/HttpCtrl/response_storage.py new file mode 100755 index 0000000000000000000000000000000000000000..d33c9bd4450c98c0814749e0d5c81ad3ef991074 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/response_storage.py @@ -0,0 +1,49 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +import threading + +from robot.api import logger + +from HttpCtrl.utils.singleton import Singleton + + +class ResponseStorage(metaclass=Singleton): + def __init__(self): + self.__response = None + self.__event_incoming = threading.Condition() + + + def __ready(self): + return self.__response is not None + + + def push(self, response): + with self.__event_incoming: + logger.info("Push response to the Response Storage: %s" % response) + self.__response = response + self.__event_incoming.notify() + + + def pop(self, timeout=5.0): + with self.__event_incoming: + if not self.__ready(): + result = self.__event_incoming.wait_for(self.__ready, timeout) + if result is False: + return None + + response = self.__response + self.__response = None + return response + + + def clear(self): + with self.__event_incoming: + self.__response = None diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/utils/__init__.py b/libraries/robotframework-httpctrl/src/HttpCtrl/utils/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..81e5d964ea2b524beaf9a9c26d66528824442d72 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/utils/__init__.py @@ -0,0 +1,9 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/utils/logger.py b/libraries/robotframework-httpctrl/src/HttpCtrl/utils/logger.py new file mode 100755 index 0000000000000000000000000000000000000000..7ceb4c1545b455fa66615d8ffafc0fd0d3d30eb3 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/utils/logger.py @@ -0,0 +1,37 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + + +from robot.api import logger + + +class LoggerAssistant: + __MAX_BODY_SIZE_TO_LOG = 512 + + + @staticmethod + def set_body_size(body_size): + if body_size is None: + logger.info("Disable the limit for the body size to log.") + else: + logger.info("Set the limit for the body size to log: '%d'." % body_size) + + LoggerAssistant.__MAX_BODY_SIZE_TO_LOG = body_size + + + @staticmethod + def get_body(body): + if body is not None: + if (LoggerAssistant.__MAX_BODY_SIZE_TO_LOG is None) or (len(body) < LoggerAssistant.__MAX_BODY_SIZE_TO_LOG): + return body + else: + return "%s...\n...\n" % (body[:LoggerAssistant.__MAX_BODY_SIZE_TO_LOG], LoggerAssistant.__MAX_BODY_SIZE_TO_LOG, len(body)) + + return "" diff --git a/libraries/robotframework-httpctrl/src/HttpCtrl/utils/singleton.py b/libraries/robotframework-httpctrl/src/HttpCtrl/utils/singleton.py new file mode 100755 index 0000000000000000000000000000000000000000..9f89e284e71b1045644a1f8816cf3eb86102b006 --- /dev/null +++ b/libraries/robotframework-httpctrl/src/HttpCtrl/utils/singleton.py @@ -0,0 +1,17 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2022 +Copyright: The 3-Clause BSD License + +""" + +class Singleton(type): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + + return cls._instances[cls] \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/PKG-INFO b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/PKG-INFO new file mode 100644 index 0000000000000000000000000000000000000000..4e59177a8a2dc79665b5f1db9b4680b7b35dff8d --- /dev/null +++ b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/PKG-INFO @@ -0,0 +1,157 @@ +Metadata-Version: 2.4 +Name: robotframework-httpctrl +Version: 0.3.1 +Summary: robotframework-httpctrl is a library for Robot Framework that provides HTTP/HTTPS client and HTTP server services +Home-page: https://github.com/annoviko/robotframework-httpctrl +Author: Andrei Novikov +Author-email: spb.andr@yandex.ru +License: BSD-3-Clause +Project-URL: Homepage, https://annoviko.github.io/robotframework-httpctrl/ +Project-URL: Repository, https://github.com/annoviko/robotframework-httpctrl +Project-URL: Documentation, https://annoviko.github.io/robotframework-httpctrl/ +Project-URL: Bug Tracker, https://github.com/annoviko/robotframework-httpctrl/issues +Keywords: httpctrl http https robotframework client server json test testing +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Telecommunications Industry +Classifier: License :: OSI Approved :: BSD License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Education +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers +Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: Robot Framework :: Library +Requires-Python: >=3.8 +License-File: LICENSE +Requires-Dist: robotframework +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: platform +Dynamic: project-url +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary + +HttpCtrl library for Robot Framework +==================================== + +**RobotFramework-HttpCtrl** is a library for Robot Framework that provides HTTP/HTTPS client and HTTP (IPv4 and IPv6) server services +to make REST API testing easy. + +**License**: The 3-Clause BSD License + +**Documentation**: https://annoviko.github.io/robotframework-httpctrl/ + + +Dependencies +============ + +**Python version**: >=3.8 + + +Installation +============ + +Installation using pip3 tool: + +.. code:: bash + + $ pip3 install robotframework-httpctrl + + +Brief Overview of the Library Content +===================================== + +**HttpCtrl** contains following general libraries: + +- **HttpCtrl.Client** - provides API to work with HTTP/HTTPS client [`link client documentation`_]. + +- **HttpCtrl.Server** - provides API to work with HTTP server [`link server documentation`_]. + +- **HttpCtrl.Json** - provides API to work Json messages [`link json documentation`_]. + +- **HttpCtrl.Logging** - provides API to configure the logging system that is used by `HttpCtrl` library [`link logging documentation`_]. + +.. _link client documentation: https://annoviko.github.io/robotframework-httpctrl/client.html +.. _link server documentation: https://annoviko.github.io/robotframework-httpctrl/server.html +.. _link json documentation: https://annoviko.github.io/robotframework-httpctrl/json.html +.. _link logging documentation: https://annoviko.github.io/robotframework-httpctrl/logging.html + + +Examples +======== + +Send GET request to obtain origin IP address and check that is not empty: + +.. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Get Origin Address + Initialize Client www.httpbin.org + Send HTTP Request GET /ip + + ${response status}= Get Response Status + ${response body}= Get Response Body + ${response body}= Decode Bytes To String ${response body} UTF-8 + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${origin}= Get Json Value From String ${response body} origin + Should Not Be Empty ${origin} + + +In this example HTTP client sends POST request to HTTP server. HTTP server receives it and checks incoming +request for correctness. + +.. code:: robotframework + + *** Settings *** + + Library String + Library HttpCtrl.Client + Library HttpCtrl.Server + + Test Setup Initialize HTTP Client And Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + Receive And Reply To POST + ${request body}= Set Variable { "message": "Hello!" } + Send HTTP Request Async POST /post ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${request body} + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server diff --git a/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/SOURCES.txt b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/SOURCES.txt new file mode 100644 index 0000000000000000000000000000000000000000..6e3ef2f9f4805cc7d68ff5eb9f00563327c82f7c --- /dev/null +++ b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/SOURCES.txt @@ -0,0 +1,23 @@ +CHANGES +LICENSE +PKG-INFO.rst +README.rst +VERSION +setup.py +src/HttpCtrl/__init__.py +src/HttpCtrl/http_handler.py +src/HttpCtrl/http_server.py +src/HttpCtrl/http_stub.py +src/HttpCtrl/internal_messages.py +src/HttpCtrl/request.py +src/HttpCtrl/request_storage.py +src/HttpCtrl/response.py +src/HttpCtrl/response_storage.py +src/HttpCtrl/utils/__init__.py +src/HttpCtrl/utils/logger.py +src/HttpCtrl/utils/singleton.py +src/robotframework_httpctrl.egg-info/PKG-INFO +src/robotframework_httpctrl.egg-info/SOURCES.txt +src/robotframework_httpctrl.egg-info/dependency_links.txt +src/robotframework_httpctrl.egg-info/requires.txt +src/robotframework_httpctrl.egg-info/top_level.txt \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/dependency_links.txt b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/dependency_links.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/requires.txt b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/requires.txt new file mode 100644 index 0000000000000000000000000000000000000000..b779ff39900b9c8e54f3cc0ecdea0a348c6cb84b --- /dev/null +++ b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/requires.txt @@ -0,0 +1 @@ +robotframework diff --git a/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/top_level.txt b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..f64ac6ecd64e2c64581e4ebd166f2fe67a5ee9ae --- /dev/null +++ b/libraries/robotframework-httpctrl/src/robotframework_httpctrl.egg-info/top_level.txt @@ -0,0 +1 @@ +HttpCtrl diff --git a/libraries/robotframework-httpctrl/tst/client_basic.robot b/libraries/robotframework-httpctrl/tst/client_basic.robot new file mode 100755 index 0000000000000000000000000000000000000000..b2b8d84cd6fad88a5f9c5cc1568f988631f301a3 --- /dev/null +++ b/libraries/robotframework-httpctrl/tst/client_basic.robot @@ -0,0 +1,289 @@ +*** Settings *** + +Library HttpCtrl.Client +Library HttpCtrl.Server +Library HttpCtrl.Json + +Library Collections +Library DateTime +Library OperatingSystem +Library String + + +*** Test Cases *** + +Send Request Without Reply + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Send HTTP Request Async POST /post Hello Server! + + Wait For Request + + ${message}= Get Request Body + ${message}= Decode Bytes To String ${message} UTF-8 + Should Be Equal ${message} Hello Server! + + ${status}= Get Response Status + ${body}= Get Response Body + + Should Be Equal ${status} ${None} + Should Be Equal ${body} ${None} + + +Wait Request During 2 Seconds + [Teardown] Stop Server + Start Server 127.0.0.1 8000 + + ${start time}= Get Current Date + Run Keyword And Expect Error * Wait For Request 2 + ${end time}= Get Current Date + ${duration}= Subtract Date From Date ${end time} ${start time} + Should Be Equal As Numbers ${2} ${duration} precision=1 + + +Receive No Async Responses + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection 1}= Send HTTP Request Async POST /post Post Message + ${connection 2}= Send HTTP Request Async PUT /put Put Message + + ${response}= Get Async Response ${connection 1} + Should Be Equal ${response} ${None} + + ${response}= Get Async Response ${connection 2} + Should Be Equal ${response} ${None} + + +Receive Async For One Connection + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection 1}= Send HTTP Request Async POST /post Post Message + + ${response}= Get Async Response ${connection 1} 1 + Should Be Equal ${response} ${None} + + Wait For Request + Reply By 200 Post Response + + ${response}= Get Async Response ${connection 1} 1 + Should Not Be Equal ${response} ${None} + + +Receive Async For One Connection IPv6 + [Teardown] Stop Server + Initialize Client 0000:0000:0000:0000:0000:0000:0000:0001 42001 + Start Server 0000:0000:0000:0000:0000:0000:0000:0001 42001 + + ${connection 1}= Send HTTP Request Async POST /post Post Message + + ${response}= Get Async Response ${connection 1} 1 + Should Be Equal ${response} ${None} + + Wait For Request + Reply By 200 Post Response + + ${response}= Get Async Response ${connection 1} 1 + Should Not Be Equal ${response} ${None} + + +Receive Async Responses + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection 1}= Send HTTP Request Async POST /post Post Message + ${connection 2}= Send HTTP Request Async PUT /put Put Message + + Wait For Request + Set Reply Header Header-Key Header-Value + Reply By 200 Post Response + + Wait For Request + Set Reply Header Another-Key Another-Value + Reply by 201 Put Response + + ${response}= Get Async Response ${connection 1} 1 + Should Not Be Equal ${response} ${None} + ${status}= Get Status From Response ${response} + ${body}= Get Body From Response ${response} + ${body}= Decode Bytes To String ${body} UTF-8 + ${headers}= Get Headers From Response ${response} + Should Be Equal ${status} ${200} + Should Be Equal ${body} Post Response + Dictionary Should Contain Item ${headers} Header-Key Header-Value + + ${response}= Get Async Response ${connection 2} 1 + Should Not Be Equal ${response} ${None} + ${status}= Get Status From Response ${response} + ${body}= Get Body From Response ${response} + ${body}= Decode Bytes To String ${body} UTF-8 + ${headers}= Get Headers From Response ${response} + Should Be Equal ${status} ${201} + Should Be Equal ${body} Put Response + Dictionary Should Contain Item ${headers} Another-Key Another-Value + + ${response}= Get Async Response ${connection 1} + Should Be Equal ${response} ${None} + + ${response}= Get Async Response ${connection 2} + Should Be Equal ${response} ${None} + + +Receive Only One Async Response + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection 1}= Send HTTP Request Async POST /post Post Message + Sleep 200ms + ${connection 2}= Send HTTP Request Async PUT /put Put Message + + Wait And Ignore Request + + Wait For Request + Set Reply Header Another-Key Another-Value + Reply by 201 Put Response + + ${response}= Get Async Response ${connection 1} 1 + Should Be Equal ${response} ${None} + + ${response}= Get Async Response ${connection 2} 1 + Should Not Be Equal ${response} ${None} + ${status}= Get Status From Response ${response} + ${body}= Get Body From Response ${response} + ${body}= Decode Bytes To String ${body} UTF-8 + ${headers}= Get Headers From Response ${response} + Should Be Equal ${status} ${201} + Should Be Equal ${body} Put Response + Dictionary Should Contain Item ${headers} Another-Key Another-Value + + ${response}= Get Async Response ${connection 1} + Should Be Equal ${response} ${None} + + ${response}= Get Async Response ${connection 2} + Should Be Equal ${response} ${None} + + +Bind Client to Address and Port + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 127.0.0.1 8001 + Start Server 127.0.0.1 8000 + + ${connection}= Send HTTP Request Async POST /post Post Message + + ${response}= Get Async Response ${connection} 1 + Should Be Equal ${response} ${None} + + Wait For Request + + ${source address}= Get Request Source Address + ${source port}= Get Request Source Port + ${source port as integer}= Get Request Source Port As Integer + + Should Be Equal ${source address} 127.0.0.1 + Should Be Equal ${source port} 8001 + Should Be Equal ${source port as integer} ${8001} + + +Bind Client to Address Only + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 127.0.0.1 + Start Server 127.0.0.1 8000 + + ${connection}= Send HTTP Request Async POST /post Post Message + + ${response}= Get Async Response ${connection} 1 + Should Be Equal ${response} ${None} + + Wait For Request + + ${source address}= Get Request Source Address + + Should Be Equal ${source address} 127.0.0.1 + + +Read Response Body to File + [Teardown] Stop Server + Initialize Client www.httpbin.org + + ${body content}= Set Variable Sync Response Body in File + ${filename}= Set Variable sync_resp_body.txt + + Send HTTP Request POST /post ${body content} resp_body_to_file=${filename} + + ${response status}= Get Response Status + Should Be Equal ${response status} ${200} + + File Should Exist ${filename} + File Should Not Be Empty ${filename} + + ${response body}= Get Response Body + ${data node}= Get Json Value From String ${response body} data + Should Be Equal ${data node} ${body content} + + Remove File ${filename} + + +Read Response Body with PNG to File + [Teardown] Stop Server + Initialize Client www.httpbin.org + + ${filename}= Set Variable random_image.png + + Send HTTP Request GET /image/png resp_body_to_file=${filename} + + ${response status}= Get Response Status + Should Be Equal ${response status} ${200} + + File Should Exist ${filename} + File Should Not Be Empty ${filename} + + Remove File ${filename} + + +Read Response Body with PNG to RAM + [Teardown] Stop Server + Initialize Client www.httpbin.org + + ${filename}= Set Variable random_image.png + + Send HTTP Request GET /image/png + + ${response status}= Get Response Status + ${response body}= Get Response Body + + Should Not Be Equal ${response body} ${None} + Should Be Equal ${response status} ${200} + + +Read Response Body to File Async + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${body content}= Set Variable Async Response Body in File + ${filename}= Set Variable async_resp_body.txt + + ${connection}= Send HTTP Request Async GET /get resp_body_to_file=${filename} + + Wait For Request + + Reply By 200 ${body content} + + ${response}= Get Async Response ${connection} 1 + + File Should Exist ${filename} + File Should Not Be Empty ${filename} + + Should Not Be Equal ${response} ${None} + ${body}= Get Body From Response ${response} + + Should Be Equal ${body content} ${body} + + Remove File ${filename} diff --git a/libraries/robotframework-httpctrl/tst/client_get_ip.robot b/libraries/robotframework-httpctrl/tst/client_get_ip.robot new file mode 100755 index 0000000000000000000000000000000000000000..6d89c3e914b7bd841835364f05992f9e118effc0 --- /dev/null +++ b/libraries/robotframework-httpctrl/tst/client_get_ip.robot @@ -0,0 +1,30 @@ +*** Settings *** + +Library HttpCtrl.Client +Library HttpCtrl.Json + + +*** Test Cases *** + +Get Origin Address + Initialize Client www.httpbin.org + Send HTTP Request GET /ip + + ${response status}= Get Response Status + ${response body}= Get Response Body + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${origin}= Get Json Value From String ${response body} origin + Should Not Be Empty ${origin} + + +Get Origin Using Wrong URL + Initialize Client www.httpbin.org + Send HTTP Request GET /some_wrong_url + + ${response status}= Get Response Status + + ${expected status}= Convert To Integer 404 + Should Be Equal ${response status} ${expected status} \ No newline at end of file diff --git a/libraries/robotframework-httpctrl/tst/client_methods.robot b/libraries/robotframework-httpctrl/tst/client_methods.robot new file mode 100755 index 0000000000000000000000000000000000000000..5cda3562d8d9864b1497f97bdae221ac5b8522fc --- /dev/null +++ b/libraries/robotframework-httpctrl/tst/client_methods.robot @@ -0,0 +1,74 @@ +*** Settings *** + +Library HttpCtrl.Client +Library HttpCtrl.Json + + +*** Test Cases *** + +Send HTTP POST Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "message": "Hello World!" } + Send HTTP Request POST /post ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${message}= Get Json Value From String ${response body} data + Should Be Equal ${message} ${body} + + +Send HTTPS PATCH Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "volume": 77, "mute": false } + Send HTTPS Request PATCH /patch ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${volume}= Get Json Value From String ${response body} json/volume + Should Be Equal ${volume} ${77} + + ${mute}= Get Json Value From String ${response body} json/mute + Should Be Equal ${mute} ${False} + + +Send HTTP GET Request + Initialize Client www.httpbin.org + + Send HTTPS Request GET /get + ${response status}= Get Response Status + + Should Be Equal ${response status} ${200} + + +Send HTTP PUT Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "message": "Welcome!" } + Send HTTPS Request PUT /put ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + + Should Be Equal ${response status} ${200} + ${data}= Get Json Value From String ${response body} data + Should Be Equal ${data} ${body} + + +Send HTTP DELETE Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "file": "file1.txt" } + Send HTTPS Request DELETE /delete ${body} + + ${response status}= Get Response Status + Should Be Equal ${response status} ${200} diff --git a/libraries/robotframework-httpctrl/tst/json_basic.robot b/libraries/robotframework-httpctrl/tst/json_basic.robot new file mode 100755 index 0000000000000000000000000000000000000000..e18fc7e21ced945f7e4044cdd04d84afe82e8041 --- /dev/null +++ b/libraries/robotframework-httpctrl/tst/json_basic.robot @@ -0,0 +1,94 @@ +*** Settings *** + +Library HttpCtrl.Json + + +*** Test Cases *** + +Write And Read Json Value + ${json template}= Catenate + ... { + ... "id": "200" + ... } + + ${json message}= Set Json Value In String ${json template} id 300 + ${id}= Get Json Value From String ${json message} id + + Should Be Equal 300 ${id} + + +Write And Read Json Nesting Value + ${json template}= Catenate + ... { + ... "book": { + ... "title": "St Petersburg: A Cultural History", + ... "author": "Solomon Volkov", + ... "price": 0, + ... "currency": "" + ... } + ... } + + ${catalog}= Set Json Value In String ${json template} book/price ${500} + ${catalog}= Set Json Value In String ${catalog} book/currency RUB + + ${title}= Get Json Value From String ${catalog} book/title + ${price}= Get Json Value From String ${catalog} book/price + ${currency}= Get Json Value From String ${catalog} book/currency + + Should Be Equal St Petersburg: A Cultural History ${title} + Should Be Equal ${500} ${price} + Should Be Equal ${currency} RUB + + +Write And Read Json Integer Value + ${json template}= Catenate + ... { + ... "integer": 1703 + ... } + + ${json message}= Set Json Value In String ${json template} integer ${1812} + ${value}= Get Json Value From String ${json message} integer + + Should Be Equal ${1812} ${value} + + +Write And Read Json Array Value + ${json template}= Catenate + ... { + ... "array": [ + ... "red", "green", "blue", "yellow" + ... ] + ... } + + ${colors}= Set Json Value In String ${json template} array/3 white + + ${red}= Get Json Value From String ${colors} array/0 + ${green}= Get Json Value From String ${colors} array/1 + ${blue}= Get Json Value From String ${colors} array/2 + ${white}= Get Json Value From String ${colors} array/3 + + Should Be Equal red ${red} + Should Be Equal green ${green} + Should Be Equal blue ${blue} + Should Be Equal white ${white} + + +Write And Read Json Array Netsting Value + ${json template}= Catenate + ... { + ... "array": [ + ... { "color": "red" }, + ... { "color": "green" }, + ... { "color": "white" } + ... ] + ... } + + ${colors}= Set Json Value In String ${json template} array/2/color blue + + ${red}= Get Json Value From String ${colors} array/0/color + ${green}= Get Json Value From String ${colors} array/1/color + ${blue}= Get Json Value From String ${colors} array/2/color + + Should Be Equal red ${red} + Should Be Equal green ${green} + Should Be Equal blue ${blue} diff --git a/libraries/robotframework-httpctrl/tst/server_basic.robot b/libraries/robotframework-httpctrl/tst/server_basic.robot new file mode 100755 index 0000000000000000000000000000000000000000..f5166832ff638ce8a29e5c855b72b9c127f3d970 --- /dev/null +++ b/libraries/robotframework-httpctrl/tst/server_basic.robot @@ -0,0 +1,277 @@ +*** Settings *** + +Library DateTime +Library String +Library HttpCtrl.Logging +Library HttpCtrl.Client +Library HttpCtrl.Server + + +*** Test Cases *** + +Start Stop Server + Start Server 127.0.0.1 8000 + Stop Server + + +Double Server Start + Start Server 127.0.0.1 8000 + Start Server 127.0.0.1 8001 + Stop Server + + +Empty Queue after Server Stop + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Send HTTP Request Async POST /post Hello Server! + Stop Server + + Start Server 127.0.0.1 8000 + ${start time}= Get Current Date + Wait For No Request 2 + ${end time}= Get Current Date + ${duration}= Subtract Date From Date ${end time} ${start time} + Should Be Equal As Numbers ${2} ${duration} precision=1 + + Stop Server + + +No Requests During 1 Second + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${start time}= Get Current Date + Wait For No Request 1 + ${end time}= Get Current Date + ${duration}= Subtract Date From Date ${end time} ${start time} + Should Be Equal As Numbers 1 ${duration} precision=1 + + Stop Server + + +Server Receives Two Messages at Once + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection 1}= Send HTTP Request Async POST /post Message to Post + ${connection 2}= Send HTTP Request Async PUT /put Message to Put + + Wait For Request + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} Message to Post + Reply By 200 + + Wait For Request + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + Should Be Equal ${method} PUT + Should Be Equal ${url} /put + Should Be Equal ${body} Message to Put + Reply By 201 + + Sleep 200ms + + ${response 1}= Get Async Response ${connection 1} + ${response 2}= Get Async Response ${connection 2} + + ${response status}= Get Status From Response ${response 1} + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${response status}= Get Status From Response ${response 2} + ${expected status}= Convert To Integer 201 + Should Be Equal ${response status} ${expected status} + + +Response Body + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection}= Send HTTP Request Async POST /post Post Message + + ${response}= Get Async Response ${connection} 1 + Should Be Equal ${response} ${None} + + Wait For Request + Reply By 200 Post Response + + ${response}= Get Async Response ${connection} 1 + ${response status}= Get Status From Response ${response} + ${response reason}= Get Reason From Response ${response} + + Should Be Equal ${response status} ${200} + Should Be Equal ${response reason} OK + + +Big Response Body + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${body content}= Set Variable 1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm + ${connection}= Send HTTP Request Async POST /post ${body content} + + Wait For Request + + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + Should Be Equal ${body} ${body content} + + Reply By 200 ${body content} + + ${response}= Get Async Response ${connection} 1 + ${response status}= Get Status From Response ${response} + ${response body}= Get Body From Response ${response} + ${response body}= Decode Bytes To String ${response body} UTF-8 + + Should Be Equal ${response status} ${200} + Should Be Equal ${response body} ${body content} + + +Big Response Body Without Logging Limit + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Set Body Size Limit to Log ${None} + + ${body content}= Set Variable 1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm + ${connection}= Send HTTP Request Async POST /post ${body content} + + Wait For Request + + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${body} ${body content} + + Reply By 200 ${body content} + + ${response}= Get Async Response ${connection} 1 + ${response status}= Get Status From Response ${response} + ${response body}= Get Body From Response ${response} + ${response body}= Decode Bytes To String ${response body} UTF-8 + + Should Be Equal ${response status} ${200} + Should Be Equal ${response body} ${body content} + + +Reply by Bytes Body + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + ${connection}= Send HTTP Request Async POST /post Post Message + + ${response}= Get Async Response ${connection} 1 + Should Be Equal ${response} ${None} + + ${body bytes}= Evaluate bytes((0x0a, 0x12, 0x0a)) + Wait For Request + Reply By 200 ${body bytes} + + ${response}= Get Async Response ${connection} 1 + ${response body bytes}= Get Body From Response ${response} + + Should Be Equal ${response body bytes} ${body bytes} + + +Set Signle Stub And One Call + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Set Stub Reply POST /api/v1/post 200 Post Message + + Check Stub Statistic POST /api/v1/post ${0} + Send Request and Check Stub POST /api/v1/post ${200} Post Message ${1} + + +Set Single Stub And Four Calls + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Set Stub Reply POST /api/v1/post 200 Post Message + + Check Stub Statistic POST /api/v1/post ${0} + + FOR ${index} IN RANGE 1 5 + Send Request and Check Stub POST /api/v1/post ${200} Post Message ${index} + END + + +Set Two Stubs And Five Calls Consequentially + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Set Stub Reply POST /api/v1/post 202 Post Message + Set Stub Reply GET /api/v1/get 201 Get Message + + Check Stub Statistic POST /api/v1/post ${0} + Check Stub Statistic GET /api/v1/get ${0} + + FOR ${index} IN RANGE 1 6 + Send Request and Check Stub POST /api/v1/post ${202} Post Message ${index} + Check Stub Statistic GET /api/v1/get ${0} + END + + FOR ${index} IN RANGE 1 6 + Check Stub Statistic POST /api/v1/post ${5} + Send Request and Check Stub GET /api/v1/get ${201} Get Message ${index} + END + + +Set Two Stubs And Seven Calls Simultaneously + [Teardown] Stop Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Set Stub Reply POST /api/v1/post 202 Post Message + Set Stub Reply GET /api/v1/get 201 Get Message + + Check Stub Statistic POST /api/v1/post ${0} + Check Stub Statistic GET /api/v1/get ${0} + + FOR ${index} IN RANGE 1 8 + Send Request and Check Stub POST /api/v1/post ${202} Post Message ${index} + Send Request and Check Stub GET /api/v1/get ${201} Get Message ${index} + END + + +*** Keywords *** + +Send Request and Check Stub + [Arguments] ${stub method} ${stub url} ${expected status} ${expected body} ${expected count} + + Send HTTP Request ${stub method} ${stub url} + + ${status}= Get Response Status + ${body}= Get Response Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${status} ${expected status} + Should Be Equal ${body} ${expected body} + + ${count}= Get Stub Count ${stub method} ${stub url} + + Should Be Equal ${count} ${expected count} + + +Check Stub Statistic + [Arguments] ${stub method} ${stub url} ${expected count} + + ${count}= Get Stub Count ${stub method} ${stub url} + + Should Be Equal ${count} ${expected count} diff --git a/libraries/robotframework-httpctrl/tst/server_methods.robot b/libraries/robotframework-httpctrl/tst/server_methods.robot new file mode 100755 index 0000000000000000000000000000000000000000..f24bafcac74540bb6d8526d4a3e43047234787ed --- /dev/null +++ b/libraries/robotframework-httpctrl/tst/server_methods.robot @@ -0,0 +1,238 @@ +*** Settings *** + +Library String + +Library HttpCtrl.Client +Library HttpCtrl.Server + +Test Setup Initialize HTTP Client And Server +Test Teardown Terminate HTTP Server + + +*** Test Cases *** + +Receive And Reply To POST + ${request body}= Set Variable { "method": "POST" } + Send HTTP Request Async POST /post ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${request body} + + +Receive And Reply To POST without Body + Send HTTP Request Async POST /post + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${None} + + +Receive And Reply To PUT + ${request body}= Set Variable { "method": "PUT" } + Send HTTP Request Async PUT /put ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} PUT + Should Be Equal ${url} /put + Should Be Equal ${body} ${request body} + + +Receive And Reply To PUT without Body + Send HTTP Request Async PUT /put + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + + Should Be Equal ${method} PUT + Should Be Equal ${url} /put + Should Be Equal ${body} ${None} + + +Receive And Reply To PATCH + ${request body}= Set Variable { "method": "PATCH" } + Send HTTP Request Async PATCH /patch ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} PATCH + Should Be Equal ${url} /patch + Should Be Equal ${body} ${request body} + + +Receive And Reply To PATCH without Body + Send HTTP Request Async PATCH /patch + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + + Should Be Equal ${method} PATCH + Should Be Equal ${url} /patch + Should Be Equal ${body} ${None} + + +Receive And Reply To GET + Send HTTP Request Async GET /get + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + + Should Be Equal ${method} GET + Should Be Equal ${url} /get + + +Receive And Reply To GET with Body + ${request body}= Set Variable { "method": "GET" } + Send HTTP Request Async GET /get ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} GET + Should Be Equal ${url} /get + Should Be Equal ${body} ${request body} + + +Receive And Reply To DELETE + Send HTTP Request Async DELETE /delete + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + + Should Be Equal ${method} DELETE + Should Be Equal ${url} /delete + + +Receive And Reply To DELETE with Body + ${request body}= Set Variable { "method": "DELETE" } + Send HTTP Request Async DELETE /delete ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} DELETE + Should Be Equal ${url} /delete + Should Be Equal ${body} ${request body} + + +Receive And Reply To HEAD + Send HTTP Request Async HEAD /head + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + + Should Be Equal ${method} HEAD + Should Be Equal ${url} /head + + +Receive And Reply To HEAD with Body + ${request body}= Set Variable { "method": "HEAD" } + Send HTTP Request Async HEAD /head ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} HEAD + Should Be Equal ${url} /head + Should Be Equal ${body} ${request body} + + +Receive And Reply To OPTIONS + Send HTTP Request Async OPTIONS /options + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + + Should Be Equal ${method} OPTIONS + Should Be Equal ${url} /options + + +Receive And Reply To OPTIONS with Body + ${request body}= Set Variable { "method": "OPTIONS" } + Send HTTP Request Async OPTIONS /options ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + ${body}= Decode Bytes To String ${body} UTF-8 + + Should Be Equal ${method} OPTIONS + Should Be Equal ${url} /options + Should Be Equal ${body} ${request body} + + +*** Keywords *** + +Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + +Terminate HTTP Server + Stop Server \ No newline at end of file