Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tx termination tests #574

Merged
merged 13 commits into from
Jul 4, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def _assert_is_timeout_exception(self, e):
def _assert_is_client_exception(self, e):
if get_driver_name() in ["java"]:
self.assertEqual(
"org.neo4j.driver.exceptions.ClientException",
"org.neo4j.driver.exceptions.TransactionTerminatedException",
e.errorType
)
elif get_driver_name() in ["ruby"]:
injectives marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
19 changes: 19 additions & 0 deletions tests/stub/tx_run/scripts/tx_error_on_pull.script
injectives marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
!: BOLT 5.3

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET
C: BEGIN {"{}": "*"}
S: SUCCESS {}
C: RUN "failing on pull" {"{}": "*"} {"{}": "*"}
S: SUCCESS {"fields": ["n"], "qid": 1}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
----
C: PULL {"n": {"Z": "*"}, "qid": 1}
}}
S: RECORD [1]
RECORD [2]
FAILURE {"code": "Neo.ClientError.MadeUp.Code", "message": "message"}
*: RESET
injectives marked this conversation as resolved.
Show resolved Hide resolved
?: GOODBYE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
!: BOLT 5.3

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET
C: BEGIN {"{}": "*"}
S: SUCCESS {}
C: RUN "RETURN 1 AS n" {"{}": "*"} {"{}": "*"}
S: SUCCESS {"fields": ["n"], "qid": 1}
{?
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
----
C: PULL {"n": {"Z": "*"}, "qid": 1}
}}
S: RECORD [1]
RECORD [2]
SUCCESS {"has_more": true, "type": "r"}
injectives marked this conversation as resolved.
Show resolved Hide resolved
?}
C: RUN "failing on pull" {"{}": "*"} {"{}": "*"}
S: SUCCESS {"fields": ["n"], "qid": 2}
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
----
C: PULL {"n": {"Z": "*"}, "qid": 2}
}}
S: FAILURE {"code": "Neo.ClientError.MadeUp.Code", "message": "message"}
*: RESET
?: GOODBYE
27 changes: 27 additions & 0 deletions tests/stub/tx_run/scripts/tx_res0_success_res1_error_on_run.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
!: BOLT 5.3

A: HELLO {"{}": "*"}
A: LOGON {"{}": "*"}
*: RESET
C: BEGIN {"{}": "*"}
S: SUCCESS {}
C: RUN "RETURN 1 AS n" {"{}": "*"} {"{}": "*"}
S: SUCCESS {"fields": ["n"], "qid": 1}
{?
{{
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
----
C: PULL {"n": {"Z": "*"}, "qid": 1}
}}
S: RECORD [1]
RECORD [2]
SUCCESS {"has_more": true, "type": "r"}
?}
C: RUN "invalid" {"{}": "*"} {"{}": "*"}
S: FAILURE {"code": "Neo.ClientError.Statement.SyntaxError", "message": "Invalid input"}
{?
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
S: IGNORED
?}
*: RESET
?: GOODBYE
173 changes: 173 additions & 0 deletions tests/stub/tx_run/test_tx_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,176 @@ def test_failed_tx_run_allows_rollback(self):

def test_failed_tx_run_allows_skipping_rollback(self):
self._test_failed_tx_run(rollback=False)

def test_should_prevent_pull_after_tx_termination_on_run(self):
def _test():
self._create_direct_driver()
script = "tx_res0_success_res1_error_on_run.script"
self._server1.start(path=self.script_path(script))
self._session = self._driver.session("r", fetch_size=2)
tx = self._session.begin_transaction()
res = tx.run("RETURN 1 AS n")

# initiate another stream that fails on RUN
with self.assertRaises(types.DriverError) as exc:
failed_res = tx.run("invalid")
if get_driver_name() in ["javascript"]:
failed_res.next()
injectives marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(exc.exception.code,
"Neo.ClientError.Statement.SyntaxError")
self._assert_is_client_exception(exc)

# there must be no further PULL and an exception must be raised
with self.assertRaises(types.DriverError) as exc:
if iterate == "true":
for _i in range(0, 3):
res.next()
else:
fetch_all = types.Feature.OPT_RESULT_LIST_FETCH_ALL
if self.driver_supports_features(fetch_all):
res.list()
else:
# only explicit iteration is tested if the fetch all is
injectives marked this conversation as resolved.
Show resolved Hide resolved
# not supported
list(res)
# the streaming result surfaces the termination exception
self.assertEqual(exc.exception.code,
"Neo.ClientError.Statement.SyntaxError")
self._assert_is_client_exception(exc)

tx.close()
self._session.close()
self._session = None
self._server1.done()
for iterate in ["true", "false"]:
injectives marked this conversation as resolved.
Show resolved Hide resolved
with self.subTest(iterate=iterate):
_test()
self._server1.reset()

def test_should_prevent_discard_after_tx_termination_on_run(self):
self._create_direct_driver()
script = "tx_res0_success_res1_error_on_run.script"
self._server1.start(path=self.script_path(script))
self._session = self._driver.session("r", fetch_size=2)
tx = self._session.begin_transaction()
res = tx.run("RETURN 1 AS n")

# initiate another stream that fails on RUN
with self.assertRaises(types.DriverError) as exc:
failed_res = tx.run("invalid")
if get_driver_name() in ["javascript"]:
failed_res.next()
self.assertEqual(exc.exception.code,
"Neo.ClientError.Statement.SyntaxError")
self._assert_is_client_exception(exc)

with self.assertRaises(types.DriverError) as exc:
res.consume()
# the streaming result surfaces the termination exception
self.assertEqual(exc.exception.code,
"Neo.ClientError.Statement.SyntaxError")
self._assert_is_client_exception(exc)

tx.close()
self._session.close()
self._session = None
self._server1.done()

def test_should_prevent_run_after_tx_termination_on_pull(self):
def _test():
self._create_direct_driver()
script = "tx_error_on_pull.script"
self._server1.start(path=self.script_path(script))
self._session = self._driver.session("r", fetch_size=2)
tx = self._session.begin_transaction()
res = tx.run("failing on pull")

# res fails on PULL
with self.assertRaises(types.DriverError) as exc:
if iterate == "true":
for _i in range(0, 3):
res.next()
else:
fetch_all = types.Feature.OPT_RESULT_LIST_FETCH_ALL
if self.driver_supports_features(fetch_all):
res.list()
else:
# only explicit iteration is tested if the fetch all is
# not supported
list(res)
self.assertEqual(exc.exception.code,
"Neo.ClientError.MadeUp.Code")
self._assert_is_client_exception(exc)

with self.assertRaises(types.DriverError) as exc:
tx.run("invalid")
# new actions on the transaction result in a tx terminated
# exception, a subclass of the client exception
self._assert_is_tx_terminated_exception(exc)

tx.close()
self._session.close()
self._session = None
self._server1.done()
for iterate in ["true", "false"]:
injectives marked this conversation as resolved.
Show resolved Hide resolved
with self.subTest(iterate=iterate):
_test()
self._server1.reset()

def test_should_prevent_pull_after_tx_termination_on_pull(self):
def _test():
self._create_direct_driver()
script = "tx_res0_success_res1_error_on_pull.script"
self._server1.start(path=self.script_path(script))
self._session = self._driver.session("r", fetch_size=2)
tx = self._session.begin_transaction()
res = tx.run("RETURN 1 AS n")

# initiate another stream that fails on PULL
with self.assertRaises(types.DriverError) as exc:
failed_res = tx.run("failing on pull")
failed_res.next()
self.assertEqual(exc.exception.code,
"Neo.ClientError.MadeUp.Code")
self._assert_is_client_exception(exc)

# there must be no further PULL and an exception must be raised
with self.assertRaises(types.DriverError):
if iterate == "true":
for _i in range(0, 3):
res.next()
else:
fetch_all = types.Feature.OPT_RESULT_LIST_FETCH_ALL
if self.driver_supports_features(fetch_all):
res.list()
else:
# if the fetch all is not supported, only explicit
# iteration can be tested
list(res)
# the streaming result surfaces the termination exception
self.assertEqual(exc.exception.code,
"Neo.ClientError.MadeUp.Code")
self._assert_is_client_exception(exc)

tx.close()
self._session.close()
self._session = None
self._server1.done()
for iterate in ["true", "false"]:
injectives marked this conversation as resolved.
Show resolved Hide resolved
with self.subTest(iterate=iterate):
_test()
self._server1.reset()

def _assert_is_client_exception(self, e):
if get_driver_name() in ["java"]:
self.assertEqual(
"org.neo4j.driver.exceptions.ClientException",
e.exception.errorType
)

def _assert_is_tx_terminated_exception(self, e):
if get_driver_name() in ["java"]:
self.assertEqual(
"org.neo4j.driver.exceptions.TransactionTerminatedException",
e.exception.errorType
)