diff --git a/bin/sapcli b/bin/sapcli index ce264357..357d9cf3 100755 --- a/bin/sapcli +++ b/bin/sapcli @@ -76,8 +76,8 @@ def parse_command_line(argv): arg_parser.add_argument("--msserv", default=os.getenv("SAP_MSSERV"), help="Message server port") arg_parser.add_argument("--sysid", default=os.getenv("SAP_SYSID"), help="System ID (use if connecting via a " "message server") - arg_parser.add_argument("--group", default=os.getenv("SAP_GROUP"), help="Group (use if connecting via a message " - "server") + arg_parser.add_argument("--rfc_group", # name "group" is already in use + default=os.getenv("SAP_GROUP"), help="Group (use if connecting via a message server)") arg_parser.add_argument("--snc_qop", default=os.getenv("SNC_QOP"), help="SAP Secure Login Client QOP") arg_parser.add_argument("--snc_myname", default=os.getenv("SNC_MYNAME"), help="SAP Secure Login Client MyName") arg_parser.add_argument("--snc_partnername", @@ -86,6 +86,11 @@ def parse_command_line(argv): help="SAP Secure Login Client library (e.g. " "/Applications/Secure Login Client.app/Contents/MacOS/lib/libsapcrypto.dylib") + arg_parser.add_argument("--rest-over-rfc", action='store_true', dest="rest_over_rfc", + default=os.getenv("SAP_REST_OVER_RFC") not in [None, 'n', 'no', 'false', 'off'], + help="Prefer doing rest call over SAP RFC client " + "(to use SNC or if HTTP port is firewalled)") + subparsers = arg_parser.add_subparsers() # pylint: disable=not-an-iterable for connection, cmd in sap.cli.get_commands(): diff --git a/doc/configuration.md b/doc/configuration.md index 440a5105..b07c517e 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -83,6 +83,40 @@ Your SAP user's password. This parameter is mandatory and if you do not provided it on the command line or as the environment variable `SAP_PASSWORD`, sapcli will prompt you for it. +### --mshost, --msport + +Message server address resp. port, if connecting via RFC to a Netweaver server via a load balancer. + +### --sysid + +System ID if connection via a message server. + +### --rfc_group + +Group used if connection via message server. + +### --snc_qop + +SAP Secure Login Client's quality of protection. + +### --snc_myname + +Your name on SAP Secure Login Client. + +Example: `"p:CN=I0123456, O=SAP-AG, C=DE"` + +### --snc_partnername + +SAP Secure Login Client Partner name + +### --snc_lib + +Location of SAP Secure Login Client Library. E.g., `/Applications/Secure Login Client.app/Contents/MacOS/lib/libsapcrypto.dylib` for mac. + +### --rest-over-rfc + +Use RFC connection to tunnel REST request to the Netweaver server. This is useful if the HTTP are firewalled of you want to use SAP Secure Login Client for authentication. + ## Environment variables - `SAP_ASHOST` : default value for the command line parameter --ashost @@ -100,3 +134,7 @@ or as the environment variable `SAP_PASSWORD`, sapcli will prompt you for it. - `SAPCLI_LOG_LEVEL` : pass the desired log level - the lower number the more messages (`CRITICAL=50, ERROR=40, WARNING=30, INFO=20, DEBUG=10, NOTSET=0`) - `SAPCLI_HTTP_TIMEOUT` : floating point number representing timeout for HTTP requests; default=900s +- `SAP_MSHOST`, `SAP_MSSERV` default values for command line parameters --mshost resp. --msserv. +- `SAP_GROUP` default value for --rfc_group +- `SNC_QOP`, `SNC_MYNAME`, `SNC_PARTNERNAME`, `SNC_LIB` default values for command line parameters of --snc_qop, --snc_myname, --snc_partnername, --snc_lib +- `SAP_REST_OVER_RFC` default value for command line parameter --rest-over-rfc. diff --git a/requirements.txt b/requirements.txt index 4007724c..761205f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests>=2.20.0 pyodata==1.7.0 -PyYAML==5.4.1 +PyYAML==5.4.1 \ No newline at end of file diff --git a/sap/adt/__init__.py b/sap/adt/__init__.py index c75c8a6a..9fd28761 100644 --- a/sap/adt/__init__.py +++ b/sap/adt/__init__.py @@ -1,6 +1,6 @@ """Base classes for ADT functionality modules""" -from sap.adt.core import Connection # noqa: F401 +from sap.adt.core import ConnectionViaHTTP, ConnectionViaRFC, Connection # noqa: F401 from sap.adt.function import FunctionGroup, FunctionModule # noqa: F401 from sap.adt.objects import ADTObject, ADTObjectType, ADTCoreData, OrderedClassMembers # noqa: F401 from sap.adt.objects import Class, Interface, DataDefinition # noqa: F401 diff --git a/sap/adt/core.py b/sap/adt/core.py index 32bede6d..3c9d4db6 100644 --- a/sap/adt/core.py +++ b/sap/adt/core.py @@ -1,6 +1,10 @@ """Base ADT functionality module""" import os +import urllib +from abc import ABC, abstractmethod +from typing import Any, NoReturn, Optional, Union, Dict, List +from dataclasses import dataclass import xml.sax from xml.sax.handler import ContentHandler @@ -15,8 +19,7 @@ HTTPRequestError, UnexpectedResponseContent, UnauthorizedError, - TimedOutRequestError -) + TimedOutRequestError) def mod_log(): @@ -87,9 +90,144 @@ def _get_collection_accepts(discovery_xml): return xml_handler.result +@dataclass +class Response: + """Response Dataclass + It abstracts from requests Response class and can also be used for RFC requests""" + text: str + headers: Dict[str, str] + status_code: int + status_line: str = "" + + # pylint: disable=too-many-instance-attributes -class Connection: - """ADT Connection for HTTP communication built on top Python requests. +class Connection(ABC): + """Base class for ADT Connection for HTTP communication. + """ + + def __init__(self): + self._adt_uri = 'sap/bc/adt' + self._query_args = '' + self._base_url = '' + self._collection_types = None + + @property + def uri(self) -> str: + """ADT path for building URLs (e.g. sap/bc/adt)""" + + return self._adt_uri + + def _build_adt_url(self, adt_uri) -> str: + """Creates complete URL from a fragment of ADT URI + where the fragment usually refers to an ADT object + """ + + return f'{self._base_url}/{adt_uri}?{self._query_args}' + + @abstractmethod + def _execute_raw(self, method: str, uri: str, params: Optional[Dict[str, + str]], + headers: Optional[Dict[str, str]], + body: Optional[str]) -> Response: + pass + + # pylint: disable=too-many-arguments + def execute(self, + method: str, + adt_uri: str, + params: Optional[Dict[str, str]] = None, + headers: Optional[Dict[str, str]] = None, + body: Optional[str] = None, + accept: Optional[Union[str, List[str]]] = None, + content_type: Optional[str] = None) -> Response: + """Executes the given ADT URI as an HTTP request and returns + the requests response object + """ + + url = self._build_adt_url(adt_uri) + + if headers is None: + headers = {} + + if accept is not None: + if isinstance(accept, list): + headers['Accept'] = ', '.join(accept) + else: + headers['Accept'] = accept + + if content_type is not None: + headers['Content-Type'] = content_type + + if not headers: + headers = None + + method = method.upper() + + resp = self._execute_raw(method, url, params, headers, body) + + if accept: + resp_content_type = resp.headers['Content-Type'] + + if isinstance(accept, str): + accept = [accept] + + if not any((resp_content_type.startswith(accepted) + for accepted in accept)): + raise UnexpectedResponseContent(accept, resp_content_type, + resp.text) + + return resp + + def get_text(self, relativeuri): + """Executes a GET HTTP request with the headers Accept = text/plain. + """ + + return self.execute('GET', + relativeuri, + headers={ + 'Accept': 'text/plain' + }).text + + @property + def collection_types(self): + """Returns dictionary of Object type URI fragment and list of + supported MIME types. + """ + + if self._collection_types is None: + response = self.execute('GET', 'discovery') + self._collection_types = _get_collection_accepts(response.text) + + return self._collection_types + + def get_collection_types(self, basepath, default_mimetype): + """Returns the accepted object XML format - mime type""" + + uri = f'/{self._adt_uri}/{basepath}' + try: + return self.collection_types[uri] + except KeyError: + return [default_mimetype] + + def _handle_http_error(self, req, res: Response) -> NoReturn: + """Raise the correct exception based on response content.""" + + if res.headers['content-type'] == 'application/xml': + error = new_adt_error_from_xml(res.text) + + if error is not None: + raise error + + # else - unformatted text + if res.status_code == 401: + user = getattr(self, "_user", default="") # type: ignore + raise UnauthorizedError(req, res, user) + + raise HTTPRequestError(req, res) + + +class ConnectionViaHTTP(Connection): + """ADT Connection using requests and standard HTTP protocol. """ # pylint: disable=too-many-arguments @@ -104,6 +242,7 @@ def __init__(self, host, client, user, password, port=None, ssl=True, verify=Tru - ssl: boolean to switch between http and https - verify: boolean to switch SSL validation on/off """ + super().__init__() setup_keepalive() @@ -123,7 +262,6 @@ def __init__(self, host, client, user, password, port=None, ssl=True, verify=Tru self._user = user self._auth = HTTPBasicAuth(user, password) self._session = None - self._collection_types = None self._timeout = config_get('http_timeout') @property @@ -132,19 +270,6 @@ def user(self): return self._user - @property - def uri(self): - """ADT path for building URLs (e.g. sap/bc/adt)""" - - return self._adt_uri - - def _build_adt_url(self, adt_uri): - """Creates complete URL from a fragment of ADT URI - where the fragment usually refers to an ADT object - """ - - return f'{self._base_url}/{adt_uri}?{self._query_args}' - def _handle_http_error(self, req, res): """Raise the correct exception based on response content.""" @@ -249,66 +374,89 @@ def _get_session(self): return self._session - def execute(self, method, adt_uri, params=None, headers=None, body=None, accept=None, content_type=None): - """Executes the given ADT URI as an HTTP request and returns - the requests response object - """ + def _execute_raw(self, method: str, uri: str, params: Optional[Dict[str, + str]], + headers: Optional[Dict[str, str]], body: Optional[str]): session = self._get_session() - url = self._build_adt_url(adt_uri) + resp = self._execute_with_session(session, + method, + uri, + params=params, + headers=headers, + body=body) - if headers is None: - headers = {} + return Response(text=resp.text or "", + headers=resp.headers or {}, + status_code=resp.status_code, + status_line="") - if accept is not None: - if isinstance(accept, list): - headers['Accept'] = ', '.join(accept) - else: - headers['Accept'] = accept - if content_type is not None: - headers['Content-Type'] = content_type +class ConnectionViaRFC(Connection): + """ConnetionViaRFC is a ADT Connection that dispatches the HTTP requests via SAP's RFC connector""" - if not headers: - headers = None - - resp = self._execute_with_session(session, method, url, params=params, headers=headers, body=body) - - if accept: - resp_content_type = resp.headers['Content-Type'] - - if isinstance(accept, str): - accept = [accept] - - if not any((resp_content_type.startswith(accepted) for accepted in accept)): - raise UnexpectedResponseContent(accept, resp_content_type, resp.text) - - return resp - - def get_text(self, relativeuri): - """Executes a GET HTTP request with the headers Accept = text/plain. - """ - - return self.execute('GET', relativeuri, headers={'Accept': 'text/plain'}).text - - @property - def collection_types(self): - """Returns dictionary of Object type URI fragment and list of - supported MIME types. - """ - - if self._collection_types is None: - response = self.execute('GET', 'discovery') - self._collection_types = _get_collection_accepts(response.text) - - return self._collection_types - - def get_collection_types(self, basepath, default_mimetype): - """Returns the accepted object XML format - mime type""" - - uri = f'/{self._adt_uri}/{basepath}' - try: - return self.collection_types[uri] - except KeyError: - return [default_mimetype] + def __init__(self, rfc_conn): + super().__init__() + self.rfc_conn = rfc_conn + + def _make_request(self, method: str, uri: str, params: Optional[Dict[str, + str]], + headers: Optional[Dict[str, str]], + body: Optional[str]) -> Dict[str, Any]: + if params: + params_encoded = "?" + urllib.parse.urlencode(params) + else: + params_encoded = "" + + req: Dict[str, Any] = { + "REQUEST_LINE": { + "METHOD": method, + "URI": f"/{self._adt_uri}{uri}{params_encoded}", + } + } + + if headers: + req['HEADER_FIELDS'] = [{ + "NAME": name, + "VALUE": value + } for name, value in headers.items()] + + if body: + req["MESSAGE_BODY"] = body.encode("utf-8") + + return req + + @staticmethod + def _parse_response(resp) -> Response: + status_code = int(resp["STATUS_LINE"]["STATUS_CODE"]) + status_line = resp['STATUS_LINE']['REASON_PHRASE'] + + body = resp["MESSAGE_BODY"].decode("utf-8", "strict") + headers = {} + for field in resp["HEADER_FIELDS"]: + headers[field["NAME"].lower()] = field["VALUE"] + + return Response(text=body, + headers=headers, + status_code=status_code, + status_line=status_line) + + def _execute_raw(self, method: str, uri: str, params: Optional[Dict[str, + str]], + headers: Optional[Dict[str, str]], + body: Optional[str]) -> Response: + req = self._make_request(method=method, + uri=uri, + params=params, + headers=headers, + body=body) + mod_log().info('Executing RFC request line %s', req) + resp = self.rfc_conn.call("SADT_REST_RFC_ENDPOINT", REQUEST=req) + mod_log().info('Got response %s', resp) + parsed_resp = self._parse_response(resp["RESPONSE"]) + + if parsed_resp.status_code >= 400: + self._handle_http_error(req, parsed_resp) + + return parsed_resp diff --git a/sap/cli/__init__.py b/sap/cli/__init__.py index 43408020..0f00fc12 100644 --- a/sap/cli/__init__.py +++ b/sap/cli/__init__.py @@ -96,9 +96,17 @@ def adt_connection_from_args(args): import sap.adt - return sap.adt.Connection( - args.ashost, args.client, args.user, args.password, - port=args.port, ssl=args.ssl, verify=args.verify) + if args.ashost and args.password and not args.rest_over_rfc: + return sap.adt.ConnectionViaHTTP(args.ashost, + args.client, + args.user, + args.password, + port=args.port, + ssl=args.ssl, + verify=args.verify) + + rfc_connection = rfc_connection_from_args(args) + return sap.adt.ConnectionViaRFC(rfc_connection) def rfc_connection_from_args(args): @@ -107,11 +115,14 @@ def rfc_connection_from_args(args): rfc_args_name = [ "ashost", "sysnr", "client", "user", "password", "mshost", "msserv", - "sysid", "group", "snc_qop", "snc_myname", "snc_partnername", "snc_lib" + "sysid", "rfc_group", "snc_qop", "snc_myname", "snc_partnername", + "snc_lib" ] + name_rewrite = {"password": "passwd", "rfc_group": "group"} + rfc_args = { - name if name != "password" else "passwd": getattr(args, name) + name_rewrite.get(name, name): getattr(args, name) for name in rfc_args_name if name in args and getattr(args, name) } @@ -125,6 +136,9 @@ def gcts_connection_from_args(args): import sap.rest + if args.rest_over_rfc: + raise NotImplementedError + return sap.rest.Connection('sap/bc/cts_abapvcs', 'system', args.ashost, args.client, args.user, args.password, port=args.port, ssl=args.ssl, verify=args.verify) @@ -135,6 +149,10 @@ def odata_connection_from_args(service_name, args): """ import sap.odata + + if args.rest_over_rfc: + raise NotImplementedError + return sap.odata.Connection(service_name, args.ashost, args.port, args.client, args.user, args.password, args.ssl, args.verify) diff --git a/sap/cli/startrfc.py b/sap/cli/startrfc.py index f355bfe5..ccd57b89 100644 --- a/sap/cli/startrfc.py +++ b/sap/cli/startrfc.py @@ -10,7 +10,6 @@ import sap.cli.core from sap.cli.core import InvalidCommandLineError - FORMATTERS = { 'human': pprint.PrettyPrinter(indent=2).pformat, 'json': json.dumps diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/unit/mock.py b/test/unit/mock.py index 763a74c0..2887e06c 100644 --- a/test/unit/mock.py +++ b/test/unit/mock.py @@ -216,7 +216,7 @@ def mock_methods(self): return [(e.method, e.adt_uri) for e in self.execs] -class Connection(sap.adt.Connection): +class ConnectionViaHTTP(sap.adt.ConnectionViaHTTP): def __init__(self, responses=None, user='ANZEIGER', collections=None, asserter=None): """ @@ -225,7 +225,7 @@ def __init__(self, responses=None, user='ANZEIGER', collections=None, asserter=N if you want to automatically check the request. Ins such case, you should also pass the argument asserter. """ - super(Connection, self).__init__('mockhost', 'mockclient', user, 'mockpass') + super().__init__('mockhost', 'mockclient', user, 'mockpass') self.collections = collections self.execs = list() diff --git a/test/unit/test_bin_sapcli.py b/test/unit/test_bin_sapcli.py index 772248ac..2e65b83f 100644 --- a/test/unit/test_bin_sapcli.py +++ b/test/unit/test_bin_sapcli.py @@ -48,7 +48,7 @@ def test_args_sanity(self): 'password': 'Down1oad', 'verify': False, 'verbose_count': 0, - 'group': None, + 'rfc_group': None, 'mshost': None, 'msserv': None, 'snc_myname': None, @@ -56,6 +56,7 @@ def test_args_sanity(self): 'snc_qop': None, 'snc_lib': "somelib.dylib", 'sysid': None, + 'rest_over_rfc': False }) def test_args_no_ashost(self): diff --git a/test/unit/test_sap_adt_atc.py b/test/unit/test_sap_adt_atc.py index 24ed3198..cbfccb3b 100644 --- a/test/unit/test_sap_adt_atc.py +++ b/test/unit/test_sap_adt_atc.py @@ -9,12 +9,12 @@ import sap.adt.objects from sap.adt.marshalling import Marshal -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Response, Request from fixtures_adt_atc import ADT_XML_ATC_CUSTOMIZING, ADT_XML_ATC_CUSTOMIZING_ATTRIBUTES, ADT_XML_ATC_RUN_REQUEST_CLASS, \ ADT_XML_ATC_RUN_RESPONSE_FAILURES, ADT_XML_ATC_RUN_RESPONSE_NO_OBJECTS, \ ADT_XML_ATC_WORKLIST_EMPTY, ADT_XML_ATC_WORKLIST_CLASS, ADT_XML_ATC_RUN_REQUEST_PACKAGE, \ ADT_XML_PROFILES_TABLE, ADT_XML_PROFILES_TRAN_TABLE, ADT_XML_PROFILES_CHECKS_TABLE, \ - ADT_XML_PROFILES_CHKMSG_LOCAL_TABLE + ADT_XML_PROFILES_CHKMSG_LOCAL_TABLE HEADER_ACCEPT = f'application/xml, {sap.adt.atc.CUSTOMIZING_MIME_TYPE_V1}' diff --git a/test/unit/test_sap_adt_aunit.py b/test/unit/test_sap_adt_aunit.py index f607b22b..db17bf20 100644 --- a/test/unit/test_sap_adt_aunit.py +++ b/test/unit/test_sap_adt_aunit.py @@ -14,7 +14,7 @@ AUNIT_NO_EXECUTION_TIME_RESULTS_XML ) -from mock import Connection +from mock import ConnectionViaHTTP as Connection class TestAUnit(unittest.TestCase): diff --git a/test/unit/test_sap_adt_businessservice.py b/test/unit/test_sap_adt_businessservice.py index 1639a711..c7816e4d 100644 --- a/test/unit/test_sap_adt_businessservice.py +++ b/test/unit/test_sap_adt_businessservice.py @@ -9,7 +9,7 @@ import sap.errors import sap.adt.businessservice -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Response, Request from fixtures_adt_businessservice import ( SERVICE_DEFINITION_ADT_XML, diff --git a/test/unit/test_sap_adt_checks.py b/test/unit/test_sap_adt_checks.py index 1caebc64..e27bc667 100644 --- a/test/unit/test_sap_adt_checks.py +++ b/test/unit/test_sap_adt_checks.py @@ -9,7 +9,7 @@ import sap.adt.checks from sap.adt.marshalling import Marshal -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt_checks import ADT_XML_CHECK_REPORTERS, ADT_XML_RUN_CHECK_2_REPORTERS diff --git a/test/unit/test_sap_adt_class.py b/test/unit/test_sap_adt_class.py index 74de7a12..a6e27d8f 100644 --- a/test/unit/test_sap_adt_class.py +++ b/test/unit/test_sap_adt_class.py @@ -5,7 +5,7 @@ from sap import get_logger import sap.adt -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import (LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK, TEST_CLASSES_READ_RESPONSE_OK, DEFINITIONS_READ_RESPONSE_OK, IMPLEMENTATIONS_READ_RESPONSE_OK) diff --git a/test/unit/test_sap_adt_connection.py b/test/unit/test_sap_adt_connection.py index 841cc27f..68eca5de 100644 --- a/test/unit/test_sap_adt_connection.py +++ b/test/unit/test_sap_adt_connection.py @@ -6,7 +6,7 @@ import sap.adt import sap.adt.errors -from mock import Request, Response, Connection +from mock import Request, Response, ConnectionViaHTTP as Connection from fixtures_adt import ERROR_XML_PACKAGE_ALREADY_EXISTS, DISCOVERY_ADT_XML @@ -15,10 +15,12 @@ class TestADTConnection(unittest.TestCase): """Connection(host, client, user, password, port=None, ssl=True)""" def setUp(self): - self.connection = sap.adt.Connection('example.host.org', '123', 'SAP*', 'PASS') + self.connection = sap.adt.ConnectionViaHTTP('example.host.org', '123', + 'SAP*', 'PASS') def test_adt_connection_init_default(self): - connection = sap.adt.Connection('localhost', '357', 'anzeiger', 'password') + connection = sap.adt.ConnectionViaHTTP('localhost', '357', 'anzeiger', + 'password') self.assertEqual(connection.user, 'anzeiger') self.assertEqual(connection.uri, 'sap/bc/adt') @@ -26,17 +28,30 @@ def test_adt_connection_init_default(self): self.assertEqual(connection._query_args, 'sap-client=357&saml2=disabled') def test_adt_connection_init_no_ssl(self): - connection = sap.adt.Connection('localhost', '357', 'anzeiger', 'password', ssl=False) + connection = sap.adt.ConnectionViaHTTP('localhost', + '357', + 'anzeiger', + 'password', + ssl=False) self.assertEqual(connection._base_url, 'http://localhost:80/sap/bc/adt') def test_adt_connection_init_ssl_own_port(self): - connection = sap.adt.Connection('localhost', '357', 'anzeiger', 'password', port=44300) + connection = sap.adt.ConnectionViaHTTP('localhost', + '357', + 'anzeiger', + 'password', + port=44300) self.assertEqual(connection._base_url, 'https://localhost:44300/sap/bc/adt') def test_adt_connection_init_no_ssl_own_port(self): - connection = sap.adt.Connection('localhost', '357', 'anzeiger', 'password', ssl=False, port=8000) + connection = sap.adt.ConnectionViaHTTP('localhost', + '357', + 'anzeiger', + 'password', + ssl=False, + port=8000) self.assertEqual(connection._base_url, 'http://localhost:8000/sap/bc/adt') @@ -84,9 +99,10 @@ def test_handle_http_error_unauthorized(self): with self.assertRaises(sap.rest.errors.UnauthorizedError): self.connection._handle_http_error(req, res) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_content_type_no_headers(self, mock_exec, mock_session, mock_adt_url): self.connection.execute('GET', 'url', content_type='application/xml') @@ -95,9 +111,10 @@ def test_execute_content_type_no_headers(self, mock_exec, mock_session, mock_adt headers={'Content-Type': 'application/xml'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_content_type_with_headers(self, mock_exec, mock_session, mock_adt_url): self.connection.execute('GET', 'example', headers={'Content-Type': 'text/plain'}, @@ -108,9 +125,10 @@ def test_execute_content_type_with_headers(self, mock_exec, mock_session, mock_a headers={'Content-Type': 'application/xml'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_accept_no_headers(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/xml'} @@ -123,9 +141,10 @@ def test_execute_accept_no_headers(self, mock_exec, mock_session, mock_adt_url): headers={'Accept': 'application/xml'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_accept_with_headers(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/xml'} @@ -139,9 +158,10 @@ def test_execute_accept_with_headers(self, mock_exec, mock_session, mock_adt_url headers={'Accept': 'application/xml'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_content_type_and_accept(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/xml'} @@ -156,9 +176,10 @@ def test_execute_content_type_and_accept(self, mock_exec, mock_session, mock_adt 'Content-Type': 'application/json'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_content_type_and_accept_with_headers(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/xml'} @@ -175,9 +196,10 @@ def test_execute_content_type_and_accept_with_headers(self, mock_exec, mock_sess 'Content-Type': 'application/json'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_accept_list(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/json'} @@ -190,9 +212,10 @@ def test_execute_accept_list(self, mock_exec, mock_session, mock_adt_url): headers={'Accept': 'application/xml, application/json'}, body=None) - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_accept_unmatched_string(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/json'} @@ -205,9 +228,10 @@ def test_execute_accept_unmatched_string(self, mock_exec, mock_session, mock_adt self.assertEqual(str(caught.exception), 'Unexpected Content-Type: application/json with: mock') - @patch('sap.adt.core.Connection._build_adt_url', return_value='url') - @patch('sap.adt.core.Connection._get_session', return_value='session') - @patch('sap.adt.core.Connection._execute_with_session') + @patch('sap.adt.core.ConnectionViaHTTP._build_adt_url', return_value='url') + @patch('sap.adt.core.ConnectionViaHTTP._get_session', + return_value='session') + @patch('sap.adt.core.ConnectionViaHTTP._execute_with_session') def test_execute_accept_unmatched_list(self, mock_exec, mock_session, mock_adt_url): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'text/plain'} @@ -221,7 +245,7 @@ def test_execute_accept_unmatched_list(self, mock_exec, mock_session, mock_adt_u 'Unexpected Content-Type: text/plain with: mock') - @patch('sap.adt.core.Connection.execute') + @patch('sap.adt.core.ConnectionViaHTTP.execute') def test_property_collection_init(self, mock_exec): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/xml'} @@ -242,7 +266,7 @@ def test_property_collection_cache(self): self.assertEqual(collection_types, fake_value) - @patch('sap.adt.core.Connection.execute') + @patch('sap.adt.core.ConnectionViaHTTP.execute') def test_parse_collection_accept(self, mock_exec): mock_exec.return_value = Mock() mock_exec.return_value.headers = {'Content-Type': 'application/xml'} @@ -271,7 +295,7 @@ def test_parse_collection_accept(self, mock_exec): self.assertEqual(act_types, exp_mimetypes) @patch('sap.adt.core._get_collection_accepts') - @patch('sap.adt.core.Connection._retrieve') + @patch('sap.adt.core.ConnectionViaHTTP._retrieve') def test_execute_session_new(self, fake_retrieve, fake_accepts): dummy_conn = Connection(responses=[ Response(status_code=200, headers={'x-csrf-token': 'first'}), @@ -286,7 +310,7 @@ def test_execute_session_new(self, fake_retrieve, fake_accepts): self.assertEqual(resp.text, 'success') @patch('sap.adt.core._get_collection_accepts') - @patch('sap.adt.core.Connection._retrieve') + @patch('sap.adt.core.ConnectionViaHTTP._retrieve') def test_execute_session_new_forbidden(self, fake_retrieve, fake_accepts): dummy_conn = Connection(responses=[ Response(text='''''', @@ -300,7 +324,7 @@ def test_execute_session_new_forbidden(self, fake_retrieve, fake_accepts): self.connection.execute('GET', 'test') @patch('sap.adt.core._get_collection_accepts') - @patch('sap.adt.core.Connection._retrieve') + @patch('sap.adt.core.ConnectionViaHTTP._retrieve') def test_execute_session_refetch_csfr(self, fake_retrieve, fake_accepts): dummy_conn = Connection(responses=[ Response(status_code=200, headers={'x-csrf-token': 'first'}), @@ -318,7 +342,7 @@ def test_execute_session_refetch_csfr(self, fake_retrieve, fake_accepts): self.assertEqual(resp.text, 'success') @patch('sap.adt.core._get_collection_accepts') - @patch('sap.adt.core.Connection._retrieve') + @patch('sap.adt.core.ConnectionViaHTTP._retrieve') def test_execute_session_refetch_csfr_headers(self, fake_retrieve, fake_accepts): dummy_conn = Connection(responses=[ Response(status_code=200, headers={'x-csrf-token': 'first'}), diff --git a/test/unit/test_sap_adt_coverage.py b/test/unit/test_sap_adt_coverage.py index 3f3df83d..d13d6736 100644 --- a/test/unit/test_sap_adt_coverage.py +++ b/test/unit/test_sap_adt_coverage.py @@ -11,7 +11,7 @@ from fixtures_adt import DummyADTObject from fixtures_adt_aunit import AUNIT_RESULTS_XML, AUNIT_NO_TEST_RESULTS_XML -from mock import Connection +from mock import ConnectionViaHTTP as Connection class TestACoverage(unittest.TestCase): diff --git a/test/unit/test_sap_adt_coverage_statements.py b/test/unit/test_sap_adt_coverage_statements.py index 54193d1c..4fb15b07 100644 --- a/test/unit/test_sap_adt_coverage_statements.py +++ b/test/unit/test_sap_adt_coverage_statements.py @@ -3,7 +3,7 @@ import unittest from fixtures_adt_coverage import ACOVERAGE_STATEMENTS_RESULTS_XML -from mock import Connection +from mock import ConnectionViaHTTP as Connection from sap.adt.acoverage_statements import parse_statements_response, ACoverageStatements, StatementRequest, StatementsBulkRequest diff --git a/test/unit/test_sap_adt_cts.py b/test/unit/test_sap_adt_cts.py index 66e093ce..d80e6cff 100644 --- a/test/unit/test_sap_adt_cts.py +++ b/test/unit/test_sap_adt_cts.py @@ -10,7 +10,7 @@ import sap.adt.cts from sap.adt.cts import Element, WorkbenchABAPObject -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Response, Request from fixtures_adt import ( TASK_NUMBER, TRANSPORT_NUMBER, diff --git a/test/unit/test_sap_adt_datadefinition.py b/test/unit/test_sap_adt_datadefinition.py index a4bf1e30..0766ea28 100644 --- a/test/unit/test_sap_adt_datadefinition.py +++ b/test/unit/test_sap_adt_datadefinition.py @@ -5,7 +5,7 @@ import sap.adt import sap.adt.wb -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK diff --git a/test/unit/test_sap_adt_function.py b/test/unit/test_sap_adt_function.py index e7382961..49783433 100644 --- a/test/unit/test_sap_adt_function.py +++ b/test/unit/test_sap_adt_function.py @@ -6,7 +6,7 @@ from sap.errors import SAPCliError import sap.adt.wb -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK, OBJECT_METADATA from fixtures_adt_function import ( diff --git a/test/unit/test_sap_adt_include.py b/test/unit/test_sap_adt_include.py index 4040b409..2620d296 100644 --- a/test/unit/test_sap_adt_include.py +++ b/test/unit/test_sap_adt_include.py @@ -4,7 +4,7 @@ import sap.adt -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK from fixtures_adt_program import ( diff --git a/test/unit/test_sap_adt_interface.py b/test/unit/test_sap_adt_interface.py index 8c18223c..c8fbce79 100644 --- a/test/unit/test_sap_adt_interface.py +++ b/test/unit/test_sap_adt_interface.py @@ -4,7 +4,7 @@ import sap.adt -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK from fixtures_adt_interface import GET_INTERFACE_ADT_XML diff --git a/test/unit/test_sap_adt_object.py b/test/unit/test_sap_adt_object.py index ae9844c0..1a5b5051 100755 --- a/test/unit/test_sap_adt_object.py +++ b/test/unit/test_sap_adt_object.py @@ -9,7 +9,7 @@ import sap.adt.wb from fixtures_adt import DummyADTObject, LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK, EMPTY_RESPONSE_OK, GET_DUMMY_OBJECT_ADT_XML -from mock import Response, Connection +from mock import Response, ConnectionViaHTTP as Connection ACTIVATE_RESPONSE_FAILED=''' diff --git a/test/unit/test_sap_adt_package.py b/test/unit/test_sap_adt_package.py index b58a89f3..ff53c0ea 100644 --- a/test/unit/test_sap_adt_package.py +++ b/test/unit/test_sap_adt_package.py @@ -7,7 +7,7 @@ import sap.errors import sap.adt -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt_package import GET_PACKAGE_ADT_XML from fixtures_adt_repository import (PACKAGE_ROOT_NODESTRUCTURE_OK_RESPONSE, diff --git a/test/unit/test_sap_adt_program.py b/test/unit/test_sap_adt_program.py index 063bc59f..91dec328 100644 --- a/test/unit/test_sap_adt_program.py +++ b/test/unit/test_sap_adt_program.py @@ -4,7 +4,7 @@ import sap.adt -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK from fixtures_adt_program import CREATE_EXECUTABLE_PROGRAM_ADT_XML, GET_EXECUTABLE_PROGRAM_ADT_XML diff --git a/test/unit/test_sap_adt_repository.py b/test/unit/test_sap_adt_repository.py index 0506e775..e4c9f347 100644 --- a/test/unit/test_sap_adt_repository.py +++ b/test/unit/test_sap_adt_repository.py @@ -5,7 +5,7 @@ import sap.adt -from mock import Connection +from mock import ConnectionViaHTTP as Connection from fixtures_adt_repository import (PACKAGE_ROOT_NODESTRUCTURE_OK_RESPONSE, PACKAGE_ROOT_REQUEST_XML, diff --git a/test/unit/test_sap_adt_search.py b/test/unit/test_sap_adt_search.py index 3917cc59..1a711145 100644 --- a/test/unit/test_sap_adt_search.py +++ b/test/unit/test_sap_adt_search.py @@ -4,7 +4,7 @@ from sap.adt.search import ADTSearch -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Response, Request FIXTURE_ADT_SEARCH_RESPONSE_FOUND=""" diff --git a/test/unit/test_sap_adt_wb.py b/test/unit/test_sap_adt_wb.py index 449aa708..856af6dd 100644 --- a/test/unit/test_sap_adt_wb.py +++ b/test/unit/test_sap_adt_wb.py @@ -8,7 +8,7 @@ from sap.adt.objects import ADTObjectReferences -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Response, Request from fixtures_adt import EMPTY_RESPONSE_OK from fixtures_adt_wb import ACTIVATION_REFERENCES_XML, INACTIVE_OBJECTS_XML, PREAUDIT_ACTIVATION_XML, \ RESPONSE_INACTIVE_OBJECTS_V1, ACTIVATION_WARNING_XML, ACTIVATION_WITH_PROPERTIES_XML, \ diff --git a/test/unit/test_sap_cli_abapclass.py b/test/unit/test_sap_cli_abapclass.py index 7669838e..2f835cfb 100755 --- a/test/unit/test_sap_cli_abapclass.py +++ b/test/unit/test_sap_cli_abapclass.py @@ -7,7 +7,7 @@ import sap.cli.abapclass -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import (EMPTY_RESPONSE_OK, LOCK_RESPONSE_OK, TEST_CLASSES_READ_RESPONSE_OK, DEFINITIONS_READ_RESPONSE_OK, IMPLEMENTATIONS_READ_RESPONSE_OK) from fixtures_adt_clas import GET_CLASS_ADT_XML diff --git a/test/unit/test_sap_cli_activation.py b/test/unit/test_sap_cli_activation.py index b7e2ef80..76f3decc 100644 --- a/test/unit/test_sap_cli_activation.py +++ b/test/unit/test_sap_cli_activation.py @@ -7,7 +7,7 @@ import sap.adt.wb import sap.cli.activation -from mock import Connection, Response, GroupArgumentParser, patch_get_print_console_with_buffer +from mock import GroupArgumentParser, patch_get_print_console_with_buffer from fixtures_adt_wb import RESPONSE_INACTIVE_OBJECTS_V1 diff --git a/test/unit/test_sap_cli_atc.py b/test/unit/test_sap_cli_atc.py index 40c94ca0..43a3f1ec 100644 --- a/test/unit/test_sap_cli_atc.py +++ b/test/unit/test_sap_cli_atc.py @@ -12,7 +12,7 @@ import sap.cli.atc from sap.adt.objects import ADTObjectSets -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection from infra import generate_parse_args diff --git a/test/unit/test_sap_cli_aunit.py b/test/unit/test_sap_cli_aunit.py index c6d3756f..ba149522 100644 --- a/test/unit/test_sap_cli_aunit.py +++ b/test/unit/test_sap_cli_aunit.py @@ -19,7 +19,7 @@ from fixtures_adt_program import GET_INCLUDE_PROGRAM_WITH_CONTEXT_ADT_XML from fixtures_adt_coverage import ACOVERAGE_RESULTS_XML, ACOVERAGE_STATEMENTS_RESULTS_XML from infra import generate_parse_args -from mock import Connection, Response, BufferConsole +from mock import ConnectionViaHTTP as Connection, Response, BufferConsole from sap.cli.aunit import ResultOptions from sap.errors import SAPCliError diff --git a/test/unit/test_sap_cli_checkout.py b/test/unit/test_sap_cli_checkout.py index 10175f72..6567fd68 100644 --- a/test/unit/test_sap_cli_checkout.py +++ b/test/unit/test_sap_cli_checkout.py @@ -12,7 +12,7 @@ import sap.platform.abap import sap.platform.abap.abapgit -from mock import Connection +from mock import ConnectionViaHTTP as Connection def parse_args(argv): diff --git a/test/unit/test_sap_cli_cts.py b/test/unit/test_sap_cli_cts.py index d9aeae57..e5b8842a 100644 --- a/test/unit/test_sap_cli_cts.py +++ b/test/unit/test_sap_cli_cts.py @@ -9,7 +9,7 @@ from sap.errors import SAPCliError import sap.cli.cts -from mock import Connection, Response, ConsoleOutputTestCase, PatcherTestCase +from mock import ConnectionViaHTTP as Connection, Response, ConsoleOutputTestCase, PatcherTestCase from fixtures_adt import ( TASK_NUMBER, TRANSPORT_NUMBER, diff --git a/test/unit/test_sap_cli_datapreview.py b/test/unit/test_sap_cli_datapreview.py index 20f89546..3a2d8f01 100644 --- a/test/unit/test_sap_cli_datapreview.py +++ b/test/unit/test_sap_cli_datapreview.py @@ -7,7 +7,7 @@ import sap.cli.datapreview -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt_datapreview import ADT_XML_FREESTYLE_TABLE_T000_ONE_ROW diff --git a/test/unit/test_sap_cli_function.py b/test/unit/test_sap_cli_function.py index ee628bda..9f9971bf 100755 --- a/test/unit/test_sap_cli_function.py +++ b/test/unit/test_sap_cli_function.py @@ -7,7 +7,7 @@ import sap.cli.function -from mock import Connection, Response +from mock import ConnectionViaHTTP as Connection, Response from fixtures_adt import EMPTY_RESPONSE_OK, LOCK_RESPONSE_OK from fixtures_adt_function import ( CLI_CREATE_FUNCTION_GROUP_ADT_XML, diff --git a/test/unit/test_sap_cli_include.py b/test/unit/test_sap_cli_include.py index 6acc262d..a3941d17 100644 --- a/test/unit/test_sap_cli_include.py +++ b/test/unit/test_sap_cli_include.py @@ -9,7 +9,7 @@ from mock import ( ConsoleOutputTestCase, - Connection, + ConnectionViaHTTP as Connection, PatcherTestCase, Response ) diff --git a/test/unit/test_sap_cli_interface.py b/test/unit/test_sap_cli_interface.py index 53e8fd48..132f2f08 100755 --- a/test/unit/test_sap_cli_interface.py +++ b/test/unit/test_sap_cli_interface.py @@ -7,7 +7,7 @@ import sap.cli.interface -from mock import Connection +from mock import ConnectionViaHTTP as Connection from fixtures_adt import EMPTY_RESPONSE_OK, LOCK_RESPONSE_OK diff --git a/test/unit/test_sap_cli_package.py b/test/unit/test_sap_cli_package.py index f1e2d7a2..979aa207 100644 --- a/test/unit/test_sap_cli_package.py +++ b/test/unit/test_sap_cli_package.py @@ -13,7 +13,7 @@ import sap.cli.package import sap.cli.core -from mock import Connection, Response, ConsoleOutputTestCase, PatcherTestCase +from mock import ConnectionViaHTTP as Connection, Response, ConsoleOutputTestCase, PatcherTestCase from fixtures_adt import EMPTY_RESPONSE_OK, ERROR_XML_PACKAGE_ALREADY_EXISTS from fixtures_adt_package import GET_PACKAGE_ADT_XML, GET_PACKAGE_ADT_XML_NOT_FOUND diff --git a/test/unit/test_sap_cli_program.py b/test/unit/test_sap_cli_program.py index 81367ef2..5014ee1f 100644 --- a/test/unit/test_sap_cli_program.py +++ b/test/unit/test_sap_cli_program.py @@ -7,7 +7,7 @@ import sap.cli.program -from mock import Connection +from mock import ConnectionViaHTTP as Connection from fixtures_adt import LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK diff --git a/test/unit/test_sap_cli_rap.py b/test/unit/test_sap_cli_rap.py index cf90a361..7b70384b 100644 --- a/test/unit/test_sap_cli_rap.py +++ b/test/unit/test_sap_cli_rap.py @@ -15,7 +15,7 @@ from infra import generate_parse_args -from mock import Connection, Response, Request +from mock import ConnectionViaHTTP as Connection, Request from fixtures_adt_wb import RESPONSE_ACTIVATION_OK parse_args = generate_parse_args(sap.cli.rap.CommandGroup()) diff --git a/test/unit/test_sap_rfc_strust_ssl_cert_storage.py b/test/unit/test_sap_rfc_strust_ssl_cert_storage.py index c5836140..0c8cb9c5 100644 --- a/test/unit/test_sap_rfc_strust_ssl_cert_storage.py +++ b/test/unit/test_sap_rfc_strust_ssl_cert_storage.py @@ -4,7 +4,7 @@ import unittest from unittest import mock -from mock import Connection +from mock import ConnectionViaHTTP as Connection class TestSSLCertStorage(unittest.TestCase):