Skip to content

Commit e29c4cb

Browse files
committed
Add tests for transaction lifetime
E.g., what happens when you commit, a closed (committed, rolled back, ...) transaction or when you try to manage a transaction inside a transaction function.
1 parent 22cce55 commit e29c4cb

File tree

7 files changed

+149
-5
lines changed

7 files changed

+149
-5
lines changed

nutkit/frontend/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .driver import Driver
2+
from .exceptions import ApplicationCodeError

nutkit/frontend/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class ApplicationCodeError(Exception):
2+
pass

nutkit/frontend/session.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
from .. import protocol
2+
from .exceptions import ApplicationCodeError
23
from .result import Result
34
from .transaction import Transaction
45

56

6-
class ApplicationCodeError(Exception):
7-
pass
8-
9-
107
class Session:
118
def __init__(self, driver, session):
129
self._driver = driver

tests/neo4j/test_tx_func_run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from nutkit.frontend.session import ApplicationCodeError
1+
from nutkit.frontend import ApplicationCodeError
22
import nutkit.protocol as types
33
from tests.neo4j.shared import (
44
get_driver,

tests/stub/tx_lifetime/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
!: BOLT 4.4
2+
3+
A: HELLO {"{}": "*"}
4+
*: RESET
5+
C: BEGIN {"{}": "*"}
6+
S: SUCCESS {}
7+
C: RUN {"U": "*"} {"{}": "*"} {"{}": "*"}
8+
S: SUCCESS {"fields": ["n"]}
9+
{*
10+
C: PULL {"n": {"Z": "*"}, "[qid]": -1}
11+
S: RECORD [1]
12+
RECORD [2]
13+
SUCCESS {"has_more": true}
14+
*}
15+
{{
16+
C: DISCARD {"n": -1, "[qid]": -1}
17+
S: SUCCESS {"type": "r"}
18+
{?
19+
{{
20+
C: ROLLBACK
21+
----
22+
C: COMMIT
23+
----
24+
C: RESET
25+
}}
26+
S: SUCCESS {}
27+
?}
28+
----
29+
A: RESET
30+
}}
31+
*: RESET
32+
?: GOODBYE
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from contextlib import contextmanager
2+
3+
from nutkit.frontend import Driver
4+
import nutkit.protocol as types
5+
from tests.shared import (
6+
get_driver_name,
7+
TestkitTestCase,
8+
)
9+
from tests.stub.shared import StubServer
10+
11+
12+
class TestTxLifetime(TestkitTestCase):
13+
def setUp(self):
14+
super().setUp()
15+
self._server = StubServer(9000)
16+
17+
def tearDown(self):
18+
# If test raised an exception this will make sure that the stub server
19+
# is killed and it's output is dumped for analysis.
20+
self._server.reset()
21+
super().tearDown()
22+
23+
@contextmanager
24+
def _start_session(self, script):
25+
uri = "bolt://%s" % self._server.address
26+
driver = Driver(self._backend, uri,
27+
types.AuthorizationToken("basic", principal="",
28+
credentials=""))
29+
self._server.start(path=self.script_path("v4x4", script))
30+
session = driver.session("r", fetch_size=2)
31+
try:
32+
yield session
33+
finally:
34+
session.close()
35+
driver.close()
36+
37+
def _asserts_tx_closed_error(self, exc):
38+
driver = get_driver_name()
39+
assert isinstance(exc, types.DriverError)
40+
if driver in ["python"]:
41+
self.assertEqual(exc.errorType,
42+
"<class 'neo4j.exceptions.TransactionError'>")
43+
self.assertIn("closed", exc.msg.lower())
44+
elif driver in ["javascript", "go", "dotnet"]:
45+
self.assertIn("transaction", exc.msg.lower())
46+
elif driver in ["java"]:
47+
self.assertEqual(exc.errorType,
48+
"org.neo4j.driver.exceptions.ClientException")
49+
else:
50+
self.fail("no error mapping is defined for %s driver" % driver)
51+
52+
def _asserts_tx_managed_error(self, exc):
53+
driver = get_driver_name()
54+
if driver in ["python"]:
55+
self.assertEqual(exc.errorType, "<class 'AttributeError'>")
56+
self.assertIn("managed", exc.msg.lower())
57+
else:
58+
self.fail("no error mapping is defined for %s driver" % driver)
59+
60+
def test_raises_tx_closed_exec(self):
61+
def test(first_action_, second_action_, tx_func_):
62+
def work(tx_):
63+
res_ = tx_.run("Query")
64+
res_.consume()
65+
with self.assertRaises(types.DriverError) as exc_:
66+
getattr(tx_, first_action_)()
67+
self._asserts_tx_managed_error(exc_.exception)
68+
raise exc_.exception
69+
70+
exc = None
71+
script = "tx_inf_results_until_end.script"
72+
with self._start_session(script) as session:
73+
if tx_func_:
74+
with self.assertRaises(types.DriverError):
75+
session.read_transaction(work)
76+
else:
77+
tx = session.begin_transaction()
78+
res = tx.run("Query")
79+
res.consume()
80+
getattr(tx, first_action_)()
81+
if second_action_ == "close":
82+
getattr(tx, second_action_)()
83+
elif second_action_ == "run":
84+
with self.assertRaises(types.DriverError) as exc:
85+
tx.run("Query").consume()
86+
else:
87+
with self.assertRaises(types.DriverError) as exc:
88+
getattr(tx, second_action_)()
89+
self._server.done()
90+
self.assertEqual(
91+
self._server.count_requests("ROLLBACK"),
92+
int(tx_func_ or first_action_ in ["rollback", "close"])
93+
)
94+
self.assertEqual(
95+
self._server.count_requests("COMMIT"),
96+
int(not tx_func_ and first_action_ == "commit")
97+
)
98+
if exc is not None:
99+
self._asserts_tx_closed_error(exc.exception)
100+
101+
for first_action in ("commit", "rollback", "close"):
102+
for tx_func in (True, False):
103+
if tx_func:
104+
second_actions = None,
105+
else:
106+
second_actions = "commit", "rollback", "close", "run"
107+
for second_action in second_actions:
108+
with self.subTest(first_action=first_action,
109+
second_action=second_action,
110+
tx_func=tx_func):
111+
test(first_action, second_action, tx_func)
112+
self._server.reset()

0 commit comments

Comments
 (0)