diff --git a/boltstub/parsing.py b/boltstub/parsing.py index 5b8dec239..670ec5bdf 100644 --- a/boltstub/parsing.py +++ b/boltstub/parsing.py @@ -5,6 +5,7 @@ import math from os import path import re +import sys from textwrap import wrap import threading from time import sleep @@ -1220,6 +1221,10 @@ def parse(script: str, substitutions: Optional[dict] = None) -> Script: def parse_file(filename): with open(filename) as fd: - script = parse(fd.read()) + try: + script = parse(fd.read()) + except Exception: + print("Error while parsing %s" % filename, file=sys.stderr) + raise script.filename = filename return script diff --git a/nutkit/frontend/driver.py b/nutkit/frontend/driver.py index df61e0c7f..997a8e46e 100644 --- a/nutkit/frontend/driver.py +++ b/nutkit/frontend/driver.py @@ -47,10 +47,11 @@ def close(self): raise Exception("Should be driver") def session(self, accessMode, bookmarks=None, database=None, - fetchSize=None): - req = protocol.NewSession(self._driver.id, accessMode, - bookmarks=bookmarks, database=database, - fetchSize=fetchSize) + fetchSize=None, impersonatedUser=None): + req = protocol.NewSession( + self._driver.id, accessMode, bookmarks=bookmarks, database=database, + fetchSize=fetchSize, impersonatedUser=impersonatedUser + ) res = self._backend.sendAndReceive(req) if not isinstance(res, protocol.Session): raise Exception("Should be session") diff --git a/nutkit/protocol/feature.py b/nutkit/protocol/feature.py index 2a072d9c9..b7d51858b 100644 --- a/nutkit/protocol/feature.py +++ b/nutkit/protocol/feature.py @@ -23,6 +23,10 @@ class Feature(Enum): # The driver supports Kerberos authentication by providing a dedicated auth # token API. AUTH_KERBEROS = "Feature:Auth:Kerberos" + # The driver supports Bolt protocol version 4.4 + BOLT_4_4 = "Feature:Bolt:4.4" + # The driver supports impersonation + IMPERSONATION = "Feature:Impersonation" # === OPTIMIZATIONS === # On receiving Neo.ClientError.Security.AuthorizationExpired, the driver diff --git a/nutkit/protocol/requests.py b/nutkit/protocol/requests.py index 747e2f28e..e61d5956e 100644 --- a/nutkit/protocol/requests.py +++ b/nutkit/protocol/requests.py @@ -149,7 +149,7 @@ class NewSession: """ def __init__(self, driverId, accessMode, bookmarks=None, - database=None, fetchSize=None): + database=None, fetchSize=None, impersonatedUser=None): # Id of driver on backend that session should be created on self.driverId = driverId # Session accessmode: 'r' for read access and 'w' for write access. @@ -158,6 +158,7 @@ def __init__(self, driverId, accessMode, bookmarks=None, self.bookmarks = bookmarks self.database = database self.fetchSize = fetchSize + self.impersonatedUser = impersonatedUser class SessionClose: diff --git a/tests/stub/disconnects/test_disconnects.py b/tests/stub/disconnects/test_disconnects.py index 1439b58de..c64ca29c1 100644 --- a/tests/stub/disconnects/test_disconnects.py +++ b/tests/stub/disconnects/test_disconnects.py @@ -15,7 +15,7 @@ class TestDisconnects(TestkitTestCase): def setUp(self): super().setUp() self._server = StubServer(9001) - self._driverName = get_driver_name() + self._driver_name = get_driver_name() auth = types.AuthorizationToken("basic", principal="neo4j", credentials="pass") uri = "bolt://%s" % self._server.address @@ -97,7 +97,7 @@ def test_disconnect_on_hello(self): self._server.done() expected_step = "after run" - if self._driverName in ["javascript"]: + if self._driver_name in ["javascript"]: expected_step = "after first next" self.assertEqual(step, expected_step) @@ -116,7 +116,7 @@ def test_disconnect_after_hello(self): self._server.done() expected_step = "after run" - if self._driverName in ["dotnet", "javascript"]: + if self._driver_name in ["dotnet", "javascript"]: expected_step = "after first next" self.assertEqual(step, expected_step) @@ -131,7 +131,7 @@ def test_disconnect_session_on_run(self): self._server.done() expected_step = "after run" - if self._driverName in ["dotnet", "javascript"]: + if self._driver_name in ["dotnet", "javascript"]: expected_step = "after first next" self.assertEqual(step, expected_step) @@ -164,7 +164,7 @@ def test_disconnect_session_on_pull_after_record(self): def test_disconnect_on_tx_begin(self): # Verifies how the driver handles when server disconnects right after # driver sent bolt BEGIN message. - if self._driverName in ["go"]: + if self._driver_name in ["go"]: self.skipTest("Driver fails on session.close") self._server.start(path=self.script_path("exit_after_tx_begin.script"), vars=self.get_vars()) @@ -174,16 +174,16 @@ def test_disconnect_on_tx_begin(self): self._server.done() expected_step = "after begin" - if self._driverName in ["go"]: + if self._driver_name in ["go"]: expected_step = "after run" - elif self._driverName in ["javascript"]: + elif self._driver_name in ["javascript"]: expected_step = "after first next" self.assertEqual(step, expected_step) def test_disconnect_on_tx_run(self): # Verifies how the driver handles when server disconnects right after # driver sent bolt RUN message within a transaction. - if self._driverName in ["go"]: + if self._driver_name in ["go"]: self.skipTest("Driver fails on session.close") self._server.start(path=self.script_path("exit_after_tx_run.script"), vars=self.get_vars()) @@ -193,14 +193,14 @@ def test_disconnect_on_tx_run(self): self._server.done() expected_step = "after run" - if self._driverName in ["javascript", "dotnet"]: + if self._driver_name in ["javascript", "dotnet"]: expected_step = "after first next" self.assertEqual(step, expected_step) def test_disconnect_on_tx_pull(self): # Verifies how the driver handles when server disconnects right after # driver sent bolt PULL message within a transaction. - if self._driverName in ["go"]: + if self._driver_name in ["go"]: self.skipTest("Driver fails on session.close") self._server.start(path=self.script_path("exit_after_tx_pull.script"), vars=self.get_vars()) @@ -216,7 +216,7 @@ def test_disconnect_session_on_tx_pull_after_record(self): # Verifies how the driver handles when server disconnects after driver # sent bolt RUN message and received a RECORD but no summary within a # transaction. - if self._driverName in ["go"]: + if self._driver_name in ["go"]: self.skipTest("Driver fails on session.close") self._server.start(path=self.script_path("exit_after_tx_record.script"), vars=self.get_vars()) @@ -286,7 +286,7 @@ def get_vars(self): } def get_extra_hello_props(self): - if self._driverName == "dotnet": + if self._driver_name == "dotnet": return ', "routing": null' else: return "" diff --git a/tests/stub/optimizations/scripts/v4x4/all_default.script b/tests/stub/optimizations/scripts/v4x4/all_default.script new file mode 100644 index 000000000..78e54c65d --- /dev/null +++ b/tests/stub/optimizations/scripts/v4x4/all_default.script @@ -0,0 +1,52 @@ +!: BOLT 4.4 + +C: HELLO {"user_agent": "*", "scheme": "basic", "principal": "*", "credentials": "*"} +S: SUCCESS {"server": "Neo4j/4.4.0", "connection_id": "example-connection-id:1"} +*: RESET +{{ + # transaction + C: BEGIN {} + S: SUCCESS {} + C: RUN "*" {} {} + S: SUCCESS {"fields": ["1"], "qid": 1} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + SUCCESS {"has_more": true} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + # Drivers that pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} + ---- + # Drivers that don't pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} + S: SUCCESS {} + C: COMMIT + S: SUCCESS {"type": "w", "bookmark": "bookmark:1"} +---- + # auto commit transaction + C: RUN "*" {} {} + S: SUCCESS {"fields": ["1"]} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + SUCCESS {"has_more": true} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + # Drivers that pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} + ---- + # Drivers that don't pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} + S: SUCCESS {"type": "w", "bookmark": "bookmark:1"} +}} +*: RESET +?: GOODBYE diff --git a/tests/stub/optimizations/scripts/v4x4/all_default_multi_query.script b/tests/stub/optimizations/scripts/v4x4/all_default_multi_query.script new file mode 100644 index 000000000..a45796e8e --- /dev/null +++ b/tests/stub/optimizations/scripts/v4x4/all_default_multi_query.script @@ -0,0 +1,47 @@ +!: BOLT 4.4 + +C: HELLO {"user_agent": "*", "scheme": "basic", "principal": "*", "credentials": "*"} +S: SUCCESS {"server": "Neo4j/4.4.0", "connection_id": "example-connection-id:1"} +*: RESET +C: BEGIN {} +S: SUCCESS {} +C: RUN "*" {} {} +S: SUCCESS {"fields": ["1"], "qid": 1} +{{ + C: PULL {"n": "*"} + S: RECORD [1] + SUCCESS {"has_more": true} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + # Drivers that pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} +---- + # Drivers that don't pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} +}} +S: SUCCESS {} +C: RUN "*" {} {} +S: SUCCESS {"fields": ["2"], "qid": 2} +{{ + C: PULL {"n": "*"} + S: RECORD [1] + SUCCESS {"has_more": true} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + # Drivers that pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} +---- + # Drivers that don't pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} +}} +S: SUCCESS {} +C: COMMIT +S: SUCCESS {"type": "w", "bookmark": "bookmark:1"} +*: RESET +?: GOODBYE diff --git a/tests/stub/optimizations/scripts/v4x4/all_default_multi_query_nested.script b/tests/stub/optimizations/scripts/v4x4/all_default_multi_query_nested.script new file mode 100644 index 000000000..29c968749 --- /dev/null +++ b/tests/stub/optimizations/scripts/v4x4/all_default_multi_query_nested.script @@ -0,0 +1,48 @@ +!: BOLT 4.4 + +C: HELLO {"user_agent": "*", "scheme": "basic", "principal": "*", "credentials": "*"} +S: SUCCESS {"server": "Neo4j/4.4.0", "connection_id": "example-connection-id:1"} +*: RESET +C: BEGIN {} +S: SUCCESS {} +C: RUN "*" {} {} +S: SUCCESS {"fields": ["1"], "qid": 1} +C: PULL {"n": "*"} +S: RECORD [1] + SUCCESS {"has_more": true} +{? + # Eager drivers might pull a record in advance + C: PULL {"n": "*"} + S: RECORD [1] + SUCCESS {"has_more": true} +?} +C: RUN "*" {} {} +S: SUCCESS {"fields": ["2"], "qid": 2} +{{ + C: PULL {"n": "*"} + S: RECORD [1] + SUCCESS {"has_more": true} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + # Drivers that pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} + }} +---- + # Drivers that don't pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*"} +}} +S: SUCCESS {} +{{ + C: PULL {"n": "*", "qid": 1} + S: RECORD [1] +---- + # Drivers that pipeline a PULL will takes this path in the discarding tests + C: DISCARD {"n": "*", "qid": 1} +}} +S: SUCCESS {} +C: COMMIT +S: SUCCESS {"type": "w", "bookmark": "bookmark:1"} +*: RESET +?: GOODBYE diff --git a/tests/stub/optimizations/scripts/v4x4/all_default_router.script b/tests/stub/optimizations/scripts/v4x4/all_default_router.script new file mode 100644 index 000000000..1da05b653 --- /dev/null +++ b/tests/stub/optimizations/scripts/v4x4/all_default_router.script @@ -0,0 +1,12 @@ +!: BOLT 4.4 +!: ALLOW CONCURRENT + +C: HELLO {"user_agent": "*", "scheme": "basic", "principal": "*", "credentials": "*", "routing": {"{}": "*"}} +S: SUCCESS {"server": "Neo4j/4.4.0", "connection_id": "example-connection-id:0"} +*: RESET +{* + C: ROUTE "*" "*" "*" + S: SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} + *: RESET +*} +?: GOODBYE diff --git a/tests/stub/optimizations/scripts/v4x4/all_default_routing.script b/tests/stub/optimizations/scripts/v4x4/all_default_routing.script new file mode 100644 index 000000000..c07c0b930 --- /dev/null +++ b/tests/stub/optimizations/scripts/v4x4/all_default_routing.script @@ -0,0 +1,35 @@ +!: BOLT 4.4 + +C: HELLO {"user_agent": "*", "scheme": "basic", "principal": "*", "credentials": "*", "routing": {"{}": "*"}} +S: SUCCESS {"server": "Neo4j/4.4.0", "connection_id": "example-connection-id:1"} +*: RESET +{{ + # transaction + C: BEGIN {} + S: SUCCESS {} + C: RUN "*" {} {} + S: SUCCESS {"fields": ["1"]} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + C: DISCARD {} + }} + S: SUCCESS {} + C: COMMIT + S: SUCCESS {"type": "w", "bookmark": "bookmark:1"} +---- + # auto commit transaction + C: RUN "*" {} {} + S: SUCCESS {"fields": ["1"]} + {{ + C: PULL {"n": "*"} + S: RECORD [1] + ---- + C: DISCARD {} + }} + S: RECORD [1] + SUCCESS {"type": "w", "bookmark": "bookmark:1"} +}} +*: RESET +?: GOODBYE diff --git a/tests/stub/optimizations/test_optimizations.py b/tests/stub/optimizations/test_optimizations.py index 807cf78e9..7df16be9b 100644 --- a/tests/stub/optimizations/test_optimizations.py +++ b/tests/stub/optimizations/test_optimizations.py @@ -233,14 +233,14 @@ def test_uses_implicit_default_arguments(self): def test(): if routing: self._router.start( - path=self.script_path("v4x3", "all_default_router.script"), + path=self.script_path(version, "all_default_router.script"), vars={"#HOST#": self._router.host} ) self._server.start(path=self.script_path( - "v4x3", "all_default_routing.script" + version, "all_default_routing.script" )) else: - self._server.start(path=self.script_path("v4x3", + self._server.start(path=self.script_path(version, "all_default.script")) auth = types.AuthorizationToken("basic", principal="neo4j", credentials="pass") @@ -274,22 +274,23 @@ def test(): if routing: self._router.done() - for use_tx in (True, False): - for consume in (True, False): - for routing in (True, False): - with self.subTest(("tx" if use_tx else "auto_commit") - + ("_discard" if consume else "_pull") - + ("_routing" - if routing else "_no_routing")): - test() - self._server.reset() - self._router.reset() + for version in ("v4x3", "v4x4"): + for use_tx in (True, False): + for consume in (True, False): + for routing in (True, False): + with self.subTest(("tx" if use_tx else "auto_commit") + + ("_discard" if consume else "_pull") + + ("_routing" + if routing else "_no_routing")): + test() + self._server.reset() + self._router.reset() @driver_feature(types.Feature.OPT_IMPLICIT_DEFAULT_ARGUMENTS) def test_uses_implicit_default_arguments_multi_query(self): def test(): self._server.start(path=self.script_path( - "v4x3", "all_default_multi_query.script" + version, "all_default_multi_query.script" )) auth = types.AuthorizationToken(scheme="basic", principal="neo4j", credentials="pass") @@ -315,19 +316,21 @@ def test(): driver.close() self._server.done() - for consume1 in (True, False): - for consume2 in (True, False): - with self.subTest(("discard1" if consume1 else "pull1") - + ("_discard2" if consume2 else "_pull2")): - test() - self._server.reset() - self._router.reset() + for version in ("v4x3", "v4x4"): + for consume1 in (True, False): + for consume2 in (True, False): + with self.subTest(("discard1" if consume1 else "pull1") + + ("_discard2" if consume2 + else "_pull2")): + test() + self._server.reset() + self._router.reset() @driver_feature(types.Feature.OPT_IMPLICIT_DEFAULT_ARGUMENTS) def test_uses_implicit_default_arguments_multi_query_nested(self): def test(): self._server.start(path=self.script_path( - "v4x3", "all_default_multi_query_nested.script" + version, "all_default_multi_query_nested.script" )) auth = types.AuthorizationToken(scheme="basic", principal="neo4j", credentials="pass") @@ -353,9 +356,11 @@ def test(): driver.close() self._server.done() - for consume1 in (True, False): - for consume2 in (True, False): - with self.subTest(("discard1" if consume1 else "pull1") - + ("_discard2" if consume2 else "_pull2")): - test() - self._server.reset() + for version in ("v4x3", "v4x4"): + for consume1 in (True, False): + for consume2 in (True, False): + with self.subTest(("discard1" if consume1 else "pull1") + + ("_discard2" + if consume2 else "_pull2")): + test() + self._server.reset() diff --git a/tests/stub/retry/test_retry.py b/tests/stub/retry/test_retry.py index 56e3f2700..d81a692ce 100644 --- a/tests/stub/retry/test_retry.py +++ b/tests/stub/retry/test_retry.py @@ -14,7 +14,7 @@ class TestRetry(TestkitTestCase): def setUp(self): super().setUp() self._server = StubServer(9001) - self._driverName = get_driver_name() + self._driver_name = get_driver_name() def tearDown(self): # If test raised an exception this will make sure that the stub server @@ -102,7 +102,7 @@ def test_disconnect_on_commit(self): # Should NOT retry when connection is lost on unconfirmed commit. # The rule could be relaxed on read transactions therefore we test on # writeTransaction. An error should be raised to indicate the failure - if self._driverName in ["java", 'dotnet']: + if self._driver_name in ["java", 'dotnet']: self.skipTest("Keeps retrying on commit despite connection " "being dropped") self._server.start(path=self.script_path("commit_disconnect.script")) diff --git a/tests/stub/routing/_routing.py b/tests/stub/routing/_routing.py index 9ff5dcf98..a4764fefc 100644 --- a/tests/stub/routing/_routing.py +++ b/tests/stub/routing/_routing.py @@ -17,6 +17,12 @@ class RoutingBase(TestkitTestCase): def setUp(self): super().setUp() + required_bolt_features = { + "4.4": (types.Feature.BOLT_4_4,), + } + self.skip_if_missing_driver_features( + *required_bolt_features.get(self.bolt_version, ()) + ) self._routingServer1 = StubServer(9000) self._routingServer2 = StubServer(9001) self._routingServer3 = StubServer(9002) @@ -45,8 +51,6 @@ def tearDown(self): self._writeServer1.reset() self._writeServer2.reset() self._writeServer3.reset() - self._routingServer1._dump() - self._writeServer1._dump() super().tearDown() @property @@ -84,7 +88,7 @@ def start_server(self, server, script_fn, vars_=None): classes = (self.__class__, *inspect.getmro(self.__class__)) tried_locations = [] for cls in classes: - if hasattr(cls, "bolt_version"): + if isinstance(getattr(cls, "bolt_version", None), str): version_folder = \ "v{}".format(cls.bolt_version.replace(".", "x")) script_path = self.script_path(version_folder, script_fn) diff --git a/tests/stub/routing/scripts/v3/reader_tx_default_db.script b/tests/stub/routing/scripts/v3/reader_tx_default_db.script new file mode 100644 index 000000000..2c4853767 --- /dev/null +++ b/tests/stub/routing/scripts/v3/reader_tx_default_db.script @@ -0,0 +1,15 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL_ALL +S: RECORD [1] + SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x1/reader_tx_default_db.script b/tests/stub/routing/scripts/v4x1/reader_tx_default_db.script new file mode 100644 index 000000000..559e056cb --- /dev/null +++ b/tests/stub/routing/scripts/v4x1/reader_tx_default_db.script @@ -0,0 +1,15 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x3/reader_tx_default_db.script b/tests/stub/routing/scripts/v4x3/reader_tx_default_db.script new file mode 100644 index 000000000..559e056cb --- /dev/null +++ b/tests/stub/routing/scripts/v4x3/reader_tx_default_db.script @@ -0,0 +1,15 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x3/empty_reader.script b/tests/stub/routing/scripts/v4x4/empty_reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/empty_reader.script rename to tests/stub/routing/scripts/v4x4/empty_reader.script diff --git a/tests/stub/routing/scripts/v4x3/reader.script b/tests/stub/routing/scripts/v4x4/reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader.script rename to tests/stub/routing/scripts/v4x4/reader.script diff --git a/tests/stub/routing/scripts/v4x4/reader_default_db.script b/tests/stub/routing/scripts/v4x4/reader_default_db.script new file mode 100644 index 000000000..df3e5d796 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/reader_default_db.script @@ -0,0 +1,11 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"mode": "r", "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x3/reader_tx.script b/tests/stub/routing/scripts/v4x4/reader_tx.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_tx.script rename to tests/stub/routing/scripts/v4x4/reader_tx.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_default_db.script b/tests/stub/routing/scripts/v4x4/reader_tx_default_db.script new file mode 100644 index 000000000..09f8b14c3 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/reader_tx_default_db.script @@ -0,0 +1,15 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r", "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x3/reader_tx_with_bookmarks.script b/tests/stub/routing/scripts/v4x4/reader_tx_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_tx_with_bookmarks.script rename to tests/stub/routing/scripts/v4x4/reader_tx_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x3/reader_tx_with_exit.script b/tests/stub/routing/scripts/v4x4/reader_tx_with_exit.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_tx_with_exit.script rename to tests/stub/routing/scripts/v4x4/reader_tx_with_exit.script diff --git a/tests/stub/routing/scripts/v4x3/reader_tx_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_tx_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x3/reader_tx_with_unexpected_interruption_on_pull.script b/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_tx_with_unexpected_interruption_on_pull.script rename to tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pull.script diff --git a/tests/stub/routing/scripts/v4x3/reader_tx_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_tx_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x3/reader_with_bookmarks.script b/tests/stub/routing/scripts/v4x4/reader_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_with_bookmarks.script rename to tests/stub/routing/scripts/v4x4/reader_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x3/reader_with_explicit_hello.script b/tests/stub/routing/scripts/v4x4/reader_with_explicit_hello.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_with_explicit_hello.script rename to tests/stub/routing/scripts/v4x4/reader_with_explicit_hello.script diff --git a/tests/stub/routing/scripts/v4x3/reader_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x3/reader_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/reader_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x4/router_adb.script b/tests/stub/routing/scripts/v4x4/router_adb.script new file mode 100644 index 000000000..bdcd85b7b --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_adb.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +C: ROUTE #ROUTINGCTX# "*" {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_adb_multi_no_bookmarks.script b/tests/stub/routing/scripts/v4x4/router_adb_multi_no_bookmarks.script new file mode 100644 index 000000000..65c13af10 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_adb_multi_no_bookmarks.script @@ -0,0 +1,12 @@ +!: BOLT #VERSION# +!: ALLOW RESTART + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +{+ + C: ROUTE #ROUTINGCTX# [] {"db": "adb"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} + *: RESET ++} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_and_reader.script b/tests/stub/routing/scripts/v4x4/router_and_reader.script new file mode 100644 index 000000000..fb4cb32a3 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_and_reader.script @@ -0,0 +1,22 @@ +!: BOLT #VERSION# +!: ALLOW RESTART + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +{? + C: ROUTE #ROUTINGCTX# [] {"db": "adb"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9000", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} + *: RESET + ?: GOODBYE +?} +C: BEGIN {"mode": "r", "db": "adb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} + diff --git a/tests/stub/routing/scripts/v4x4/router_and_reader_with_empty_routing_context.script b/tests/stub/routing/scripts/v4x4/router_and_reader_with_empty_routing_context.script new file mode 100644 index 000000000..0301d3e3c --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_and_reader_with_empty_routing_context.script @@ -0,0 +1,21 @@ +!: BOLT #VERSION# +!: ALLOW RESTART + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": {"address": "#HOST#:9000"}} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +{? + C: ROUTE {"address": "#HOST#:9000"} [] {"db": "adb"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9000", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} + *: RESET + ?: GOODBYE +?} +C: BEGIN {"mode": "r", "db": "adb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} diff --git a/tests/stub/routing/scripts/v4x4/router_connectivity_db.script b/tests/stub/routing/scripts/v4x4/router_connectivity_db.script new file mode 100644 index 000000000..93bfd7668 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_connectivity_db.script @@ -0,0 +1,14 @@ +!: BOLT #VERSION# + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +{{ + C: ROUTE #ROUTINGCTX# [] {} + S: SUCCESS { "rt": { "ttl": 1000, "db": "home-db", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} +---- + C: ROUTE #ROUTINGCTX# [] {"db": "system"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "system", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} +}} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_create_adb_with_bookmarks.script b/tests/stub/routing/scripts/v4x4/router_create_adb_with_bookmarks.script new file mode 100644 index 000000000..6ae5b2281 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_create_adb_with_bookmarks.script @@ -0,0 +1,21 @@ +!: BOLT #VERSION# + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +C: BEGIN {"db": "system"} +S: SUCCESS {} +C: RUN "CREATE database foo" {} {} +S: SUCCESS {"fields": []} +C: PULL {"n": 1000} +S: SUCCESS {"type": "w"} +C: COMMIT +S: SUCCESS {"bookmark": "SystemBookmark"} +*: RESET +C: RUN "RETURN 1 as n" {} {"db": "adb", "bookmarks": ["SystemBookmark"]} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_default_db.script b/tests/stub/routing/scripts/v4x4/router_default_db.script new file mode 100644 index 000000000..3882b5c67 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_default_db.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +C: ROUTE #ROUTINGCTX# [] {} +S: SUCCESS { "rt": { "ttl": 1000, "db": "homedb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_system_then_adb_with_bookmarks.script b/tests/stub/routing/scripts/v4x4/router_system_then_adb_with_bookmarks.script new file mode 100644 index 000000000..5f4fc6088 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_system_then_adb_with_bookmarks.script @@ -0,0 +1,13 @@ +!: BOLT #VERSION# +!: AUTO RESET +!: ALLOW RESTART + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +{? + C: ROUTE #ROUTINGCTX# [] {"db": "system"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "system", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9000"], "role":"READ"}, {"addresses": ["#HOST#:9020"], "role":"WRITE"}]}} + ?: GOODBYE +?} +C: ROUTE #ROUTINGCTX# [ "SystemBookmark" ] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9000"], "role":"READ"}, {"addresses": ["#HOST#:9020"], "role":"WRITE"}]}} diff --git a/tests/stub/routing/scripts/v4x4/router_unreachable_db_then_adb.script b/tests/stub/routing/scripts/v4x4/router_unreachable_db_then_adb.script new file mode 100644 index 000000000..7fcb1ad73 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_unreachable_db_then_adb.script @@ -0,0 +1,13 @@ +!: BOLT #VERSION# +!: AUTO RESET +!: ALLOW RESTART + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +{? + C: ROUTE #ROUTINGCTX# [] {"db": "unreachable"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "unreachable", "servers": []}} + ?: GOODBYE +?} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} diff --git a/tests/stub/routing/scripts/v4x4/router_with_leader_change.script b/tests/stub/routing/scripts/v4x4/router_with_leader_change.script new file mode 100644 index 000000000..e8b444a9e --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_with_leader_change.script @@ -0,0 +1,20 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9020"],"role": "WRITE"}, {"addresses": ["#HOST#:9006","#HOST#:9007"], "role": "READ"}, {"addresses": ["#HOST#:9000"], "role": "ROUTE"}]}} +*: RESET +?: GOODBYE +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9020"],"role": "WRITE"}, {"addresses": ["#HOST#:9006","#HOST#:9007"], "role": "READ"}, {"addresses": ["#HOST#:9000"], "role": "ROUTE"}]}} +*: RESET +?: GOODBYE +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": [],"role": "WRITE"}, {"addresses": ["#HOST#:9006","#HOST#:9007"], "role": "READ"}, {"addresses": ["#HOST#:9000"], "role": "ROUTE"}]}} +*: RESET +?: GOODBYE +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9021"],"role": "WRITE"}, {"addresses": ["#HOST#:9006","#HOST#:9007"], "role": "READ"}, {"addresses": ["#HOST#:9000"], "role": "ROUTE"}]}} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_db_not_found_failure.script b/tests/stub/routing/scripts/v4x4/router_yielding_db_not_found_failure.script new file mode 100644 index 000000000..04298f5ce --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_db_not_found_failure.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +*: RESET +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: FAILURE {"code": "Neo.ClientError.Database.DatabaseNotFound", "message": "wut!"} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_empty_response_then_shuts_down.script b/tests/stub/routing/scripts/v4x4/router_yielding_empty_response_then_shuts_down.script new file mode 100644 index 000000000..18cb06936 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_empty_response_then_shuts_down.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": []}} + diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_readers_any_db.script b/tests/stub/routing/scripts/v4x4/router_yielding_no_readers_any_db.script new file mode 100644 index 000000000..7c00cf00e --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_no_readers_any_db.script @@ -0,0 +1,7 @@ +!: BOLT #VERSION# +!: AUTO RESET + +A: HELLO {"{}": "*"} +C: ROUTE #ROUTINGCTX# [] {"[db]": {"U": "*"}} +S: SUCCESS { "rt": { "ttl": 1000, "db": "any-db", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": [], "role":"READ"}, {"addresses": ["#HOST#:9000"], "role":"WRITE"}]}} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb.script b/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb.script new file mode 100644 index 000000000..cc445a9c7 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# +!: AUTO RESET +!: AUTO GOODBYE + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": [], "role":"WRITE"}]}} + diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_any_db.script b/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_any_db.script new file mode 100644 index 000000000..de0d5a7d0 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_any_db.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# "*" {"[db]": {"U": "*"}} +S: SUCCESS { "rt": { "ttl": 1000, "db": "any-db", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": [], "role":"WRITE"}]}} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_procedure_not_found_failure_connectivity_db.script b/tests/stub/routing/scripts/v4x4/router_yielding_procedure_not_found_failure_connectivity_db.script new file mode 100644 index 000000000..6d964efad --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_procedure_not_found_failure_connectivity_db.script @@ -0,0 +1,13 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +{{ + C: ROUTE #ROUTINGCTX# [] {} +---- + C: ROUTE #ROUTINGCTX# [] {"db": "system"} +}} +S: FAILURE {"code": "Neo.ClientError.Procedure.ProcedureNotFound", "message": "blabla"} + IGNORED + diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_reader1_and_exit.script b/tests/stub/routing/scripts/v4x4/router_yielding_reader1_and_exit.script new file mode 100644 index 000000000..0dda637fa --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_reader1_and_exit.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"[db]": {"U": "*"}} +S: SUCCESS { "rt": { "ttl": 1000, "db": "any-db", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} + diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_reader2_adb.script b/tests/stub/routing/scripts/v4x4/router_yielding_reader2_adb.script new file mode 100644 index 000000000..faf69d244 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_reader2_adb.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# "*" {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9021"], "role":"WRITE"}]}} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_router2.script b/tests/stub/routing/scripts/v4x4/router_yielding_router2.script new file mode 100644 index 000000000..516696904 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_router2.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9012"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9022"], "role":"WRITE"}]}} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_fake_reader.script b/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_fake_reader.script new file mode 100644 index 000000000..45d6f717b --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_fake_reader.script @@ -0,0 +1,13 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +{{ + C: ROUTE #ROUTINGCTX# [] {} + S: SUCCESS { "rt": { "ttl": 1000, "db": "home-db", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9100"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9022"], "role":"WRITE"}]}} +---- + C: ROUTE #ROUTINGCTX# [] {"db": "system"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "system", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9100"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9022"], "role":"WRITE"}]}} +}} +S: diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_non_existent_reader.script b/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_non_existent_reader.script new file mode 100644 index 000000000..21d551d77 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_non_existent_reader.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9099"], "role":"READ"}, {"addresses": ["#HOST#:9020", "#HOST#:9021"], "role":"WRITE"}]}} + diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_unknown_failure.script b/tests/stub/routing/scripts/v4x4/router_yielding_unknown_failure.script new file mode 100644 index 000000000..2bfdc3960 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_unknown_failure.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"[db]": {"U": "*"}} +S: FAILURE {"code": "Neo.ClientError.General.Unknown", "message": "wut!"} + IGNORED + diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_writer1.script b/tests/stub/routing/scripts/v4x4/router_yielding_writer1.script new file mode 100644 index 000000000..82c5f7ef6 --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_writer1.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020"], "role":"WRITE"}]}} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_writer2.script b/tests/stub/routing/scripts/v4x4/router_yielding_writer2.script new file mode 100644 index 000000000..75e6bf30a --- /dev/null +++ b/tests/stub/routing/scripts/v4x4/router_yielding_writer2.script @@ -0,0 +1,8 @@ +!: BOLT #VERSION# +!: AUTO RESET + +C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "007", "routing": #ROUTINGCTX#} +S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} +C: ROUTE #ROUTINGCTX# [] {"db": "adb"} +S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9001"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9021"], "role":"WRITE"}]}} +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x3/writer.script b/tests/stub/routing/scripts/v4x4/writer.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer.script rename to tests/stub/routing/scripts/v4x4/writer.script diff --git a/tests/stub/routing/scripts/v4x3/writer_and_reader_tx_with_bookmark.script b/tests/stub/routing/scripts/v4x4/writer_and_reader_tx_with_bookmark.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_and_reader_tx_with_bookmark.script rename to tests/stub/routing/scripts/v4x4/writer_and_reader_tx_with_bookmark.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx.script b/tests/stub/routing/scripts/v4x4/writer_tx.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx.script rename to tests/stub/routing/scripts/v4x4/writer_tx.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_with_bookmarks.script b/tests/stub/routing/scripts/v4x4/writer_tx_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_with_bookmarks.script rename to tests/stub/routing/scripts/v4x4/writer_tx_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_with_leader_switch_and_retry.script b/tests/stub/routing/scripts/v4x4/writer_tx_with_leader_switch_and_retry.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_with_leader_switch_and_retry.script rename to tests/stub/routing/scripts/v4x4/writer_tx_with_leader_switch_and_retry.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_with_multiple_bookmarks.script b/tests/stub/routing/scripts/v4x4/writer_tx_with_multiple_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_with_multiple_bookmarks.script rename to tests/stub/routing/scripts/v4x4/writer_tx_with_multiple_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_with_unexpected_interruption_on_pull.script b/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_with_unexpected_interruption_on_pull.script rename to tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pull.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_yielding_database_unavailable_failure.script b/tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_yielding_database_unavailable_failure.script rename to tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_yielding_database_unavailable_failure_on_commit.script b/tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure_on_commit.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_yielding_database_unavailable_failure_on_commit.script rename to tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure_on_commit.script diff --git a/tests/stub/routing/scripts/v4x3/writer_tx_yielding_failure_on_run.script b/tests/stub/routing/scripts/v4x4/writer_tx_yielding_failure_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_tx_yielding_failure_on_run.script rename to tests/stub/routing/scripts/v4x4/writer_tx_yielding_failure_on_run.script diff --git a/tests/stub/routing/scripts/v4x3/writer_with_bookmark.script b/tests/stub/routing/scripts/v4x4/writer_with_bookmark.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_with_bookmark.script rename to tests/stub/routing/scripts/v4x4/writer_with_bookmark.script diff --git a/tests/stub/routing/scripts/v4x3/writer_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x3/writer_with_unexpected_interruption_on_pull.script b/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_with_unexpected_interruption_on_pull.script rename to tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pull.script diff --git a/tests/stub/routing/scripts/v4x3/writer_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x3/writer_yielding_failure_on_run.script b/tests/stub/routing/scripts/v4x4/writer_yielding_failure_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x3/writer_yielding_failure_on_run.script rename to tests/stub/routing/scripts/v4x4/writer_yielding_failure_on_run.script diff --git a/tests/stub/routing/test_routing_v3.py b/tests/stub/routing/test_routing_v3.py index 15c766098..1e93d4d71 100644 --- a/tests/stub/routing/test_routing_v3.py +++ b/tests/stub/routing/test_routing_v3.py @@ -1,10 +1,10 @@ from nutkit.frontend import Driver import nutkit.protocol as types from ...shared import get_driver_name -from .test_routing_v4x3 import RoutingV4x3 +from .test_routing_v4x4 import RoutingV4x4 -class RoutingV3(RoutingV4x3): +class RoutingV3(RoutingV4x4): bolt_version = "3" server_agent = "Neo4j/3.5.0" diff --git a/tests/stub/routing/test_routing_v4x1.py b/tests/stub/routing/test_routing_v4x1.py index ececa6853..8358dd829 100644 --- a/tests/stub/routing/test_routing_v4x1.py +++ b/tests/stub/routing/test_routing_v4x1.py @@ -1,10 +1,10 @@ import json from nutkit.frontend import Driver -from .test_routing_v4x3 import RoutingV4x3 +from .test_routing_v4x4 import RoutingV4x4 -class RoutingV4x1(RoutingV4x3): +class RoutingV4x1(RoutingV4x4): bolt_version = "4.1" server_agent = "Neo4j/4.1.0" diff --git a/tests/stub/routing/test_routing_v4x3.py b/tests/stub/routing/test_routing_v4x3.py index 84ec1c7b4..1eb24fac2 100644 --- a/tests/stub/routing/test_routing_v4x3.py +++ b/tests/stub/routing/test_routing_v4x3.py @@ -9,2173 +9,273 @@ driver_feature ) from ._routing import RoutingBase +from .test_routing_v4x4 import RoutingV4x4 -class RoutingV4x3(RoutingBase): +class RoutingV4x3(RoutingV4x4): bolt_version = "4.3" server_agent = "Neo4j/4.3.0" adb = "adb" - def route_call_count(self, server): - return server.count_requests("ROUTE") - def test_should_successfully_get_routing_table_with_context(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("needs verifyConnectivity support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_connectivity_db.script") - driver.verifyConnectivity() - driver.close() - - self._routingServer1.done() + super().test_should_successfully_get_routing_table_with_context() def test_should_successfully_get_routing_table(self): - # TODO: remove this block once all languages support routing table test - # API - # TODO: when all driver support this, - # test_should_successfully_get_routing_table_with_context - # and all tests (ab)using verifyConnectivity to refresh the RT - # should be updated. Tests for verifyConnectivity should be added. - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - vars_ = self.get_vars() - self.start_server(self._routingServer1, "router_adb.script", - vars_=vars_) - driver.updateRoutingTable(self.adb) - self._routingServer1.done() - rt = driver.getRoutingTable(self.adb) - driver.close() - assert rt.database == self.adb - assert rt.ttl == 1000 - assert rt.routers == [vars_["#HOST#"] + ":9000"] - assert sorted(rt.readers) == [vars_["#HOST#"] + ":9010", - vars_["#HOST#"] + ":9011"] - assert sorted(rt.writers) == [vars_["#HOST#"] + ":9020", - vars_["#HOST#"] + ":9021"] + super().test_should_successfully_get_routing_table() # Checks that routing is used to connect to correct server and that # parameters for session run is passed on to the target server # (not the router). def test_should_read_successfully_from_reader_using_session_run(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - summary = result.consume() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual(summary.server_info.address, - get_dns_resolved_server_address(self._readServer1)) - self.assertEqual([1], sequence) + super().test_should_read_successfully_from_reader_using_session_run() def test_should_read_successfully_from_reader_using_session_run_with_default_db_driver(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_default_db.script") - self.start_server(self._readServer1, "reader_default_db.script") - - session = driver.session('r') - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - summary = result.consume() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual(summary.server_info.address, - get_dns_resolved_server_address(self._readServer1)) - self.assertEqual([1], sequence) - - # Same test as for session.run but for transaction run. - def test_should_read_successfully_from_reader_using_tx_run(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader_tx.script") - - session = driver.session('r', database=self.adb) - tx = session.beginTransaction() - result = tx.run("RETURN 1 as n") - sequence = self.collectRecords(result) - summary = result.consume() - tx.commit() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual(summary.server_info.address, - get_dns_resolved_server_address(self._readServer1)) - self.assertEqual([1], sequence) + super().test_should_read_successfully_from_reader_using_session_run_with_default_db_driver() + + def test_should_read_successfully_from_reader_using_tx_run_default_db(self): + super().test_should_read_successfully_from_reader_using_tx_run_default_db() def test_should_send_system_bookmark_with_route(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_system_then_adb_with_bookmarks.script" - ) - self.start_server( - self._writeServer1, - "router_create_adb_with_bookmarks.script" - ) - - session = driver.session('w', database='system') - tx = session.beginTransaction() - list(tx.run("CREATE database foo")) - tx.commit() - - session2 = driver.session('w', bookmarks=session.lastBookmarks(), - database=self.adb) - result = session2.run("RETURN 1 as n") - sequence2 = self.collectRecords(result) - session.close() - session2.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertEqual([1], sequence2) + super().test_should_send_system_bookmark_with_route() def test_should_read_successfully_from_reader_using_tx_function(self): - # TODO remove this block once all languages work - if get_driver_name() in ['dotnet']: - self.skipTest("crashes the backend") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader_tx.script") - - session = driver.session('r', database=self.adb) - sequences = [] - summaries = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - summaries.append(result.consume()) - - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual([[1]], sequences) - self.assertEqual(len(summaries), 1) - self.assertEqual(summaries[0].server_info.address, - get_dns_resolved_server_address(self._readServer1)) - - def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( - self, interrupting_reader_script): - # TODO remove this block once all languages wor - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, interrupting_reader_script) - - session = driver.session('r', database=self.adb) - failed = False - try: - # drivers doing eager loading will fail here - session.run("RETURN 1 as n") - except types.DriverError as e: - session.close() - failed = True - else: - try: - # else they should fail here - session.close() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType) - failed = True - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(failed) + super().test_should_read_successfully_from_reader_using_tx_function() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( self): - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( - "reader_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run() def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run( self): - # TODO remove this block once all languages work - if get_driver_name() in ['javascript']: - self.skipTest("requires investigation") - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( - "reader_with_unexpected_interruption_on_run.script" - ) - - def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( - self, interrupting_reader_script): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, interrupting_reader_script) - - session = driver.session('r', database=self.adb) - tx = session.beginTransaction() - failed = False - try: - # drivers doing eager loading will fail here - tx.run("RETURN 1 as n") - # else they should fail here - tx.commit() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - failed = True - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( self): - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( - "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run() def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run( self): - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( - "reader_tx_with_unexpected_interruption_on_run.script" - ) - - def _should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( - self, interrupting_reader_script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, maxTxRetryTimeMs=5000) - self.start_server(self._routingServer1, - "router_adb_multi_no_bookmarks.script") - self.start_server(self._readServer1, interrupting_reader_script) - self.start_server(self._readServer2, interrupting_reader_script) - - session = driver.session('r', database=self.adb) - - def work(tx): - # drivers doing eager loading will fail here - tx.run("RETURN 1 as n") - # else they should fail here - tx.commit() - - with self.assertRaises(types.DriverError) as exc: - session.readTransaction(work) - - session.close() - driver.close() - - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - exc.exception.errorType - ) - self._routingServer1.done() - self._readServer1.done() - self._readServer2.done() + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run() @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME, types.Feature.OPT_PULL_PIPELINING) def test_should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( self): - self._should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( - "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function() @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME) def test_should_fail_when_reading_from_unexpectedly_interrupting_readers_on_run_using_tx_function( self): - # TODO remove this block once all languages work - if get_driver_name() in ['javascript']: - self.skipTest("requires investigation") - self._should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( - "reader_tx_with_unexpected_interruption_on_run.script" - ) - - def _should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( - self, interrupting_writer_script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, maxTxRetryTimeMs=5000) - self.start_server(self._routingServer1, - "router_adb_multi_no_bookmarks.script") - self.start_server(self._writeServer1, interrupting_writer_script) - self.start_server(self._writeServer2, interrupting_writer_script) - - session = driver.session('w', database=self.adb) - - def work(tx): - # drivers doing eager loading will fail here - tx.run("RETURN 1 as n") - # else they should fail here - tx.commit() - - with self.assertRaises(types.DriverError) as exc: - session.writeTransaction(work) - - session.close() - driver.close() - - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - exc.exception.errorType - ) - self._routingServer1.done() - self._writeServer1.done() - self._writeServer2.done() + super().test_should_fail_when_reading_from_unexpectedly_interrupting_readers_on_run_using_tx_function() @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME, types.Feature.OPT_PULL_PIPELINING) def test_should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( self): - self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function() @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME) def test_should_fail_when_writing_to_unexpectedly_interrupting_writers_on_run_using_tx_function( self): - self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( - "writer_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_fail_when_writing_to_unexpectedly_interrupting_writers_on_run_using_tx_function() - # Checks that write server is used def test_should_write_successfully_on_writer_using_session_run(self): - # FIXME: test assumes that first writer in RT will be contacted first - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, "writer.script") - - session = driver.session('w', database=self.adb) - res = session.run("RETURN 1 as n") - list(res) - summary = res.consume() - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - assert (summary.server_info.address - == get_dns_resolved_server_address(self._writeServer1)) - - # Checks that write server is used + super().test_should_write_successfully_on_writer_using_session_run() + def test_should_write_successfully_on_writer_using_tx_run(self): - # FIXME: test assumes that first writer in RT will be contacted first - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, "writer_tx.script") - - session = driver.session('w', database=self.adb) - tx = session.beginTransaction() - res = tx.run("RETURN 1 as n") - list(res) - summary = res.consume() - tx.commit() - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - assert (summary.server_info.address - == get_dns_resolved_server_address(self._writeServer1)) + super().test_should_write_successfully_on_writer_using_tx_run() def test_should_write_successfully_on_writer_using_tx_function(self): - # TODO remove this block once all languages work - if get_driver_name() in ['dotnet']: - self.skipTest("crashes the backend") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, "writer_tx.script") - - session = driver.session('w', database=self.adb) - res = None - summary = None - - def work(tx): - nonlocal res, summary - res = tx.run("RETURN 1 as n") - list(res) - summary = res.consume() - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertIsNotNone(res) - assert (summary.server_info.address - == get_dns_resolved_server_address(self._writeServer1)) + super().test_should_write_successfully_on_writer_using_tx_function() def test_should_write_successfully_on_leader_switch_using_tx_function(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, None) - self.start_server(self._routingServer1, - "router_adb_multi_no_bookmarks.script") - self.start_server( - self._writeServer1, - "writer_tx_with_leader_switch_and_retry.script" - ) - - session = driver.session('w', database=self.adb) - sequences = [] - - work_count = 1 - def work(tx): - nonlocal work_count - try: - result = tx.run("RETURN %i.1 as n" % work_count) - sequences.append(self.collectRecords(result)) - result = tx.run("RETURN %i.2 as n" % work_count) - sequences.append(self.collectRecords(result)) - finally: - # don't simply increase work_count: there is a second writer in - # in the RT that the driver could try to contact. In that case - # the tx function will be called 3 times in total - work_count = 2 - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - if self.driver_supports_features(types.Feature.OPT_CONNECTION_REUSE): - self.assertEqual(self._writeServer1.count_responses(""), 1) - else: - self.assertLessEqual( - self._writeServer1.count_responses(""), 2 - ) - self.assertEqual([[1], [1]], sequences) - self.assertEqual(self.route_call_count(self._routingServer1), 2) - - def _should_retry_write_until_success_with_leader_change_using_tx_function( - self, leader_switch_script): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_with_leader_change.script" - ) - self.start_server(self._writeServer1, leader_switch_script) - self.start_server(self._writeServer2, "writer_tx.script") - - session = driver.session('w', database=self.adb) - sequences = [] - num_retries = 0 - - def work(tx): - nonlocal num_retries - num_retries = num_retries + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self._writeServer2.done() - self.assertEqual([[]], sequences) - self.assertEqual(2, num_retries) + super().test_should_write_successfully_on_leader_switch_using_tx_function() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_retry_write_until_success_with_leader_change_using_tx_function( self): - self._should_retry_write_until_success_with_leader_change_using_tx_function( - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_retry_write_until_success_with_leader_change_using_tx_function() def test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function( self): - # TODO remove this block once all languages work - if get_driver_name() in ['javascript']: - self.skipTest("requires investigation") - self._should_retry_write_until_success_with_leader_change_using_tx_function( - "writer_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function() def test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function( self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_with_leader_change.script" - ) - self.start_server( - self._writeServer1, - "writer_tx_yielding_database_unavailable_failure_on_commit.script" - ) - self.start_server(self._writeServer2, "writer_tx.script") - - session = driver.session('w', database=self.adb) - sequences = [] - num_retries = 0 - - def work(tx): - nonlocal num_retries - num_retries = num_retries + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self._writeServer2.done() - self.assertEqual([[], []], sequences) - self.assertEqual(2, num_retries) - - def _should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( - self, interrupting_writer_script): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, interrupting_writer_script) - - session = driver.session('w', database=self.adb) - failed = False - try: - # drivers doing eager loading will fail here - result = session.run("RETURN 1 as n") - # drivers doing lazy loading should fail here - result.next() - except types.DriverError as e: - session.close() - failed = True - else: - try: - # else they should fail here - session.close() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - elif get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - failed = True - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( - "writer_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run() def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run( self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( - "writer_with_unexpected_interruption_on_run.script" - ) + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run() def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run( self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( - "writer_with_unexpected_interruption_on_pull.script" - ) - - def _should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( - self, interrupting_writer_script): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, interrupting_writer_script) - - session = driver.session('w', database=self.adb) - tx = session.beginTransaction() - failed = False - try: - # drivers doing eager loading will fail here - tx.run("RETURN 1 as n") - # else they should fail here - tx.commit() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - failed = True - session.close() - driver.close() - - self.assertNotIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run() def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run( self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( - "writer_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run() def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run( self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( - "writer_tx_with_unexpected_interruption_on_pull.script" - ) + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run() def test_should_fail_discovery_when_router_fails_with_procedure_not_found_code( self): - # TODO add support and remove this block - if get_driver_name() in ['go']: - self.skipTest("verifyConnectivity not implemented in backend") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_procedure_not_found_failure_connectivity_db.script" - ) - - failed = False - try: - driver.verifyConnectivity() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.ServiceUnavailableException', - e.errorType - ) - elif get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - failed = True - driver.close() - - self._routingServer1.done() - self.assertTrue(failed) + super().test_should_fail_discovery_when_router_fails_with_procedure_not_found_code() def test_should_fail_discovery_when_router_fails_with_unknown_code(self): - # TODO add support and remove this block - if get_driver_name() in ['go']: - self.skipTest("verifyConnectivity not implemented in backend") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_unknown_failure.script" - ) - - failed = False - try: - driver.verifyConnectivity() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.ServiceUnavailableException', - e.errorType - ) - failed = True - driver.close() - - self._routingServer1.done() - self.assertTrue(failed) + super().test_should_fail_discovery_when_router_fails_with_unknown_code() def test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' - '"message": "blabla"}' - } - ) - - session = driver.session('w', database=self.adb) - failed = False - try: - session.run("RETURN 1 as n").consume() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - elif get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - self.assertEqual( - "Neo.ClientError.Cluster.NotALeader", - e.code - ) - failed = True - session.close() - driver.close() - - self.assertNotIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code() def test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": - '{"code": ' - '"Neo.ClientError.General.ForbiddenOnReadOnlyDatabase", ' - '"message": "Unable to write"}' - } - ) - - session = driver.session('w', database=self.adb) - failed = False - try: - session.run("RETURN 1 as n").consume() - except types.DriverError as e: - if get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - self.assertEqual( - "Neo.ClientError.General.ForbiddenOnReadOnlyDatabase", - e.code - ) - failed = True - session.close() - driver.close() - - self.assertNotIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database() def test_should_fail_when_writing_on_writer_that_returns_database_unavailable(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": - '{"code": ' - '"Neo.ClientError.General.DatabaseUnavailable", ' - '"message": "Database is busy doing store copy"}' - } - ) - - session = driver.session('w', database=self.adb) - failed = False - try: - session.run("RETURN 1 as n").consume() - except types.DriverError as e: - if get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - self.assertEqual( - "Neo.ClientError.General.DatabaseUnavailable", - e.code - ) - failed = True - session.close() - driver.close() - - self.assertIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_on_writer_that_returns_database_unavailable() def test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader",' - ' "message": "blabla"}' - } - ) - - session = driver.session('w', database=self.adb) - failed = False - - try: - # drivers doing eager loading will fail here - result = session.run("RETURN 1 as n") - # drivers doing lazy loading should fail here - result.next() - except types.DriverError as e: - session.close() - failed = True - else: - try: - # else they should fail here - session.close() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - elif get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - failed = True - - driver.close() - - self.assertNotIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code() def test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code_using_tx_run(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("consume not implemented in backend") - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_tx_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' - '"message": "blabla"}' - } - ) - - session = driver.session('w', database=self.adb) - tx = session.beginTransaction() - failed = False - try: - tx.run("RETURN 1 as n").consume() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - elif get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - failed = True - session.close() - driver.close() - - self.assertNotIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code_using_tx_run() def test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code_using_tx_run(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - # TODO remove this block once all languages work - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_tx_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' - '"message": "blabla"}' - } - ) - - session = driver.session('w', database=self.adb) - tx = session.beginTransaction() - failed = False - try: - # drivers doing eager loading will fail here - tx.run("RETURN 1 as n") - # else they should fail here - tx.commit() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - elif get_driver_name() in ['python']: - self.assertEqual( - "", - e.errorType - ) - failed = True - session.close() - driver.close() - - self.assertNotIn(self._writeServer1.address, - driver.getRoutingTable(self.adb).writers) - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + super().test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code_using_tx_run() def test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run(self): - # TODO remove this block once all languages work - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_tx_with_bookmarks.script" - ) - - session = driver.session('w', bookmarks=["OldBookmark"], - database=self.adb) - tx = session.beginTransaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - last_bookmarks = session.lastBookmarks() - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertEqual(["NewBookmark"], last_bookmarks) + super().test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run() def test_should_use_read_session_mode_and_initial_bookmark_when_reading_using_tx_run(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader_tx_with_bookmarks.script") - - session = driver.session('r', bookmarks=["OldBookmark"], - database=self.adb) - tx = session.beginTransaction() - result = tx.run("RETURN 1 as n") - sequence = self.collectRecords(result) - tx.commit() - last_bookmarks = session.lastBookmarks() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual([1], sequence) - self.assertEqual(["NewBookmark"], last_bookmarks) + super().test_should_use_read_session_mode_and_initial_bookmark_when_reading_using_tx_run() def test_should_pass_bookmark_from_tx_to_tx_using_tx_run(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_and_reader_tx_with_bookmark.script" - ) - - session = driver.session('w', bookmarks=["BookmarkA"], - database=self.adb) - tx = session.beginTransaction() - list(tx.run("CREATE (n {name:'Bob'})")) - tx.commit() - first_bookmark = session.lastBookmarks() - tx = session.beginTransaction() - result = tx.run("MATCH (n) RETURN n.name AS name") - sequence = self.collectRecords(result) - tx.commit() - second_bookmark = session.lastBookmarks() - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertEqual(['Bob'], sequence) - self.assertEqual(["BookmarkB"], first_bookmark) - self.assertEqual(["BookmarkC"], second_bookmark) - - def _should_retry_read_tx_until_success_on_error( - self, interrupting_reader_script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, interrupting_reader_script) - self.start_server(self._readServer2, interrupting_reader_script) - - session = driver.session('r', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - try: - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - except types.DriverError: - reader1_con_count = \ - self._readServer1.count_responses("") - reader2_con_count = \ - self._readServer2.count_responses("") - if reader1_con_count == 1 and reader2_con_count == 0: - working_reader = self._readServer2 - elif reader1_con_count == 0 and reader2_con_count == 1: - working_reader = self._readServer1 - else: - raise - working_reader.reset() - self.start_server(working_reader, "reader_tx.script") - raise - - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self._readServer2.done() - self.assertEqual([[1]], sequences) - self.assertEqual(2, try_count) + super().test_should_pass_bookmark_from_tx_to_tx_using_tx_run() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_retry_read_tx_until_success_on_error(self): - self._should_retry_read_tx_until_success_on_error( - "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_retry_read_tx_until_success_on_error() def test_should_retry_read_tx_until_success_on_run_error(self): - self._should_retry_read_tx_until_success_on_error( - "reader_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_retry_read_tx_until_success_on_run_error() def test_should_retry_read_tx_until_success_on_pull_error(self): - self._should_retry_read_tx_until_success_on_error( - "reader_tx_with_unexpected_interruption_on_pull.script" - ) + super().test_should_retry_read_tx_until_success_on_pull_error() def test_should_retry_read_tx_until_success_on_no_connection(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._readServer1, - "reader_tx.script" - ) - self.start_server( - self._readServer2, - "reader_tx.script" - ) - - session = driver.session('r', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - self._routingServer1.done() - connection_counts = ( - self._readServer1.count_responses(""), - self._readServer2.count_responses("") - ) - self.assertIn(connection_counts, {(0, 1), (1, 0)}) - if connection_counts == (1, 0): - self._readServer1.done() - else: - self._readServer2.done() - self.assertEqual([[1]], sequences) - self.assertEqual(1, try_count) - - session.readTransaction(work) - session.close() - driver.close() - - self._readServer1.done() - self._readServer2.done() - self.assertEqual([[1], [1]], sequences) - # Drivers might or might not try the first server again - self.assertLessEqual(try_count, 3) - # TODO: Design a test that makes sure the driver doesn't run the tx func - # if it can't establish a working connection to the server. So - # that `try_count == 2`. When doing so be aware that drivers could - # do round robin, e.g. Java. - - def _should_retry_write_tx_until_success_on_error( - self, interrupting_writer_script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, interrupting_writer_script) - self.start_server(self._writeServer2, interrupting_writer_script) - - session = driver.session('w', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - try: - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - except types.DriverError: - writer1_con_count = \ - self._writeServer1.count_responses("") - writer2_con_count = \ - self._writeServer2.count_responses("") - if writer1_con_count == 1 and writer2_con_count == 0: - working_writer = self._writeServer2 - elif writer1_con_count == 0 and writer2_con_count == 1: - working_writer = self._writeServer1 - else: - raise - working_writer.reset() - self.start_server(working_writer, "writer_tx.script") - raise - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self._writeServer2.done() - self.assertEqual([[]], sequences) - self.assertEqual(2, try_count) + super().test_should_retry_read_tx_until_success_on_no_connection() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_retry_write_tx_until_success_on_error(self): - self._should_retry_write_tx_until_success_on_error( - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_retry_write_tx_until_success_on_error() def test_should_retry_write_tx_until_success_on_run_error(self): - self._should_retry_write_tx_until_success_on_error( - "writer_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_retry_write_tx_until_success_on_run_error() def test_should_retry_write_tx_until_success_on_pull_error(self): - self._should_retry_write_tx_until_success_on_error( - "writer_tx_with_unexpected_interruption_on_pull.script" - ) + super().test_should_retry_write_tx_until_success_on_pull_error() def test_should_retry_write_tx_until_success_on_no_connection(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server( - self._writeServer1, - "writer_tx.script" - ) - self.start_server( - self._writeServer2, - "writer_tx.script" - ) - - session = driver.session('r', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.writeTransaction(work) - self._routingServer1.done() - connection_counts = ( - self._writeServer1.count_responses(""), - self._writeServer2.count_responses("") - ) - self.assertIn(connection_counts, {(0, 1), (1, 0)}) - if connection_counts == (1, 0): - self._writeServer1.done() - else: - self._writeServer2.done() - self.assertEqual([[]], sequences) - self.assertEqual(1, try_count) - - session.writeTransaction(work) - session.close() - driver.close() - - self._writeServer1.done() - self._writeServer2.done() - self.assertEqual([[], []], sequences) - # Drivers might or might not try the first server again - self.assertLessEqual(try_count, 3) - # TODO: Design a test that makes sure the driver doesn't run the tx func - # if it can't establish a working connection to the server. So - # that `try_count == 2`. When doing so be aware that drivers could - # do round robin, e.g. Java. - - def _should_retry_read_tx_and_rediscovery_until_success( - self, interrupting_reader_script): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_router2.script" - ) - self.start_server(self._routingServer2, - "router_yielding_reader2_adb.script") - self.start_server(self._readServer1, interrupting_reader_script) - self.start_server(self._readServer2, "reader_tx.script") - self.start_server(self._readServer3, interrupting_reader_script) - - session = driver.session('r', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer2.done() - self._readServer1.done() - self._readServer2.done() - self._readServer3.done() - self.assertEqual([[1]], sequences) - self.assertEqual(3, try_count) + super().test_should_retry_write_tx_until_success_on_no_connection() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_retry_read_tx_and_rediscovery_until_success(self): - self._should_retry_read_tx_and_rediscovery_until_success( - "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_retry_read_tx_and_rediscovery_until_success() def test_should_retry_read_tx_and_rediscovery_until_success_on_run_failure( self): - self._should_retry_read_tx_and_rediscovery_until_success( - "reader_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_retry_read_tx_and_rediscovery_until_success_on_run_failure() def test_should_retry_read_tx_and_rediscovery_until_success_on_pull_failure( self): - self._should_retry_read_tx_and_rediscovery_until_success( - "reader_tx_with_unexpected_interruption_on_pull.script" - ) - - def _should_retry_write_tx_and_rediscovery_until_success( - self, interrupting_writer_script): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_router2.script" - ) - self.start_server(self._routingServer2, - "router_yielding_reader2_adb.script") - self.start_server(self._writeServer1, interrupting_writer_script) - self.start_server(self._writeServer2, "writer_tx.script") - self.start_server(self._writeServer3, interrupting_writer_script) - - session = driver.session('w', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer2.done() - self._writeServer1.done() - self._writeServer2.done() - self._writeServer3.done() - self.assertEqual([[]], sequences) - self.assertEqual(3, try_count) + super().test_should_retry_read_tx_and_rediscovery_until_success_on_pull_failure() @driver_feature(types.Feature.OPT_PULL_PIPELINING) def test_should_retry_write_tx_and_rediscovery_until_success(self): - self._should_retry_write_tx_and_rediscovery_until_success( - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) + super().test_should_retry_write_tx_and_rediscovery_until_success() def test_should_retry_write_tx_and_rediscovery_until_success_on_run_failure( self): - self._should_retry_write_tx_and_rediscovery_until_success( - "writer_tx_with_unexpected_interruption_on_run.script" - ) + super().test_should_retry_write_tx_and_rediscovery_until_success_on_run_failure() def test_should_retry_write_tx_and_rediscovery_until_success_on_pull_failure( self): - self._should_retry_write_tx_and_rediscovery_until_success( - "writer_tx_with_unexpected_interruption_on_pull.script" - ) + super().test_should_retry_write_tx_and_rediscovery_until_success_on_pull_failure() def test_should_use_initial_router_for_discovery_when_others_unavailable( self): - # TODO add support and remove this block - if get_driver_name() in ['go']: - self.skipTest("verifyConnectivity not implemented in backend") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_router2_and_fake_reader.script" - ) - self.start_server(self._readServer1, "reader_tx.script") - - driver.verifyConnectivity() - self._routingServer1.done() - self.start_server(self._routingServer1, "router_adb.script") - session = driver.session('r', database=self.adb) - sequences = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual([[1]], sequences) + super().test_should_use_initial_router_for_discovery_when_others_unavailable() def test_should_successfully_read_from_readable_router_using_tx_function(self): - # TODO remove this block once all languages work - if get_driver_name() in ['dotnet']: - self.skipTest("Test failing for some reason") - # Some drivers (for instance, java) may use separate connections for - # readers and writers when they are addressed by domain names in routing - # table. Since this test is not for testing DNS resolution, it has been - # switched to IP-based address model. - ip_address = get_ip_addresses()[0] - driver = Driver( - self._backend, - self._uri_template_with_context % (ip_address, - self._routingServer1.port), - self._auth, - self._userAgent - ) - self.start_server( - self._routingServer1, - "router_and_reader.script", - vars_=self.get_vars(host=ip_address)) - - session = driver.session('r', database=self.adb) - sequences = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - # TODO: it's not a gold badge to connect more than once. - self.assertLessEqual( - self._routingServer1.count_responses(""), 2 - ) - self.assertEqual(self._routingServer1.count_requests("COMMIT"), 1) - self.assertEqual([[1]], sequences) + super().test_should_successfully_read_from_readable_router_using_tx_function() def test_should_send_empty_hello(self): - # Some drivers (for instance, java) may use separate connections for - # readers and writers when they are addressed by domain names in routing - # table. Since this test is not for testing DNS resolution, it has been - # switched to IP-based address model. - ip_address = get_ip_addresses()[0] - driver = Driver( - self._backend, - self._uri_template % (ip_address, self._routingServer1.port), - self._auth, - self._userAgent - ) - self.start_server( - self._routingServer1, - "router_and_reader_with_empty_routing_context.script", - vars_=self.get_vars(host=ip_address) - ) - - session = driver.session('r', database=self.adb) - sequences = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self.assertEqual(self._routingServer1.count_requests("COMMIT"), 1) - self.assertEqual([[1]], sequences) + super().test_should_send_empty_hello() def test_should_serve_reads_and_fail_writes_when_no_writers_available(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("consume not implemented in backend " - "or requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_no_writers_adb.script" - ) - self.start_server( - self._routingServer2, - "router_yielding_no_writers_adb.script" - ) - self.start_server(self._readServer1, "reader_tx.script") - - session = driver.session('w', database=self.adb) - sequences = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - - failed = False - try: - session.run("CREATE (n {name:'Bob'})").consume() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.SessionExpiredException', - e.errorType - ) - failed = True - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer2.done() - self._readServer1.done() - self.assertEqual([[1]], sequences) - self.assertTrue(failed) + super().test_should_serve_reads_and_fail_writes_when_no_writers_available() def test_should_accept_routing_table_without_writers_and_then_rediscover(self): - # TODO add support and remove this block - if get_driver_name() in ['go']: - self.skipTest("verifyConnectivity not implemented in backend") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_no_writers_any_db.script" - ) - self.start_server(self._readServer1, "reader_tx_with_bookmarks.script") - self.start_server(self._writeServer1, "writer_with_bookmark.script") - - driver.verifyConnectivity() - session = driver.session('w', bookmarks=["OldBookmark"], - database=self.adb) - sequences = [] - self._routingServer1.done() - try: - driver.verifyConnectivity() - except types.DriverError: - # make sure the driver noticed that its old connection to - # _routingServer1 is dead - pass - self.start_server(self._routingServer1, "router_adb.script") - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - list(session.run("RETURN 1 as n")) - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self._writeServer1.done() - self.assertEqual([[1]], sequences) + super().test_should_accept_routing_table_without_writers_and_then_rediscover() def test_should_fail_on_routing_table_with_no_reader(self): - if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: - self.skipTest("needs routing table API support") - self.start_server( - self._routingServer1, - "router_yielding_no_readers_any_db.script" - ) - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - failed = False - try: - driver.updateRoutingTable() - except types.DriverError as exc: - failed = True - if get_driver_name() in ['python']: - self.assertEqual( - exc.errorType, - "" - ) - - self.assertTrue(failed) - routing_table = driver.getRoutingTable() - self.assertEqual(routing_table.routers, []) - self.assertEqual(routing_table.readers, []) - self.assertEqual(routing_table.writers, []) - self._routingServer1.done() - driver.close() + super().test_should_fail_on_routing_table_with_no_reader() def test_should_accept_routing_table_with_single_router(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader.script") - self.start_server(self._readServer2, "reader.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - session.close() - driver.close() - - self._routingServer1.done() - - connection_count_rs1 = self._readServer1.count_responses("") - connection_count_rs2 = self._readServer2.count_responses("") - self.assertEqual(connection_count_rs1 + connection_count_rs2, 1) - if connection_count_rs1 == 1: - self._readServer1.done() - self._readServer2.reset() - else: - self._readServer1.reset() - self._readServer2.done() - self.assertEqual([1], sequence) + super().test_should_accept_routing_table_with_single_router() def test_should_successfully_send_multiple_bookmarks(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._writeServer1, - "writer_tx_with_multiple_bookmarks.script") - - session = driver.session( - 'w', - bookmarks=[ - "neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", - "neo4j:bookmark:v1:tx94", "neo4j:bookmark:v1:tx56", - "neo4j:bookmark:v1:tx16", "neo4j:bookmark:v1:tx68" - ], - database=self.adb - ) - tx = session.beginTransaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - last_bookmarks = session.lastBookmarks() - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertEqual(["neo4j:bookmark:v1:tx95"], last_bookmarks) + super().test_should_successfully_send_multiple_bookmarks() def test_should_forget_address_on_database_unavailable_error(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, - "router_yielding_writer1.script") - self.start_server( - self._writeServer1, - "writer_tx_yielding_database_unavailable_failure.script" - ) - self.start_server( - self._routingServer2, - "router_yielding_writer2.script" - ) - self.start_server(self._writeServer2, "writer_tx.script") - - session = driver.session('w', database=self.adb) - sequences = [] - try_count = 0 - - def work(tx): - nonlocal try_count - try_count = try_count + 1 - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.writeTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer2.done() - self._writeServer1.done() - self._writeServer2.done() - self.assertEqual([[]], sequences) - self.assertEqual(2, try_count) + super().test_should_forget_address_on_database_unavailable_error() def test_should_use_resolver_during_rediscovery_when_existing_routers_fail(self): - resolver_invoked = 0 - - def resolver(address): - nonlocal resolver_invoked - if address != self._routingServer1.address: - return [address] - - resolver_invoked += 1 - if resolver_invoked == 1: - return [address] - elif resolver_invoked == 2: - return [self._routingServer2.address] - self.fail("unexpected") - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, resolverFn=resolver) - self.start_server( - self._routingServer1, - "router_yielding_reader1_and_exit.script" - ) - self.start_server(self._routingServer2, "router_adb.script") - self.start_server(self._readServer1, "reader_tx_with_exit.script") - self.start_server(self._readServer2, "reader_tx.script") - - session = driver.session('w', database=self.adb) - sequences = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - session.readTransaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer2.done() - self._readServer1.done() - self._readServer2.done() - self.assertEqual([[1], [1]], sequences) + super().test_should_use_resolver_during_rediscovery_when_existing_routers_fail() def test_should_revert_to_initial_router_if_known_router_throws_protocol_errors(self): - resolver_calls = defaultdict(lambda: 0) - - def resolver(address): - resolver_calls[address] += 1 - if address == self._routingServer1.address: - return [self._routingServer1.address, - self._routingServer3.address] - return [address] - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, resolver) - self.start_server( - self._routingServer1, - "router_yielding_router2_and_non_existent_reader.script" - ) - self.start_server( - self._routingServer2, - "router_yielding_empty_response_then_shuts_down.script" - ) - self.start_server(self._routingServer3, "router_adb.script") - self.start_server(self._readServer1, "reader_tx.script") - - session = driver.session('r', database=self.adb) - sequences = [] - - def work(tx): - result = tx.run("RETURN 1 as n") - sequences.append(self.collectRecords(result)) - - session.readTransaction(work) - - session.close() - driver.close() - self._routingServer1.done() - self._routingServer2.done() - self._routingServer3.done() - self._readServer1.done() - self.assertEqual([[1]], sequences) - if len(resolver_calls) == 1: - # driver that calls resolver function only on initial router address - self.assertEqual(resolver_calls.keys(), - {self._routingServer1.address}) - # depending on whether the resolve result is treated equally to a - # RT table entry or is discarded after an RT has been retrieved - # successfully. - self.assertEqual(resolver_calls[self._routingServer1.address], 2) - else: - fake_reader_address = self._routingServer1.host + ":9099" - # driver that calls resolver function for every address (initial - # router and server addresses returned in routing table - self.assertLessEqual(resolver_calls.keys(), - {self._routingServer1.address, - fake_reader_address, - self._routingServer2.address, - self._readServer1.address, - # readServer2 isn't part of this test but is - # in the RT of router_script_adb by default - self._readServer2.address}) - self.assertEqual(resolver_calls[self._routingServer1.address], 2) - - self.assertEqual(resolver_calls[fake_reader_address], 1) - self.assertEqual(resolver_calls[self._readServer1.address], 1) - - def should_support_multi_db(self): - return True + super().test_should_revert_to_initial_router_if_known_router_throws_protocol_errors() def test_should_successfully_check_if_support_for_multi_db_is_available(self): - # TODO add support and remove this block - if get_driver_name() in ['go']: - self.skipTest("supportsMultiDb not implemented in backend") - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_default_db.script") - self.start_server(self._readServer1, "empty_reader.script") - - supports_multi_db = driver.supportsMultiDB() - - # we don't expect the router or the reader to play the whole - # script - self._routingServer1.reset() - self._readServer1.reset() - driver.close() - self.assertLessEqual(self._readServer1.count_responses(""), 1) - self.assertEqual(self._readServer1.count_requests("RUN"), 0) - self.assertEqual(self.should_support_multi_db(), supports_multi_db) + super().test_should_successfully_check_if_support_for_multi_db_is_available() def test_should_read_successfully_on_empty_discovery_result_using_session_run(self): - def resolver(address): - if address == self._routingServer1.address: - return (self._routingServer1.address, - self._routingServer2.address) - return address, - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, resolver) - self.start_server( - self._routingServer1, - "router_yielding_empty_response_then_shuts_down.script" - ) - self.start_server(self._routingServer2, "router_adb.script") - self.start_server(self._readServer1, "reader.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer2.done() - self._readServer1.done() - self.assertEqual([1], sequence) + super().test_should_read_successfully_on_empty_discovery_result_using_session_run() def test_should_fail_with_routing_failure_on_db_not_found_discovery_failure(self): - if not self.should_support_multi_db(): - return - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_db_not_found_failure.script" - ) - - session = driver.session('r', database=self.adb) - failed = False - try: - result = session.run("RETURN 1 as n") - result.next() - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.FatalDiscoveryException', - e.errorType - ) - self.assertEqual('Neo.ClientError.Database.DatabaseNotFound', - e.code) - failed = True - session.close() - driver.close() - - self._routingServer1.done() - self.assertTrue(failed) + super().test_should_fail_with_routing_failure_on_db_not_found_discovery_failure() def test_should_read_successfully_from_reachable_db_after_trying_unreachable_db(self): - if get_driver_name() in ['go']: - self.skipTest("requires investigation") - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_unreachable_db_then_adb.script" - ) - self.start_server(self._readServer1, "reader.script") - - session = driver.session('r', database="unreachable") - failed_on_unreachable = False - try: - result = session.run("RETURN 1 as n") - self.collectRecords(result) - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'org.neo4j.driver.exceptions.ServiceUnavailableException', - e.errorType - ) - failed_on_unreachable = True - session.close() - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - self.assertEqual(self.route_call_count(self._routingServer1), 2) - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(failed_on_unreachable) - self.assertEqual([1], sequence) - - def test_should_pass_system_bookmark_when_getting_rt_for_multi_db(self): - pass + super().test_should_read_successfully_from_reachable_db_after_trying_unreachable_db() def test_should_ignore_system_bookmark_when_getting_rt_for_multi_db(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader_with_bookmarks.script") - - session = driver.session('r', database=self.adb, - bookmarks=["sys:1234", "foo:5678"]) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - last_bookmarks = session.lastBookmarks() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual([1], sequence) - self.assertEqual(["foo:6678"], last_bookmarks) + super().test_should_ignore_system_bookmark_when_getting_rt_for_multi_db() def test_should_request_rt_from_all_initial_routers_until_successful(self): - # TODO add support and remove this block - if get_driver_name() in ['go']: - self.skipTest("add resolvers and connection timeout support") - - resolver_calls = {} - domain_name_resolver_call_num = 0 - resolved_addresses = [ - "host1:%s" % self._routingServer1.port, - "host2:%s" % self._routingServer2.port, - "host3:%s" % self._routingServer3.port - ] - resolved_domain_name_addresses = [ - self._routingServer1.host, - self._routingServer2.host, - self._routingServer3.host - ] - - # The resolver is used to convert the original address to multiple fake - # domain names. - def resolver(address): - nonlocal resolver_calls - nonlocal resolved_addresses - resolver_calls[address] = resolver_calls.get(address, 0) + 1 - if address != self._routingServer1.address: - return [address] - return resolved_addresses - - # The domain name resolver is used to verify that the fake domain names - # are given to it and to convert them to routing server addresses. - # This resolver is expected to be called multiple times. - # The combined use of resolver and domain name resolver allows to host - # multiple initial routers on a single IP. - def domainNameResolver(name): - nonlocal domain_name_resolver_call_num - nonlocal resolved_addresses - nonlocal resolved_domain_name_addresses - if domain_name_resolver_call_num >= len(resolved_addresses): - return [name] - expected_name = \ - resolved_addresses[domain_name_resolver_call_num].split(":")[0] - self.assertEqual(expected_name, name) - resolved_domain_name_address = \ - resolved_domain_name_addresses[domain_name_resolver_call_num] - domain_name_resolver_call_num += 1 - return [resolved_domain_name_address] - - driver = Driver( - self._backend, self._uri_with_context, self._auth, self._userAgent, - resolverFn=resolver, domainNameResolverFn=domainNameResolver, - connectionTimeoutMs=1000 - ) - self.start_server( - self._routingServer1, - "router_yielding_unknown_failure.script" - ) - # _routingServer2 is deliberately turned off - self.start_server(self._routingServer3, "router_adb.script") - self.start_server(self._readServer1, "reader.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - session.close() - driver.close() - - self._routingServer1.done() - self._routingServer3.done() - self._readServer1.done() - self.assertEqual([1], sequence) - self.assertGreaterEqual(resolver_calls.items(), - {self._routingServer1.address: 1}.items()) - self.assertTrue(all(count == 1 for count in resolver_calls.values())) + super().test_should_request_rt_from_all_initial_routers_until_successful() def test_should_successfully_acquire_rt_when_router_ip_changes(self): - # TODO remove this block once all languages work - if get_driver_name() in ['go']: - self.skipTest("needs verifyConnectivity support") - ip_addresses = get_ip_addresses() - if len(ip_addresses) < 2: - self.skipTest("at least 2 IP addresses are required for this test " - "and only linux is supported at the moment") - - router_ip_address = ip_addresses[0] - - def domain_name_resolver(_): - nonlocal router_ip_address - return [router_ip_address] - - driver = Driver( - self._backend, self._uri_with_context, self._auth, self._userAgent, - domainNameResolverFn=domain_name_resolver - ) - self.start_server( - self._routingServer1, - "router_yielding_reader1_and_exit.script" - ) - - driver.verifyConnectivity() - self._routingServer1.done() - router_ip_address = ip_addresses[1] - self.start_server( - self._routingServer1, - "router_yielding_reader1_and_exit.script" - ) - driver.verifyConnectivity() - # we don't expect the second router to play the whole script - self._routingServer1.reset() - driver.close() + super().test_should_successfully_acquire_rt_when_router_ip_changes() def test_should_successfully_get_server_protocol_version(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - userAgent=self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - list(result) - summary = result.consume() - protocol_version = summary.server_info.protocol_version - session.close() - driver.close() - - # the server info returns protocol versions in x.y format - expected_protocol_version = self.bolt_version - if "." not in expected_protocol_version: - expected_protocol_version = expected_protocol_version + ".0" - self.assertEqual(expected_protocol_version, protocol_version) - self._routingServer1.done() - self._readServer1.done() + super().test_should_successfully_get_server_protocol_version() def test_should_successfully_get_server_agent(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - userAgent=self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, - "reader_with_explicit_hello.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - list(result) - summary = result.consume() - agent = summary.server_info.agent - session.close() - driver.close() - - self.assertEqual(self.server_agent, agent) - self._routingServer1.done() - self._readServer1.done() - - def test_should_fail_when_driver_closed_using_session_run( - self): - # TODO remove this block once fixed - if get_driver_name() in ["dotnet", "go", "javascript"]: - self.skipTest("Skipped because it needs investigation") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, "router_adb.script") - self.start_server(self._readServer1, "reader.script") - - session = driver.session('r', database=self.adb) - result = session.run("RETURN 1 as n") - sequence = self.collectRecords(result) - summary = result.consume() - session.close() - session = driver.session('r', database=self.adb) - driver.close() - - failed_on_run = False - try: - session.run("RETURN 1 as n") - except types.DriverError as e: - if get_driver_name() in ['java']: - self.assertEqual( - 'java.lang.IllegalStateException', - e.errorType - ) - failed_on_run = True - - self.assertTrue(failed_on_run) - self._routingServer1.done() - self._readServer1.done() - self.assertEqual(summary.server_info.address, - get_dns_resolved_server_address(self._readServer1)) - self.assertEqual([1], sequence) + super().test_should_successfully_get_server_agent() + + def test_should_fail_when_driver_closed_using_session_run(self): + super().test_should_fail_when_driver_closed_using_session_run() diff --git a/tests/stub/routing/test_routing_v4x4.py b/tests/stub/routing/test_routing_v4x4.py new file mode 100644 index 000000000..6b9adb6a2 --- /dev/null +++ b/tests/stub/routing/test_routing_v4x4.py @@ -0,0 +1,2199 @@ +from collections import defaultdict + +from nutkit.frontend import Driver +import nutkit.protocol as types +from tests.shared import ( + get_dns_resolved_server_address, + get_driver_name, + get_ip_addresses, + driver_feature +) +from ._routing import RoutingBase + + +class RoutingV4x4(RoutingBase): + bolt_version = "4.4" + server_agent = "Neo4j/4.4.0" + adb = "adb" + + def route_call_count(self, server): + return server.count_requests("ROUTE") + + def test_should_successfully_get_routing_table_with_context(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("needs verifyConnectivity support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_connectivity_db.script") + driver.verifyConnectivity() + driver.close() + + self._routingServer1.done() + + def test_should_successfully_get_routing_table(self): + # TODO: remove this block once all languages support routing table test + # API + # TODO: when all driver support this, + # test_should_successfully_get_routing_table_with_context + # and all tests (ab)using verifyConnectivity to refresh the RT + # should be updated. Tests for verifyConnectivity should be added. + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + vars_ = self.get_vars() + self.start_server(self._routingServer1, "router_adb.script", + vars_=vars_) + driver.updateRoutingTable(self.adb) + self._routingServer1.done() + rt = driver.getRoutingTable(self.adb) + driver.close() + assert rt.database == self.adb + assert rt.ttl == 1000 + assert rt.routers == [vars_["#HOST#"] + ":9000"] + assert sorted(rt.readers) == [vars_["#HOST#"] + ":9010", + vars_["#HOST#"] + ":9011"] + assert sorted(rt.writers) == [vars_["#HOST#"] + ":9020", + vars_["#HOST#"] + ":9021"] + + # Checks that routing is used to connect to correct server and that + # parameters for session run is passed on to the target server + # (not the router). + def test_should_read_successfully_from_reader_using_session_run(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + summary = result.consume() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual(summary.server_info.address, + get_dns_resolved_server_address(self._readServer1)) + self.assertEqual([1], sequence) + + def test_should_read_successfully_from_reader_using_session_run_with_default_db_driver(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_default_db.script") + self.start_server(self._readServer1, "reader_default_db.script") + + session = driver.session('r') + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + summary = result.consume() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual(summary.server_info.address, + get_dns_resolved_server_address(self._readServer1)) + self.assertEqual([1], sequence) + + # Same test as for session.run but for transaction run. + def test_should_read_successfully_from_reader_using_tx_run_adb(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader_tx.script") + + session = driver.session('r', database=self.adb) + tx = session.beginTransaction() + result = tx.run("RETURN 1 as n") + sequence = self.collectRecords(result) + summary = result.consume() + tx.commit() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual(summary.server_info.address, + get_dns_resolved_server_address(self._readServer1)) + self.assertEqual([1], sequence) + + def test_should_read_successfully_from_reader_using_tx_run_default_db(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_default_db.script") + self.start_server(self._readServer1, "reader_tx_default_db.script") + + session = driver.session('r') + tx = session.beginTransaction() + result = tx.run("RETURN 1 as n") + sequence = self.collectRecords(result) + summary = result.consume() + tx.commit() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual(summary.server_info.address, + get_dns_resolved_server_address(self._readServer1)) + self.assertEqual([1], sequence) + + def test_should_send_system_bookmark_with_route(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_system_then_adb_with_bookmarks.script" + ) + self.start_server( + self._writeServer1, + "router_create_adb_with_bookmarks.script" + ) + + session = driver.session('w', database='system') + tx = session.beginTransaction() + list(tx.run("CREATE database foo")) + tx.commit() + + session2 = driver.session('w', bookmarks=session.lastBookmarks(), + database=self.adb) + result = session2.run("RETURN 1 as n") + sequence2 = self.collectRecords(result) + session.close() + session2.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertEqual([1], sequence2) + + def test_should_read_successfully_from_reader_using_tx_function(self): + # TODO remove this block once all languages work + if get_driver_name() in ['dotnet']: + self.skipTest("crashes the backend") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader_tx.script") + + session = driver.session('r', database=self.adb) + sequences = [] + summaries = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + summaries.append(result.consume()) + + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual([[1]], sequences) + self.assertEqual(len(summaries), 1) + self.assertEqual(summaries[0].server_info.address, + get_dns_resolved_server_address(self._readServer1)) + + def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( + self, interrupting_reader_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, interrupting_reader_script) + + session = driver.session('r', database=self.adb) + failed = False + try: + # drivers doing eager loading will fail here + session.run("RETURN 1 as n") + except types.DriverError as e: + session.close() + failed = True + else: + try: + # else they should fail here + session.close() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType) + failed = True + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(failed) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( + "reader_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run( + self): + # TODO remove this block once all languages work + if get_driver_name() in ['javascript']: + self.skipTest("requires investigation") + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( + "reader_with_unexpected_interruption_on_run.script" + ) + + def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( + self, interrupting_reader_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, interrupting_reader_script) + + session = driver.session('r', database=self.adb) + tx = session.beginTransaction() + failed = False + try: + # drivers doing eager loading will fail here + tx.run("RETURN 1 as n") + # else they should fail here + tx.commit() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + failed = True + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(failed) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( + "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run( + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( + "reader_tx_with_unexpected_interruption_on_run.script" + ) + + def _should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( + self, interrupting_reader_script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, maxTxRetryTimeMs=5000) + self.start_server(self._routingServer1, + "router_adb_multi_no_bookmarks.script") + self.start_server(self._readServer1, interrupting_reader_script) + self.start_server(self._readServer2, interrupting_reader_script) + + session = driver.session('r', database=self.adb) + + def work(tx): + # drivers doing eager loading will fail here + tx.run("RETURN 1 as n") + # else they should fail here + tx.commit() + + with self.assertRaises(types.DriverError) as exc: + session.readTransaction(work) + + session.close() + driver.close() + + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + exc.exception.errorType + ) + self._routingServer1.done() + self._readServer1.done() + self._readServer2.done() + + @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME, + types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( + "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME) + def test_should_fail_when_reading_from_unexpectedly_interrupting_readers_on_run_using_tx_function( + self): + # TODO remove this block once all languages work + if get_driver_name() in ['javascript']: + self.skipTest("requires investigation") + self._should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( + "reader_tx_with_unexpected_interruption_on_run.script" + ) + + def _should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( + self, interrupting_writer_script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, maxTxRetryTimeMs=5000) + self.start_server(self._routingServer1, + "router_adb_multi_no_bookmarks.script") + self.start_server(self._writeServer1, interrupting_writer_script) + self.start_server(self._writeServer2, interrupting_writer_script) + + session = driver.session('w', database=self.adb) + + def work(tx): + # drivers doing eager loading will fail here + tx.run("RETURN 1 as n") + # else they should fail here + tx.commit() + + with self.assertRaises(types.DriverError) as exc: + session.writeTransaction(work) + + session.close() + driver.close() + + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + exc.exception.errorType + ) + self._routingServer1.done() + self._writeServer1.done() + self._writeServer2.done() + + @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME, + types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( + self): + self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + @driver_feature(types.Feature.TMP_DRIVER_MAX_TX_RETRY_TIME) + def test_should_fail_when_writing_to_unexpectedly_interrupting_writers_on_run_using_tx_function( + self): + self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + # Checks that write server is used + def test_should_write_successfully_on_writer_using_session_run(self): + # FIXME: test assumes that first writer in RT will be contacted first + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, "writer.script") + + session = driver.session('w', database=self.adb) + res = session.run("RETURN 1 as n") + list(res) + summary = res.consume() + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + assert (summary.server_info.address + == get_dns_resolved_server_address(self._writeServer1)) + + # Checks that write server is used + def test_should_write_successfully_on_writer_using_tx_run(self): + # FIXME: test assumes that first writer in RT will be contacted first + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, "writer_tx.script") + + session = driver.session('w', database=self.adb) + tx = session.beginTransaction() + res = tx.run("RETURN 1 as n") + list(res) + summary = res.consume() + tx.commit() + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + assert (summary.server_info.address + == get_dns_resolved_server_address(self._writeServer1)) + + def test_should_write_successfully_on_writer_using_tx_function(self): + # TODO remove this block once all languages work + if get_driver_name() in ['dotnet']: + self.skipTest("crashes the backend") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, "writer_tx.script") + + session = driver.session('w', database=self.adb) + res = None + summary = None + + def work(tx): + nonlocal res, summary + res = tx.run("RETURN 1 as n") + list(res) + summary = res.consume() + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertIsNotNone(res) + assert (summary.server_info.address + == get_dns_resolved_server_address(self._writeServer1)) + + def test_should_write_successfully_on_leader_switch_using_tx_function(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, None) + self.start_server(self._routingServer1, + "router_adb_multi_no_bookmarks.script") + self.start_server( + self._writeServer1, + "writer_tx_with_leader_switch_and_retry.script" + ) + + session = driver.session('w', database=self.adb) + sequences = [] + + work_count = 1 + def work(tx): + nonlocal work_count + try: + result = tx.run("RETURN %i.1 as n" % work_count) + sequences.append(self.collectRecords(result)) + result = tx.run("RETURN %i.2 as n" % work_count) + sequences.append(self.collectRecords(result)) + finally: + # don't simply increase work_count: there is a second writer in + # in the RT that the driver could try to contact. In that case + # the tx function will be called 3 times in total + work_count = 2 + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + if self.driver_supports_features(types.Feature.OPT_CONNECTION_REUSE): + self.assertEqual(self._writeServer1.count_responses(""), 1) + else: + self.assertLessEqual( + self._writeServer1.count_responses(""), 2 + ) + self.assertEqual([[1], [1]], sequences) + self.assertEqual(self.route_call_count(self._routingServer1), 2) + + def _should_retry_write_until_success_with_leader_change_using_tx_function( + self, leader_switch_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_with_leader_change.script" + ) + self.start_server(self._writeServer1, leader_switch_script) + self.start_server(self._writeServer2, "writer_tx.script") + + session = driver.session('w', database=self.adb) + sequences = [] + num_retries = 0 + + def work(tx): + nonlocal num_retries + num_retries = num_retries + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self._writeServer2.done() + self.assertEqual([[]], sequences) + self.assertEqual(2, num_retries) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_retry_write_until_success_with_leader_change_using_tx_function( + self): + self._should_retry_write_until_success_with_leader_change_using_tx_function( + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function( + self): + # TODO remove this block once all languages work + if get_driver_name() in ['javascript']: + self.skipTest("requires investigation") + self._should_retry_write_until_success_with_leader_change_using_tx_function( + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function( + self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_with_leader_change.script" + ) + self.start_server( + self._writeServer1, + "writer_tx_yielding_database_unavailable_failure_on_commit.script" + ) + self.start_server(self._writeServer2, "writer_tx.script") + + session = driver.session('w', database=self.adb) + sequences = [] + num_retries = 0 + + def work(tx): + nonlocal num_retries + num_retries = num_retries + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self._writeServer2.done() + self.assertEqual([[], []], sequences) + self.assertEqual(2, num_retries) + + def _should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( + self, interrupting_writer_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, interrupting_writer_script) + + session = driver.session('w', database=self.adb) + failed = False + try: + # drivers doing eager loading will fail here + result = session.run("RETURN 1 as n") + # drivers doing lazy loading should fail here + result.next() + except types.DriverError as e: + session.close() + failed = True + else: + try: + # else they should fail here + session.close() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + elif get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + failed = True + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( + "writer_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run( + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( + "writer_with_unexpected_interruption_on_run.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run( + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( + "writer_with_unexpected_interruption_on_pull.script" + ) + + def _should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( + self, interrupting_writer_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, interrupting_writer_script) + + session = driver.session('w', database=self.adb) + tx = session.beginTransaction() + failed = False + try: + # drivers doing eager loading will fail here + tx.run("RETURN 1 as n") + # else they should fail here + tx.commit() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + failed = True + session.close() + driver.close() + + self.assertNotIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run( + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run( + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( + "writer_tx_with_unexpected_interruption_on_pull.script" + ) + + def test_should_fail_discovery_when_router_fails_with_procedure_not_found_code( + self): + # TODO add support and remove this block + if get_driver_name() in ['go']: + self.skipTest("verifyConnectivity not implemented in backend") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_procedure_not_found_failure_connectivity_db.script" + ) + + failed = False + try: + driver.verifyConnectivity() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.ServiceUnavailableException', + e.errorType + ) + elif get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + failed = True + driver.close() + + self._routingServer1.done() + self.assertTrue(failed) + + def test_should_fail_discovery_when_router_fails_with_unknown_code(self): + # TODO add support and remove this block + if get_driver_name() in ['go']: + self.skipTest("verifyConnectivity not implemented in backend") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_unknown_failure.script" + ) + + failed = False + try: + driver.verifyConnectivity() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.ServiceUnavailableException', + e.errorType + ) + failed = True + driver.close() + + self._routingServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' + '"message": "blabla"}' + } + ) + + session = driver.session('w', database=self.adb) + failed = False + try: + session.run("RETURN 1 as n").consume() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + elif get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + self.assertEqual( + "Neo.ClientError.Cluster.NotALeader", + e.code + ) + failed = True + session.close() + driver.close() + + self.assertNotIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": + '{"code": ' + '"Neo.ClientError.General.ForbiddenOnReadOnlyDatabase", ' + '"message": "Unable to write"}' + } + ) + + session = driver.session('w', database=self.adb) + failed = False + try: + session.run("RETURN 1 as n").consume() + except types.DriverError as e: + if get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + self.assertEqual( + "Neo.ClientError.General.ForbiddenOnReadOnlyDatabase", + e.code + ) + failed = True + session.close() + driver.close() + + self.assertNotIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_on_writer_that_returns_database_unavailable(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": + '{"code": ' + '"Neo.ClientError.General.DatabaseUnavailable", ' + '"message": "Database is busy doing store copy"}' + } + ) + + session = driver.session('w', database=self.adb) + failed = False + try: + session.run("RETURN 1 as n").consume() + except types.DriverError as e: + if get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + self.assertEqual( + "Neo.ClientError.General.DatabaseUnavailable", + e.code + ) + failed = True + session.close() + driver.close() + + self.assertIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader",' + ' "message": "blabla"}' + } + ) + + session = driver.session('w', database=self.adb) + failed = False + + try: + # drivers doing eager loading will fail here + result = session.run("RETURN 1 as n") + # drivers doing lazy loading should fail here + result.next() + except types.DriverError as e: + session.close() + failed = True + else: + try: + # else they should fail here + session.close() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + elif get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + failed = True + + driver.close() + + self.assertNotIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code_using_tx_run(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("consume not implemented in backend") + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_tx_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' + '"message": "blabla"}' + } + ) + + session = driver.session('w', database=self.adb) + tx = session.beginTransaction() + failed = False + try: + tx.run("RETURN 1 as n").consume() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + elif get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + failed = True + session.close() + driver.close() + + self.assertNotIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code_using_tx_run(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + # TODO remove this block once all languages work + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_tx_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' + '"message": "blabla"}' + } + ) + + session = driver.session('w', database=self.adb) + tx = session.beginTransaction() + failed = False + try: + # drivers doing eager loading will fail here + tx.run("RETURN 1 as n") + # else they should fail here + tx.commit() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + elif get_driver_name() in ['python']: + self.assertEqual( + "", + e.errorType + ) + failed = True + session.close() + driver.close() + + self.assertNotIn(self._writeServer1.address, + driver.getRoutingTable(self.adb).writers) + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run(self): + # TODO remove this block once all languages work + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_tx_with_bookmarks.script" + ) + + session = driver.session('w', bookmarks=["OldBookmark"], + database=self.adb) + tx = session.beginTransaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + last_bookmarks = session.lastBookmarks() + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertEqual(["NewBookmark"], last_bookmarks) + + def test_should_use_read_session_mode_and_initial_bookmark_when_reading_using_tx_run(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader_tx_with_bookmarks.script") + + session = driver.session('r', bookmarks=["OldBookmark"], + database=self.adb) + tx = session.beginTransaction() + result = tx.run("RETURN 1 as n") + sequence = self.collectRecords(result) + tx.commit() + last_bookmarks = session.lastBookmarks() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual([1], sequence) + self.assertEqual(["NewBookmark"], last_bookmarks) + + def test_should_pass_bookmark_from_tx_to_tx_using_tx_run(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_and_reader_tx_with_bookmark.script" + ) + + session = driver.session('w', bookmarks=["BookmarkA"], + database=self.adb) + tx = session.beginTransaction() + list(tx.run("CREATE (n {name:'Bob'})")) + tx.commit() + first_bookmark = session.lastBookmarks() + tx = session.beginTransaction() + result = tx.run("MATCH (n) RETURN n.name AS name") + sequence = self.collectRecords(result) + tx.commit() + second_bookmark = session.lastBookmarks() + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertEqual(['Bob'], sequence) + self.assertEqual(["BookmarkB"], first_bookmark) + self.assertEqual(["BookmarkC"], second_bookmark) + + def _should_retry_read_tx_until_success_on_error( + self, interrupting_reader_script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, interrupting_reader_script) + self.start_server(self._readServer2, interrupting_reader_script) + + session = driver.session('r', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + try: + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + except types.DriverError: + reader1_con_count = \ + self._readServer1.count_responses("") + reader2_con_count = \ + self._readServer2.count_responses("") + if reader1_con_count == 1 and reader2_con_count == 0: + working_reader = self._readServer2 + elif reader1_con_count == 0 and reader2_con_count == 1: + working_reader = self._readServer1 + else: + raise + working_reader.reset() + self.start_server(working_reader, "reader_tx.script") + raise + + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self._readServer2.done() + self.assertEqual([[1]], sequences) + self.assertEqual(2, try_count) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_retry_read_tx_until_success_on_error(self): + self._should_retry_read_tx_until_success_on_error( + "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_retry_read_tx_until_success_on_run_error(self): + self._should_retry_read_tx_until_success_on_error( + "reader_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_retry_read_tx_until_success_on_pull_error(self): + self._should_retry_read_tx_until_success_on_error( + "reader_tx_with_unexpected_interruption_on_pull.script" + ) + + def test_should_retry_read_tx_until_success_on_no_connection(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._readServer1, + "reader_tx.script" + ) + self.start_server( + self._readServer2, + "reader_tx.script" + ) + + session = driver.session('r', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + self._routingServer1.done() + connection_counts = ( + self._readServer1.count_responses(""), + self._readServer2.count_responses("") + ) + self.assertIn(connection_counts, {(0, 1), (1, 0)}) + if connection_counts == (1, 0): + self._readServer1.done() + else: + self._readServer2.done() + self.assertEqual([[1]], sequences) + self.assertEqual(1, try_count) + + session.readTransaction(work) + session.close() + driver.close() + + self._readServer1.done() + self._readServer2.done() + self.assertEqual([[1], [1]], sequences) + # Drivers might or might not try the first server again + self.assertLessEqual(try_count, 3) + # TODO: Design a test that makes sure the driver doesn't run the tx func + # if it can't establish a working connection to the server. So + # that `try_count == 2`. When doing so be aware that drivers could + # do round robin, e.g. Java. + + def _should_retry_write_tx_until_success_on_error( + self, interrupting_writer_script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, interrupting_writer_script) + self.start_server(self._writeServer2, interrupting_writer_script) + + session = driver.session('w', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + try: + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + except types.DriverError: + writer1_con_count = \ + self._writeServer1.count_responses("") + writer2_con_count = \ + self._writeServer2.count_responses("") + if writer1_con_count == 1 and writer2_con_count == 0: + working_writer = self._writeServer2 + elif writer1_con_count == 0 and writer2_con_count == 1: + working_writer = self._writeServer1 + else: + raise + working_writer.reset() + self.start_server(working_writer, "writer_tx.script") + raise + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self._writeServer2.done() + self.assertEqual([[]], sequences) + self.assertEqual(2, try_count) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_retry_write_tx_until_success_on_error(self): + self._should_retry_write_tx_until_success_on_error( + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_retry_write_tx_until_success_on_run_error(self): + self._should_retry_write_tx_until_success_on_error( + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_retry_write_tx_until_success_on_pull_error(self): + self._should_retry_write_tx_until_success_on_error( + "writer_tx_with_unexpected_interruption_on_pull.script" + ) + + def test_should_retry_write_tx_until_success_on_no_connection(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server( + self._writeServer1, + "writer_tx.script" + ) + self.start_server( + self._writeServer2, + "writer_tx.script" + ) + + session = driver.session('r', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.writeTransaction(work) + self._routingServer1.done() + connection_counts = ( + self._writeServer1.count_responses(""), + self._writeServer2.count_responses("") + ) + self.assertIn(connection_counts, {(0, 1), (1, 0)}) + if connection_counts == (1, 0): + self._writeServer1.done() + else: + self._writeServer2.done() + self.assertEqual([[]], sequences) + self.assertEqual(1, try_count) + + session.writeTransaction(work) + session.close() + driver.close() + + self._writeServer1.done() + self._writeServer2.done() + self.assertEqual([[], []], sequences) + # Drivers might or might not try the first server again + self.assertLessEqual(try_count, 3) + # TODO: Design a test that makes sure the driver doesn't run the tx func + # if it can't establish a working connection to the server. So + # that `try_count == 2`. When doing so be aware that drivers could + # do round robin, e.g. Java. + + def _should_retry_read_tx_and_rediscovery_until_success( + self, interrupting_reader_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_router2.script" + ) + self.start_server(self._routingServer2, + "router_yielding_reader2_adb.script") + self.start_server(self._readServer1, interrupting_reader_script) + self.start_server(self._readServer2, "reader_tx.script") + self.start_server(self._readServer3, interrupting_reader_script) + + session = driver.session('r', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer2.done() + self._readServer1.done() + self._readServer2.done() + self._readServer3.done() + self.assertEqual([[1]], sequences) + self.assertEqual(3, try_count) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_retry_read_tx_and_rediscovery_until_success(self): + self._should_retry_read_tx_and_rediscovery_until_success( + "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_retry_read_tx_and_rediscovery_until_success_on_run_failure( + self): + self._should_retry_read_tx_and_rediscovery_until_success( + "reader_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_retry_read_tx_and_rediscovery_until_success_on_pull_failure( + self): + self._should_retry_read_tx_and_rediscovery_until_success( + "reader_tx_with_unexpected_interruption_on_pull.script" + ) + + def _should_retry_write_tx_and_rediscovery_until_success( + self, interrupting_writer_script): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_router2.script" + ) + self.start_server(self._routingServer2, + "router_yielding_reader2_adb.script") + self.start_server(self._writeServer1, interrupting_writer_script) + self.start_server(self._writeServer2, "writer_tx.script") + self.start_server(self._writeServer3, interrupting_writer_script) + + session = driver.session('w', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer2.done() + self._writeServer1.done() + self._writeServer2.done() + self._writeServer3.done() + self.assertEqual([[]], sequences) + self.assertEqual(3, try_count) + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_retry_write_tx_and_rediscovery_until_success(self): + self._should_retry_write_tx_and_rediscovery_until_success( + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_retry_write_tx_and_rediscovery_until_success_on_run_failure( + self): + self._should_retry_write_tx_and_rediscovery_until_success( + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_retry_write_tx_and_rediscovery_until_success_on_pull_failure( + self): + self._should_retry_write_tx_and_rediscovery_until_success( + "writer_tx_with_unexpected_interruption_on_pull.script" + ) + + def test_should_use_initial_router_for_discovery_when_others_unavailable( + self): + # TODO add support and remove this block + if get_driver_name() in ['go']: + self.skipTest("verifyConnectivity not implemented in backend") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_router2_and_fake_reader.script" + ) + self.start_server(self._readServer1, "reader_tx.script") + + driver.verifyConnectivity() + self._routingServer1.done() + self.start_server(self._routingServer1, "router_adb.script") + session = driver.session('r', database=self.adb) + sequences = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual([[1]], sequences) + + def test_should_successfully_read_from_readable_router_using_tx_function(self): + # TODO remove this block once all languages work + if get_driver_name() in ['dotnet']: + self.skipTest("Test failing for some reason") + # Some drivers (for instance, java) may use separate connections for + # readers and writers when they are addressed by domain names in routing + # table. Since this test is not for testing DNS resolution, it has been + # switched to IP-based address model. + ip_address = get_ip_addresses()[0] + driver = Driver( + self._backend, + self._uri_template_with_context % (ip_address, + self._routingServer1.port), + self._auth, + self._userAgent + ) + self.start_server( + self._routingServer1, + "router_and_reader.script", + vars_=self.get_vars(host=ip_address)) + + session = driver.session('r', database=self.adb) + sequences = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + # TODO: it's not a gold badge to connect more than once. + self.assertLessEqual( + self._routingServer1.count_responses(""), 2 + ) + self.assertEqual(self._routingServer1.count_requests("COMMIT"), 1) + self.assertEqual([[1]], sequences) + + def test_should_send_empty_hello(self): + # Some drivers (for instance, java) may use separate connections for + # readers and writers when they are addressed by domain names in routing + # table. Since this test is not for testing DNS resolution, it has been + # switched to IP-based address model. + ip_address = get_ip_addresses()[0] + driver = Driver( + self._backend, + self._uri_template % (ip_address, self._routingServer1.port), + self._auth, + self._userAgent + ) + self.start_server( + self._routingServer1, + "router_and_reader_with_empty_routing_context.script", + vars_=self.get_vars(host=ip_address) + ) + + session = driver.session('r', database=self.adb) + sequences = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self.assertEqual(self._routingServer1.count_requests("COMMIT"), 1) + self.assertEqual([[1]], sequences) + + def test_should_serve_reads_and_fail_writes_when_no_writers_available(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("consume not implemented in backend " + "or requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_no_writers_adb.script" + ) + self.start_server( + self._routingServer2, + "router_yielding_no_writers_adb.script" + ) + self.start_server(self._readServer1, "reader_tx.script") + + session = driver.session('w', database=self.adb) + sequences = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + + failed = False + try: + session.run("CREATE (n {name:'Bob'})").consume() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.SessionExpiredException', + e.errorType + ) + failed = True + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer2.done() + self._readServer1.done() + self.assertEqual([[1]], sequences) + self.assertTrue(failed) + + def test_should_accept_routing_table_without_writers_and_then_rediscover(self): + # TODO add support and remove this block + if get_driver_name() in ['go']: + self.skipTest("verifyConnectivity not implemented in backend") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_no_writers_any_db.script" + ) + self.start_server(self._readServer1, "reader_tx_with_bookmarks.script") + self.start_server(self._writeServer1, "writer_with_bookmark.script") + + driver.verifyConnectivity() + session = driver.session('w', bookmarks=["OldBookmark"], + database=self.adb) + sequences = [] + self._routingServer1.done() + try: + driver.verifyConnectivity() + except types.DriverError: + # make sure the driver noticed that its old connection to + # _routingServer1 is dead + pass + self.start_server(self._routingServer1, "router_adb.script") + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + list(session.run("RETURN 1 as n")) + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self._writeServer1.done() + self.assertEqual([[1]], sequences) + + def test_should_fail_on_routing_table_with_no_reader(self): + if get_driver_name() in ['go', 'java', 'javascript', 'dotnet']: + self.skipTest("needs routing table API support") + self.start_server( + self._routingServer1, + "router_yielding_no_readers_any_db.script" + ) + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + failed = False + try: + driver.updateRoutingTable() + except types.DriverError as exc: + failed = True + if get_driver_name() in ['python']: + self.assertEqual( + exc.errorType, + "" + ) + + self.assertTrue(failed) + routing_table = driver.getRoutingTable() + self.assertEqual(routing_table.routers, []) + self.assertEqual(routing_table.readers, []) + self.assertEqual(routing_table.writers, []) + self._routingServer1.done() + driver.close() + + def test_should_accept_routing_table_with_single_router(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader.script") + self.start_server(self._readServer2, "reader.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + session.close() + driver.close() + + self._routingServer1.done() + + connection_count_rs1 = self._readServer1.count_responses("") + connection_count_rs2 = self._readServer2.count_responses("") + self.assertEqual(connection_count_rs1 + connection_count_rs2, 1) + if connection_count_rs1 == 1: + self._readServer1.done() + self._readServer2.reset() + else: + self._readServer1.reset() + self._readServer2.done() + self.assertEqual([1], sequence) + + def test_should_successfully_send_multiple_bookmarks(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._writeServer1, + "writer_tx_with_multiple_bookmarks.script") + + session = driver.session( + 'w', + bookmarks=[ + "neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", + "neo4j:bookmark:v1:tx94", "neo4j:bookmark:v1:tx56", + "neo4j:bookmark:v1:tx16", "neo4j:bookmark:v1:tx68" + ], + database=self.adb + ) + tx = session.beginTransaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + last_bookmarks = session.lastBookmarks() + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertEqual(["neo4j:bookmark:v1:tx95"], last_bookmarks) + + def test_should_forget_address_on_database_unavailable_error(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, + "router_yielding_writer1.script") + self.start_server( + self._writeServer1, + "writer_tx_yielding_database_unavailable_failure.script" + ) + self.start_server( + self._routingServer2, + "router_yielding_writer2.script" + ) + self.start_server(self._writeServer2, "writer_tx.script") + + session = driver.session('w', database=self.adb) + sequences = [] + try_count = 0 + + def work(tx): + nonlocal try_count + try_count = try_count + 1 + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.writeTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer2.done() + self._writeServer1.done() + self._writeServer2.done() + self.assertEqual([[]], sequences) + self.assertEqual(2, try_count) + + def test_should_use_resolver_during_rediscovery_when_existing_routers_fail(self): + resolver_invoked = 0 + + def resolver(address): + nonlocal resolver_invoked + if address != self._routingServer1.address: + return [address] + + resolver_invoked += 1 + if resolver_invoked == 1: + return [address] + elif resolver_invoked == 2: + return [self._routingServer2.address] + self.fail("unexpected") + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, resolverFn=resolver) + self.start_server( + self._routingServer1, + "router_yielding_reader1_and_exit.script" + ) + self.start_server(self._routingServer2, "router_adb.script") + self.start_server(self._readServer1, "reader_tx_with_exit.script") + self.start_server(self._readServer2, "reader_tx.script") + + session = driver.session('w', database=self.adb) + sequences = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + session.readTransaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer2.done() + self._readServer1.done() + self._readServer2.done() + self.assertEqual([[1], [1]], sequences) + + def test_should_revert_to_initial_router_if_known_router_throws_protocol_errors(self): + resolver_calls = defaultdict(lambda: 0) + + def resolver(address): + resolver_calls[address] += 1 + if address == self._routingServer1.address: + return [self._routingServer1.address, + self._routingServer3.address] + return [address] + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, resolver) + self.start_server( + self._routingServer1, + "router_yielding_router2_and_non_existent_reader.script" + ) + self.start_server( + self._routingServer2, + "router_yielding_empty_response_then_shuts_down.script" + ) + self.start_server(self._routingServer3, "router_adb.script") + self.start_server(self._readServer1, "reader_tx.script") + + session = driver.session('r', database=self.adb) + sequences = [] + + def work(tx): + result = tx.run("RETURN 1 as n") + sequences.append(self.collectRecords(result)) + + session.readTransaction(work) + + session.close() + driver.close() + self._routingServer1.done() + self._routingServer2.done() + self._routingServer3.done() + self._readServer1.done() + self.assertEqual([[1]], sequences) + if len(resolver_calls) == 1: + # driver that calls resolver function only on initial router address + self.assertEqual(resolver_calls.keys(), + {self._routingServer1.address}) + # depending on whether the resolve result is treated equally to a + # RT table entry or is discarded after an RT has been retrieved + # successfully. + self.assertEqual(resolver_calls[self._routingServer1.address], 2) + else: + fake_reader_address = self._routingServer1.host + ":9099" + # driver that calls resolver function for every address (initial + # router and server addresses returned in routing table + self.assertLessEqual(resolver_calls.keys(), + {self._routingServer1.address, + fake_reader_address, + self._routingServer2.address, + self._readServer1.address, + # readServer2 isn't part of this test but is + # in the RT of router_script_adb by default + self._readServer2.address}) + self.assertEqual(resolver_calls[self._routingServer1.address], 2) + + self.assertEqual(resolver_calls[fake_reader_address], 1) + self.assertEqual(resolver_calls[self._readServer1.address], 1) + + def should_support_multi_db(self): + return True + + def test_should_successfully_check_if_support_for_multi_db_is_available(self): + # TODO add support and remove this block + if get_driver_name() in ['go']: + self.skipTest("supportsMultiDb not implemented in backend") + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_default_db.script") + self.start_server(self._readServer1, "empty_reader.script") + + supports_multi_db = driver.supportsMultiDB() + + # we don't expect the router or the reader to play the whole + # script + self._routingServer1.reset() + self._readServer1.reset() + driver.close() + self.assertLessEqual(self._readServer1.count_responses(""), 1) + self.assertEqual(self._readServer1.count_requests("RUN"), 0) + self.assertEqual(self.should_support_multi_db(), supports_multi_db) + + def test_should_read_successfully_on_empty_discovery_result_using_session_run(self): + def resolver(address): + if address == self._routingServer1.address: + return (self._routingServer1.address, + self._routingServer2.address) + return address, + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, resolver) + self.start_server( + self._routingServer1, + "router_yielding_empty_response_then_shuts_down.script" + ) + self.start_server(self._routingServer2, "router_adb.script") + self.start_server(self._readServer1, "reader.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer2.done() + self._readServer1.done() + self.assertEqual([1], sequence) + + def test_should_fail_with_routing_failure_on_db_not_found_discovery_failure(self): + if not self.should_support_multi_db(): + return + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_db_not_found_failure.script" + ) + + session = driver.session('r', database=self.adb) + failed = False + try: + result = session.run("RETURN 1 as n") + result.next() + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.FatalDiscoveryException', + e.errorType + ) + self.assertEqual('Neo.ClientError.Database.DatabaseNotFound', + e.code) + failed = True + session.close() + driver.close() + + self._routingServer1.done() + self.assertTrue(failed) + + def test_should_read_successfully_from_reachable_db_after_trying_unreachable_db(self): + if get_driver_name() in ['go']: + self.skipTest("requires investigation") + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_unreachable_db_then_adb.script" + ) + self.start_server(self._readServer1, "reader.script") + + session = driver.session('r', database="unreachable") + failed_on_unreachable = False + try: + result = session.run("RETURN 1 as n") + self.collectRecords(result) + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'org.neo4j.driver.exceptions.ServiceUnavailableException', + e.errorType + ) + failed_on_unreachable = True + session.close() + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + self.assertEqual(self.route_call_count(self._routingServer1), 2) + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(failed_on_unreachable) + self.assertEqual([1], sequence) + + def test_should_ignore_system_bookmark_when_getting_rt_for_multi_db(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader_with_bookmarks.script") + + session = driver.session('r', database=self.adb, + bookmarks=["sys:1234", "foo:5678"]) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + last_bookmarks = session.lastBookmarks() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual([1], sequence) + self.assertEqual(["foo:6678"], last_bookmarks) + + def test_should_request_rt_from_all_initial_routers_until_successful(self): + # TODO add support and remove this block + if get_driver_name() in ['go']: + self.skipTest("add resolvers and connection timeout support") + + resolver_calls = {} + domain_name_resolver_call_num = 0 + resolved_addresses = [ + "host1:%s" % self._routingServer1.port, + "host2:%s" % self._routingServer2.port, + "host3:%s" % self._routingServer3.port + ] + resolved_domain_name_addresses = [ + self._routingServer1.host, + self._routingServer2.host, + self._routingServer3.host + ] + + # The resolver is used to convert the original address to multiple fake + # domain names. + def resolver(address): + nonlocal resolver_calls + nonlocal resolved_addresses + resolver_calls[address] = resolver_calls.get(address, 0) + 1 + if address != self._routingServer1.address: + return [address] + return resolved_addresses + + # The domain name resolver is used to verify that the fake domain names + # are given to it and to convert them to routing server addresses. + # This resolver is expected to be called multiple times. + # The combined use of resolver and domain name resolver allows to host + # multiple initial routers on a single IP. + def domainNameResolver(name): + nonlocal domain_name_resolver_call_num + nonlocal resolved_addresses + nonlocal resolved_domain_name_addresses + if domain_name_resolver_call_num >= len(resolved_addresses): + return [name] + expected_name = \ + resolved_addresses[domain_name_resolver_call_num].split(":")[0] + self.assertEqual(expected_name, name) + resolved_domain_name_address = \ + resolved_domain_name_addresses[domain_name_resolver_call_num] + domain_name_resolver_call_num += 1 + return [resolved_domain_name_address] + + driver = Driver( + self._backend, self._uri_with_context, self._auth, self._userAgent, + resolverFn=resolver, domainNameResolverFn=domainNameResolver, + connectionTimeoutMs=1000 + ) + self.start_server( + self._routingServer1, + "router_yielding_unknown_failure.script" + ) + # _routingServer2 is deliberately turned off + self.start_server(self._routingServer3, "router_adb.script") + self.start_server(self._readServer1, "reader.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + session.close() + driver.close() + + self._routingServer1.done() + self._routingServer3.done() + self._readServer1.done() + self.assertEqual([1], sequence) + self.assertGreaterEqual(resolver_calls.items(), + {self._routingServer1.address: 1}.items()) + self.assertTrue(all(count == 1 for count in resolver_calls.values())) + + def test_should_successfully_acquire_rt_when_router_ip_changes(self): + # TODO remove this block once all languages work + if get_driver_name() in ['go']: + self.skipTest("needs verifyConnectivity support") + ip_addresses = get_ip_addresses() + if len(ip_addresses) < 2: + self.skipTest("at least 2 IP addresses are required for this test " + "and only linux is supported at the moment") + + router_ip_address = ip_addresses[0] + + def domain_name_resolver(_): + nonlocal router_ip_address + return [router_ip_address] + + driver = Driver( + self._backend, self._uri_with_context, self._auth, self._userAgent, + domainNameResolverFn=domain_name_resolver + ) + self.start_server( + self._routingServer1, + "router_yielding_reader1_and_exit.script" + ) + + driver.verifyConnectivity() + self._routingServer1.done() + router_ip_address = ip_addresses[1] + self.start_server( + self._routingServer1, + "router_yielding_reader1_and_exit.script" + ) + driver.verifyConnectivity() + # we don't expect the second router to play the whole script + self._routingServer1.reset() + driver.close() + + def test_should_successfully_get_server_protocol_version(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + userAgent=self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + list(result) + summary = result.consume() + protocol_version = summary.server_info.protocol_version + session.close() + driver.close() + + # the server info returns protocol versions in x.y format + expected_protocol_version = self.bolt_version + if "." not in expected_protocol_version: + expected_protocol_version = expected_protocol_version + ".0" + self.assertEqual(expected_protocol_version, protocol_version) + self._routingServer1.done() + self._readServer1.done() + + def test_should_successfully_get_server_agent(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + userAgent=self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, + "reader_with_explicit_hello.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + list(result) + summary = result.consume() + agent = summary.server_info.agent + session.close() + driver.close() + + self.assertEqual(self.server_agent, agent) + self._routingServer1.done() + self._readServer1.done() + + def test_should_fail_when_driver_closed_using_session_run( + self): + # TODO remove this block once fixed + if get_driver_name() in ["dotnet", "go", "javascript"]: + self.skipTest("Skipped because it needs investigation") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, "router_adb.script") + self.start_server(self._readServer1, "reader.script") + + session = driver.session('r', database=self.adb) + result = session.run("RETURN 1 as n") + sequence = self.collectRecords(result) + summary = result.consume() + session.close() + session = driver.session('r', database=self.adb) + driver.close() + + failed_on_run = False + try: + session.run("RETURN 1 as n") + except types.DriverError as e: + if get_driver_name() in ['java']: + self.assertEqual( + 'java.lang.IllegalStateException', + e.errorType + ) + failed_on_run = True + + self.assertTrue(failed_on_run) + self._routingServer1.done() + self._readServer1.done() + self.assertEqual(summary.server_info.address, + get_dns_resolved_server_address(self._readServer1)) + self.assertEqual([1], sequence) diff --git a/tests/stub/session_run_parameters/scripts/access_mode_read.script b/tests/stub/session_run_parameters/scripts/access_mode_read.script index 6cd0514ef..67081503e 100644 --- a/tests/stub/session_run_parameters/scripts/access_mode_read.script +++ b/tests/stub/session_run_parameters/scripts/access_mode_read.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/session_run_parameters/scripts/access_mode_read_homedb.script b/tests/stub/session_run_parameters/scripts/access_mode_read_homedb.script new file mode 100644 index 000000000..1cd254095 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/access_mode_read_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"mode": "r", "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "r"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/access_mode_write.script b/tests/stub/session_run_parameters/scripts/access_mode_write.script index 63de4adb6..fdad10240 100644 --- a/tests/stub/session_run_parameters/scripts/access_mode_write.script +++ b/tests/stub/session_run_parameters/scripts/access_mode_write.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/session_run_parameters/scripts/access_mode_write_homedb.script b/tests/stub/session_run_parameters/scripts/access_mode_write_homedb.script new file mode 100644 index 000000000..9d8bd0b96 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/access_mode_write_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/adb.script b/tests/stub/session_run_parameters/scripts/adb.script new file mode 100644 index 000000000..37070925b --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/adb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"db": "adb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/bookmarks.script b/tests/stub/session_run_parameters/scripts/bookmarks.script index 7a46a81f4..71fe9ce4f 100644 --- a/tests/stub/session_run_parameters/scripts/bookmarks.script +++ b/tests/stub/session_run_parameters/scripts/bookmarks.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/session_run_parameters/scripts/bookmarks_homedb.script b/tests/stub/session_run_parameters/scripts/bookmarks_homedb.script new file mode 100644 index 000000000..e7e06df13 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/bookmarks_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"bookmarks{}": ["b1", "b2"], "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/combined.script b/tests/stub/session_run_parameters/scripts/combined.script index a1617f85a..43c7fe212 100644 --- a/tests/stub/session_run_parameters/scripts/combined.script +++ b/tests/stub/session_run_parameters/scripts/combined.script @@ -1,8 +1,8 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET -C: RUN "RETURN 1 as n" {"p": 1} {"bookmarks": ["b0"], "tx_metadata": {"k": "v"}, "mode": "r", "tx_timeout": 11} +C: RUN "RETURN 1 as n" {"p": 1} {"bookmarks": ["b0"], "tx_metadata": {"k": "v"}, "mode": "r", "tx_timeout": 11, "imp_user": "that-other-dude", "db": "adb"} S: SUCCESS {"fields": ["n"]} C: PULL {"n": 1000} S: RECORD [1] diff --git a/tests/stub/session_run_parameters/scripts/empty_query.script b/tests/stub/session_run_parameters/scripts/empty_query.script index e016a3e57..46dbfa101 100644 --- a/tests/stub/session_run_parameters/scripts/empty_query.script +++ b/tests/stub/session_run_parameters/scripts/empty_query.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/session_run_parameters/scripts/empty_query_homedb.script b/tests/stub/session_run_parameters/scripts/empty_query_homedb.script new file mode 120000 index 000000000..118221a18 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/empty_query_homedb.script @@ -0,0 +1 @@ +empty_query.script \ No newline at end of file diff --git a/tests/stub/session_run_parameters/scripts/imp_user.script b/tests/stub/session_run_parameters/scripts/imp_user.script new file mode 100644 index 000000000..3ed999e00 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/imp_user.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"imp_user": "that-other-dude"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/imp_user_homedb.script b/tests/stub/session_run_parameters/scripts/imp_user_homedb.script new file mode 100644 index 000000000..4194ccb7e --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/imp_user_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"imp_user": "that-other-dude", "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/imp_user_v4x3.script b/tests/stub/session_run_parameters/scripts/imp_user_v4x3.script new file mode 100644 index 000000000..4593756c5 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/imp_user_v4x3.script @@ -0,0 +1,14 @@ +!: BOLT 4.3 + +A: HELLO {"{}": "*"} +*: RESET +# Impersonation is not a feature that bolt 4.3 supports. However, this server +# understands it anyway. This way, we can check that the driver raises the +# problem even before contacting the server. +C: RUN "RETURN 1 as n" {} {"imp_user": "that-other-dude"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/imp_user_v4x3_homedb.script b/tests/stub/session_run_parameters/scripts/imp_user_v4x3_homedb.script new file mode 100644 index 000000000..60907f4d8 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/imp_user_v4x3_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.3 + +A: HELLO {"{}": "*"} +*: RESET +# Impersonation is not a feature that bolt 4.3 supports. However, this server +# understands it anyway. This way, we can check that the driver raises the +# problem even before contacting the server. +C: RUN "RETURN 1 as n" {} {"imp_user": "that-other-dude", "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/parameters.script b/tests/stub/session_run_parameters/scripts/parameters.script new file mode 100644 index 000000000..4779217a1 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/parameters.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {"p": 1} {"[mode]": "w"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/parameters_homedb.script b/tests/stub/session_run_parameters/scripts/parameters_homedb.script new file mode 100644 index 000000000..e27be3900 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/parameters_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {"p": 1} {"[mode]": "w", "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/router_adb.script b/tests/stub/session_run_parameters/scripts/router_adb.script new file mode 100644 index 000000000..aa709d8a8 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/router_adb.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {"db": "adb"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/session_run_parameters/scripts/router_adb_impersonation.script b/tests/stub/session_run_parameters/scripts/router_adb_impersonation.script new file mode 100644 index 000000000..2bd17f1a0 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/router_adb_impersonation.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {"db": "adb", "[imp_user]": "that-other-dude"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/session_run_parameters/scripts/router_default_db.script b/tests/stub/session_run_parameters/scripts/router_default_db.script new file mode 100644 index 000000000..4e3f02947 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/router_default_db.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {} + S: SUCCESS { "rt": { "ttl": 1000, "db": "homedb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/session_run_parameters/scripts/router_default_db_impersonation.script b/tests/stub/session_run_parameters/scripts/router_default_db_impersonation.script new file mode 100644 index 000000000..b6d9d1ec2 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/router_default_db_impersonation.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {"imp_user": "that-other-dude"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "homedb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/session_run_parameters/scripts/timeout.script b/tests/stub/session_run_parameters/scripts/timeout.script index 0065c9cb5..8ff12d53f 100644 --- a/tests/stub/session_run_parameters/scripts/timeout.script +++ b/tests/stub/session_run_parameters/scripts/timeout.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/session_run_parameters/scripts/timeout_homedb.script b/tests/stub/session_run_parameters/scripts/timeout_homedb.script new file mode 100644 index 000000000..8e1fb4d53 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/timeout_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"tx_timeout": 17, "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/scripts/tx_meta.script b/tests/stub/session_run_parameters/scripts/tx_meta.script index 754a8e307..3a4986483 100644 --- a/tests/stub/session_run_parameters/scripts/tx_meta.script +++ b/tests/stub/session_run_parameters/scripts/tx_meta.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/session_run_parameters/scripts/tx_meta_homedb.script b/tests/stub/session_run_parameters/scripts/tx_meta_homedb.script new file mode 100644 index 000000000..34baf1833 --- /dev/null +++ b/tests/stub/session_run_parameters/scripts/tx_meta_homedb.script @@ -0,0 +1,11 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"tx_metadata": {"akey": "aval"}, "db": "homedb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: RECORD [1] + SUCCESS {"type": "w"} +*: RESET +?: GOODBYE diff --git a/tests/stub/session_run_parameters/test_session_run_parameters.py b/tests/stub/session_run_parameters/test_session_run_parameters.py index 9cbe430f6..5989998d0 100644 --- a/tests/stub/session_run_parameters/test_session_run_parameters.py +++ b/tests/stub/session_run_parameters/test_session_run_parameters.py @@ -1,6 +1,7 @@ from nutkit.frontend import Driver import nutkit.protocol as types from tests.shared import ( + driver_feature, get_driver_name, TestkitTestCase, ) @@ -18,76 +19,183 @@ class TestSessionRunParameters(TestkitTestCase): def setUp(self): super().setUp() + self._router = StubServer(9000) self._server = StubServer(9001) - self._driverName = get_driver_name() - uri = "bolt://%s" % self._server.address - self._driver = Driver(self._backend, uri, - types.AuthorizationToken("basic", principal="", - credentials="")) + self._driver_name = get_driver_name() def tearDown(self): # If test raised an exception this will make sure that the stub server # is killed and it's output is dumped for analysis. self._server.reset() + self._router.reset() super().tearDown() - def _run(self, access_mode, params=None, bookmarks=None, tx_meta=None, - timeout=None): - session = self._driver.session(access_mode, bookmarks=bookmarks) + def _start_servers_and_driver(self, script, routing, db, impersonation): + if routing: + router_script = "router%s%%s.script" + if db: + router_script = router_script % ("_" + db) + else: + router_script = router_script % "_default_db" + if impersonation: + router_script = router_script % "_impersonation" + else: + router_script = router_script % "" + self._router.start(path=self.script_path(router_script), + vars={"#HOST#": self._router.host}) + if routing and not db: + script += "_homedb.script" + else: + script += ".script" + self._server.start(path=self.script_path(script)) + if routing: + uri = "neo4j://%s" % self._router.address + else: + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, + types.AuthorizationToken("basic", principal="", + credentials="")) + + def _run(self, script, routing, session_args=None, session_kwargs=None, + run_args=None, run_kwargs=None): + if session_args is None: + session_args = () + if session_kwargs is None: + session_kwargs = {} + if run_args is None: + run_args = () + if run_kwargs is None: + run_kwargs = {} + self._start_servers_and_driver(script, routing, + session_kwargs.get("database"), + session_kwargs.get("impersonatedUser")) + session = self._driver.session(*session_args, **session_kwargs) try: - result = session.run("RETURN 1 as n", params=params, txMeta=tx_meta, - timeout=timeout) + result = session.run("RETURN 1 as n", *run_args, **run_kwargs) result.next() + self._server.done() finally: + self._server.reset() + self._router.reset() session.close() + self._driver.close() - def _start_server(self, script): - self._server.start(path=self.script_path(script)) - + @driver_feature(types.Feature.BOLT_4_4) def test_access_mode_read(self): - self._start_server("access_mode_read.script") - self._run("r") - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("access_mode_read", routing, + session_args=("r",)) + @driver_feature(types.Feature.BOLT_4_4) def test_access_mode_write(self): - self._start_server("access_mode_write.script") - self._run("w") - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("access_mode_write", routing, + session_args=("w",)) + @driver_feature(types.Feature.BOLT_4_4) + def test_parameters(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("parameters", routing, + session_args=("w",), + run_kwargs={"params": {"p": types.CypherInt(1)}}) + + @driver_feature(types.Feature.BOLT_4_4) def test_bookmarks(self): - self._start_server("bookmarks.script") - self._run("w", bookmarks=["b1", "b2"]) - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("bookmarks", routing, + session_args=("w",), + session_kwargs={"bookmarks": ["b1", "b2"]}) + @driver_feature(types.Feature.BOLT_4_4) def test_tx_meta(self): - self._start_server("tx_meta.script") - self._run("w", tx_meta={"akey": "aval"}) - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("tx_meta", routing, + session_args=("w",), + run_kwargs={"txMeta": {"akey": "aval"}}) + @driver_feature(types.Feature.BOLT_4_4) def test_timeout(self): - self._start_server("timeout.script") - self._run("w", timeout=17) - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("timeout", routing, + session_args=("w",), run_kwargs={"timeout": 17}) + + @driver_feature(types.Feature.BOLT_4_4) + def test_database(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("adb", routing, + session_args=("w",), + session_kwargs={"database": "adb"}) + @driver_feature(types.Feature.IMPERSONATION, + types.Feature.BOLT_4_4) + def test_impersonation(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("imp_user", routing, + session_args=("w",), + session_kwargs={ + "impersonatedUser": "that-other-dude" + }) + + @driver_feature(types.Feature.IMPERSONATION, + types.Feature.BOLT_4_4) + def test_impersonation_fails_on_v4x3(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + with self.assertRaises(types.DriverError) as exc: + self._run("imp_user_v4x3", routing, + session_args=("w",), + session_kwargs={ + "impersonatedUser": "that-other-dude" + }) + if self._driver_name in ["python"]: + self.assertEqual( + exc.exception.errorType, + "" + ) + self.assertIn("that-other-dude", exc.exception.msg) + if self._driver_name in ["java"]: + self.assertEqual( + exc.exception.errorType, + "org.neo4j.driver.exceptions.ClientException" + ) + + @driver_feature(types.Feature.IMPERSONATION, + types.Feature.BOLT_4_4) def test_combined(self): - self._start_server("combined.script") - self._run("r", params={"p": types.CypherInt(1)}, bookmarks=["b0"], - tx_meta={"k": "v"}, timeout=11) - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("combined", routing, + session_args=("r",), + session_kwargs={ + "bookmarks": ["b0"], + "database": "adb", + "impersonatedUser": "that-other-dude" + }, + run_kwargs={ + "params": {"p": types.CypherInt(1)}, + "txMeta": {"k": "v"}, "timeout": 11 + }) + @driver_feature(types.Feature.BOLT_4_4) def test_empty_query(self): + # Driver should pass empty string to server + # TODO remove this block once all languages work if get_driver_name() in ["javascript", "java"]: self.skipTest("rejects empty string") - # Driver should pass empty string to server - self._start_server("empty_query.script") - session = self._driver.session("w") - session.run("").next() - session.close() - self._driver.close() - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._start_servers_and_driver("empty_query", routing, + None, None) + session = self._driver.session("w") + session.run("").next() + session.close() + self._driver.close() + self._server.done() diff --git a/tests/stub/transport/test_transport.py b/tests/stub/transport/test_transport.py index 8b2e61049..0f7c39153 100644 --- a/tests/stub/transport/test_transport.py +++ b/tests/stub/transport/test_transport.py @@ -12,7 +12,7 @@ class TestTransport(TestkitTestCase): def setUp(self): super().setUp() self._server = StubServer(9001) - self._driverName = get_driver_name() + self._driver_name = get_driver_name() auth = types.AuthorizationToken("basic", principal="neo4j", credentials="pass") uri = "bolt://%s" % self._server.address diff --git a/tests/stub/tx_begin_parameters/scripts/access_mode_read.script b/tests/stub/tx_begin_parameters/scripts/access_mode_read.script index b02bbb60c..ab797dde2 100644 --- a/tests/stub/tx_begin_parameters/scripts/access_mode_read.script +++ b/tests/stub/tx_begin_parameters/scripts/access_mode_read.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/tx_begin_parameters/scripts/access_mode_read_homedb.script b/tests/stub/tx_begin_parameters/scripts/access_mode_read_homedb.script new file mode 100644 index 000000000..eac39bf59 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/access_mode_read_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r", "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/access_mode_write.script b/tests/stub/tx_begin_parameters/scripts/access_mode_write.script index 745e48aeb..10a8a3b68 100644 --- a/tests/stub/tx_begin_parameters/scripts/access_mode_write.script +++ b/tests/stub/tx_begin_parameters/scripts/access_mode_write.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/tx_begin_parameters/scripts/access_mode_write_homedb.script b/tests/stub/tx_begin_parameters/scripts/access_mode_write_homedb.script new file mode 100644 index 000000000..89cd9bd0a --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/access_mode_write_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/adb.script b/tests/stub/tx_begin_parameters/scripts/adb.script new file mode 100644 index 000000000..0a3d54661 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/adb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"db": "adb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/bookmarks.script b/tests/stub/tx_begin_parameters/scripts/bookmarks.script index 9197aa87c..76aef45e4 100644 --- a/tests/stub/tx_begin_parameters/scripts/bookmarks.script +++ b/tests/stub/tx_begin_parameters/scripts/bookmarks.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/tx_begin_parameters/scripts/bookmarks_homedb.script b/tests/stub/tx_begin_parameters/scripts/bookmarks_homedb.script new file mode 100644 index 000000000..4fbb56dee --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/bookmarks_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"bookmarks{}": ["b1", "b2"], "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/combined.script b/tests/stub/tx_begin_parameters/scripts/combined.script index 138536711..76f7a68c2 100644 --- a/tests/stub/tx_begin_parameters/scripts/combined.script +++ b/tests/stub/tx_begin_parameters/scripts/combined.script @@ -1,10 +1,10 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET -C: BEGIN {"bookmarks": ["b0"], "tx_metadata": {"k": "v"}, "mode": "r", "tx_timeout": 11} +C: BEGIN {"bookmarks": ["b0"], "tx_metadata": {"k": "v"}, "mode": "r", "tx_timeout": 11, "imp_user": "that-other-dude", "db": "adb"} S: SUCCESS {} -C: RUN "RETURN 1 as n" {} {} +C: RUN "RETURN 1 as n" {"p": 1} {} S: SUCCESS {"fields": ["n"]} C: PULL {"n": 1000} S: SUCCESS {"type": "r"} diff --git a/tests/stub/tx_begin_parameters/scripts/imp_user.script b/tests/stub/tx_begin_parameters/scripts/imp_user.script new file mode 100644 index 000000000..95654fa04 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/imp_user.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"imp_user": "that-other-dude"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/imp_user_homedb.script b/tests/stub/tx_begin_parameters/scripts/imp_user_homedb.script new file mode 100644 index 000000000..359f59af7 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/imp_user_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"imp_user": "that-other-dude", "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/imp_user_v4x3.script b/tests/stub/tx_begin_parameters/scripts/imp_user_v4x3.script new file mode 100644 index 000000000..8b3c4a320 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/imp_user_v4x3.script @@ -0,0 +1,17 @@ +!: BOLT 4.3 + +A: HELLO {"{}": "*"} +*: RESET +# Impersonation is not a feature that bolt 4.3 supports. However, this server +# understands it anyway. This way, we can check that the driver raises the +# problem even before contacting the server. +C: BEGIN {"imp_user": "that-other-dude"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/imp_user_v4x3_homedb.script b/tests/stub/tx_begin_parameters/scripts/imp_user_v4x3_homedb.script new file mode 100644 index 000000000..70aae311c --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/imp_user_v4x3_homedb.script @@ -0,0 +1,17 @@ +!: BOLT 4.3 + +A: HELLO {"{}": "*"} +*: RESET +# Impersonation is not a feature that bolt 4.3 supports. However, this server +# understands it anyway. This way, we can check that the driver raises the +# problem even before contacting the server. +C: BEGIN {"imp_user": "that-other-dude", "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/parameters.script b/tests/stub/tx_begin_parameters/scripts/parameters.script new file mode 100644 index 000000000..12ac25dde --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/parameters.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"[mode]": "w"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {"p": 1} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/parameters_homedb.script b/tests/stub/tx_begin_parameters/scripts/parameters_homedb.script new file mode 100644 index 000000000..cc6b56b0d --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/parameters_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"[mode]": "w", "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {"p": 1} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/router_adb.script b/tests/stub/tx_begin_parameters/scripts/router_adb.script new file mode 100644 index 000000000..aa709d8a8 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/router_adb.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {"db": "adb"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/tx_begin_parameters/scripts/router_adb_impersonation.script b/tests/stub/tx_begin_parameters/scripts/router_adb_impersonation.script new file mode 100644 index 000000000..2bd17f1a0 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/router_adb_impersonation.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {"db": "adb", "[imp_user]": "that-other-dude"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "adb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/tx_begin_parameters/scripts/router_default_db.script b/tests/stub/tx_begin_parameters/scripts/router_default_db.script new file mode 100644 index 000000000..4e3f02947 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/router_default_db.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {} + S: SUCCESS { "rt": { "ttl": 1000, "db": "homedb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/tx_begin_parameters/scripts/router_default_db_impersonation.script b/tests/stub/tx_begin_parameters/scripts/router_default_db_impersonation.script new file mode 100644 index 000000000..b6d9d1ec2 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/router_default_db_impersonation.script @@ -0,0 +1,10 @@ +!: BOLT 4.4 +!: AUTO RESET +!: AUTO GOODBYE +!: ALLOW CONCURRENT + +A: HELLO {"{}": "*"} +{* + C: ROUTE {"{}": "*"} {"[]": "*"} {"imp_user": "that-other-dude"} + S: SUCCESS { "rt": { "ttl": 1000, "db": "homedb", "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9001"], "role":"READ"}, {"addresses": ["#HOST#:9001"], "role":"WRITE"}]}} +*} diff --git a/tests/stub/tx_begin_parameters/scripts/timeout.script b/tests/stub/tx_begin_parameters/scripts/timeout.script index bc4b1bcd1..3af0cf9b3 100644 --- a/tests/stub/tx_begin_parameters/scripts/timeout.script +++ b/tests/stub/tx_begin_parameters/scripts/timeout.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/tx_begin_parameters/scripts/timeout_homedb.script b/tests/stub/tx_begin_parameters/scripts/timeout_homedb.script new file mode 100644 index 000000000..04253ecca --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/timeout_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"tx_timeout": 17, "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/scripts/tx_meta.script b/tests/stub/tx_begin_parameters/scripts/tx_meta.script index ccb881546..9efead84a 100644 --- a/tests/stub/tx_begin_parameters/scripts/tx_meta.script +++ b/tests/stub/tx_begin_parameters/scripts/tx_meta.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/tx_begin_parameters/scripts/tx_meta_homedb.script b/tests/stub/tx_begin_parameters/scripts/tx_meta_homedb.script new file mode 100644 index 000000000..cccbfc5b3 --- /dev/null +++ b/tests/stub/tx_begin_parameters/scripts/tx_meta_homedb.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"tx_metadata": {"akey": "aval"}, "db": "homedb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "r"} +C: COMMIT +S: SUCCESS {} +*: RESET +?: GOODBYE diff --git a/tests/stub/tx_begin_parameters/test_tx_begin_parameters.py b/tests/stub/tx_begin_parameters/test_tx_begin_parameters.py index b8b64ae54..99a02ae95 100644 --- a/tests/stub/tx_begin_parameters/test_tx_begin_parameters.py +++ b/tests/stub/tx_begin_parameters/test_tx_begin_parameters.py @@ -1,6 +1,7 @@ from nutkit.frontend import Driver import nutkit.protocol as types from tests.shared import ( + driver_feature, get_driver_name, TestkitTestCase, ) @@ -18,91 +19,203 @@ class TestTxBeginParameters(TestkitTestCase): def setUp(self): super().setUp() + self._router = StubServer(9000) self._server = StubServer(9001) - self._driverName = get_driver_name() - uri = "bolt://%s" % self._server.address - self._driver = Driver(self._backend, uri, - types.AuthorizationToken("basic", principal="", - credentials="")) + self._driver_name = get_driver_name() def tearDown(self): # If test raised an exception this will make sure that the stub server # is killed and it's output is dumped for analysis. - self._driver.close() self._server.reset() + self._router.reset() super().tearDown() - def _run(self, session_access_mode, tx_func_access_mode=None, params=None, - bookmarks=None, tx_meta=None, timeout=None): + def _start_servers_and_driver(self, script, routing, db, impersonation): + if routing: + router_script = "router%s%%s.script" + if db: + router_script = router_script % ("_" + db) + else: + router_script = router_script % "_default_db" + if impersonation: + router_script = router_script % "_impersonation" + else: + router_script = router_script % "" + self._router.start(path=self.script_path(router_script), + vars={"#HOST#": self._router.host}) + if routing and not db: + script += "_homedb.script" + else: + script += ".script" + self._server.start(path=self.script_path(script)) + if routing: + uri = "neo4j://%s" % self._router.address + else: + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, + types.AuthorizationToken("basic", principal="", + credentials="")) + + def _run(self, script, routing, tx_func_access_mode=None, + session_args=None, session_kwargs=None, + tx_args=None, tx_kwargs=None, + run_args=None, run_kwargs=None): def work(tx_): # Need to do something on the tx, driver might do lazy begin - list(tx_.run("RETURN 1 as n")) - - # FIXME: params are not used and dotnet fails when using them - session = self._driver.session(session_access_mode, bookmarks=bookmarks) - if tx_func_access_mode is None: - tx = session.beginTransaction(tx_meta, timeout) - work(tx) - tx.commit() - elif tx_func_access_mode == "w": - session.writeTransaction(work, txMeta=tx_meta, timeout=timeout) - elif tx_func_access_mode == "r": - session.readTransaction(work, txMeta=tx_meta, timeout=timeout) - else: - raise ValueError(tx_func_access_mode) - session.close() + list(tx_.run("RETURN 1 as n", *run_args, **run_kwargs)) - def _start_server(self, script): - self._server.start(path=self.script_path(script)) + if session_args is None: + session_args = () + if session_kwargs is None: + session_kwargs = {} + if tx_args is None: + tx_args = () + if tx_kwargs is None: + tx_kwargs = {} + if run_args is None: + run_args = () + if run_kwargs is None: + run_kwargs = {} + self._start_servers_and_driver(script, routing, + session_kwargs.get("database"), + session_kwargs.get("impersonatedUser")) + session = self._driver.session(*session_args, **session_kwargs) + try: + if tx_func_access_mode is None: + tx = session.beginTransaction(*tx_args, **tx_kwargs) + work(tx) + tx.commit() + elif tx_func_access_mode == "w": + session.writeTransaction(work, *tx_args, **tx_kwargs) + elif tx_func_access_mode == "r": + session.readTransaction(work, *tx_args, **tx_kwargs) + else: + raise ValueError(tx_func_access_mode) + self._server.done() + finally: + self._server.reset() + self._router.reset() + session.close() + self._driver.close() + @driver_feature(types.Feature.BOLT_4_4) def test_access_mode_read(self): - self._start_server("access_mode_read.script") - self._run("r") - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("access_mode_read", routing, session_args=("r",)) + self._server.done() + @driver_feature(types.Feature.BOLT_4_4) def test_tx_func_access_mode_read(self): - for session_access_mode in ("r", "w"): - with self.subTest("session_mode_" + session_access_mode): - self._start_server("access_mode_read.script") - try: - self._run(session_access_mode[0], tx_func_access_mode="r") - self._server.done() - finally: - self._server.reset() + for routing in (True, False): + for session_access_mode in ("r", "w"): + with self.subTest("session_mode_" + session_access_mode + + "_routing" if routing else "_direct"): + self._run("access_mode_read", routing, + session_args=(session_access_mode[0],), + tx_func_access_mode="r") + @driver_feature(types.Feature.BOLT_4_4) def test_access_mode_write(self): - self._start_server("access_mode_write.script") - self._run("w") - self._server.done() - - def test_tx_func_access_mode_write(self): - for session_access_mode in ("r", "w"): - with self.subTest("session_mode_" + session_access_mode): - self._start_server("access_mode_write.script") - try: - self._run(session_access_mode[0], tx_func_access_mode="w") - self._server.done() - finally: - self._server.reset() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("access_mode_write", routing, + session_args=("w",)) + self._server.done() + + @driver_feature(types.Feature.BOLT_4_4) + def test_tx_func_access_mode(self): + for routing in (True, False): + for session_access_mode in ("r", "w"): + with self.subTest("session_mode_" + session_access_mode): + self._run("access_mode_write", routing, + session_args=(session_access_mode[0],), + tx_func_access_mode="w") + + @driver_feature(types.Feature.BOLT_4_4) + def test_parameters(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("parameters", routing, + session_args=("w",), + run_kwargs={"params": {"p": types.CypherInt(1)}}) + @driver_feature(types.Feature.BOLT_4_4) def test_bookmarks(self): - self._start_server("bookmarks.script") - self._run("w", bookmarks=["b1", "b2"]) - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("bookmarks", routing, + session_args=("w",), + session_kwargs={"bookmarks": ["b1", "b2"]}) + @driver_feature(types.Feature.BOLT_4_4) def test_tx_meta(self): - self._start_server("tx_meta.script") - self._run("w", tx_meta={"akey": "aval"}) - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("tx_meta", routing, + session_args=("w",), + tx_kwargs={"txMeta": {"akey": "aval"}}) + @driver_feature(types.Feature.BOLT_4_4) def test_timeout(self): - self._start_server("timeout.script") - self._run("w", timeout=17) - self._server.done() + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("timeout", routing, + session_args=("w",), tx_kwargs={"timeout": 17}) - def test_combined(self): - self._start_server("combined.script") - self._run("r", params={"p": types.CypherInt(1)}, bookmarks=["b0"], - tx_meta={"k": "v"}, timeout=11) - self._server.done() + @driver_feature(types.Feature.BOLT_4_4) + def test_database(self): + for routing in (True, False)[1:]: + with self.subTest("routing" if routing else "direct"): + self._run("adb", routing, + session_args=("w",), + session_kwargs={"database": "adb"}) + + @driver_feature(types.Feature.IMPERSONATION, + types.Feature.BOLT_4_4) + def test_impersonation(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("imp_user", routing, + session_args=("w",), + session_kwargs={ + "impersonatedUser": "that-other-dude" + }) + @driver_feature(types.Feature.IMPERSONATION, + types.Feature.BOLT_4_4) + def test_impersonation_fails_on_v4x3(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + with self.assertRaises(types.DriverError) as exc: + self._run("imp_user_v4x3", routing, + session_args=("w",), + session_kwargs={ + "impersonatedUser": "that-other-dude" + }) + if self._driver_name in ["python"]: + self.assertEqual( + exc.exception.errorType, + "" + ) + self.assertIn("that-other-dude", exc.exception.msg) + if self._driver_name in ["java"]: + self.assertEqual( + exc.exception.errorType, + "org.neo4j.driver.exceptions.ClientException" + ) + + @driver_feature(types.Feature.IMPERSONATION, + types.Feature.BOLT_4_4) + def test_combined(self): + for routing in (True, False): + with self.subTest("routing" if routing else "direct"): + self._run("combined", routing, + session_args=("r",), + run_kwargs={"params": {"p": types.CypherInt(1)}}, + session_kwargs={ + "bookmarks": ["b0"], + "database": "adb", + "impersonatedUser": "that-other-dude" + }, + tx_kwargs={"txMeta": {"k": "v"}, "timeout": 11})