diff --git a/boltstub/bolt_protocol.py b/boltstub/bolt_protocol.py index d918101fd..808d41a35 100644 --- a/boltstub/bolt_protocol.py +++ b/boltstub/bolt_protocol.py @@ -16,8 +16,7 @@ def get_bolt_protocol(version): if version is None: raise BoltMissingVersionError() for sub in recursive_subclasses(BoltProtocol): - if (version == sub.protocol_version - or version in sub.version_aliases): + if version == sub.protocol_version or version in sub.version_aliases: return sub raise BoltUnknownVersionError( "unsupported bolt version {}".format(version) @@ -371,3 +370,13 @@ class Bolt4x4Protocol(Bolt4x3Protocol): equivalent_versions = set() server_agent = "Neo4j/4.4.0" + + +class Bolt5x0Protocol(Bolt4x4Protocol): + + protocol_version = (5, 0) + version_aliases = set() + # allow the server to negotiate other bolt versions + equivalent_versions = set() + + server_agent = "Neo4j/5.0.0" diff --git a/nutkit/protocol/feature.py b/nutkit/protocol/feature.py index aa7078fb3..e335ad78c 100644 --- a/nutkit/protocol/feature.py +++ b/nutkit/protocol/feature.py @@ -47,8 +47,6 @@ class Feature(Enum): AUTH_KERBEROS = "Feature:Auth:Kerberos" # The driver supports Bolt protocol version 3 BOLT_3_0 = "Feature:Bolt:3.0" - # The driver supports Bolt protocol version 4.0 - BOLT_4_0 = "Feature:Bolt:4.0" # The driver supports Bolt protocol version 4.1 BOLT_4_1 = "Feature:Bolt:4.1" # The driver supports Bolt protocol version 4.2 @@ -57,6 +55,8 @@ class Feature(Enum): BOLT_4_3 = "Feature:Bolt:4.3" # The driver supports Bolt protocol version 4.4 BOLT_4_4 = "Feature:Bolt:4.4" + # The driver supports Bolt protocol version 5.0 + BOLT_5_0 = "Feature:Bolt:5.0" # The driver supports impersonation IMPERSONATION = "Feature:Impersonation" # The driver supports TLS 1.1 connections. diff --git a/tests/neo4j/shared.py b/tests/neo4j/shared.py index b0f663cbc..aad463a5a 100644 --- a/tests/neo4j/shared.py +++ b/tests/neo4j/shared.py @@ -140,7 +140,7 @@ def get_valid_test_case(*args, **kwargs): return args[0] @wraps(func) - @requires_min_bolt_version(protocol.Feature.BOLT_4_0) + @requires_min_bolt_version("4.0") def wrapper(*args, **kwargs): test_case = get_valid_test_case(*args, **kwargs) if not get_server_info().supports_multi_db: @@ -149,13 +149,7 @@ def wrapper(*args, **kwargs): return wrapper -def requires_min_bolt_version(feature): - if not isinstance(feature, protocol.Feature): - raise TypeError("The arguments must be instances of Feature") - if not feature.name.startswith("BOLT_"): - raise ValueError("Bolt version feature expected") - - min_version = feature.value.split(":")[-1] +def requires_min_bolt_version(min_version): server_max_version = get_server_info().max_protocol_version all_viable_versions = [ f for f in protocol.Feature diff --git a/tests/stub/authorization/scripts/v4x0/reader_return_1_failure_return_2_3_4_and_5_succeed.script b/tests/stub/authorization/scripts/v4x4/reader_return_1_failure_return_2_3_4_and_5_succeed.script similarity index 100% rename from tests/stub/authorization/scripts/v4x0/reader_return_1_failure_return_2_3_4_and_5_succeed.script rename to tests/stub/authorization/scripts/v4x4/reader_return_1_failure_return_2_3_4_and_5_succeed.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx.script b/tests/stub/authorization/scripts/v5x0/reader_tx.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx.script rename to tests/stub/authorization/scripts/v5x0/reader_tx.script diff --git a/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_begin.script b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_begin.script new file mode 100644 index 000000000..a10b6540e --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_begin.script @@ -0,0 +1,7 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r", "db": "adb"} +S: FAILURE #ERROR# +S: diff --git a/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_commit.script b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_commit.script new file mode 100644 index 000000000..7379f37aa --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_commit.script @@ -0,0 +1,13 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +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: FAILURE #ERROR# + diff --git a/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_commit_with_pull_or_discard.script b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_commit_with_pull_or_discard.script new file mode 100644 index 000000000..f670277a2 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_commit_with_pull_or_discard.script @@ -0,0 +1,18 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +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: DISCARD {"n": -1} + S: SUCCESS {} +}} +C: COMMIT +S: FAILURE #ERROR# + diff --git a/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_pull.script b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_pull.script new file mode 100644 index 000000000..942532446 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_pull.script @@ -0,0 +1,10 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +C: BEGIN {"mode": "r", "db": "adb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: FAILURE #ERROR# + diff --git a/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_rollback_with_pull_or_discard.script b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_rollback_with_pull_or_discard.script new file mode 100644 index 000000000..0a684e9ff --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_rollback_with_pull_or_discard.script @@ -0,0 +1,18 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +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: DISCARD {"n": -1} + S: SUCCESS {} +}} +C: ROLLBACK +S: FAILURE #ERROR# + diff --git a/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_run.script b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_run.script new file mode 100644 index 000000000..7c505d74c --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_tx_yielding_error_on_run.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"mode": "r", "db": "adb"} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: FAILURE #ERROR# + diff --git a/tests/stub/authorization/scripts/v5x0/reader_yielding_error_on_pull.script b/tests/stub/authorization/scripts/v5x0/reader_yielding_error_on_pull.script new file mode 100644 index 000000000..7f5e85f62 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/reader_yielding_error_on_pull.script @@ -0,0 +1,9 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +C: RUN "RETURN 1 as n" {} {"mode": "r", "db": "adb"} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: FAILURE #ERROR# + diff --git a/tests/stub/authorization/scripts/v5x0/router.script b/tests/stub/authorization/scripts/v5x0/router.script new file mode 100644 index 000000000..6a710ba0d --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/router.script @@ -0,0 +1,7 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +C: ROUTE #ROUTINGCTX# [] {"{}": {"db": "adb"}} +S: SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010", "#HOST#:9011"], "role":"READ"}, {"addresses": ["#HOST#:9020"], "role":"WRITE"}]}} +?: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_basic.script b/tests/stub/authorization/scripts/v5x0/scheme_basic.script new file mode 100644 index 000000000..86cb639b3 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_basic.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "basic", "principal": "neo4j", "credentials": "pass", "[routing]": null, "[realm]": ""} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_basic_minimal.script b/tests/stub/authorization/scripts/v5x0/scheme_basic_minimal.script new file mode 100644 index 000000000..b0063df35 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_basic_minimal.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "basic", "principal": "neo4j", "credentials": "pass"} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_basic_realm_foobar.script b/tests/stub/authorization/scripts/v5x0/scheme_basic_realm_foobar.script new file mode 100644 index 000000000..99f058f78 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_basic_realm_foobar.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "basic", "principal": "neo4j", "credentials": "pass", "realm": "foobar", "[routing]": null} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_basic_realm_foobar_minimal.script b/tests/stub/authorization/scripts/v5x0/scheme_basic_realm_foobar_minimal.script new file mode 100644 index 000000000..4938b5ca9 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_basic_realm_foobar_minimal.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "basic", "principal": "neo4j", "credentials": "pass", "realm": "foobar"} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_bearer.script b/tests/stub/authorization/scripts/v5x0/scheme_bearer.script new file mode 100644 index 000000000..78da58917 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_bearer.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "bearer", "credentials": "QmFuYW5hIQ==", "[principal]": "", "[routing]": null, "[realm]": ""} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_bearer_minimal.script b/tests/stub/authorization/scripts/v5x0/scheme_bearer_minimal.script new file mode 100644 index 000000000..6a4f355bd --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_bearer_minimal.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "bearer", "credentials": "QmFuYW5hIQ=="} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_custom.script b/tests/stub/authorization/scripts/v5x0/scheme_custom.script new file mode 100644 index 000000000..512d66885 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_custom.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "wild-scheme", "principal": "I See Something", "credentials": "You Don't See!", "realm": "And it's blue.", "parameters": {"sky?": "no", "my eyes": 0.1, "da be dee da be daa?": true}, "[routing]": null} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_custom_empty.script b/tests/stub/authorization/scripts/v5x0/scheme_custom_empty.script new file mode 100644 index 000000000..6ec20239c --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_custom_empty.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "minimal-scheme", "principal": "", "[credentials]": "", "[realm]": "", "[parameters]": {}, "[routing]": null} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_custom_empty_minimal.script b/tests/stub/authorization/scripts/v5x0/scheme_custom_empty_minimal.script new file mode 100644 index 000000000..e346e397f --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_custom_empty_minimal.script @@ -0,0 +1,14 @@ +!: BOLT 5.0 + +# Server versions pre 4.4 require empty principal field. So for backwards compatibility, we expect the driver to send it. +A: HELLO {"user_agent": "*", "scheme": "minimal-scheme", "principal": ""} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_custom_minimal.script b/tests/stub/authorization/scripts/v5x0/scheme_custom_minimal.script new file mode 100644 index 000000000..447ca498f --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_custom_minimal.script @@ -0,0 +1,13 @@ +!: BOLT 5.0 + +A: HELLO {"user_agent": "*", "scheme": "wild-scheme", "principal": "I See Something", "credentials": "You Don't See!", "realm": "And it's blue.", "parameters": {"sky?": "no", "my eyes": 0.1, "da be dee da be daa?": true}} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_kerberos.script b/tests/stub/authorization/scripts/v5x0/scheme_kerberos.script new file mode 100644 index 000000000..2212537b0 --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_kerberos.script @@ -0,0 +1,14 @@ +!: BOLT 5.0 + +# Server versions pre 4.4 require empty principal field. So for backwards compatibility, we expect the driver to send it. +A: HELLO {"user_agent": "*", "scheme": "kerberos", "principal": "", "credentials": "QmFuYW5hIQ==", "[routing]": null, "[realm]": ""} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/scripts/v5x0/scheme_kerberos_minimal.script b/tests/stub/authorization/scripts/v5x0/scheme_kerberos_minimal.script new file mode 100644 index 000000000..96985430f --- /dev/null +++ b/tests/stub/authorization/scripts/v5x0/scheme_kerberos_minimal.script @@ -0,0 +1,14 @@ +!: BOLT 5.0 + +# Server versions pre 4.4 require empty principal field. So for backwards compatibility, we expect the driver to send it. +A: HELLO {"user_agent": "*", "scheme": "kerberos", "principal": "", "credentials": "QmFuYW5hIQ=="} +*: RESET + +C: RUN "RETURN 1 AS n" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL "*" +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET +?: GOODBYE diff --git a/tests/stub/authorization/test_authorization.py b/tests/stub/authorization/test_authorization.py index bb5f38b63..7560b19d4 100644 --- a/tests/stub/authorization/test_authorization.py +++ b/tests/stub/authorization/test_authorization.py @@ -518,10 +518,10 @@ def work(tx): with self.assertRaises(types.DriverError) as exc: session.read_transaction(work, hooks={ "on_send_RetryableNegative": lambda _: - self.switch_unused_servers( - (self._read_server1, self._read_server2), - "reader_tx.script" - ) + self.switch_unused_servers( + (self._read_server1, self._read_server2), + "reader_tx.script" + ) }) self.assert_is_token_error(exc.exception) session.close() @@ -607,10 +607,10 @@ def work(tx): with self.assertRaises(types.DriverError) as exc: session.read_transaction(work, hooks={ "on_send_RetryableNegative": lambda _: - self.switch_unused_servers( - (self._read_server1, self._read_server2), - "reader_tx.script" - ) + self.switch_unused_servers( + (self._read_server1, self._read_server2), + "reader_tx.script" + ) }) self.assert_is_token_error(exc.exception) session.close() @@ -697,10 +697,10 @@ def work(tx): with self.assertRaises(types.DriverError) as exc: session.read_transaction(work, hooks={ "on_send_RetryableNegative": lambda _: - self.switch_unused_servers( - (self._read_server1, self._read_server2), - "reader_tx.script" - ) + self.switch_unused_servers( + (self._read_server1, self._read_server2), + "reader_tx.script" + ) }) self.assert_is_token_error(exc.exception) session.close() @@ -723,7 +723,6 @@ def work(tx): class TestAuthorizationV4x1(TestAuthorizationV4x3): - required_features = types.Feature.BOLT_4_1, def get_vars(self, host=None): @@ -737,6 +736,21 @@ def get_vars(self, host=None): } +class TestAuthorizationV5x0(TestAuthorizationV4x3): + + required_features = types.Feature.BOLT_5_0, + + def get_vars(self, host=None): + if host is None: + host = self._routing_server1.host + return { + "#VERSION#": "5.0", + "#HOST#": host, + "#ROUTINGMODE#": '"mode": "r", ', + "#ROUTINGCTX#": '{"address": "' + host + ':9000"}' + } + + class TestAuthorizationV3(TestAuthorizationV4x3): required_features = types.Feature.BOLT_3_0, @@ -755,7 +769,7 @@ def get_db(self): class TestNoRoutingAuthorization(AuthorizationBase): - required_features = types.Feature.BOLT_4_0, + required_features = types.Feature.BOLT_4_4, def setUp(self): super().setUp() @@ -772,7 +786,7 @@ def tearDown(self): def get_vars(self): return { - "#VERSION#": "4.0" + "#VERSION#": "4.4" } @driver_feature(types.Feature.OPT_AUTHORIZATION_EXPIRED_TREATMENT) @@ -812,8 +826,7 @@ def test_should_drop_connection_after_AuthorizationExpired(self): # noqa: N802, driver.close() @driver_feature(types.Feature.OPT_AUTHORIZATION_EXPIRED_TREATMENT) - def test_should_be_able_to_use_current_sessions_after_AuthorizationExpired( # noqa: N802,E501 - self): + def test_should_be_able_to_use_current_sessions_after_AuthorizationExpired(self): # noqa: N802,E501 self.start_server( self._server, "reader_return_1_failure_return_2_3_4_and_5_succeed.script" @@ -837,8 +850,7 @@ def test_should_be_able_to_use_current_sessions_after_AuthorizationExpired( # n session1.close() @driver_feature(types.Feature.OPT_AUTHORIZATION_EXPIRED_TREATMENT) - def test_should_be_able_to_use_current_tx_after_AuthorizationExpired( # noqa: N802,E501 - self): + def test_should_be_able_to_use_current_tx_after_AuthorizationExpired(self): # noqa: N802,E501 self.start_server( self._server, "reader_return_1_failure_return_2_3_4_and_5_succeed.script" diff --git a/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_two_write_tx.script b/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_two_write_tx.script index 47cfc03b8..3bcb338d1 100644 --- a/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_two_write_tx.script +++ b/tests/stub/bookmarks/scripts/v4/send_and_receive_bookmark_two_write_tx.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_read_tx.script b/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_read_tx.script new file mode 100644 index 000000000..7fc2a5308 --- /dev/null +++ b/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_read_tx.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"bookmarks": ["neo4j:bookmark:v1:tx42"], "mode": "r"} +S: SUCCESS {} +C: RUN "MATCH (n) RETURN n.name AS name" {} {} +S: SUCCESS {"fields": ["name"]} +C: PULL {"n": 1000} +S: SUCCESS {} +C: COMMIT +S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_two_write_tx.script b/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_two_write_tx.script new file mode 100644 index 000000000..3bcb338d1 --- /dev/null +++ b/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_two_write_tx.script @@ -0,0 +1,23 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"bookmarks": ["neo4j:bookmark:v1:tx42"]} +S: SUCCESS {} +C: RUN "MATCH (n) RETURN n.name AS name" {} {} +S: SUCCESS {"fields": ["name"]} +C: PULL {"n": 1000} +S: SUCCESS {} +C: COMMIT +S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} +*: RESET +C: BEGIN {"bookmarks": ["neo4j:bookmark:v1:tx4242"]} +S: SUCCESS {} +C: RUN "MATCH (n) RETURN n.name AS name" {} {} +S: SUCCESS {"fields": ["name"]} +C: PULL {"n": 1000} +S: SUCCESS {} +C: COMMIT +S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx424242"} +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_write_tx.script b/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_write_tx.script new file mode 100644 index 000000000..586d50b5a --- /dev/null +++ b/tests/stub/bookmarks/scripts/v5/send_and_receive_bookmark_write_tx.script @@ -0,0 +1,14 @@ +!: BOLT 4.4 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {"bookmarks{}": #BOOKMARKS# } +S: SUCCESS {} +C: RUN "MATCH (n) RETURN n.name AS name" {} {} +S: SUCCESS {"fields": ["name"]} +C: PULL {"n": 1000} +S: SUCCESS {} +C: COMMIT +S: SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"} +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/scripts/v5/send_bookmark_write_tx.script b/tests/stub/bookmarks/scripts/v5/send_bookmark_write_tx.script new file mode 100644 index 000000000..9a93c6085 --- /dev/null +++ b/tests/stub/bookmarks/scripts/v5/send_bookmark_write_tx.script @@ -0,0 +1,14 @@ +!: BOLT 5.0 + +A: HELLO {"{}": "*"} +*: RESET +C: BEGIN {} +S: SUCCESS {} +C: RUN "RETURN 1 as n" {} {} +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": 1000} +S: SUCCESS {"type": "w"} +C: COMMIT +S: SUCCESS {"bookmark": "bm"} +*: RESET +?: GOODBYE diff --git a/tests/stub/bookmarks/test_bookmarks_v4.py b/tests/stub/bookmarks/test_bookmarks_v4.py index 3caf4f448..b6f878118 100644 --- a/tests/stub/bookmarks/test_bookmarks_v4.py +++ b/tests/stub/bookmarks/test_bookmarks_v4.py @@ -11,7 +11,7 @@ # Tests bookmarks from transaction class TestBookmarksV4(TestkitTestCase): - required_features = types.Feature.BOLT_4_0, + required_features = types.Feature.BOLT_4_4, version_dir = "v4" diff --git a/tests/stub/bookmarks/test_bookmarks_v5.py b/tests/stub/bookmarks/test_bookmarks_v5.py new file mode 100644 index 000000000..2ed4ec954 --- /dev/null +++ b/tests/stub/bookmarks/test_bookmarks_v5.py @@ -0,0 +1,154 @@ +from nutkit import protocol as types +from nutkit.frontend import Driver +from nutkit.protocol import AuthorizationToken +from tests.shared import TestkitTestCase +from tests.stub.shared import StubServer + + +# Tests bookmarks from transaction +class TestBookmarksV5(TestkitTestCase): + + required_features = types.Feature.BOLT_5_0, + + version_dir = "v5" + + def setUp(self): + super().setUp() + self._server = StubServer(9001) + uri = "bolt://%s" % self._server.address + auth = AuthorizationToken("basic", principal="", credentials="") + self._driver = Driver(self._backend, uri, auth) + + 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() + super().tearDown() + + def test_bookmarks_can_be_set(self): + def test(): + bookmarks = ["bm:%i" % (i + 1) for i in range(bm_count)] + session = self._driver.session(mode[0], bookmarks=bookmarks) + self.assertEqual(session.last_bookmarks(), bookmarks) + session.close() + + for mode in ("read", "write"): + # TODO: decide what we expect to happen when multiple bookmarks are + # passed in: return all or only the last one? + for bm_count in (0, 1): + with self.subTest(mode + "_%i_bookmarks" % bm_count): + test() + + # Tests that a committed transaction can return the last bookmark + def test_last_bookmark(self): + self._server.start( + path=self.script_path(self.version_dir, + "send_bookmark_write_tx.script") + ) + session = self._driver.session("w") + tx = session.begin_transaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + bookmarks = session.last_bookmarks() + session.close() + self._driver.close() + self._server.done() + + self.assertEqual(bookmarks, ["bm"]) + + def test_send_and_receive_bookmarks_read_tx(self): + self._server.start( + path=self.script_path(self.version_dir, + "send_and_receive_bookmark_read_tx.script") + ) + session = self._driver.session( + access_mode="r", + bookmarks=["neo4j:bookmark:v1:tx42"] + ) + tx = session.begin_transaction() + result = tx.run("MATCH (n) RETURN n.name AS name") + result.next() + tx.commit() + bookmarks = session.last_bookmarks() + + self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) + self._server.done() + + def test_send_and_receive_bookmarks_write_tx(self): + self._server.start( + path=self.script_path(self.version_dir, + "send_and_receive_bookmark_write_tx.script"), + vars_={ + "#BOOKMARKS#": '["neo4j:bookmark:v1:tx42"]' + } + ) + session = self._driver.session( + access_mode="w", + bookmarks=["neo4j:bookmark:v1:tx42"] + ) + tx = session.begin_transaction() + result = tx.run("MATCH (n) RETURN n.name AS name") + result.next() + tx.commit() + bookmarks = session.last_bookmarks() + + self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) + self._server.done() + + def test_sequence_of_writing_and_reading_tx(self): + self._server.start( + path=self.script_path( + self.version_dir, + "send_and_receive_bookmark_two_write_tx.script" + ) + ) + session = self._driver.session( + access_mode="w", + bookmarks=["neo4j:bookmark:v1:tx42"] + ) + tx = session.begin_transaction() + result = tx.run("MATCH (n) RETURN n.name AS name") + result.next() + tx.commit() + + bookmarks = session.last_bookmarks() + self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) + + tx_read = session.begin_transaction() + result = tx_read.run("MATCH (n) RETURN n.name AS name") + result.next() + tx_read.commit() + + bookmarks = session.last_bookmarks() + self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx424242"]) + + self._server.done() + + def test_send_and_receive_multiple_bookmarks_write_tx(self): + self._server.start( + path=self.script_path(self.version_dir, + "send_and_receive_bookmark_write_tx.script"), + vars_={ + "#BOOKMARKS#": '["neo4j:bookmark:v1:tx42", ' + '"neo4j:bookmark:v1:tx43", ' + '"neo4j:bookmark:v1:tx44", ' + '"neo4j:bookmark:v1:tx45", ' + '"neo4j:bookmark:v1:tx46"] ' + } + ) + session = self._driver.session( + access_mode="w", + bookmarks=[ + "neo4j:bookmark:v1:tx42", "neo4j:bookmark:v1:tx43", + "neo4j:bookmark:v1:tx44", "neo4j:bookmark:v1:tx45", + "neo4j:bookmark:v1:tx46" + ] + ) + tx = session.begin_transaction() + result = tx.run("MATCH (n) RETURN n.name AS name") + result.next() + tx.commit() + bookmarks = session.last_bookmarks() + + self.assertEqual(bookmarks, ["neo4j:bookmark:v1:tx4242"]) + self._server.done() diff --git a/tests/stub/configuration_hints/scripts/1_second_exceeds.script b/tests/stub/configuration_hints/scripts/1_second_exceeds.script index e444d8630..d33027527 100644 --- a/tests/stub/configuration_hints/scripts/1_second_exceeds.script +++ b/tests/stub/configuration_hints/scripts/1_second_exceeds.script @@ -1,8 +1,8 @@ -!: BOLT 4.3 +!: BOLT 4.4 !: ALLOW RESTART C: HELLO {"{}": "*"} -S: SUCCESS {"server": "Neo4j/4.3.1", "hints": {"connection.recv_timeout_seconds": 1}, "connection_id": "bolt-3"} +S: SUCCESS {"server": "Neo4j/4.4.1", "hints": {"connection.recv_timeout_seconds": 1}, "connection_id": "bolt-3"} ?: RESET {{ C: RUN "timeout" "*" "*" diff --git a/tests/stub/configuration_hints/scripts/1_second_exceeds_tx.script b/tests/stub/configuration_hints/scripts/1_second_exceeds_tx.script index 331fe678f..faff75d38 100644 --- a/tests/stub/configuration_hints/scripts/1_second_exceeds_tx.script +++ b/tests/stub/configuration_hints/scripts/1_second_exceeds_tx.script @@ -1,8 +1,8 @@ -!: BOLT 4.3 +!: BOLT 4.4 !: ALLOW RESTART C: HELLO {"{}": "*"} -S: SUCCESS {"server": "Neo4j/4.3.1", "hints": {"connection.recv_timeout_seconds": 1}, "connection_id": "bolt-2"} +S: SUCCESS {"server": "Neo4j/4.4.1", "hints": {"connection.recv_timeout_seconds": 1}, "connection_id": "bolt-2"} ?: RESET C: BEGIN {} S: SUCCESS {} diff --git a/tests/stub/configuration_hints/scripts/1_second_exceeds_tx_retry.script b/tests/stub/configuration_hints/scripts/1_second_exceeds_tx_retry.script index 9cc2ca558..9b75e4f6b 100644 --- a/tests/stub/configuration_hints/scripts/1_second_exceeds_tx_retry.script +++ b/tests/stub/configuration_hints/scripts/1_second_exceeds_tx_retry.script @@ -1,8 +1,8 @@ -!: BOLT 4.3 +!: BOLT 4.4 !: ALLOW RESTART C: HELLO {"{}": "*"} -S: SUCCESS {"server": "Neo4j/4.3.1", "hints": {"connection.recv_timeout_seconds": 1}, "connection_id": "bolt-1"} +S: SUCCESS {"server": "Neo4j/4.4.1", "hints": {"connection.recv_timeout_seconds": 1}, "connection_id": "bolt-1"} C: BEGIN {} S: SUCCESS {} {{ diff --git a/tests/stub/configuration_hints/scripts/2_seconds_in_time.script b/tests/stub/configuration_hints/scripts/2_seconds_in_time.script index 7ee470b91..ce9784640 100644 --- a/tests/stub/configuration_hints/scripts/2_seconds_in_time.script +++ b/tests/stub/configuration_hints/scripts/2_seconds_in_time.script @@ -1,7 +1,7 @@ -!: BOLT 4.3 +!: BOLT 4.4 C: HELLO {"{}": "*"} -S: SUCCESS {"server": "Neo4j/4.3.1", "hints": {"connection.recv_timeout_seconds": 2}, "connection_id": "bolt-6"} +S: SUCCESS {"server": "Neo4j/4.4.1", "hints": {"connection.recv_timeout_seconds": 2}, "connection_id": "bolt-6"} ?: RESET C: RUN "*" {} {} S: 1 diff --git a/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx.script b/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx.script index 0381c56fb..2433a4dc3 100644 --- a/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx.script +++ b/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx.script @@ -1,7 +1,7 @@ -!: BOLT 4.3 +!: BOLT 4.4 C: HELLO {"{}": "*"} -S: SUCCESS {"server": "Neo4j/4.3.1", "hints": {"connection.recv_timeout_seconds": 2}, "connection_id": "bolt-5"} +S: SUCCESS {"server": "Neo4j/4.4.1", "hints": {"connection.recv_timeout_seconds": 2}, "connection_id": "bolt-5"} C: BEGIN {} S: 1 diff --git a/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx_retry.script b/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx_retry.script index 7b1f42b17..faeb75dd5 100644 --- a/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx_retry.script +++ b/tests/stub/configuration_hints/scripts/2_seconds_in_time_tx_retry.script @@ -1,7 +1,7 @@ -!: BOLT 4.3 +!: BOLT 4.4 C: HELLO {"{}": "*"} -S: SUCCESS {"server": "Neo4j/4.3.1", "hints": {"connection.recv_timeout_seconds": 2}, "connection_id": "bolt-4"} +S: SUCCESS {"server": "Neo4j/4.4.1", "hints": {"connection.recv_timeout_seconds": 2}, "connection_id": "bolt-4"} C: BEGIN {} S: 1 diff --git a/tests/stub/configuration_hints/scripts/router.script b/tests/stub/configuration_hints/scripts/router.script index 2a0943d1b..448978eed 100644 --- a/tests/stub/configuration_hints/scripts/router.script +++ b/tests/stub/configuration_hints/scripts/router.script @@ -1,4 +1,4 @@ -!: BOLT 4.3 +!: BOLT 4.4 !: AUTO RESET !: ALLOW RESTART diff --git a/tests/stub/configuration_hints/test_connection_recv_timeout_seconds.py b/tests/stub/configuration_hints/test_connection_recv_timeout_seconds.py index d3389d7d2..42f9b797f 100644 --- a/tests/stub/configuration_hints/test_connection_recv_timeout_seconds.py +++ b/tests/stub/configuration_hints/test_connection_recv_timeout_seconds.py @@ -13,7 +13,7 @@ class TestDirectConnectionRecvTimeout(TestkitTestCase): required_features = ( - types.Feature.BOLT_4_3, + types.Feature.BOLT_4_4, types.Feature.CONF_HINT_CON_RECV_TIMEOUT ) diff --git a/tests/stub/disconnects/scripts/exit_after_hello.script b/tests/stub/disconnects/scripts/exit_after_hello.script index 7a8b490fa..2a166d28d 100644 --- a/tests/stub/disconnects/scripts/exit_after_hello.script +++ b/tests/stub/disconnects/scripts/exit_after_hello.script @@ -1,4 +1,4 @@ -!: BOLT 4.3 +!: BOLT 4.4 C: HELLO {"user_agent": "Modesty", "scheme": "basic", "principal": "neo4j", "credentials": "pass" #EXTRA_HELLO_PARAMS# } S: diff --git a/tests/stub/disconnects/scripts/exit_after_hello_success.script b/tests/stub/disconnects/scripts/exit_after_hello_success.script index 4a3f28b64..1900a649e 100644 --- a/tests/stub/disconnects/scripts/exit_after_hello_success.script +++ b/tests/stub/disconnects/scripts/exit_after_hello_success.script @@ -1,4 +1,4 @@ -!: BOLT 4.3 +!: BOLT 4.4 C: HELLO {"user_agent": "Modesty", "scheme": "basic", "principal": "neo4j", "credentials": "pass" #EXTRA_HELLO_PARAMS# } S: SUCCESS {"server": "Neo4j/4.3.0", "connection_id": "bolt-0"} diff --git a/tests/stub/disconnects/scripts/exit_after_pull.script b/tests/stub/disconnects/scripts/exit_after_pull.script index 776bd8ac8..ca4ccd5f1 100644 --- a/tests/stub/disconnects/scripts/exit_after_pull.script +++ b/tests/stub/disconnects/scripts/exit_after_pull.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/exit_after_record.script b/tests/stub/disconnects/scripts/exit_after_record.script index 06212713c..83b46f392 100644 --- a/tests/stub/disconnects/scripts/exit_after_record.script +++ b/tests/stub/disconnects/scripts/exit_after_record.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/exit_after_run.script b/tests/stub/disconnects/scripts/exit_after_run.script index a396b0b54..e719b4786 100644 --- a/tests/stub/disconnects/scripts/exit_after_run.script +++ b/tests/stub/disconnects/scripts/exit_after_run.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 !: AUTO RESET A: HELLO {"{}": "*"} diff --git a/tests/stub/disconnects/scripts/exit_after_tx_begin.script b/tests/stub/disconnects/scripts/exit_after_tx_begin.script index 5891a0349..ee20f9da2 100644 --- a/tests/stub/disconnects/scripts/exit_after_tx_begin.script +++ b/tests/stub/disconnects/scripts/exit_after_tx_begin.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 !: AUTO RESET A: HELLO {"{}": "*"} diff --git a/tests/stub/disconnects/scripts/exit_after_tx_commit.script b/tests/stub/disconnects/scripts/exit_after_tx_commit.script index 78afbe4e0..2e17c6f23 100644 --- a/tests/stub/disconnects/scripts/exit_after_tx_commit.script +++ b/tests/stub/disconnects/scripts/exit_after_tx_commit.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/exit_after_tx_pull.script b/tests/stub/disconnects/scripts/exit_after_tx_pull.script index 742b88648..08e28ff59 100644 --- a/tests/stub/disconnects/scripts/exit_after_tx_pull.script +++ b/tests/stub/disconnects/scripts/exit_after_tx_pull.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/exit_after_tx_record.script b/tests/stub/disconnects/scripts/exit_after_tx_record.script index d30f89983..25bb4196d 100644 --- a/tests/stub/disconnects/scripts/exit_after_tx_record.script +++ b/tests/stub/disconnects/scripts/exit_after_tx_record.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/exit_after_tx_run.script b/tests/stub/disconnects/scripts/exit_after_tx_run.script index a5c236fb6..b08e4ab3b 100644 --- a/tests/stub/disconnects/scripts/exit_after_tx_run.script +++ b/tests/stub/disconnects/scripts/exit_after_tx_run.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/explicit_goodbye_after_run.script b/tests/stub/disconnects/scripts/explicit_goodbye_after_run.script index 7d7bc36dc..347442543 100644 --- a/tests/stub/disconnects/scripts/explicit_goodbye_after_run.script +++ b/tests/stub/disconnects/scripts/explicit_goodbye_after_run.script @@ -1,4 +1,4 @@ -!: BOLT 4.3 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/disconnects/scripts/failure_on_reset_after_success.script b/tests/stub/disconnects/scripts/failure_on_reset_after_success.script index 6813e3492..a9c520dbe 100644 --- a/tests/stub/disconnects/scripts/failure_on_reset_after_success.script +++ b/tests/stub/disconnects/scripts/failure_on_reset_after_success.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 !: AUTO RESET A: HELLO {"{}": "*"} diff --git a/tests/stub/disconnects/test_disconnects.py b/tests/stub/disconnects/test_disconnects.py index 729edcac4..2de78d179 100644 --- a/tests/stub/disconnects/test_disconnects.py +++ b/tests/stub/disconnects/test_disconnects.py @@ -13,7 +13,7 @@ class TestDisconnects(TestkitTestCase): - required_features = types.Feature.BOLT_4_3, + required_features = types.Feature.BOLT_4_4, def setUp(self): super().setUp() diff --git a/tests/stub/iteration/_common.py b/tests/stub/iteration/_common.py index 6cbb287f8..c1985fc2b 100644 --- a/tests/stub/iteration/_common.py +++ b/tests/stub/iteration/_common.py @@ -21,7 +21,7 @@ def _session(self, script_fn, fetch_size=2, vars_=None): driver = Driver(self._backend, uri, types.AuthorizationToken(scheme="basic", principal="", credentials="")) - self._server.start(path=self.script_path("v4x0", script_fn), + self._server.start(path=self.script_path("v4x4", script_fn), vars_=vars_) try: session = driver.session("w", fetch_size=fetch_size) diff --git a/tests/stub/iteration/scripts/v4x0/disconnect_on_pull.script b/tests/stub/iteration/scripts/v4x4/disconnect_on_pull.script similarity index 91% rename from tests/stub/iteration/scripts/v4x0/disconnect_on_pull.script rename to tests/stub/iteration/scripts/v4x4/disconnect_on_pull.script index c3b904582..5f41eff87 100644 --- a/tests/stub/iteration/scripts/v4x0/disconnect_on_pull.script +++ b/tests/stub/iteration/scripts/v4x4/disconnect_on_pull.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/error_on_pull.script b/tests/stub/iteration/scripts/v4x4/error_on_pull.script similarity index 94% rename from tests/stub/iteration/scripts/v4x0/error_on_pull.script rename to tests/stub/iteration/scripts/v4x4/error_on_pull.script index 3316db804..f0da06729 100644 --- a/tests/stub/iteration/scripts/v4x0/error_on_pull.script +++ b/tests/stub/iteration/scripts/v4x4/error_on_pull.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_2_end_empty_batch.script b/tests/stub/iteration/scripts/v4x4/pull_2_end_empty_batch.script similarity index 96% rename from tests/stub/iteration/scripts/v4x0/pull_2_end_empty_batch.script rename to tests/stub/iteration/scripts/v4x4/pull_2_end_empty_batch.script index 873628669..0c926af36 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_2_end_empty_batch.script +++ b/tests/stub/iteration/scripts/v4x4/pull_2_end_empty_batch.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_2_end_error.script b/tests/stub/iteration/scripts/v4x4/pull_2_end_error.script similarity index 97% rename from tests/stub/iteration/scripts/v4x0/pull_2_end_error.script rename to tests/stub/iteration/scripts/v4x4/pull_2_end_error.script index e1bb60683..bd3c94270 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_2_end_error.script +++ b/tests/stub/iteration/scripts/v4x4/pull_2_end_error.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_2_end_full_batch.script b/tests/stub/iteration/scripts/v4x4/pull_2_end_full_batch.script similarity index 96% rename from tests/stub/iteration/scripts/v4x0/pull_2_end_full_batch.script rename to tests/stub/iteration/scripts/v4x4/pull_2_end_full_batch.script index fd7ac2402..e649c0d03 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_2_end_full_batch.script +++ b/tests/stub/iteration/scripts/v4x4/pull_2_end_full_batch.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_2_end_half_batch.script b/tests/stub/iteration/scripts/v4x4/pull_2_end_half_batch.script similarity index 96% rename from tests/stub/iteration/scripts/v4x0/pull_2_end_half_batch.script rename to tests/stub/iteration/scripts/v4x4/pull_2_end_half_batch.script index d8c83b7e4..e0ab2576e 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_2_end_half_batch.script +++ b/tests/stub/iteration/scripts/v4x4/pull_2_end_half_batch.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_2_then_discard.script b/tests/stub/iteration/scripts/v4x4/pull_2_then_discard.script similarity index 97% rename from tests/stub/iteration/scripts/v4x0/pull_2_then_discard.script rename to tests/stub/iteration/scripts/v4x4/pull_2_then_discard.script index 330dd41a1..a3d554e13 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_2_then_discard.script +++ b/tests/stub/iteration/scripts/v4x4/pull_2_then_discard.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_2_then_optimized_list.script b/tests/stub/iteration/scripts/v4x4/pull_2_then_optimized_list.script similarity index 98% rename from tests/stub/iteration/scripts/v4x0/pull_2_then_optimized_list.script rename to tests/stub/iteration/scripts/v4x4/pull_2_then_optimized_list.script index 59abc9fe0..94c4ac6f5 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_2_then_optimized_list.script +++ b/tests/stub/iteration/scripts/v4x4/pull_2_then_optimized_list.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_all.script b/tests/stub/iteration/scripts/v4x4/pull_all.script similarity index 95% rename from tests/stub/iteration/scripts/v4x0/pull_all.script rename to tests/stub/iteration/scripts/v4x4/pull_all.script index 01246536e..82ea32a32 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_all.script +++ b/tests/stub/iteration/scripts/v4x4/pull_all.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_all_slow_connection.script b/tests/stub/iteration/scripts/v4x4/pull_all_slow_connection.script similarity index 95% rename from tests/stub/iteration/scripts/v4x0/pull_all_slow_connection.script rename to tests/stub/iteration/scripts/v4x4/pull_all_slow_connection.script index 96167dc02..e5464236a 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_all_slow_connection.script +++ b/tests/stub/iteration/scripts/v4x4/pull_all_slow_connection.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_list.script b/tests/stub/iteration/scripts/v4x4/pull_list.script similarity index 96% rename from tests/stub/iteration/scripts/v4x0/pull_list.script rename to tests/stub/iteration/scripts/v4x4/pull_list.script index de5082c91..76e70e5ad 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_list.script +++ b/tests/stub/iteration/scripts/v4x4/pull_list.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/pull_optimized_list.script b/tests/stub/iteration/scripts/v4x4/pull_optimized_list.script similarity index 97% rename from tests/stub/iteration/scripts/v4x0/pull_optimized_list.script rename to tests/stub/iteration/scripts/v4x4/pull_optimized_list.script index 042214933..29e2e829b 100644 --- a/tests/stub/iteration/scripts/v4x0/pull_optimized_list.script +++ b/tests/stub/iteration/scripts/v4x4/pull_optimized_list.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_error_on_pull.script b/tests/stub/iteration/scripts/v4x4/tx_error_on_pull.script similarity index 97% rename from tests/stub/iteration/scripts/v4x0/tx_error_on_pull.script rename to tests/stub/iteration/scripts/v4x4/tx_error_on_pull.script index 06b9f567d..f21b8cc54 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_error_on_pull.script +++ b/tests/stub/iteration/scripts/v4x4/tx_error_on_pull.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_1_nested.script b/tests/stub/iteration/scripts/v4x4/tx_pull_1_nested.script similarity index 98% rename from tests/stub/iteration/scripts/v4x0/tx_pull_1_nested.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_1_nested.script index 76ce8db71..bfce30b22 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_1_nested.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_1_nested.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_2.script b/tests/stub/iteration/scripts/v4x4/tx_pull_2.script similarity index 96% rename from tests/stub/iteration/scripts/v4x0/tx_pull_2.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_2.script index f4c5397ce..45766fdf2 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_2.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_2.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_2_then_optimized_list.script b/tests/stub/iteration/scripts/v4x4/tx_pull_2_then_optimized_list.script similarity index 98% rename from tests/stub/iteration/scripts/v4x0/tx_pull_2_then_optimized_list.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_2_then_optimized_list.script index d2af57977..6d8e14398 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_2_then_optimized_list.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_2_then_optimized_list.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_all.script b/tests/stub/iteration/scripts/v4x4/tx_pull_all.script similarity index 95% rename from tests/stub/iteration/scripts/v4x0/tx_pull_all.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_all.script index 7ba185c84..1ed947950 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_all.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_all.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_all_slow_connection.script b/tests/stub/iteration/scripts/v4x4/tx_pull_all_slow_connection.script similarity index 95% rename from tests/stub/iteration/scripts/v4x0/tx_pull_all_slow_connection.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_all_slow_connection.script index a2a234a93..562c96641 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_all_slow_connection.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_all_slow_connection.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_list.script b/tests/stub/iteration/scripts/v4x4/tx_pull_list.script similarity index 96% rename from tests/stub/iteration/scripts/v4x0/tx_pull_list.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_list.script index 4665b5ea1..855f33a9a 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_list.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_list.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/tx_pull_optimized_list.script b/tests/stub/iteration/scripts/v4x4/tx_pull_optimized_list.script similarity index 97% rename from tests/stub/iteration/scripts/v4x0/tx_pull_optimized_list.script rename to tests/stub/iteration/scripts/v4x4/tx_pull_optimized_list.script index fd8ff963e..7a2bd31db 100644 --- a/tests/stub/iteration/scripts/v4x0/tx_pull_optimized_list.script +++ b/tests/stub/iteration/scripts/v4x4/tx_pull_optimized_list.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/yield_0_records.script b/tests/stub/iteration/scripts/v4x4/yield_0_records.script similarity index 93% rename from tests/stub/iteration/scripts/v4x0/yield_0_records.script rename to tests/stub/iteration/scripts/v4x4/yield_0_records.script index b1bed64ca..beecbeb9a 100644 --- a/tests/stub/iteration/scripts/v4x0/yield_0_records.script +++ b/tests/stub/iteration/scripts/v4x4/yield_0_records.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/yield_1_record.script b/tests/stub/iteration/scripts/v4x4/yield_1_record.script similarity index 93% rename from tests/stub/iteration/scripts/v4x0/yield_1_record.script rename to tests/stub/iteration/scripts/v4x4/yield_1_record.script index 72013eadc..7388acfbf 100644 --- a/tests/stub/iteration/scripts/v4x0/yield_1_record.script +++ b/tests/stub/iteration/scripts/v4x4/yield_1_record.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/scripts/v4x0/yield_2_records.script b/tests/stub/iteration/scripts/v4x4/yield_2_records.script similarity index 97% rename from tests/stub/iteration/scripts/v4x0/yield_2_records.script rename to tests/stub/iteration/scripts/v4x4/yield_2_records.script index 27dcb9cb5..94d084d43 100644 --- a/tests/stub/iteration/scripts/v4x0/yield_2_records.script +++ b/tests/stub/iteration/scripts/v4x4/yield_2_records.script @@ -1,4 +1,4 @@ -!: BOLT 4 +!: BOLT 4.4 A: HELLO {"{}": "*"} *: RESET diff --git a/tests/stub/iteration/test_iteration_session_run.py b/tests/stub/iteration/test_iteration_session_run.py index 1e7b5095b..f3cbfb6c9 100644 --- a/tests/stub/iteration/test_iteration_session_run.py +++ b/tests/stub/iteration/test_iteration_session_run.py @@ -19,7 +19,7 @@ def tearDown(self): super().tearDown() def _run(self, n, script_fn, expected_sequence, expected_error=False, - protocol_version="v4x0"): + protocol_version="v4x4"): uri = "bolt://%s" % self._server.address driver = Driver(self._backend, uri, types.AuthorizationToken("basic", principal="", @@ -44,29 +44,29 @@ def _run(self, n, script_fn, expected_sequence, expected_error=False, self.assertEqual(expected_error, got_error) # Last fetched batch is a full batch - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_full_batch(self): self._run(2, "pull_2_end_full_batch.script", ["1", "2", "3", "4", "5", "6"]) # Last fetched batch is half full (or more important not full) - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_half_batch(self): self._run(2, "pull_2_end_half_batch.script", ["1", "2", "3", "4", "5"]) # Last fetched batch is empty - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_empty_batch(self): self._run(2, "pull_2_end_empty_batch.script", ["1", "2", "3", "4"]) # Last batch returns an error - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_error(self): self._run(2, "pull_2_end_error.script", ["1", "2", "3", "4", "5"], expected_error=True) # Support -1, not batched at all - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_all(self): self._run(-1, "pull_all.script", ["1", "2", "3", "4", "5", "6"]) @@ -96,7 +96,7 @@ def test(version_, script_): self.assertEqual(self._server.count_requests("DISCARD"), 0) session.close() self._server.done() - if (version_ == "v4x0" + if (version_ == "v4x4" and get_driver_name() not in ["java", "javascript", "ruby"]): # assert only JAVA and JS pulls results eagerly. @@ -106,11 +106,11 @@ def test(version_, script_): self._server.reset() for version, script in (("v3", "pull_all_any_mode.script"), - ("v4x0", "pull_2_then_discard.script")): + ("v4x4", "pull_2_then_discard.script")): if not self.driver_supports_bolt(version): continue # TODO: remove this block once all drivers work - if version == "v4x0" and get_driver_name() in ["javascript"]: + if version == "v4x4" and get_driver_name() in ["javascript"]: # driver would eagerly pull all available results in the # background continue diff --git a/tests/stub/iteration/test_iteration_tx_run.py b/tests/stub/iteration/test_iteration_tx_run.py index dcf516240..4504e5783 100644 --- a/tests/stub/iteration/test_iteration_tx_run.py +++ b/tests/stub/iteration/test_iteration_tx_run.py @@ -19,7 +19,7 @@ def tearDown(self): super().tearDown() def _iterate(self, n, script_fn, expected_sequence, expected_error=False, - protocol_version="v4x0"): + protocol_version="v4x4"): uri = "bolt://%s" % self._server.address driver = Driver(self._backend, uri, types.AuthorizationToken("basic", principal="", @@ -45,15 +45,15 @@ def _iterate(self, n, script_fn, expected_sequence, expected_error=False, self.assertEqual(expected_sequence, sequence) self.assertEqual(expected_error, got_error) - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_batch(self): self._iterate(2, "tx_pull_2.script", [1, 2, 3]) - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_all(self): self._iterate(-1, "tx_pull_all.script", [1, 2, 3]) - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_all_slow_connection(self): self._iterate(-1, "tx_pull_all_slow_connection.script", [1, 2, 3]) @@ -68,7 +68,7 @@ def test_all_v3(self): self._iterate(-1, "tx_pull_all.script", [1, 2, 3], protocol_version="v3") - @driver_feature(types.Feature.BOLT_4_0) + @driver_feature(types.Feature.BOLT_4_4) def test_nested(self): # ex JAVA - java completely pulls the first query before running the # second @@ -80,7 +80,7 @@ def test_nested(self): driver = Driver(self._backend, uri, types.AuthorizationToken("basic", principal="", credentials="")) - self._server.start(path=self.script_path("v4x0", + self._server.start(path=self.script_path("v4x4", "tx_pull_1_nested.script")) session = driver.session("w", fetch_size=1) tx = session.begin_transaction() diff --git a/tests/stub/iteration/test_result_list.py b/tests/stub/iteration/test_result_list.py index 4f0e1c1c0..a7f0bc7f1 100644 --- a/tests/stub/iteration/test_result_list.py +++ b/tests/stub/iteration/test_result_list.py @@ -6,7 +6,7 @@ class TestResultList(IterationTestBase): - required_features = (types.Feature.BOLT_4_0, + required_features = (types.Feature.BOLT_4_4, types.Feature.API_RESULT_LIST) def _assert_connection_error(self, error): diff --git a/tests/stub/iteration/test_result_peek.py b/tests/stub/iteration/test_result_peek.py index b3235b1d5..c0f59f1bf 100644 --- a/tests/stub/iteration/test_result_peek.py +++ b/tests/stub/iteration/test_result_peek.py @@ -9,7 +9,7 @@ class TestResultPeek(IterationTestBase): - required_features = types.Feature.BOLT_4_0, + required_features = types.Feature.BOLT_4_4, def _assert_connection_error(self, error): self.assertIsInstance(error, types.DriverError) diff --git a/tests/stub/iteration/test_result_single.py b/tests/stub/iteration/test_result_single.py index 96f656a47..84a62cd28 100644 --- a/tests/stub/iteration/test_result_single.py +++ b/tests/stub/iteration/test_result_single.py @@ -9,7 +9,7 @@ class TestResultSingle(IterationTestBase): - required_features = types.Feature.BOLT_4_0, + required_features = types.Feature.BOLT_4_4, def _assert_not_exactly_one_record_error(self, error): self.assertIsInstance(error, types.DriverError) diff --git a/tests/stub/routing/scripts/v4x4/empty_reader.script b/tests/stub/routing/scripts/v5x0/empty_reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/empty_reader.script rename to tests/stub/routing/scripts/v5x0/empty_reader.script diff --git a/tests/stub/routing/scripts/v4x4/reader.script b/tests/stub/routing/scripts/v5x0/reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader.script rename to tests/stub/routing/scripts/v5x0/reader.script diff --git a/tests/stub/routing/scripts/v4x4/reader_default_db.script b/tests/stub/routing/scripts/v5x0/reader_default_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_default_db.script rename to tests/stub/routing/scripts/v5x0/reader_default_db.script diff --git a/tests/stub/routing/scripts/v5x0/reader_tx.script b/tests/stub/routing/scripts/v5x0/reader_tx.script new file mode 100644 index 000000000..b7f1a4a8d --- /dev/null +++ b/tests/stub/routing/scripts/v5x0/reader_tx.script @@ -0,0 +1,15 @@ +!: BOLT #VERSION# + +A: HELLO {"{}": "*"} +*: RESET +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 {} +*: RESET +?: GOODBYE diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_default_db.script b/tests/stub/routing/scripts/v5x0/reader_tx_default_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_default_db.script rename to tests/stub/routing/scripts/v5x0/reader_tx_default_db.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_with_bookmarks.script b/tests/stub/routing/scripts/v5x0/reader_tx_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_with_bookmarks.script rename to tests/stub/routing/scripts/v5x0/reader_tx_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_with_exit.script b/tests/stub/routing/scripts/v5x0/reader_tx_with_exit.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_with_exit.script rename to tests/stub/routing/scripts/v5x0/reader_tx_with_exit.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pull.script b/tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_pull.script rename to tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_pull.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_second_run.script b/tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_second_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_tx_with_unexpected_interruption_on_second_run.script rename to tests/stub/routing/scripts/v5x0/reader_tx_with_unexpected_interruption_on_second_run.script diff --git a/tests/stub/routing/scripts/v4x4/reader_with_bookmarks.script b/tests/stub/routing/scripts/v5x0/reader_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_with_bookmarks.script rename to tests/stub/routing/scripts/v5x0/reader_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/reader_with_explicit_hello.script b/tests/stub/routing/scripts/v5x0/reader_with_explicit_hello.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_with_explicit_hello.script rename to tests/stub/routing/scripts/v5x0/reader_with_explicit_hello.script diff --git a/tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v5x0/reader_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v5x0/reader_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v5x0/reader_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/reader_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v5x0/reader_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x4/router_adb.script b/tests/stub/routing/scripts/v5x0/router_adb.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_adb.script rename to tests/stub/routing/scripts/v5x0/router_adb.script diff --git a/tests/stub/routing/scripts/v4x4/router_adb_multi_no_bookmarks.script b/tests/stub/routing/scripts/v5x0/router_adb_multi_no_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_adb_multi_no_bookmarks.script rename to tests/stub/routing/scripts/v5x0/router_adb_multi_no_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/router_and_reader.script b/tests/stub/routing/scripts/v5x0/router_and_reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_and_reader.script rename to tests/stub/routing/scripts/v5x0/router_and_reader.script diff --git a/tests/stub/routing/scripts/v4x4/router_and_reader_with_empty_routing_context.script b/tests/stub/routing/scripts/v5x0/router_and_reader_with_empty_routing_context.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_and_reader_with_empty_routing_context.script rename to tests/stub/routing/scripts/v5x0/router_and_reader_with_empty_routing_context.script diff --git a/tests/stub/routing/scripts/v4x4/router_and_writer_with_sequential_access_and_bookmark.script b/tests/stub/routing/scripts/v5x0/router_and_writer_with_sequential_access_and_bookmark.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_and_writer_with_sequential_access_and_bookmark.script rename to tests/stub/routing/scripts/v5x0/router_and_writer_with_sequential_access_and_bookmark.script diff --git a/tests/stub/routing/scripts/v4x4/router_connectivity_db.script b/tests/stub/routing/scripts/v5x0/router_connectivity_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_connectivity_db.script rename to tests/stub/routing/scripts/v5x0/router_connectivity_db.script diff --git a/tests/stub/routing/scripts/v4x4/router_create_adb_with_bookmarks.script b/tests/stub/routing/scripts/v5x0/router_create_adb_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_create_adb_with_bookmarks.script rename to tests/stub/routing/scripts/v5x0/router_create_adb_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/router_default_db.script b/tests/stub/routing/scripts/v5x0/router_default_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_default_db.script rename to tests/stub/routing/scripts/v5x0/router_default_db.script diff --git a/tests/stub/routing/scripts/v4x4/router_system_then_adb_with_bookmarks.script b/tests/stub/routing/scripts/v5x0/router_system_then_adb_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_system_then_adb_with_bookmarks.script rename to tests/stub/routing/scripts/v5x0/router_system_then_adb_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/router_unreachable_db_then_adb.script b/tests/stub/routing/scripts/v5x0/router_unreachable_db_then_adb.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_unreachable_db_then_adb.script rename to tests/stub/routing/scripts/v5x0/router_unreachable_db_then_adb.script diff --git a/tests/stub/routing/scripts/v4x4/router_with_leader_change.script b/tests/stub/routing/scripts/v5x0/router_with_leader_change.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_with_leader_change.script rename to tests/stub/routing/scripts/v5x0/router_with_leader_change.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_any_security_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_any_security_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_any_security_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_any_security_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_authorization_expired_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_authorization_expired_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_authorization_expired_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_authorization_expired_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_db_not_found_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_db_not_found_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_db_not_found_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_db_not_found_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_empty_response_then_shuts_down.script b/tests/stub/routing/scripts/v5x0/router_yielding_empty_response_then_shuts_down.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_empty_response_then_shuts_down.script rename to tests/stub/routing/scripts/v5x0/router_yielding_empty_response_then_shuts_down.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_forbidden_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_forbidden_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_forbidden_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_forbidden_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_invalid_bookmark_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_invalid_bookmark_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_invalid_bookmark_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_invalid_bookmark_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_invalid_bookmark_mixture_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_invalid_bookmark_mixture_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_invalid_bookmark_mixture_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_invalid_bookmark_mixture_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_readers_any_db.script b/tests/stub/routing/scripts/v5x0/router_yielding_no_readers_any_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_no_readers_any_db.script rename to tests/stub/routing/scripts/v5x0/router_yielding_no_readers_any_db.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb.script b/tests/stub/routing/scripts/v5x0/router_yielding_no_writers_adb.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb.script rename to tests/stub/routing/scripts/v5x0/router_yielding_no_writers_adb.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb_sequentially.script b/tests/stub/routing/scripts/v5x0/router_yielding_no_writers_adb_sequentially.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_no_writers_adb_sequentially.script rename to tests/stub/routing/scripts/v5x0/router_yielding_no_writers_adb_sequentially.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_no_writers_any_db.script b/tests/stub/routing/scripts/v5x0/router_yielding_no_writers_any_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_no_writers_any_db.script rename to tests/stub/routing/scripts/v5x0/router_yielding_no_writers_any_db.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_procedure_not_found_failure_connectivity_db.script b/tests/stub/routing/scripts/v5x0/router_yielding_procedure_not_found_failure_connectivity_db.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_procedure_not_found_failure_connectivity_db.script rename to tests/stub/routing/scripts/v5x0/router_yielding_procedure_not_found_failure_connectivity_db.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_reader1_and_exit.script b/tests/stub/routing/scripts/v5x0/router_yielding_reader1_and_exit.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_reader1_and_exit.script rename to tests/stub/routing/scripts/v5x0/router_yielding_reader1_and_exit.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_reader2_adb.script b/tests/stub/routing/scripts/v5x0/router_yielding_reader2_adb.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_reader2_adb.script rename to tests/stub/routing/scripts/v5x0/router_yielding_reader2_adb.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_router2.script b/tests/stub/routing/scripts/v5x0/router_yielding_router2.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_router2.script rename to tests/stub/routing/scripts/v5x0/router_yielding_router2.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_fake_reader.script b/tests/stub/routing/scripts/v5x0/router_yielding_router2_and_fake_reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_router2_and_fake_reader.script rename to tests/stub/routing/scripts/v5x0/router_yielding_router2_and_fake_reader.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_router2_and_non_existent_reader.script b/tests/stub/routing/scripts/v5x0/router_yielding_router2_and_non_existent_reader.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_router2_and_non_existent_reader.script rename to tests/stub/routing/scripts/v5x0/router_yielding_router2_and_non_existent_reader.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_unknown_failure.script b/tests/stub/routing/scripts/v5x0/router_yielding_unknown_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_unknown_failure.script rename to tests/stub/routing/scripts/v5x0/router_yielding_unknown_failure.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_writer1.script b/tests/stub/routing/scripts/v5x0/router_yielding_writer1.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_writer1.script rename to tests/stub/routing/scripts/v5x0/router_yielding_writer1.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_writer1_sequentially.script b/tests/stub/routing/scripts/v5x0/router_yielding_writer1_sequentially.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_writer1_sequentially.script rename to tests/stub/routing/scripts/v5x0/router_yielding_writer1_sequentially.script diff --git a/tests/stub/routing/scripts/v4x4/router_yielding_writer2.script b/tests/stub/routing/scripts/v5x0/router_yielding_writer2.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/router_yielding_writer2.script rename to tests/stub/routing/scripts/v5x0/router_yielding_writer2.script diff --git a/tests/stub/routing/scripts/v4x4/writer.script b/tests/stub/routing/scripts/v5x0/writer.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer.script rename to tests/stub/routing/scripts/v5x0/writer.script diff --git a/tests/stub/routing/scripts/v4x4/writer_and_reader_tx_with_bookmark.script b/tests/stub/routing/scripts/v5x0/writer_and_reader_tx_with_bookmark.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_and_reader_tx_with_bookmark.script rename to tests/stub/routing/scripts/v5x0/writer_and_reader_tx_with_bookmark.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx.script b/tests/stub/routing/scripts/v5x0/writer_tx.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx.script rename to tests/stub/routing/scripts/v5x0/writer_tx.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_bookmarks.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_bookmarks.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_leader_switch_and_retry.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_leader_switch_and_retry.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_leader_switch_and_retry.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_leader_switch_and_retry.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_multiple_bookmarks.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_multiple_bookmarks.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_multiple_bookmarks.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_multiple_bookmarks.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pull.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_pull.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_pull.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_run_path.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_run_path.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_run_path.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_run_path.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_status_check.script b/tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_status_check.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_with_unexpected_interruption_on_status_check.script rename to tests/stub/routing/scripts/v5x0/writer_tx_with_unexpected_interruption_on_status_check.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure.script b/tests/stub/routing/scripts/v5x0/writer_tx_yielding_database_unavailable_failure.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure.script rename to tests/stub/routing/scripts/v5x0/writer_tx_yielding_database_unavailable_failure.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure_on_commit.script b/tests/stub/routing/scripts/v5x0/writer_tx_yielding_database_unavailable_failure_on_commit.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_yielding_database_unavailable_failure_on_commit.script rename to tests/stub/routing/scripts/v5x0/writer_tx_yielding_database_unavailable_failure_on_commit.script diff --git a/tests/stub/routing/scripts/v4x4/writer_tx_yielding_failure_on_run.script b/tests/stub/routing/scripts/v5x0/writer_tx_yielding_failure_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_tx_yielding_failure_on_run.script rename to tests/stub/routing/scripts/v5x0/writer_tx_yielding_failure_on_run.script diff --git a/tests/stub/routing/scripts/v4x4/writer_with_bookmark.script b/tests/stub/routing/scripts/v5x0/writer_with_bookmark.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_with_bookmark.script rename to tests/stub/routing/scripts/v5x0/writer_with_bookmark.script diff --git a/tests/stub/routing/scripts/v4x4/writer_with_sequential_access_and_bookmark.script b/tests/stub/routing/scripts/v5x0/writer_with_sequential_access_and_bookmark.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_with_sequential_access_and_bookmark.script rename to tests/stub/routing/scripts/v5x0/writer_with_sequential_access_and_bookmark.script diff --git a/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pipelined_pull.script b/tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_pipelined_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pipelined_pull.script rename to tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_pipelined_pull.script diff --git a/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pull.script b/tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_pull.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_pull.script rename to tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_pull.script diff --git a/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_run.script b/tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_run.script rename to tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_run.script diff --git a/tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_second_run.script b/tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_second_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_with_unexpected_interruption_on_second_run.script rename to tests/stub/routing/scripts/v5x0/writer_with_unexpected_interruption_on_second_run.script diff --git a/tests/stub/routing/scripts/v4x4/writer_yielding_failure_on_run.script b/tests/stub/routing/scripts/v5x0/writer_yielding_failure_on_run.script similarity index 100% rename from tests/stub/routing/scripts/v4x4/writer_yielding_failure_on_run.script rename to tests/stub/routing/scripts/v5x0/writer_yielding_failure_on_run.script diff --git a/tests/stub/routing/test_routing_v4x4.py b/tests/stub/routing/test_routing_v4x4.py index 3a26f021b..f2bbbd345 100644 --- a/tests/stub/routing/test_routing_v4x4.py +++ b/tests/stub/routing/test_routing_v4x4.py @@ -1,2896 +1,250 @@ -from collections import defaultdict -import time - -from nutkit.frontend import Driver import nutkit.protocol as types -from tests.shared import ( - driver_feature, - get_dns_resolved_server_address, - get_driver_name, - get_ip_addresses, -) +from tests.shared import driver_feature -from ._routing import RoutingBase +from .test_routing_v5x0 import RoutingV5x0 -class RoutingV4x4(RoutingBase): +class RoutingV4x4(RoutingV5x0): required_features = types.Feature.BOLT_4_4, bolt_version = "4.4" server_agent = "Neo4j/4.4.0" adb = "adb" - def route_call_count(self, server): - return server.count_requests("ROUTE") - - @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) 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.update_routing_table() - driver.close() - - self._routingServer1.done() - - @driver_feature(types.Feature.BACKEND_RT_FETCH) + 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. - 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.update_routing_table(self.adb) - self._routingServer1.done() - rt = driver.get_routing_table(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). + super().test_should_successfully_get_routing_table() + 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.collect_records(result) - summary = result.consume() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._readServer1), - self._readServer1.address]) - self.assertEqual([1], sequence) - - def test_should_read_successfully_from_reader_using_session_run_with_default_db_driver( # noqa: E501 - 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.collect_records(result) - summary = result.consume() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._readServer1), - self._readServer1.address]) - 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.begin_transaction() - result = tx.run("RETURN 1 as n") - sequence = self.collect_records(result) - summary = result.consume() - tx.commit() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._readServer1), - self._readServer1.address]) - 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.begin_transaction() - result = tx.run("RETURN 1 as n") - sequence = self.collect_records(result) - summary = result.consume() - tx.commit() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._readServer1), - self._readServer1.address]) - 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): # noqa: E501 + super().test_should_read_successfully_from_reader_using_session_run_with_default_db_driver() # noqa: E501 + + def test_should_read_successfully_from_reader_using_tx_run_default_db(self): # noqa: E501 + super().test_should_read_successfully_from_reader_using_tx_run_default_db() # noqa: E501 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.begin_transaction() - list(tx.run("CREATE database foo")) - tx.commit() - - session2 = driver.session("w", bookmarks=session.last_bookmarks(), - database=self.adb) - result = session2.run("RETURN 1 as n") - sequence2 = self.collect_records(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.collect_records(result)) - summaries.append(result.consume()) - - session.read_transaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual([[1]], sequences) - self.assertEqual(len(summaries), 1) - self.assertTrue(summaries[0].server_info.address in - [get_dns_resolved_server_address(self._readServer1), - self._readServer1.address]) - - def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( # noqa: E501 - 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: - 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 ["ruby"]: - self.assertEqual( - "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( # noqa: E501 - self): - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( # noqa: E501 - "reader_with_unexpected_interruption_on_pipelined_pull.script" - ) - - def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run( # noqa: E501 - 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( # noqa: E501 - "reader_with_unexpected_interruption_on_run.script" - ) - - def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 - 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.begin_transaction() - 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 ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - e.errorType - ) - failed = True - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(failed) + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run(self): # noqa: E501 + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run() # noqa: E501 + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run(self): # noqa: E501 + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run() # noqa: E501 + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run() # noqa: E501 + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run() # noqa: E501 + + def test_should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function(self): # noqa: E501 + super().test_should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function() # noqa: E501 + + def test_should_fail_when_reading_from_unexpectedly_interrupting_readers_on_run_using_tx_function(self): # noqa: E501 + super().test_should_fail_when_reading_from_unexpectedly_interrupting_readers_on_run_using_tx_function() # noqa: E501 + + def test_should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function(self): # noqa: E501 + super().test_should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function() # noqa: E501 + + def test_should_fail_when_writing_to_unexpectedly_interrupting_writers_on_run_using_tx_function(self): # noqa: E501 + super().test_should_fail_when_writing_to_unexpectedly_interrupting_writers_on_run_using_tx_function() # noqa: E501 - @driver_feature(types.Feature.OPT_PULL_PIPELINING) - def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 - self): - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 - "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) - - def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run( # noqa: E501 - self): - self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 - "reader_tx_with_unexpected_interruption_on_run.script" - ) - - def _should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( # noqa: E501 - self, interrupting_reader_script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, max_tx_retry_time_ms=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.read_transaction(work) - - session.close() - driver.close() - - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SessionExpiredException", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "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( # noqa: E501 - self): - self._should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( # noqa: E501 - "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( # noqa: E501 - 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( # noqa: E501 - "reader_tx_with_unexpected_interruption_on_run.script" - ) - - def _should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( # noqa: E501 - self, interrupting_writer_script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, max_tx_retry_time_ms=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.write_transaction(work) - - session.close() - driver.close() - - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SessionExpiredException", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "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( # noqa: E501 - self): - self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( # noqa: E501 - "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( # noqa: E501 - self): - self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( # noqa: E501 - "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() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._writeServer1), - self._writeServer1.address]) - - # 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.begin_transaction() - res = tx.run("RETURN 1 as n") - list(res) - summary = res.consume() - tx.commit() - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._writeServer1), - self._writeServer1.address]) + 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.write_transaction(work) - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertIsNotNone(res) - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._writeServer1), - self._writeServer1.address]) - - 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.collect_records(result)) - result = tx.run("RETURN %i.2 as n" % work_count) - sequences.append(self.collect_records(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.write_transaction(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.collect_records(result)) - - session.write_transaction(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_writer_using_tx_function() - @driver_feature(types.Feature.OPT_PULL_PIPELINING) - def test_should_retry_write_until_success_with_leader_change_using_tx_function( # noqa: E501 - self): - self._should_retry_write_until_success_with_leader_change_using_tx_function( # noqa: E501 - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) - - def test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function( # noqa: E501 - 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( # noqa: E501 - "writer_tx_with_unexpected_interruption_on_run.script" - ) - - def test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function( # noqa: E501 - 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.collect_records(result)) - - session.write_transaction(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( # noqa: E501 - 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: - 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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - e.errorType - ) - failed = True - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) + def test_should_write_successfully_on_leader_switch_using_tx_function(self): # noqa: E501 + super().test_should_write_successfully_on_leader_switch_using_tx_function() # noqa: E501 - @driver_feature(types.Feature.OPT_PULL_PIPELINING) - def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 - self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 - "writer_with_unexpected_interruption_on_pipelined_pull.script" - ) - - def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run( # noqa: E501 - self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 - "writer_with_unexpected_interruption_on_run.script" - ) - - def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run( # noqa: E501 - self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 - "writer_with_unexpected_interruption_on_pull.script" - ) - - def _should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 - self, interrupting_writer_script, fails_on_next=False): - # TODO remove this block once all languages work - if get_driver_name() in ["go"]: - self.skipTest("requires investigation") - if get_driver_name() in ["go", "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.begin_transaction() - pipelining_driver = self.driver_supports_features( - types.Feature.OPT_PULL_PIPELINING - ) - # TODO: It will be removed as soon as JS Driver - # has async iterator api - if get_driver_name() in ["javascript"] or not pipelining_driver: - fails_on_next = True - if fails_on_next: - result = tx.run("RETURN 1 as n") - with self.assertRaises(types.DriverError) as exc: - result.next() - else: - with self.assertRaises(types.DriverError) as exc: - tx.run("RETURN 1 as n") - - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SessionExpiredException", - exc.exception.errorType - ) - session.close() - - self.assertNotIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - self._routingServer1.done() - self._writeServer1.done() + def test_should_retry_write_until_success_with_leader_change_using_tx_function(self): # noqa: E501 + super().test_should_retry_write_until_success_with_leader_change_using_tx_function() # noqa: E501 - @driver_feature(types.Feature.OPT_PULL_PIPELINING) - def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 - self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 - "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" - ) - - def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run( # noqa: E501 - self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 - "writer_tx_with_unexpected_interruption_on_run.script" - ) - - def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run( # noqa: E501 - self): - self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 - "writer_tx_with_unexpected_interruption_on_pull.script", - fails_on_next=True - ) - - def test_should_fail_discovery_when_router_fails_with_procedure_not_found_code( # noqa: E501 - 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.verify_connectivity() - 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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::ServiceUnavailableException", - e.errorType - ) - failed = True - driver.close() - - self._routingServer1.done() - self.assertTrue(failed) + def test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function(self): # noqa: E501 + super().test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function() # noqa: E501 + + def test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function(self): # noqa: E501 + super().test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function() # noqa: E501 + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run() # noqa: E501 + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run() # noqa: E501 + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run() # noqa: E501 + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run() # noqa: E501 + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run() # noqa: E501 + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run() # noqa: E501 + + def test_should_fail_discovery_when_router_fails_with_procedure_not_found_code(self): # noqa: E501 + super().test_should_fail_discovery_when_router_fails_with_procedure_not_found_code() # noqa: E501 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.verify_connectivity() - 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 ["ruby"]: - self.assertEqual( - "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", "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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - e.errorType - ) - failed = True - session.close() - - self.assertNotIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) - - def test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database( # noqa: E501 - self): - # TODO remove this block once all languages work - if get_driver_name() in ["go", "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() - - self.assertNotIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) - - def test_should_fail_when_writing_on_writer_that_returns_database_unavailable( # noqa: E501 - self): - # TODO remove this block once all languages work - if get_driver_name() in ["go", "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() - - self.assertIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - 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( # noqa: E501 - 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", "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: - 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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - e.errorType - ) - failed = True - - self.assertNotIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - 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( # noqa: E501 - 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", "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.begin_transaction() - 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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - e.errorType - ) - failed = True - session.close() - - self.assertNotIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - 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( # noqa: E501 - 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", "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.begin_transaction() - 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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - e.errorType - ) - failed = True - session.close() - - self.assertNotIn(self._writeServer1.address, - driver.get_routing_table(self.adb).writers) - - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self.assertTrue(failed) - - def test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run( # noqa: E501 - 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.begin_transaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - last_bookmarks = session.last_bookmarks() - 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( # noqa: E501 - 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.begin_transaction() - result = tx.run("RETURN 1 as n") - sequence = self.collect_records(result) - tx.commit() - last_bookmarks = session.last_bookmarks() - session.close() - driver.close() - - self._routingServer1.done() - self._readServer1.done() - self.assertEqual([1], sequence) - self.assertEqual(["NewBookmark"], last_bookmarks) + super().test_should_fail_discovery_when_router_fails_with_unknown_code() # noqa: E501 + + def test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code(self): # noqa: E501 + super().test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code() # noqa: E501 + + def test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database(self): # noqa: E501 + super().test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database() # noqa: E501 + + def test_should_fail_when_writing_on_writer_that_returns_database_unavailable(self): # noqa: E501 + super().test_should_fail_when_writing_on_writer_that_returns_database_unavailable() # noqa: E501 + + def test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code(self): # noqa: E501 + super().test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code() # noqa: E501 + + def test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code_using_tx_run() # noqa: E501 + + def test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code_using_tx_run(self): # noqa: E501 + super().test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code_using_tx_run() # noqa: E501 + + def test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run(self): # noqa: E501 + super().test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run() # noqa: E501 + + def test_should_use_read_session_mode_and_initial_bookmark_when_reading_using_tx_run(self): # noqa: E501 + super().test_should_use_read_session_mode_and_initial_bookmark_when_reading_using_tx_run() # noqa: E501 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.begin_transaction() - list(tx.run("CREATE (n {name:'Bob'})")) - tx.commit() - first_bookmark = session.last_bookmarks() - tx = session.begin_transaction() - result = tx.run("MATCH (n) RETURN n.name AS name") - sequence = self.collect_records(result) - tx.commit() - second_bookmark = session.last_bookmarks() - 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.collect_records(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.read_transaction(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.collect_records(result)) - - session.read_transaction(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.read_transaction(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.collect_records(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.write_transaction(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.collect_records(result)) - - session.write_transaction(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.write_transaction(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.collect_records(result)) - - session.read_transaction(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" - ) - - 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( # noqa: E501 - 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.collect_records(result)) - - session.write_transaction(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() + + def test_should_retry_read_tx_and_rediscovery_until_success_on_run_failure(self): # noqa: E501 + super().test_should_retry_read_tx_and_rediscovery_until_success_on_run_failure() # noqa: E501 + + def test_should_retry_read_tx_and_rediscovery_until_success_on_pull_failure(self): # noqa: E501 + super().test_should_retry_read_tx_and_rediscovery_until_success_on_pull_failure() # noqa: E501 - @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( # noqa: E501 - 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( # noqa: E501 - self): - self._should_retry_write_tx_and_rediscovery_until_success( - "writer_tx_with_unexpected_interruption_on_pull.script" - ) - - @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) - 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.update_routing_table() - 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.collect_records(result)) - - session.read_transaction(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.collect_records(result)) - - session.read_transaction(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_retry_write_tx_and_rediscovery_until_success() + + def test_should_retry_write_tx_and_rediscovery_until_success_on_run_failure(self): # noqa: E501 + super().test_should_retry_write_tx_and_rediscovery_until_success_on_run_failure() # noqa: E501 + + def test_should_retry_write_tx_and_rediscovery_until_success_on_pull_failure(self): # noqa: E501 + super().test_should_retry_write_tx_and_rediscovery_until_success_on_pull_failure() # noqa: E501 + + def test_should_use_initial_router_for_discovery_when_others_unavailable(self): # noqa: E501 + super().test_should_use_initial_router_for_discovery_when_others_unavailable() # noqa: E501 + + def test_should_successfully_read_from_readable_router_using_tx_function(self): # noqa: E501 + super().test_should_successfully_read_from_readable_router_using_tx_function() # noqa: E501 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.collect_records(result)) - - session.read_transaction(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.collect_records(result)) - - session.read_transaction(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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "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) - - @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) - 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.update_routing_table() - session = driver.session("w", bookmarks=["OldBookmark"], - database=self.adb) - sequences = [] - self._routingServer1.done() - try: - driver.update_routing_table() - 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.collect_records(result)) - - session.read_transaction(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) - - @driver_feature(types.Feature.BACKEND_RT_FETCH, - types.Feature.BACKEND_RT_FORCE_UPDATE) + super().test_should_send_empty_hello() + + def test_should_serve_reads_and_fail_writes_when_no_writers_available(self): # noqa: E501 + super().test_should_serve_reads_and_fail_writes_when_no_writers_available() # noqa: E501 + + def test_should_accept_routing_table_without_writers_and_then_rediscover(self): # noqa: E501 + super().test_should_accept_routing_table_without_writers_and_then_rediscover() # noqa: E501 + def test_should_fail_on_routing_table_with_no_reader(self): - if get_driver_name() in ["go", "java", "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) - - with self.assertRaises(types.DriverError) as exc: - driver.update_routing_table() - - if get_driver_name() in ["python"]: - self.assertEqual( - exc.exception.errorType, - "" - ) - elif get_driver_name() in ["javascript"]: - self.assertEqual( - exc.exception.code, - "ServiceUnavailable" - ) - - routing_table = driver.get_routing_table() - 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.collect_records(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.begin_transaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - last_bookmarks = session.last_bookmarks() - 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.collect_records(result)) - - session.write_transaction(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, resolver_fn=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.collect_records(result)) - - session.read_transaction(work) - session.read_transaction(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( # noqa: E501 - 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.collect_records(result)) - - session.read_transaction(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.supports_multi_db() - - # 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( # noqa: E501 - 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.collect_records(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( # noqa: E501 - 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 - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - e.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "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_fast_fail_discover(self, script): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, script) - - session = driver.session("r", database=self.adb, bookmarks=["foobar"]) - with self.assertRaises(types.DriverError) as exc: - result = session.run("RETURN 1 as n") - result.next() - - session.close() - driver.close() - - self._routingServer1.done() - - return exc - - @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) - def test_should_fail_with_routing_failure_on_invalid_bookmark_discovery_failure( # noqa: E501 - self): - exc = self._test_fast_fail_discover( - "router_yielding_invalid_bookmark_failure.script", - ) - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.ClientException", - exc.exception.errorType - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::ClientException", - exc.exception.errorType - ) - self.assertEqual("Neo.ClientError.Transaction.InvalidBookmark", - exc.exception.code) - - @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) - def test_should_fail_with_routing_failure_on_invalid_bookmark_mixture_discovery_failure( # noqa: E501 - self): - exc = self._test_fast_fail_discover( - "router_yielding_invalid_bookmark_mixture_failure.script", - ) - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.ClientException", - exc.exception.errorType - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::ClientException", - exc.exception.errorType - ) - - self.assertEqual("Neo.ClientError.Transaction.InvalidBookmarkMixture", - exc.exception.code) - - @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) - def test_should_fail_with_routing_failure_on_forbidden_discovery_failure( - self): - exc = self._test_fast_fail_discover( - "router_yielding_forbidden_failure.script", - ) - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SecurityException", - exc.exception.errorType - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - exc.exception.errorType - ) - if get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SecurityException", - exc.exception.errorType - ) - self.assertEqual( - "Neo.ClientError.Security.Forbidden", - exc.exception.code) - - @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) - def test_should_fail_with_routing_failure_on_any_security_discovery_failure( # noqa: E501 - self): - exc = self._test_fast_fail_discover( - "router_yielding_any_security_failure.script", - ) - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SecurityException", - exc.exception.errorType - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SecurityException", - exc.exception.errorType - ) - self.assertEqual( - "Neo.ClientError.Security.MadeUpSecurityError", - exc.exception.code) - - def test_should_read_successfully_from_reachable_db_after_trying_unreachable_db( # noqa: E501 - 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.collect_records(result) - 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 ["ruby"]: - self.assertEqual( - "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.collect_records(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) + super().test_should_forget_address_on_database_unavailable_error() + + def test_should_use_resolver_during_rediscovery_when_existing_routers_fail(self): # noqa: E501 + super().test_should_use_resolver_during_rediscovery_when_existing_routers_fail() # noqa: E501 + + def test_should_revert_to_initial_router_if_known_router_throws_protocol_errors(self): # noqa: E501 + super().test_should_revert_to_initial_router_if_known_router_throws_protocol_errors() # noqa: E501 + + def test_should_successfully_check_if_support_for_multi_db_is_available(self): # noqa: E501 + super().test_should_successfully_check_if_support_for_multi_db_is_available() # noqa: E501 + + def test_should_read_successfully_on_empty_discovery_result_using_session_run(self): # noqa: E501 + super().test_should_read_successfully_on_empty_discovery_result_using_session_run() # noqa: E501 + + def test_should_fail_with_routing_failure_on_db_not_found_discovery_failure(self): # noqa: E501 + super().test_should_fail_with_routing_failure_on_db_not_found_discovery_failure() # noqa: E501 + + def test_should_fail_with_routing_failure_on_invalid_bookmark_discovery_failure(self): # noqa: E501 + super().test_should_fail_with_routing_failure_on_invalid_bookmark_discovery_failure() # noqa: E501 + + def test_should_fail_with_routing_failure_on_invalid_bookmark_mixture_discovery_failure(self): # noqa: E501 + super().test_should_fail_with_routing_failure_on_invalid_bookmark_mixture_discovery_failure() # noqa: E501 + + def test_should_fail_with_routing_failure_on_forbidden_discovery_failure(self): # noqa: E501 + super().test_should_fail_with_routing_failure_on_forbidden_discovery_failure() # noqa: E501 + + def test_should_fail_with_routing_failure_on_any_security_discovery_failure(self): # noqa: E501 + super().test_should_fail_with_routing_failure_on_any_security_discovery_failure() # noqa: E501 + + def test_should_read_successfully_from_reachable_db_after_trying_unreachable_db(self): # noqa: E501 + super().test_should_read_successfully_from_reachable_db_after_trying_unreachable_db() # noqa: E501 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.collect_records(result) - last_bookmarks = session.last_bookmarks() - 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, failure_script): - - 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 domain_name_resolver(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, - resolver_fn=resolver, domain_name_resolver_fn=domain_name_resolver, - connection_timeout_ms=1000 - ) - self.start_server(self._routingServer1, 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.collect_records(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())) - - @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) - def test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure( # noqa: E501 - self): - self._test_should_request_rt_from_all_initial_routers_until_successful( - "router_yielding_unknown_failure.script" - ) - - @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) - def test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired( # noqa: E501 - self): - self._test_should_request_rt_from_all_initial_routers_until_successful( - "router_yielding_authorization_expired_failure.script" - ) - - @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) + super().test_should_ignore_system_bookmark_when_getting_rt_for_multi_db() # noqa: E501 + + def test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure(self): # noqa: E501 + super().test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure() # noqa: E501 + + def test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired(self): # noqa: E501 + super().test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired() # noqa: E501 + 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") - - 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, - domain_name_resolver_fn=domain_name_resolver - ) - self.start_server( - self._routingServer1, - "router_yielding_reader1_and_exit.script" - ) - - driver.update_routing_table() - self._routingServer1.done() - router_ip_address = ip_addresses[1] - self.start_server( - self._routingServer1, - "router_yielding_reader1_and_exit.script" - ) - driver.update_routing_table() - # 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, - user_agent=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, - user_agent=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() + super().test_should_successfully_get_server_agent() 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.collect_records(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 - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::IllegalStateException", - e.errorType - ) - failed_on_run = True - - self.assertTrue(failed_on_run) - self._routingServer1.done() - self._readServer1.done() - self.assertTrue(summary.server_info.address in - [get_dns_resolved_server_address(self._readServer1), - self._readServer1.address]) - self.assertEqual([1], sequence) - - def test_should_fail_when_writing_without_writers_using_session_run(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server(self._routingServer1, - "router_yielding_no_writers_adb_sequentially.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: - 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._routingServer1.done() - self.assertTrue(failed) - - def test_should_write_successfully_after_leader_switch_using_tx_run(self): - # TODO remove this block once fixed - if get_driver_name() in ["go"]: - self.skipTest("Fails on tx rollback attempt") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, None) - self.start_server(self._routingServer1, - "router_with_leader_change.script") - self.start_server( - self._writeServer1, - "writer_tx_yielding_failure_on_run.script", - vars_={ - **self.get_vars(), - "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' - '"message": "message"}' - } - ) - self.start_server( - self._writeServer2, - "writer_tx.script" - ) - - session = driver.session("w", database=self.adb) - - # Attempts: - # 1 - 1 writer that returns an error - # 2 - 1 writer that does not respond - # 3 - 0 writers - for attempt in range(3): - with self.assertRaises(types.DriverError) as e: - tx = session.begin_transaction() - result = tx.run("RETURN 1 as n") - # drivers doing lazy loading should fail here - result.next() - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SessionExpiredException", - e.exception.errorType - ) - elif get_driver_name() in ["python"]: - if attempt == 0: - self.assertEqual( - "", - e.exception.errorType - ) - else: - self.assertEqual( - "", - e.exception.errorType - ) - if attempt == 0: - tx.rollback() - self._writeServer1.done() - - tx = session.begin_transaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - - session.close() - driver.close() - - self._routingServer1.done() - self._writeServer2.done() - - def test_should_rediscover_when_all_connections_fail_using_s_and_tx_run( - self): - # TODO remove this block once fixed - if get_driver_name() in ["go"]: - self.skipTest("Session close fails with ConnectivityError") - # TODO remove this block once fixed - if get_driver_name() in ["javascript"]: - self.skipTest("write_session result consumption times out") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_writer1_sequentially.script") - self.start_server( - self._writeServer1, - "writer_with_unexpected_interruption_on_second_run.script") - self.start_server( - self._readServer1, - "reader_tx_with_unexpected_interruption_on_second_run.script") - self.start_server( - self._readServer2, - "reader_tx_with_unexpected_interruption_on_second_run.script") - - write_session = driver.session("w", database=self.adb) - read_session1 = driver.session("r", database=self.adb) - read_tx1 = read_session1.begin_transaction() - read_session2 = driver.session("r", database=self.adb) - read_tx2 = read_session2.begin_transaction() - - list(write_session.run("RETURN 1 as n")) - list(read_tx1.run("RETURN 1 as n")) - list(read_tx2.run("RETURN 1 as n")) - read_tx1.commit() - read_tx2.commit() - - def run_and_assert_error(runner): - with self.assertRaises(types.DriverError) as exc: - # drivers doing eager loading will fail here - result = runner.run("RETURN 1 as n") - # drivers doing lazy loading should fail here - result.next() - - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SessionExpiredException", - exc.exception.errorType - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - exc.exception.errorType - ) - - run_and_assert_error(write_session) - run_and_assert_error(read_session1.begin_transaction()) - run_and_assert_error(read_session2.begin_transaction()) - - write_session.close() - read_session1.close() - read_session2.close() - route_count1 = self.route_call_count(self._routingServer1) - self._writeServer1.done() - self._readServer1.done() - self._readServer2.done() - - self.start_server(self._writeServer1, "writer.script") - self.start_server(self._readServer1, "reader.script") - - write_session = driver.session("w", database=self.adb) - list(write_session.run("RETURN 1 as n")) - - read_session1 = driver.session("r", database=self.adb) - list(read_session1.run("RETURN 1 as n")) - - driver.close() - self._routingServer1.done() - route_count2 = self.route_call_count(self._routingServer1) - self.assertTrue(route_count2 > route_count1 > 0) - self._writeServer1.done() - self._readServer1.done() - - def test_should_succeed_when_another_conn_fails_and_discover_using_tx_run( - self): - # TODO remove this block once fixed - if get_driver_name() in ["go"]: - self.skipTest("Session close fails with ConnectivityError") - # TODO remove this block once fixed - if get_driver_name() in ["javascript"]: - self.skipTest("Transaction result consumption times out") - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_yielding_writer1_sequentially.script") - self.start_server( - self._writeServer1, - "writer_tx_with_unexpected_interruption_on_run_path.script") - - session1 = driver.session("w", database=self.adb) - tx1 = session1.begin_transaction() - session2 = driver.session("w", database=self.adb) - tx2 = session2.begin_transaction() - - list(tx1.run("RETURN 1 as n")) - - with self.assertRaises(types.DriverError) as exc: - # drivers doing eager loading will fail here - result = tx2.run("RETURN 5 as n") - # drivers doing lazy loading should fail here - result.next() - - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.SessionExpiredException", - exc.exception.errorType - ) - elif get_driver_name() in ["python"]: - self.assertEqual( - "", - exc.exception.errorType - ) - elif get_driver_name() in ["ruby"]: - self.assertEqual( - "Neo4j::Driver::Exceptions::SessionExpiredException", - exc.exception.errorType - ) - - tx1.commit() - session1.close() - session2.close() - route_count1 = self.route_call_count(self._routingServer1) - - # Another discovery is expected since the only writer failed in tx2 - session = driver.session("w", database=self.adb) - tx = session.begin_transaction() - list(tx.run("RETURN 1 as n")) - tx.commit() - - session.close() - driver.close() - self._routingServer1.done() - route_count2 = self.route_call_count(self._routingServer1) - self.assertTrue(route_count2 > route_count1 > 0) - self._writeServer1.done() - - def test_should_get_rt_from_leader_w_and_r_via_leader_using_session_run( - self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - self.start_server( - self._routingServer1, - "router_and_writer_with_sequential_access_and_bookmark.script" - ) - - session = driver.session("w", database=self.adb) - list(session.run("RETURN 1 as n")) - records = list(session.run("RETURN 5 as n")) - session.close() - driver.close() - - self._routingServer1.done() - self.assertEqual([types.Record(values=[types.CypherInt(1)])], records) - - def test_should_get_rt_from_follower_w_and_r_via_leader_using_session_run( - self): - # TODO remove this block once fixed - if get_driver_name() in ["javascript"]: - 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, - "writer_with_sequential_access_and_bookmark.script" - ) - - session = driver.session("w", database=self.adb) - list(session.run("RETURN 1 as n")) - records = list(session.run("RETURN 5 as n")) - session.close() - driver.close() - - self._routingServer1.done() - self.assertEqual([types.Record(values=[types.CypherInt(1)])], records) - - @driver_feature(types.Feature.API_LIVENESS_CHECK, - types.Feature.TMP_GET_CONNECTION_POOL_METRICS) - def test_should_drop_connections_failing_liveness_check(self): - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, liveness_check_timeout_ms=0) - self.start_server(self._routingServer1, - "router_adb_multi_no_bookmarks.script") - self.start_server( - self._writeServer1, - "writer_tx_with_unexpected_interruption_on_status_check.script" - ) - - sessions = [] - txs = [] - - for _ in range(5): - session = driver.session("w", database=self.adb) - tx = session.begin_transaction() - sessions.append(session) - txs.append(tx) - - for tx in txs: - list(tx.run("RETURN 1 as n")) - tx.commit() - - for session in sessions: - session.close() - - self._wait_for_idle_connections(driver, 5) - - session = driver.session("w", database=self.adb) - list(session.run("RETURN 1 as n")) - session.close() - - self._wait_for_idle_connections(driver, 1) - driver.close() - self._routingServer1.done() - self._writeServer1.done() - - @driver_feature(types.Feature.TMP_DRIVER_MAX_CONNECTION_POOL_SIZE, - types.Feature.TMP_CONNECTION_ACQUISITION_TIMEOUT) - def test_should_enforce_pool_size_per_cluster_member(self): - acq_timeout_ms = 100 - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent, max_connection_pool_size=1, - connection_acquisition_timeout_ms=acq_timeout_ms) - self.start_server(self._routingServer1, - "router_adb_multi_no_bookmarks.script") - self.start_server(self._writeServer1, "writer_tx.script") - self.start_server(self._writeServer2, "writer_tx.script") - self.start_server(self._readServer1, "reader_tx.script") - - session0 = driver.session("w", database=self.adb) - tx0 = session0.begin_transaction() - - session1 = driver.session("w", database=self.adb) - tx1 = session1.begin_transaction() - - session2 = driver.session("w", database=self.adb) - - if self.driver_supports_features(types.Feature.OPT_EAGER_TX_BEGIN): - with self.assertRaises(types.DriverError) as exc: - session2.begin_transaction() - else: - with self.assertRaises(types.DriverError) as exc: - tx = session2.begin_transaction() - list(tx.run("RETURN 1 as n")) - - if get_driver_name() in ["java"]: - self.assertEqual( - "org.neo4j.driver.exceptions.ClientException", - exc.exception.errorType - ) - self.assertTrue("Unable to acquire connection from the " - "pool within configured maximum time of " - f"{acq_timeout_ms}ms" - in exc.exception.msg) - - session2.close() - - session3 = driver.session("r", database=self.adb) - tx3 = session3.begin_transaction() - list(tx3.run("RETURN 1 as n")) - tx3.commit() - session3.close() - - list(tx0.run("RETURN 1 as n")) - tx0.commit() - session0.close() - list(tx1.run("RETURN 1 as n")) - tx1.commit() - session1.close() - driver.close() - - self._routingServer1.done() - self._writeServer1.done() - self._readServer1.done() - - def _wait_for_idle_connections(self, driver, expected_idle_connections): - attempts = 0 - while True: - metrics = driver.get_connection_pool_metrics( - self._writeServer1.address) - if metrics.idle == expected_idle_connections: - break - attempts += 1 - if attempts == 10: - self.fail("Timeout out waiting for idle connections") - time.sleep(.1) - - def test_does_not_use_read_connection_for_write(self): - # TODO: remove this block once all languages work - if get_driver_name() in ["javascript", "go", "dotnet", "ruby"]: - self.skipTest("Requires address field in summary") - - def read(tx): - result = tx.run("RETURN 1 as n") - list(result) # exhaust the result - return result.consume() - - def write(tx): - result = tx.run("RETURN 1 as n") - list(result) # exhaust the result - return result.consume() - - self.start_server(self._routingServer1, - "router_adb_multi_no_bookmarks.script") - self.start_server(self._writeServer1, "writer_tx.script") - self.start_server(self._readServer1, "reader_tx.script") - - driver = Driver(self._backend, self._uri_with_context, self._auth, - self._userAgent) - - session = driver.session("w", database=self.adb) - - read_summary = session.read_transaction(read) - write_summary = session.write_transaction(write) - - self.assertNotEqual(read_summary.server_info.address, - write_summary.server_info.address) + super().test_should_fail_when_driver_closed_using_session_run() diff --git a/tests/stub/routing/test_routing_v5x0.py b/tests/stub/routing/test_routing_v5x0.py new file mode 100644 index 000000000..d7b86e7c7 --- /dev/null +++ b/tests/stub/routing/test_routing_v5x0.py @@ -0,0 +1,2896 @@ +from collections import defaultdict +import time + +from nutkit.frontend import Driver +import nutkit.protocol as types +from tests.shared import ( + driver_feature, + get_dns_resolved_server_address, + get_driver_name, + get_ip_addresses, +) + +from ._routing import RoutingBase + + +class RoutingV5x0(RoutingBase): + + required_features = types.Feature.BOLT_5_0, + bolt_version = "5.0" + server_agent = "Neo4j/5.0.0" + adb = "adb" + + def route_call_count(self, server): + return server.count_requests("ROUTE") + + @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) + 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.update_routing_table() + driver.close() + + self._routingServer1.done() + + @driver_feature(types.Feature.BACKEND_RT_FETCH) + 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. + 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.update_routing_table(self.adb) + self._routingServer1.done() + rt = driver.get_routing_table(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.collect_records(result) + summary = result.consume() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._readServer1), + self._readServer1.address]) + self.assertEqual([1], sequence) + + def test_should_read_successfully_from_reader_using_session_run_with_default_db_driver( # noqa: E501 + 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.collect_records(result) + summary = result.consume() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._readServer1), + self._readServer1.address]) + 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.begin_transaction() + result = tx.run("RETURN 1 as n") + sequence = self.collect_records(result) + summary = result.consume() + tx.commit() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._readServer1), + self._readServer1.address]) + 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.begin_transaction() + result = tx.run("RETURN 1 as n") + sequence = self.collect_records(result) + summary = result.consume() + tx.commit() + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._readServer1), + self._readServer1.address]) + 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.begin_transaction() + list(tx.run("CREATE database foo")) + tx.commit() + + session2 = driver.session("w", bookmarks=session.last_bookmarks(), + database=self.adb) + result = session2.run("RETURN 1 as n") + sequence2 = self.collect_records(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.collect_records(result)) + summaries.append(result.consume()) + + session.read_transaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._readServer1.done() + self.assertEqual([[1]], sequences) + self.assertEqual(len(summaries), 1) + self.assertTrue(summaries[0].server_info.address in + [get_dns_resolved_server_address(self._readServer1), + self._readServer1.address]) + + def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( # noqa: E501 + 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: + 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 ["ruby"]: + self.assertEqual( + "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( # noqa: E501 + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run( # noqa: E501 + "reader_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_session_run( # noqa: E501 + 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( # noqa: E501 + "reader_with_unexpected_interruption_on_run.script" + ) + + def _should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 + 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.begin_transaction() + 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 ["ruby"]: + self.assertEqual( + "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( # noqa: E501 + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 + "reader_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_reading_from_unexpectedly_interrupting_reader_on_run_using_tx_run( # noqa: E501 + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run( # noqa: E501 + "reader_tx_with_unexpected_interruption_on_run.script" + ) + + def _should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( # noqa: E501 + self, interrupting_reader_script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, max_tx_retry_time_ms=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.read_transaction(work) + + session.close() + driver.close() + + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SessionExpiredException", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "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( # noqa: E501 + self): + self._should_fail_when_reading_from_unexpectedly_interrupting_readers_using_tx_function( # noqa: E501 + "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( # noqa: E501 + 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( # noqa: E501 + "reader_tx_with_unexpected_interruption_on_run.script" + ) + + def _should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( # noqa: E501 + self, interrupting_writer_script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, max_tx_retry_time_ms=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.write_transaction(work) + + session.close() + driver.close() + + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SessionExpiredException", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "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( # noqa: E501 + self): + self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( # noqa: E501 + "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( # noqa: E501 + self): + self._should_fail_when_writing_to_unexpectedly_interrupting_writers_using_tx_function( # noqa: E501 + "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() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._writeServer1), + self._writeServer1.address]) + + # 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.begin_transaction() + res = tx.run("RETURN 1 as n") + list(res) + summary = res.consume() + tx.commit() + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._writeServer1), + self._writeServer1.address]) + + 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.write_transaction(work) + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertIsNotNone(res) + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._writeServer1), + self._writeServer1.address]) + + 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.collect_records(result)) + result = tx.run("RETURN %i.2 as n" % work_count) + sequences.append(self.collect_records(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.write_transaction(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.collect_records(result)) + + session.write_transaction(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( # noqa: E501 + self): + self._should_retry_write_until_success_with_leader_change_using_tx_function( # noqa: E501 + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_retry_write_until_success_with_leader_change_on_run_using_tx_function( # noqa: E501 + 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( # noqa: E501 + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function( # noqa: E501 + 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.collect_records(result)) + + session.write_transaction(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( # noqa: E501 + 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: + 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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + 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( # noqa: E501 + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 + "writer_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_session_run( # noqa: E501 + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 + "writer_with_unexpected_interruption_on_run.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_session_run( # noqa: E501 + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run( # noqa: E501 + "writer_with_unexpected_interruption_on_pull.script" + ) + + def _should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 + self, interrupting_writer_script, fails_on_next=False): + # TODO remove this block once all languages work + if get_driver_name() in ["go"]: + self.skipTest("requires investigation") + if get_driver_name() in ["go", "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.begin_transaction() + pipelining_driver = self.driver_supports_features( + types.Feature.OPT_PULL_PIPELINING + ) + # TODO: It will be removed as soon as JS Driver + # has async iterator api + if get_driver_name() in ["javascript"] or not pipelining_driver: + fails_on_next = True + if fails_on_next: + result = tx.run("RETURN 1 as n") + with self.assertRaises(types.DriverError) as exc: + result.next() + else: + with self.assertRaises(types.DriverError) as exc: + tx.run("RETURN 1 as n") + + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SessionExpiredException", + exc.exception.errorType + ) + session.close() + + self.assertNotIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + + @driver_feature(types.Feature.OPT_PULL_PIPELINING) + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 + "writer_tx_with_unexpected_interruption_on_pipelined_pull.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run( # noqa: E501 + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 + "writer_tx_with_unexpected_interruption_on_run.script" + ) + + def test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run( # noqa: E501 + self): + self._should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run( # noqa: E501 + "writer_tx_with_unexpected_interruption_on_pull.script", + fails_on_next=True + ) + + def test_should_fail_discovery_when_router_fails_with_procedure_not_found_code( # noqa: E501 + 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.verify_connectivity() + 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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::ServiceUnavailableException", + 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.verify_connectivity() + 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 ["ruby"]: + self.assertEqual( + "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", "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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + e.errorType + ) + failed = True + session.close() + + self.assertNotIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_on_writer_that_returns_forbidden_on_read_only_database( # noqa: E501 + self): + # TODO remove this block once all languages work + if get_driver_name() in ["go", "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() + + self.assertNotIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_fail_when_writing_on_writer_that_returns_database_unavailable( # noqa: E501 + self): + # TODO remove this block once all languages work + if get_driver_name() in ["go", "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() + + self.assertIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + 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( # noqa: E501 + 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", "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: + 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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + e.errorType + ) + failed = True + + self.assertNotIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + 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( # noqa: E501 + 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", "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.begin_transaction() + 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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + e.errorType + ) + failed = True + session.close() + + self.assertNotIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + 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( # noqa: E501 + 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", "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.begin_transaction() + 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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + e.errorType + ) + failed = True + session.close() + + self.assertNotIn(self._writeServer1.address, + driver.get_routing_table(self.adb).writers) + + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self.assertTrue(failed) + + def test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run( # noqa: E501 + 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.begin_transaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + last_bookmarks = session.last_bookmarks() + 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( # noqa: E501 + 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.begin_transaction() + result = tx.run("RETURN 1 as n") + sequence = self.collect_records(result) + tx.commit() + last_bookmarks = session.last_bookmarks() + 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.begin_transaction() + list(tx.run("CREATE (n {name:'Bob'})")) + tx.commit() + first_bookmark = session.last_bookmarks() + tx = session.begin_transaction() + result = tx.run("MATCH (n) RETURN n.name AS name") + sequence = self.collect_records(result) + tx.commit() + second_bookmark = session.last_bookmarks() + 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.collect_records(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.read_transaction(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.collect_records(result)) + + session.read_transaction(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.read_transaction(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.collect_records(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.write_transaction(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.collect_records(result)) + + session.write_transaction(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.write_transaction(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.collect_records(result)) + + session.read_transaction(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( # noqa: E501 + 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.collect_records(result)) + + session.write_transaction(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( # noqa: E501 + 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( # noqa: E501 + self): + self._should_retry_write_tx_and_rediscovery_until_success( + "writer_tx_with_unexpected_interruption_on_pull.script" + ) + + @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) + 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.update_routing_table() + 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.collect_records(result)) + + session.read_transaction(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.collect_records(result)) + + session.read_transaction(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.collect_records(result)) + + session.read_transaction(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.collect_records(result)) + + session.read_transaction(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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "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) + + @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) + 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.update_routing_table() + session = driver.session("w", bookmarks=["OldBookmark"], + database=self.adb) + sequences = [] + self._routingServer1.done() + try: + driver.update_routing_table() + 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.collect_records(result)) + + session.read_transaction(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) + + @driver_feature(types.Feature.BACKEND_RT_FETCH, + types.Feature.BACKEND_RT_FORCE_UPDATE) + def test_should_fail_on_routing_table_with_no_reader(self): + if get_driver_name() in ["go", "java", "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) + + with self.assertRaises(types.DriverError) as exc: + driver.update_routing_table() + + if get_driver_name() in ["python"]: + self.assertEqual( + exc.exception.errorType, + "" + ) + elif get_driver_name() in ["javascript"]: + self.assertEqual( + exc.exception.code, + "ServiceUnavailable" + ) + + routing_table = driver.get_routing_table() + 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.collect_records(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.begin_transaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + last_bookmarks = session.last_bookmarks() + 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.collect_records(result)) + + session.write_transaction(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, resolver_fn=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.collect_records(result)) + + session.read_transaction(work) + session.read_transaction(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( # noqa: E501 + 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.collect_records(result)) + + session.read_transaction(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.supports_multi_db() + + # 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( # noqa: E501 + 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.collect_records(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( # noqa: E501 + 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 + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + e.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "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_fast_fail_discover(self, script): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, script) + + session = driver.session("r", database=self.adb, bookmarks=["foobar"]) + with self.assertRaises(types.DriverError) as exc: + result = session.run("RETURN 1 as n") + result.next() + + session.close() + driver.close() + + self._routingServer1.done() + + return exc + + @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) + def test_should_fail_with_routing_failure_on_invalid_bookmark_discovery_failure( # noqa: E501 + self): + exc = self._test_fast_fail_discover( + "router_yielding_invalid_bookmark_failure.script", + ) + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.ClientException", + exc.exception.errorType + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::ClientException", + exc.exception.errorType + ) + self.assertEqual("Neo.ClientError.Transaction.InvalidBookmark", + exc.exception.code) + + @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) + def test_should_fail_with_routing_failure_on_invalid_bookmark_mixture_discovery_failure( # noqa: E501 + self): + exc = self._test_fast_fail_discover( + "router_yielding_invalid_bookmark_mixture_failure.script", + ) + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.ClientException", + exc.exception.errorType + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::ClientException", + exc.exception.errorType + ) + + self.assertEqual("Neo.ClientError.Transaction.InvalidBookmarkMixture", + exc.exception.code) + + @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) + def test_should_fail_with_routing_failure_on_forbidden_discovery_failure( + self): + exc = self._test_fast_fail_discover( + "router_yielding_forbidden_failure.script", + ) + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SecurityException", + exc.exception.errorType + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + exc.exception.errorType + ) + if get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SecurityException", + exc.exception.errorType + ) + self.assertEqual( + "Neo.ClientError.Security.Forbidden", + exc.exception.code) + + @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) + def test_should_fail_with_routing_failure_on_any_security_discovery_failure( # noqa: E501 + self): + exc = self._test_fast_fail_discover( + "router_yielding_any_security_failure.script", + ) + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SecurityException", + exc.exception.errorType + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SecurityException", + exc.exception.errorType + ) + self.assertEqual( + "Neo.ClientError.Security.MadeUpSecurityError", + exc.exception.code) + + def test_should_read_successfully_from_reachable_db_after_trying_unreachable_db( # noqa: E501 + 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.collect_records(result) + 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 ["ruby"]: + self.assertEqual( + "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.collect_records(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.collect_records(result) + last_bookmarks = session.last_bookmarks() + 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, failure_script): + + 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 domain_name_resolver(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, + resolver_fn=resolver, domain_name_resolver_fn=domain_name_resolver, + connection_timeout_ms=1000 + ) + self.start_server(self._routingServer1, 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.collect_records(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())) + + @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) + def test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure( # noqa: E501 + self): + self._test_should_request_rt_from_all_initial_routers_until_successful( + "router_yielding_unknown_failure.script" + ) + + @driver_feature(types.Feature.TMP_FAST_FAILING_DISCOVERY) + def test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired( # noqa: E501 + self): + self._test_should_request_rt_from_all_initial_routers_until_successful( + "router_yielding_authorization_expired_failure.script" + ) + + @driver_feature(types.Feature.BACKEND_RT_FORCE_UPDATE) + 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") + + 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, + domain_name_resolver_fn=domain_name_resolver + ) + self.start_server( + self._routingServer1, + "router_yielding_reader1_and_exit.script" + ) + + driver.update_routing_table() + self._routingServer1.done() + router_ip_address = ip_addresses[1] + self.start_server( + self._routingServer1, + "router_yielding_reader1_and_exit.script" + ) + driver.update_routing_table() + # 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, + user_agent=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, + user_agent=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.collect_records(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 + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::IllegalStateException", + e.errorType + ) + failed_on_run = True + + self.assertTrue(failed_on_run) + self._routingServer1.done() + self._readServer1.done() + self.assertTrue(summary.server_info.address in + [get_dns_resolved_server_address(self._readServer1), + self._readServer1.address]) + self.assertEqual([1], sequence) + + def test_should_fail_when_writing_without_writers_using_session_run(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server(self._routingServer1, + "router_yielding_no_writers_adb_sequentially.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: + 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._routingServer1.done() + self.assertTrue(failed) + + def test_should_write_successfully_after_leader_switch_using_tx_run(self): + # TODO remove this block once fixed + if get_driver_name() in ["go"]: + self.skipTest("Fails on tx rollback attempt") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, None) + self.start_server(self._routingServer1, + "router_with_leader_change.script") + self.start_server( + self._writeServer1, + "writer_tx_yielding_failure_on_run.script", + vars_={ + **self.get_vars(), + "#FAILURE#": '{"code": "Neo.ClientError.Cluster.NotALeader", ' + '"message": "message"}' + } + ) + self.start_server( + self._writeServer2, + "writer_tx.script" + ) + + session = driver.session("w", database=self.adb) + + # Attempts: + # 1 - 1 writer that returns an error + # 2 - 1 writer that does not respond + # 3 - 0 writers + for attempt in range(3): + with self.assertRaises(types.DriverError) as e: + tx = session.begin_transaction() + result = tx.run("RETURN 1 as n") + # drivers doing lazy loading should fail here + result.next() + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SessionExpiredException", + e.exception.errorType + ) + elif get_driver_name() in ["python"]: + if attempt == 0: + self.assertEqual( + "", + e.exception.errorType + ) + else: + self.assertEqual( + "", + e.exception.errorType + ) + if attempt == 0: + tx.rollback() + self._writeServer1.done() + + tx = session.begin_transaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + + session.close() + driver.close() + + self._routingServer1.done() + self._writeServer2.done() + + def test_should_rediscover_when_all_connections_fail_using_s_and_tx_run( + self): + # TODO remove this block once fixed + if get_driver_name() in ["go"]: + self.skipTest("Session close fails with ConnectivityError") + # TODO remove this block once fixed + if get_driver_name() in ["javascript"]: + self.skipTest("write_session result consumption times out") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_writer1_sequentially.script") + self.start_server( + self._writeServer1, + "writer_with_unexpected_interruption_on_second_run.script") + self.start_server( + self._readServer1, + "reader_tx_with_unexpected_interruption_on_second_run.script") + self.start_server( + self._readServer2, + "reader_tx_with_unexpected_interruption_on_second_run.script") + + write_session = driver.session("w", database=self.adb) + read_session1 = driver.session("r", database=self.adb) + read_tx1 = read_session1.begin_transaction() + read_session2 = driver.session("r", database=self.adb) + read_tx2 = read_session2.begin_transaction() + + list(write_session.run("RETURN 1 as n")) + list(read_tx1.run("RETURN 1 as n")) + list(read_tx2.run("RETURN 1 as n")) + read_tx1.commit() + read_tx2.commit() + + def run_and_assert_error(runner): + with self.assertRaises(types.DriverError) as exc: + # drivers doing eager loading will fail here + result = runner.run("RETURN 1 as n") + # drivers doing lazy loading should fail here + result.next() + + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SessionExpiredException", + exc.exception.errorType + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + exc.exception.errorType + ) + + run_and_assert_error(write_session) + run_and_assert_error(read_session1.begin_transaction()) + run_and_assert_error(read_session2.begin_transaction()) + + write_session.close() + read_session1.close() + read_session2.close() + route_count1 = self.route_call_count(self._routingServer1) + self._writeServer1.done() + self._readServer1.done() + self._readServer2.done() + + self.start_server(self._writeServer1, "writer.script") + self.start_server(self._readServer1, "reader.script") + + write_session = driver.session("w", database=self.adb) + list(write_session.run("RETURN 1 as n")) + + read_session1 = driver.session("r", database=self.adb) + list(read_session1.run("RETURN 1 as n")) + + driver.close() + self._routingServer1.done() + route_count2 = self.route_call_count(self._routingServer1) + self.assertTrue(route_count2 > route_count1 > 0) + self._writeServer1.done() + self._readServer1.done() + + def test_should_succeed_when_another_conn_fails_and_discover_using_tx_run( + self): + # TODO remove this block once fixed + if get_driver_name() in ["go"]: + self.skipTest("Session close fails with ConnectivityError") + # TODO remove this block once fixed + if get_driver_name() in ["javascript"]: + self.skipTest("Transaction result consumption times out") + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_yielding_writer1_sequentially.script") + self.start_server( + self._writeServer1, + "writer_tx_with_unexpected_interruption_on_run_path.script") + + session1 = driver.session("w", database=self.adb) + tx1 = session1.begin_transaction() + session2 = driver.session("w", database=self.adb) + tx2 = session2.begin_transaction() + + list(tx1.run("RETURN 1 as n")) + + with self.assertRaises(types.DriverError) as exc: + # drivers doing eager loading will fail here + result = tx2.run("RETURN 5 as n") + # drivers doing lazy loading should fail here + result.next() + + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.SessionExpiredException", + exc.exception.errorType + ) + elif get_driver_name() in ["python"]: + self.assertEqual( + "", + exc.exception.errorType + ) + elif get_driver_name() in ["ruby"]: + self.assertEqual( + "Neo4j::Driver::Exceptions::SessionExpiredException", + exc.exception.errorType + ) + + tx1.commit() + session1.close() + session2.close() + route_count1 = self.route_call_count(self._routingServer1) + + # Another discovery is expected since the only writer failed in tx2 + session = driver.session("w", database=self.adb) + tx = session.begin_transaction() + list(tx.run("RETURN 1 as n")) + tx.commit() + + session.close() + driver.close() + self._routingServer1.done() + route_count2 = self.route_call_count(self._routingServer1) + self.assertTrue(route_count2 > route_count1 > 0) + self._writeServer1.done() + + def test_should_get_rt_from_leader_w_and_r_via_leader_using_session_run( + self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + self.start_server( + self._routingServer1, + "router_and_writer_with_sequential_access_and_bookmark.script" + ) + + session = driver.session("w", database=self.adb) + list(session.run("RETURN 1 as n")) + records = list(session.run("RETURN 5 as n")) + session.close() + driver.close() + + self._routingServer1.done() + self.assertEqual([types.Record(values=[types.CypherInt(1)])], records) + + def test_should_get_rt_from_follower_w_and_r_via_leader_using_session_run( + self): + # TODO remove this block once fixed + if get_driver_name() in ["javascript"]: + 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, + "writer_with_sequential_access_and_bookmark.script" + ) + + session = driver.session("w", database=self.adb) + list(session.run("RETURN 1 as n")) + records = list(session.run("RETURN 5 as n")) + session.close() + driver.close() + + self._routingServer1.done() + self.assertEqual([types.Record(values=[types.CypherInt(1)])], records) + + @driver_feature(types.Feature.API_LIVENESS_CHECK, + types.Feature.TMP_GET_CONNECTION_POOL_METRICS) + def test_should_drop_connections_failing_liveness_check(self): + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, liveness_check_timeout_ms=0) + self.start_server(self._routingServer1, + "router_adb_multi_no_bookmarks.script") + self.start_server( + self._writeServer1, + "writer_tx_with_unexpected_interruption_on_status_check.script" + ) + + sessions = [] + txs = [] + + for _ in range(5): + session = driver.session("w", database=self.adb) + tx = session.begin_transaction() + sessions.append(session) + txs.append(tx) + + for tx in txs: + list(tx.run("RETURN 1 as n")) + tx.commit() + + for session in sessions: + session.close() + + self._wait_for_idle_connections(driver, 5) + + session = driver.session("w", database=self.adb) + list(session.run("RETURN 1 as n")) + session.close() + + self._wait_for_idle_connections(driver, 1) + driver.close() + self._routingServer1.done() + self._writeServer1.done() + + @driver_feature(types.Feature.TMP_DRIVER_MAX_CONNECTION_POOL_SIZE, + types.Feature.TMP_CONNECTION_ACQUISITION_TIMEOUT) + def test_should_enforce_pool_size_per_cluster_member(self): + acq_timeout_ms = 100 + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent, max_connection_pool_size=1, + connection_acquisition_timeout_ms=acq_timeout_ms) + self.start_server(self._routingServer1, + "router_adb_multi_no_bookmarks.script") + self.start_server(self._writeServer1, "writer_tx.script") + self.start_server(self._writeServer2, "writer_tx.script") + self.start_server(self._readServer1, "reader_tx.script") + + session0 = driver.session("w", database=self.adb) + tx0 = session0.begin_transaction() + + session1 = driver.session("w", database=self.adb) + tx1 = session1.begin_transaction() + + session2 = driver.session("w", database=self.adb) + + if self.driver_supports_features(types.Feature.OPT_EAGER_TX_BEGIN): + with self.assertRaises(types.DriverError) as exc: + session2.begin_transaction() + else: + with self.assertRaises(types.DriverError) as exc: + tx = session2.begin_transaction() + list(tx.run("RETURN 1 as n")) + + if get_driver_name() in ["java"]: + self.assertEqual( + "org.neo4j.driver.exceptions.ClientException", + exc.exception.errorType + ) + self.assertTrue("Unable to acquire connection from the " + "pool within configured maximum time of " + f"{acq_timeout_ms}ms" + in exc.exception.msg) + + session2.close() + + session3 = driver.session("r", database=self.adb) + tx3 = session3.begin_transaction() + list(tx3.run("RETURN 1 as n")) + tx3.commit() + session3.close() + + list(tx0.run("RETURN 1 as n")) + tx0.commit() + session0.close() + list(tx1.run("RETURN 1 as n")) + tx1.commit() + session1.close() + driver.close() + + self._routingServer1.done() + self._writeServer1.done() + self._readServer1.done() + + def _wait_for_idle_connections(self, driver, expected_idle_connections): + attempts = 0 + while True: + metrics = driver.get_connection_pool_metrics( + self._writeServer1.address) + if metrics.idle == expected_idle_connections: + break + attempts += 1 + if attempts == 10: + self.fail("Timeout out waiting for idle connections") + time.sleep(.1) + + def test_does_not_use_read_connection_for_write(self): + # TODO: remove this block once all languages work + if get_driver_name() in ["javascript", "go", "dotnet", "ruby"]: + self.skipTest("Requires address field in summary") + + def read(tx): + result = tx.run("RETURN 1 as n") + list(result) # exhaust the result + return result.consume() + + def write(tx): + result = tx.run("RETURN 1 as n") + list(result) # exhaust the result + return result.consume() + + self.start_server(self._routingServer1, + "router_adb_multi_no_bookmarks.script") + self.start_server(self._writeServer1, "writer_tx.script") + self.start_server(self._readServer1, "reader_tx.script") + + driver = Driver(self._backend, self._uri_with_context, self._auth, + self._userAgent) + + session = driver.session("w", database=self.adb) + + read_summary = session.read_transaction(read) + write_summary = session.write_transaction(write) + + self.assertNotEqual(read_summary.server_info.address, + write_summary.server_info.address) diff --git a/tests/stub/versions/scripts/v4x0_return_1.script b/tests/stub/versions/scripts/v5x0_return_1.script similarity index 96% rename from tests/stub/versions/scripts/v4x0_return_1.script rename to tests/stub/versions/scripts/v5x0_return_1.script index 76c7ef993..8f5728a55 100644 --- a/tests/stub/versions/scripts/v4x0_return_1.script +++ b/tests/stub/versions/scripts/v5x0_return_1.script @@ -1,4 +1,4 @@ -!: BOLT 4.0 +!: BOLT 5.0 C: HELLO {"{}": "*"} S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"} diff --git a/tests/stub/versions/test_versions.py b/tests/stub/versions/test_versions.py index a4a20bb44..7bc5a0157 100644 --- a/tests/stub/versions/test_versions.py +++ b/tests/stub/versions/test_versions.py @@ -117,10 +117,6 @@ def _run(self, version, server_agent=None, check_version=False, def test_supports_bolt_3x0(self): self._run("3") - @driver_feature(types.Feature.BOLT_4_0) - def test_supports_bolt_4x0(self): - self._run("4x0") - @driver_feature(types.Feature.BOLT_4_1) def test_supports_bolt_4x1(self): self._run("4x1") @@ -137,15 +133,19 @@ def test_supports_bolt_4x3(self): def test_supports_bolt4x4(self): self._run("4x4") + @driver_feature(types.Feature.BOLT_5_0) + def test_supports_bolt5x0(self): + self._run("5x0") + def test_server_version(self): - for version in ("4x4", "4x3", "4x2", "4x1", "4x0", "3"): + for version in ("5x0", "4x4", "4x3", "4x2", "4x1", "3"): if not self.driver_supports_bolt(version): continue with self.subTest(version): self._run(version, check_version=True) def test_server_agent(self): - for version in ("4x4", "4x3", "4x2", "4x1", "4x0", "3"): + for version in ("5x0", "4x4", "4x3", "4x2", "4x1", "3"): for agent, reject in ( ("Neo4j/4.3.0", False), ("Neo4j/4.1.0", False), @@ -175,9 +175,9 @@ def test_server_agent(self): def test_server_address_in_summary(self): # TODO: remove block when all drivers support the address field - if get_driver_name() in ["javascript", "go", "dotnet"]: + if get_driver_name() in ["javascript", "dotnet"]: self.skipTest("Backend doesn't support server address in summary") - for version in ("4x3", "4x2", "4x1", "4x0", "3"): + for version in ("5x0", "4x4", "4x3", "4x2", "4x1", "3"): if not self.driver_supports_bolt(version): continue with self.subTest(version): @@ -185,7 +185,7 @@ def test_server_address_in_summary(self): def test_obtain_summary_twice(self): # TODO: remove block when all drivers support the address field - if get_driver_name() in ["javascript", "go", "dotnet"]: + if get_driver_name() in ["javascript", "dotnet"]: self.skipTest("Backend doesn't support server address in summary") with self._get_session( self.script_path("v4x4_return_1.script"), @@ -210,15 +210,6 @@ def test_should_reject_server_using_verify_connectivity_bolt_3x0(self): self.skipTest("Skipped because it needs investigation") self._test_should_reject_server_using_verify_connectivity(version="3") - @driver_feature(types.Feature.BOLT_4_0) - def test_should_reject_server_using_verify_connectivity_bolt_4x0(self): - # TODO remove this block once fixed - if get_driver_name() in ["dotnet", "go", "javascript"]: - self.skipTest("Skipped because it needs investigation") - self._test_should_reject_server_using_verify_connectivity( - version="4.0" - ) - @driver_feature(types.Feature.BOLT_4_1) def test_should_reject_server_using_verify_connectivity_bolt_4x1(self): # TODO remove this block once fixed @@ -255,6 +246,15 @@ def test_should_reject_server_using_verify_connectivity_bolt_4x4(self): version="4.4" ) + @driver_feature(types.Feature.BOLT_5_0) + def test_should_reject_server_using_verify_connectivity_bolt_5x0(self): + # TODO remove this block once fixed + if get_driver_name() in ["dotnet", "go", "javascript"]: + self.skipTest("Skipped because it needs investigation") + self._test_should_reject_server_using_verify_connectivity( + version="5.0" + ) + def _test_should_reject_server_using_verify_connectivity(self, version): uri = "bolt://%s" % self._server.address driver = Driver(self._backend, uri,