diff --git a/src/neo4j/_async/io/_bolt.py b/src/neo4j/_async/io/_bolt.py index fd4afbc69..b3a394232 100644 --- a/src/neo4j/_async/io/_bolt.py +++ b/src/neo4j/_async/io/_bolt.py @@ -947,3 +947,32 @@ def is_idle_for(self, timeout): AsyncBoltSocket.Bolt = AsyncBolt # type: ignore + + +def tx_timeout_as_ms(timeout: float) -> int: + """Round transaction timeout to milliseconds. + + Values in (0, 1], else values are rounded using the built-in round() + function (round n.5 values to nearest even). + + :param timeout: timeout in seconds (must be >= 0) + + :returns: timeout in milliseconds (rounded) + + :raise ValueError: if timeout is negative + """ + try: + timeout = float(timeout) + except (TypeError, ValueError) as e: + err_type = type(e) + msg = "Timeout must be specified as a number of seconds" + raise err_type(msg) from None + if timeout < 0: + raise ValueError("Timeout must be a positive number or 0.") + ms = int(round(1000 * timeout)) + if ms == 0 and timeout > 0: + # Special case for 0 < timeout < 0.5 ms. + # This would be rounded to 0 ms, but the server interprets this as + # infinite timeout. So we round to the smallest possible timeout: 1 ms. + ms = 1 + return ms diff --git a/src/neo4j/_async/io/_bolt3.py b/src/neo4j/_async/io/_bolt3.py index a3629c5ff..f12a887f0 100644 --- a/src/neo4j/_async/io/_bolt3.py +++ b/src/neo4j/_async/io/_bolt3.py @@ -39,6 +39,7 @@ from ._bolt import ( AsyncBolt, ServerStateManagerBase, + tx_timeout_as_ms, ) from ._common import ( check_supported_server_product, @@ -262,12 +263,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) self._append(b"\x10", fields, @@ -327,12 +323,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), diff --git a/src/neo4j/_async/io/_bolt4.py b/src/neo4j/_async/io/_bolt4.py index 2c5dfb785..12f7e1ae0 100644 --- a/src/neo4j/_async/io/_bolt4.py +++ b/src/neo4j/_async/io/_bolt4.py @@ -36,6 +36,7 @@ from ._bolt import ( AsyncBolt, ServerStateManagerBase, + tx_timeout_as_ms, ) from ._bolt3 import ( ServerStateManager, @@ -212,12 +213,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) self._append(b"\x10", fields, @@ -276,12 +272,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), @@ -555,12 +546,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) @@ -596,12 +582,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), diff --git a/src/neo4j/_async/io/_bolt5.py b/src/neo4j/_async/io/_bolt5.py index 4502a14a2..5d80bc7d0 100644 --- a/src/neo4j/_async/io/_bolt5.py +++ b/src/neo4j/_async/io/_bolt5.py @@ -38,6 +38,7 @@ from ._bolt import ( AsyncBolt, ServerStateManagerBase, + tx_timeout_as_ms, ) from ._bolt3 import ( ServerStateManager, @@ -209,12 +210,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) @@ -270,12 +266,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), @@ -567,12 +558,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) @@ -603,12 +589,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) if notifications_min_severity is not None: extra["notifications_minimum_severity"] = \ notifications_min_severity diff --git a/src/neo4j/_sync/io/_bolt.py b/src/neo4j/_sync/io/_bolt.py index abba0dce6..1253e8205 100644 --- a/src/neo4j/_sync/io/_bolt.py +++ b/src/neo4j/_sync/io/_bolt.py @@ -947,3 +947,32 @@ def is_idle_for(self, timeout): BoltSocket.Bolt = Bolt # type: ignore + + +def tx_timeout_as_ms(timeout: float) -> int: + """Round transaction timeout to milliseconds. + + Values in (0, 1], else values are rounded using the built-in round() + function (round n.5 values to nearest even). + + :param timeout: timeout in seconds (must be >= 0) + + :returns: timeout in milliseconds (rounded) + + :raise ValueError: if timeout is negative + """ + try: + timeout = float(timeout) + except (TypeError, ValueError) as e: + err_type = type(e) + msg = "Timeout must be specified as a number of seconds" + raise err_type(msg) from None + if timeout < 0: + raise ValueError("Timeout must be a positive number or 0.") + ms = int(round(1000 * timeout)) + if ms == 0 and timeout > 0: + # Special case for 0 < timeout < 0.5 ms. + # This would be rounded to 0 ms, but the server interprets this as + # infinite timeout. So we round to the smallest possible timeout: 1 ms. + ms = 1 + return ms diff --git a/src/neo4j/_sync/io/_bolt3.py b/src/neo4j/_sync/io/_bolt3.py index 4a125797f..00d8dcb58 100644 --- a/src/neo4j/_sync/io/_bolt3.py +++ b/src/neo4j/_sync/io/_bolt3.py @@ -39,6 +39,7 @@ from ._bolt import ( Bolt, ServerStateManagerBase, + tx_timeout_as_ms, ) from ._common import ( check_supported_server_product, @@ -262,12 +263,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) self._append(b"\x10", fields, @@ -327,12 +323,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), diff --git a/src/neo4j/_sync/io/_bolt4.py b/src/neo4j/_sync/io/_bolt4.py index 96cc8167f..ce763b239 100644 --- a/src/neo4j/_sync/io/_bolt4.py +++ b/src/neo4j/_sync/io/_bolt4.py @@ -36,6 +36,7 @@ from ._bolt import ( Bolt, ServerStateManagerBase, + tx_timeout_as_ms, ) from ._bolt3 import ( ServerStateManager, @@ -212,12 +213,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) self._append(b"\x10", fields, @@ -276,12 +272,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), @@ -555,12 +546,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) @@ -596,12 +582,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be specified as a number of seconds") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a positive number or 0.") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), diff --git a/src/neo4j/_sync/io/_bolt5.py b/src/neo4j/_sync/io/_bolt5.py index 1740d22d9..b89da8ba1 100644 --- a/src/neo4j/_sync/io/_bolt5.py +++ b/src/neo4j/_sync/io/_bolt5.py @@ -38,6 +38,7 @@ from ._bolt import ( Bolt, ServerStateManagerBase, + tx_timeout_as_ms, ) from ._bolt3 import ( ServerStateManager, @@ -209,12 +210,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) @@ -270,12 +266,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra,), Response(self, "begin", hydration_hooks, **handlers), @@ -567,12 +558,7 @@ def run(self, query, parameters=None, mode=None, bookmarks=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) @@ -603,12 +589,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout is not None: - try: - extra["tx_timeout"] = int(1000 * float(timeout)) - except TypeError: - raise TypeError("Timeout must be a number (in seconds)") - if extra["tx_timeout"] < 0: - raise ValueError("Timeout must be a number >= 0") + extra["tx_timeout"] = tx_timeout_as_ms(timeout) if notifications_min_severity is not None: extra["notifications_minimum_severity"] = \ notifications_min_severity diff --git a/tests/unit/async_/io/test_class_bolt3.py b/tests/unit/async_/io/test_class_bolt3.py index 56b400c68..2ae6e8402 100644 --- a/tests/unit/async_/io/test_class_bolt3.py +++ b/tests/unit/async_/io/test_class_bolt3.py @@ -297,3 +297,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt3.PACKER_CLS, + unpacker_cls=AsyncBolt3.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt3(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt4x0.py b/tests/unit/async_/io/test_class_bolt4x0.py index d194f12a0..979f4fb24 100644 --- a/tests/unit/async_/io/test_class_bolt4x0.py +++ b/tests/unit/async_/io/test_class_bolt4x0.py @@ -393,3 +393,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt4x0.PACKER_CLS, + unpacker_cls=AsyncBolt4x0.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt4x0(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt4x1.py b/tests/unit/async_/io/test_class_bolt4x1.py index 7a1fa2d24..4af0bddb9 100644 --- a/tests/unit/async_/io/test_class_bolt4x1.py +++ b/tests/unit/async_/io/test_class_bolt4x1.py @@ -410,3 +410,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt4x1.PACKER_CLS, + unpacker_cls=AsyncBolt4x1.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt4x1(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt4x2.py b/tests/unit/async_/io/test_class_bolt4x2.py index f9af5ea20..6256edd52 100644 --- a/tests/unit/async_/io/test_class_bolt4x2.py +++ b/tests/unit/async_/io/test_class_bolt4x2.py @@ -411,3 +411,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt4x2.PACKER_CLS, + unpacker_cls=AsyncBolt4x2.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt4x2(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt4x3.py b/tests/unit/async_/io/test_class_bolt4x3.py index f09e1f724..70a2e0d5d 100644 --- a/tests/unit/async_/io/test_class_bolt4x3.py +++ b/tests/unit/async_/io/test_class_bolt4x3.py @@ -438,3 +438,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt4x3.PACKER_CLS, + unpacker_cls=AsyncBolt4x3.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt4x3(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt4x4.py b/tests/unit/async_/io/test_class_bolt4x4.py index 135790540..baa852603 100644 --- a/tests/unit/async_/io/test_class_bolt4x4.py +++ b/tests/unit/async_/io/test_class_bolt4x4.py @@ -451,3 +451,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt4x4.PACKER_CLS, + unpacker_cls=AsyncBolt4x4.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt4x4(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt5x0.py b/tests/unit/async_/io/test_class_bolt5x0.py index 30b42963c..8b2b9f0a8 100644 --- a/tests/unit/async_/io/test_class_bolt5x0.py +++ b/tests/unit/async_/io/test_class_bolt5x0.py @@ -451,3 +451,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt5x0.PACKER_CLS, + unpacker_cls=AsyncBolt5x0.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt5x0(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt5x1.py b/tests/unit/async_/io/test_class_bolt5x1.py index 831a7d9dd..8897ce708 100644 --- a/tests/unit/async_/io/test_class_bolt5x1.py +++ b/tests/unit/async_/io/test_class_bolt5x1.py @@ -505,3 +505,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt5x1.PACKER_CLS, + unpacker_cls=AsyncBolt5x1.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt5x1(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt5x2.py b/tests/unit/async_/io/test_class_bolt5x2.py index 770e3016b..0de7e3872 100644 --- a/tests/unit/async_/io/test_class_bolt5x2.py +++ b/tests/unit/async_/io/test_class_bolt5x2.py @@ -523,3 +523,65 @@ async def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt5x2.PACKER_CLS, + unpacker_cls=AsyncBolt5x2.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt5x2(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/async_/io/test_class_bolt5x3.py b/tests/unit/async_/io/test_class_bolt5x3.py index ad671583c..0247bad16 100644 --- a/tests/unit/async_/io/test_class_bolt5x3.py +++ b/tests/unit/async_/io/test_class_bolt5x3.py @@ -434,3 +434,65 @@ async def test_sends_bolt_agent(fake_socket_pair, user_agent): tag, fields = await sockets.server.pop_message() extra = fields[0] assert extra["bolt_agent"] == BOLT_AGENT_DICT + + +@mark_async_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +async def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=AsyncBolt5x3.PACKER_CLS, + unpacker_cls=AsyncBolt5x3.UNPACKER_CLS) + await sockets.server.send_message(b"\x70", {}) + connection = AsyncBolt5x3(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + await connection.send_all() + tag, fields = await sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt3.py b/tests/unit/sync/io/test_class_bolt3.py index 8c209434b..1527bd86b 100644 --- a/tests/unit/sync/io/test_class_bolt3.py +++ b/tests/unit/sync/io/test_class_bolt3.py @@ -297,3 +297,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt3.PACKER_CLS, + unpacker_cls=Bolt3.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt3(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt4x0.py b/tests/unit/sync/io/test_class_bolt4x0.py index e1831cb2e..90af5f079 100644 --- a/tests/unit/sync/io/test_class_bolt4x0.py +++ b/tests/unit/sync/io/test_class_bolt4x0.py @@ -393,3 +393,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt4x0.PACKER_CLS, + unpacker_cls=Bolt4x0.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt4x0(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt4x1.py b/tests/unit/sync/io/test_class_bolt4x1.py index 132212915..8df441f9e 100644 --- a/tests/unit/sync/io/test_class_bolt4x1.py +++ b/tests/unit/sync/io/test_class_bolt4x1.py @@ -410,3 +410,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt4x1.PACKER_CLS, + unpacker_cls=Bolt4x1.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt4x1(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt4x2.py b/tests/unit/sync/io/test_class_bolt4x2.py index ed19ee522..2a6ceee92 100644 --- a/tests/unit/sync/io/test_class_bolt4x2.py +++ b/tests/unit/sync/io/test_class_bolt4x2.py @@ -411,3 +411,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt4x2.PACKER_CLS, + unpacker_cls=Bolt4x2.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt4x2(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt4x3.py b/tests/unit/sync/io/test_class_bolt4x3.py index 0721172a0..58d9b01a2 100644 --- a/tests/unit/sync/io/test_class_bolt4x3.py +++ b/tests/unit/sync/io/test_class_bolt4x3.py @@ -438,3 +438,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt4x3.PACKER_CLS, + unpacker_cls=Bolt4x3.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt4x3(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt4x4.py b/tests/unit/sync/io/test_class_bolt4x4.py index b33eaaf54..6ac7d7923 100644 --- a/tests/unit/sync/io/test_class_bolt4x4.py +++ b/tests/unit/sync/io/test_class_bolt4x4.py @@ -451,3 +451,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt4x4.PACKER_CLS, + unpacker_cls=Bolt4x4.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt4x4(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt5x0.py b/tests/unit/sync/io/test_class_bolt5x0.py index 9ae410f18..a4416a97a 100644 --- a/tests/unit/sync/io/test_class_bolt5x0.py +++ b/tests/unit/sync/io/test_class_bolt5x0.py @@ -451,3 +451,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt5x0.PACKER_CLS, + unpacker_cls=Bolt5x0.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt5x0(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt5x1.py b/tests/unit/sync/io/test_class_bolt5x1.py index 68eaf7b33..e6315df64 100644 --- a/tests/unit/sync/io/test_class_bolt5x1.py +++ b/tests/unit/sync/io/test_class_bolt5x1.py @@ -505,3 +505,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt5x1.PACKER_CLS, + unpacker_cls=Bolt5x1.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt5x1(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt5x2.py b/tests/unit/sync/io/test_class_bolt5x2.py index 69384b9c4..9dec5bacb 100644 --- a/tests/unit/sync/io/test_class_bolt5x2.py +++ b/tests/unit/sync/io/test_class_bolt5x2.py @@ -523,3 +523,65 @@ def test_does_not_send_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert "bolt_agent" not in extra + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt5x2.PACKER_CLS, + unpacker_cls=Bolt5x2.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt5x2(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res diff --git a/tests/unit/sync/io/test_class_bolt5x3.py b/tests/unit/sync/io/test_class_bolt5x3.py index f2a5fae4d..9437a3487 100644 --- a/tests/unit/sync/io/test_class_bolt5x3.py +++ b/tests/unit/sync/io/test_class_bolt5x3.py @@ -434,3 +434,65 @@ def test_sends_bolt_agent(fake_socket_pair, user_agent): tag, fields = sockets.server.pop_message() extra = fields[0] assert extra["bolt_agent"] == BOLT_AGENT_DICT + + +@mark_sync_test +@pytest.mark.parametrize( + ("func", "args", "extra_idx"), + ( + ("run", ("RETURN 1",), 2), + ("begin", (), 0), + ) +) +@pytest.mark.parametrize( + ("timeout", "res"), + ( + (None, None), + (0, 0), + (0.1, 100), + (0.001, 1), + (1e-15, 1), + (0.0005, 1), + (0.0001, 1), + (1.0015, 1002), + (1.000499, 1000), + (1.0025, 1002), + (3.0005, 3000), + (3.456, 3456), + (1, 1000), + ( + -1e-15, + ValueError("Timeout must be a positive number or 0") + ), + ( + "foo", + ValueError("Timeout must be specified as a number of seconds") + ), + ( + [1, 2], + TypeError("Timeout must be specified as a number of seconds") + ) + ) +) +def test_tx_timeout( + fake_socket_pair, func, args, extra_idx, timeout, res +): + address = neo4j.Address(("127.0.0.1", 7687)) + sockets = fake_socket_pair(address, + packer_cls=Bolt5x3.PACKER_CLS, + unpacker_cls=Bolt5x3.UNPACKER_CLS) + sockets.server.send_message(b"\x70", {}) + connection = Bolt5x3(address, sockets.client, 0) + func = getattr(connection, func) + if isinstance(res, Exception): + with pytest.raises(type(res), match=str(res)): + func(*args, timeout=timeout) + else: + func(*args, timeout=timeout) + connection.send_all() + tag, fields = sockets.server.pop_message() + extra = fields[extra_idx] + if timeout is None: + assert "tx_timeout" not in extra + else: + assert extra["tx_timeout"] == res