From 406a5cd68f68395d013c608bfa3325da93b8d421 Mon Sep 17 00:00:00 2001
From: Vincent Texier <vit@free.fr>
Date: Fri, 20 Nov 2020 11:02:19 +0100
Subject: [PATCH] [enh] #59 add GVAEndpoint and GVASUBEndpoint classes

add unit tests
example now use real GVA Endpoint inline format
---
 duniterpy/api/client.py          |   2 +-
 duniterpy/api/endpoint.py        | 161 +++++++++++++++++++++++++++++++
 duniterpy/constants.py           |   1 +
 examples/request_data_graphql.py |   2 +-
 tests/api/test_endpoints.py      |  78 +++++++++++++++
 5 files changed, 242 insertions(+), 2 deletions(-)
 create mode 100644 tests/api/test_endpoints.py

diff --git a/duniterpy/api/client.py b/duniterpy/api/client.py
index f527548a..d3864f9a 100644
--- a/duniterpy/api/client.py
+++ b/duniterpy/api/client.py
@@ -515,7 +515,7 @@ class Client:
             try:
                 result = await response.json()
             except aiohttp.client_exceptions.ContentTypeError as exception:
-                logging.error("Response is not a json format")
+                logging.error("Response is not a json format: %s", exception)
                 # return response to debug...
         return result
 
diff --git a/duniterpy/api/endpoint.py b/duniterpy/api/endpoint.py
index cde3647e..34e8139b 100644
--- a/duniterpy/api/endpoint.py
+++ b/duniterpy/api/endpoint.py
@@ -640,6 +640,165 @@ class ESSubscribtionEndpoint(Endpoint):
         return hash((ESSubscribtionEndpoint.API, self.server, self.port))
 
 
+# required to type hint cls in classmethod
+GVAEndpointType = TypeVar("GVAEndpointType", bound="GVAEndpoint")
+
+
+class GVAEndpoint(Endpoint):
+    API = "GVA"
+    re_inline = re.compile(
+        "^GVA(?: ({endpoint_flags}))?(?: ({host_regex}))?(?: ({ipv4_regex}))?(?: ({ipv6_regex}))? ([0-9]+)(?: ({path_regex}))?$".format(
+            host_regex=constants.HOST_REGEX,
+            ipv4_regex=constants.IPV4_REGEX,
+            ipv6_regex=constants.IPV6_REGEX,
+            path_regex=constants.PATH_REGEX,
+            endpoint_flags=constants.ENDPOINT_FLAGS_REGEX,
+        )
+    )
+
+    def __init__(
+        self,
+        flags: str,
+        server: str,
+        ipv4: str,
+        ipv6: str,
+        port: int,
+        path: str,
+    ) -> None:
+        """
+        Init GVAEndpoint instance
+
+        :param flags: Flags of endpoint
+        :param server: IP or domain name
+        :param ipv4: IP as IPv4 format
+        :param ipv6: IP as IPv6 format
+        :param port: Port number
+        :param path: Url path
+        """
+        self.flags = flags
+        self.server = server
+        self.ipv4 = ipv4
+        self.ipv6 = ipv6
+        self.port = port
+        self.path = path
+
+    @classmethod
+    def from_inline(cls: Type[GVAEndpointType], inline: str) -> GVAEndpointType:
+        """
+        Return GVAEndpoint instance from endpoint string
+
+        :param inline: Endpoint string
+        :return:
+        """
+        m = cls.re_inline.match(inline)
+        if m is None:
+            raise MalformedDocumentError(cls.API)
+        flags = m.group(1)
+        server = m.group(2)
+        ipv4 = m.group(3)
+        ipv6 = m.group(4)
+        port = int(m.group(5))
+        path = m.group(6)
+        if not flags:
+            flags = ""
+        if not path:
+            path = ""
+        return cls(flags, server, ipv4, ipv6, port, path)
+
+    def inline(self) -> str:
+        """
+        Return endpoint string
+
+        :return:
+        """
+        inlined = [
+            str(info)
+            for info in (
+                self.flags,
+                self.server,
+                self.ipv4,
+                self.ipv6,
+                self.port,
+                self.path,
+            )
+            if info
+        ]
+        return self.API + " " + " ".join(inlined)
+
+    def conn_handler(
+        self, session: ClientSession, proxy: str = None
+    ) -> ConnectionHandler:
+        """
+        Return connection handler instance for the endpoint
+
+        :param session: AIOHTTP client session instance
+        :param proxy: Proxy url
+        :return:
+        """
+        scheme_http = "https" if "S" in self.flags else "http"
+        scheme_ws = "wss" if "S" in self.flags else "ws"
+
+        if self.server:
+            conn_handler = ConnectionHandler(
+                scheme_http,
+                scheme_ws,
+                self.server,
+                self.port,
+                self.path,
+                session,
+                proxy,
+            )
+        elif self.ipv6:
+            conn_handler = ConnectionHandler(
+                scheme_http,
+                scheme_ws,
+                "[{0}]".format(self.ipv6),
+                self.port,
+                self.path,
+                session,
+                proxy,
+            )
+        else:
+            conn_handler = ConnectionHandler(
+                scheme_http, scheme_ws, self.ipv4, self.port, self.path, session, proxy
+            )
+
+        return conn_handler
+
+    def __str__(self) -> str:
+        return self.inline()
+
+    def __eq__(self, other: Any) -> bool:
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+        return (
+            self.server == other.server
+            and self.ipv4 == other.ipv4
+            and self.ipv6 == other.ipv6
+            and self.port == other.port
+        )
+
+    def __hash__(self) -> int:
+        return hash((self.server, self.ipv4, self.ipv6, self.port))
+
+
+# required to type hint cls in classmethod
+GVASUBEndpointType = TypeVar("GVASUBEndpointType", bound="GVASUBEndpoint")
+
+
+class GVASUBEndpoint(GVAEndpoint):
+    API = "GVASUB"
+    re_inline = re.compile(
+        "^GVASUB(?: ({endpoint_flags}))?(?: ({host_regex}))?(?: ({ipv4_regex}))?(?: ({ipv6_regex}))? ([0-9]+)(?: ({path_regex}))?$".format(
+            host_regex=constants.HOST_REGEX,
+            ipv4_regex=constants.IPV4_REGEX,
+            ipv6_regex=constants.IPV6_REGEX,
+            path_regex=constants.PATH_REGEX,
+            endpoint_flags=constants.ENDPOINT_FLAGS_REGEX,
+        )
+    )
+
+
 MANAGED_API = {
     BMAEndpoint.API: BMAEndpoint,
     SecuredBMAEndpoint.API: SecuredBMAEndpoint,
@@ -647,6 +806,8 @@ MANAGED_API = {
     ESCoreEndpoint.API: ESCoreEndpoint,
     ESUserEndpoint.API: ESUserEndpoint,
     ESSubscribtionEndpoint.API: ESSubscribtionEndpoint,
+    GVAEndpoint.API: GVAEndpoint,
+    GVASUBEndpoint.API: GVASUBEndpoint,
 }  # type: Dict[str, Any]
 
 
diff --git a/duniterpy/constants.py b/duniterpy/constants.py
index 494d7900..31f458a4 100644
--- a/duniterpy/constants.py
+++ b/duniterpy/constants.py
@@ -59,3 +59,4 @@ WS2P_PRIVATE_PREFIX_REGEX = "O[CT][SAM]"
 WS2P_PUBLIC_PREFIX_REGEX = "I[CT]"
 WS2P_HEAD_REGEX = "HEAD:?(?:[0-9]+)?"
 EMPTY_HASH = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
+ENDPOINT_FLAGS_REGEX = "[S]"
diff --git a/examples/request_data_graphql.py b/examples/request_data_graphql.py
index f2c29bbf..21ed2be7 100644
--- a/examples/request_data_graphql.py
+++ b/examples/request_data_graphql.py
@@ -11,7 +11,7 @@ from graphql.error import GraphQLSyntaxError
 # You can either use a complete defined endpoint : [NAME_OF_THE_API] [DOMAIN] [IPv4] [IPv6] [PORT]
 # or the simple definition : [NAME_OF_THE_API] [DOMAIN] [PORT]
 # Here we use the secure BASIC_MERKLED_API (BMAS) for standard http over ssl requests
-GVA_ENDPOINT = "BMAS g1.librelois.fr 443 gva"
+GVA_ENDPOINT = "GVA S g1.librelois.fr 443 gva"
 
 
 ################################################
diff --git a/tests/api/test_endpoints.py b/tests/api/test_endpoints.py
new file mode 100644
index 00000000..8fc88cf3
--- /dev/null
+++ b/tests/api/test_endpoints.py
@@ -0,0 +1,78 @@
+"""
+Copyright  2014-2020 Vincent Texier <vit@free.fr>
+
+DuniterPy is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DuniterPy is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import unittest
+
+import duniterpy.api.endpoint as endpoint
+
+
+class TestEndpoint(unittest.TestCase):
+    def test_gva(self):
+        endpoint_str = "GVA test.domain.com 127.0.0.1 2001:0db8:0000:85a3:0000:0000:ac1f:8001 10902 gva"
+
+        gva_endpoint = endpoint.GVAEndpoint.from_inline(endpoint_str)
+
+        self.assertEqual(gva_endpoint.flags, "")
+        self.assertEqual(gva_endpoint.server, "test.domain.com")
+        self.assertEqual(gva_endpoint.ipv4, "127.0.0.1")
+        self.assertEqual(gva_endpoint.ipv6, "2001:0db8:0000:85a3:0000:0000:ac1f:8001")
+        self.assertEqual(gva_endpoint.port, 10902)
+        self.assertEqual(gva_endpoint.path, "gva")
+
+        self.assertEqual(gva_endpoint.inline(), endpoint_str)
+
+        endpoint_str = "GVA S test.domain.com 10902 gva"
+
+        gva_endpoint = endpoint.GVAEndpoint.from_inline(endpoint_str)
+
+        self.assertEqual(gva_endpoint.flags, "S")
+        self.assertEqual(gva_endpoint.server, "test.domain.com")
+        self.assertEqual(gva_endpoint.ipv4, None)
+        self.assertEqual(gva_endpoint.ipv6, None)
+        self.assertEqual(gva_endpoint.port, 10902)
+        self.assertEqual(gva_endpoint.path, "gva")
+
+        self.assertEqual(gva_endpoint.inline(), endpoint_str)
+
+    def test_gva_subscription(self):
+        endpoint_str = "GVASUB test.domain.com 127.0.0.1 2001:0db8:0000:85a3:0000:0000:ac1f:8001 10902 gva"
+
+        gvasub_endpoint = endpoint.GVASUBEndpoint.from_inline(endpoint_str)
+
+        self.assertEqual(gvasub_endpoint.flags, "")
+        self.assertEqual(gvasub_endpoint.server, "test.domain.com")
+        self.assertEqual(gvasub_endpoint.ipv4, "127.0.0.1")
+        self.assertEqual(
+            gvasub_endpoint.ipv6, "2001:0db8:0000:85a3:0000:0000:ac1f:8001"
+        )
+        self.assertEqual(gvasub_endpoint.port, 10902)
+        self.assertEqual(gvasub_endpoint.path, "gva")
+
+        self.assertEqual(gvasub_endpoint.inline(), endpoint_str)
+
+        endpoint_str = "GVASUB S test.domain.com 10902 gva"
+
+        gvasub_endpoint = endpoint.GVASUBEndpoint.from_inline(endpoint_str)
+
+        self.assertEqual(gvasub_endpoint.flags, "S")
+        self.assertEqual(gvasub_endpoint.server, "test.domain.com")
+        self.assertEqual(gvasub_endpoint.ipv4, None)
+        self.assertEqual(gvasub_endpoint.ipv6, None)
+        self.assertEqual(gvasub_endpoint.port, 10902)
+        self.assertEqual(gvasub_endpoint.path, "gva")
+
+        assert gvasub_endpoint.inline(), endpoint_str
-- 
GitLab