Skip to content

Commit 2ade794

Browse files
authored
Migrate tests from java driver (#211)
* Migrate tests from java driver (#206) * Migrate tests from java driver This updated also includes: - TransactionClose request support (`Temporary:TransactionClose` feature) - Driver fetch size configuration (`Temporary:DriverFetchSize` feature) Migrated tests: - shouldThrowRollbackErrorWhenTransactionRollback -> test_should_error_on_rollback_failure_using_tx_rollback - shouldThrowRollbackErrorWhenTransactionClose -> test_should_error_on_rollback_failure_using_tx_close - shouldPropagateTransactionRollbackErrorWhenSessionClosed -> test_should_error_on_rollback_failure_using_session_close - shouldStreamingRecordsInBatches -> test_should_accept_custom_fetch_size_using_driver_configuration (protocol bump from 4 to 4.1) - shouldChangeFetchSize -> test_should_accept_custom_fetch_size_using_session_configuration (protocol bump from 4 to 4.1) * Update TMP_DRIVER_FETCH_SIZE feature comment * Update _assert_is_transient_exception check for java driver * Remove pipelining from direct scripts and update fetchSize handling * Add optional RESET to writer_with_custom_fetch_size.script * Skip direct rollback tests for go and javascript * Skip direct rollback test for javascript * Skip test_should_error_on_rollback_failure_using_session_close for dotnet * Skip test_should_accept_custom_fetch_size_using_driver_configuration in v3 (#210)
1 parent 3178db4 commit 2ade794

File tree

10 files changed

+269
-11
lines changed

10 files changed

+269
-11
lines changed

nutkit/frontend/driver.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33

44

55
class Driver:
6-
def __init__(self, backend, uri, authToken, userAgent=None, resolverFn=None, domainNameResolverFn=None,
7-
connectionTimeoutMs=None):
6+
def __init__(self, backend, uri, authToken, userAgent=None, resolverFn=None,
7+
domainNameResolverFn=None,
8+
connectionTimeoutMs=None, fetchSize=None):
89
self._backend = backend
910
self._resolverFn = resolverFn
1011
self._domainNameResolverFn = domainNameResolverFn
11-
req = protocol.NewDriver(uri, authToken, userAgent=userAgent, resolverRegistered=resolverFn is not None,
12-
domainNameResolverRegistered=domainNameResolverFn is not None,
13-
connectionTimeoutMs=connectionTimeoutMs)
12+
req = protocol.NewDriver(
13+
uri, authToken, userAgent=userAgent,
14+
resolverRegistered=resolverFn is not None,
15+
domainNameResolverRegistered=domainNameResolverFn is not None,
16+
connectionTimeoutMs=connectionTimeoutMs,
17+
fetchSize=fetchSize)
1418
res = backend.sendAndReceive(req)
1519
if not isinstance(res, protocol.Driver):
1620
raise Exception("Should be Driver but was %s" % res)

nutkit/frontend/transaction.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ def rollback(self):
2525
res = self._backend.sendAndReceive(req)
2626
if not isinstance(res, protocol.Transaction):
2727
raise Exception("Should be transaction but was: %s" % res)
28+
29+
def close(self):
30+
req = protocol.TransactionClose(self._id)
31+
res = self._backend.sendAndReceive(req)
32+
if not isinstance(res, protocol.Transaction):
33+
raise Exception("Should be transaction but was: %s" % res)

nutkit/protocol/feature.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,10 @@ class Feature(Enum):
4545
# Temporary driver feature that will be removed when all official driver
4646
# backends have implemented path and relationship types
4747
TMP_CYPHER_PATH_AND_RELATIONSHIP = "Temporary:CypherPathAndRelationship"
48+
# Temporary driver feature that will be removed when all official driver
49+
# backends have implemented the TransactionClose request
50+
TMP_TRANSACTION_CLOSE = "Temporary:TransactionClose"
51+
# TODO Update this once the decision has been made.
52+
# Temporary driver feature. There is a pending decision on whether it should
53+
# be supported in all drivers or be removed from all of them.
54+
TMP_DRIVER_FETCH_SIZE = "Temporary:DriverFetchSize"

nutkit/protocol/requests.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ class NewDriver:
3939
Backend should respond with a Driver response or an Error response.
4040
"""
4141

42-
def __init__(self, uri, authToken, userAgent=None, resolverRegistered=False, domainNameResolverRegistered=False,
43-
connectionTimeoutMs=None):
42+
def __init__(self, uri, authToken, userAgent=None, resolverRegistered=False,
43+
domainNameResolverRegistered=False,
44+
connectionTimeoutMs=None, fetchSize=None):
4445
# Neo4j URI to connect to
4546
self.uri = uri
4647
# Authorization token used by driver when connecting to Neo4j
@@ -50,6 +51,12 @@ def __init__(self, uri, authToken, userAgent=None, resolverRegistered=False, dom
5051
self.resolverRegistered = resolverRegistered
5152
self.domainNameResolverRegistered = domainNameResolverRegistered
5253
self.connectionTimeoutMs = connectionTimeoutMs
54+
# TODO: remove assertion and condition as soon as all drivers support
55+
# driver-scoped fetch-size config
56+
from .feature import Feature
57+
assert hasattr(Feature, "TMP_DRIVER_FETCH_SIZE")
58+
if fetchSize is not None:
59+
self.fetchSize = fetchSize
5360

5461

5562
class AuthorizationToken:
@@ -231,6 +238,16 @@ def __init__(self, txId):
231238
self.txId = txId
232239

233240

241+
class TransactionClose:
242+
""" Request to close the transaction instance on the backend.
243+
Backend should respond with a transaction response representing the closed
244+
transaction or an error response.
245+
"""
246+
247+
def __init__(self, txId):
248+
self.txId = txId
249+
250+
234251
class ResultNext:
235252
""" Request to retrieve the next record on a result living on the backend.
236253
Backend should respond with a Record if there is a record, an Error if an

tests/neo4j/test_direct_driver.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,6 @@ def test_multi_db_non_existing(self):
122122
return
123123
self.assertEqual(exc.code,
124124
"Neo.ClientError.Database.DatabaseNotFound")
125-
# TODO remove this block once all languages work
126-
if get_driver_name() in ["java"]:
127-
# does not set exception message
128-
return
129125
self.assertIn("test-database", exc.msg)
130126
self.assertIn("exist", exc.msg)
131127
if get_driver_name() in ["python"]:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
!: BOLT #VERSION#
2+
!: AUTO GOODBYE
3+
4+
C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "#USER_AGENT#" #ROUTING# #EXTRA_HELLO_PROPS#}
5+
S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"}
6+
C: BEGIN {}
7+
S: SUCCESS {}
8+
C: RUN "RETURN 1 as n" {} {}
9+
S: SUCCESS {"fields": ["n"]}
10+
C: PULL_ALL
11+
S: RECORD [1]
12+
SUCCESS {"type": "r"}
13+
C: ROLLBACK
14+
S: FAILURE {"code": "Neo.TransientError.General.DatabaseUnavailable", "message": "Unable to rollback"}
15+
C: RESET
16+
S: SUCCESS {}
17+
{?
18+
C: RESET
19+
S: SUCCESS {}
20+
?}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
!: BOLT #VERSION#
2+
!: AUTO GOODBYE
3+
4+
C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "#USER_AGENT#" #ROUTING# #EXTRA_HELLO_PROPS#}
5+
S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"}
6+
# Added this block for GO
7+
{?
8+
C: RESET
9+
S: SUCCESS {}
10+
?}
11+
C: RUN "RETURN 1 as n" {} {"db": "adb"}
12+
S: SUCCESS {"fields": ["n"]}
13+
C: PULL {"n": 2}
14+
S: RECORD [1]
15+
RECORD [5]
16+
SUCCESS {"has_more": true}
17+
C: PULL {"n": 2}
18+
S: RECORD [7]
19+
SUCCESS {}
20+
C: RESET
21+
S: SUCCESS {}
22+
{?
23+
C: RESET
24+
S: SUCCESS {}
25+
?}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
!: BOLT #VERSION#
2+
!: AUTO GOODBYE
3+
4+
C: HELLO {"scheme": "basic", "credentials": "c", "principal": "p", "user_agent": "#USER_AGENT#" #ROUTING# #EXTRA_HELLO_PROPS#}
5+
S: SUCCESS {"server": "#SERVER_AGENT#", "connection_id": "bolt-123456789"}
6+
C: BEGIN {"db": "adb"}
7+
S: SUCCESS {}
8+
C: RUN "RETURN 1 as n" {} {}
9+
S: SUCCESS {"fields": ["n"]}
10+
C: PULL {"n": 1000}
11+
S: RECORD [1]
12+
SUCCESS {"type": "r"}
13+
C: ROLLBACK
14+
S: FAILURE {"code": "Neo.TransientError.General.DatabaseUnavailable", "message": "Unable to rollback"}
15+
C: RESET
16+
S: SUCCESS {}
17+
{?
18+
C: RESET
19+
S: SUCCESS {}
20+
?}

tests/stub/routing/test_no_routing_v3.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,10 @@ def get_vars(self):
1919
"#USER_AGENT#": '007',
2020
"#ROUTING#": ''
2121
}
22+
23+
def test_should_accept_custom_fetch_size_using_driver_configuration(self):
24+
pass
25+
26+
def test_should_accept_custom_fetch_size_using_session_configuration(
27+
self):
28+
pass

tests/stub/routing/test_no_routing_v4x1.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
get_dns_resolved_server_address,
55
get_driver_name,
66
TestkitTestCase,
7+
driver_feature,
78
)
89
from tests.stub.shared import StubServer
910
from ._routing import get_extra_hello_props
@@ -137,3 +138,158 @@ def test_should_send_custom_user_agent_using_write_session_run(self):
137138
self.assertEqual(summary.server_info.address,
138139
get_dns_resolved_server_address(self._server))
139140
self._server.done()
141+
142+
def test_should_error_on_rollback_failure_using_tx_rollback(self):
143+
# TODO There is a pending unification task to fix this.
144+
# Once fixed, this block should be removed.
145+
if get_driver_name() in ["javascript", "go"]:
146+
self.skipTest("There is a pending unification task to fix this.")
147+
uri = "bolt://%s" % self._server.address
148+
self._server.start(
149+
path=self.script_path(self.version_dir,
150+
"writer_yielding_error_on_rollback.script"),
151+
vars=self.get_vars()
152+
)
153+
driver = Driver(self._backend, uri,
154+
types.AuthorizationToken(scheme="basic", principal="p",
155+
credentials="c"),
156+
userAgent="007")
157+
158+
session = driver.session('w', database=self.adb)
159+
tx = session.beginTransaction()
160+
res = tx.run("RETURN 1 as n")
161+
summary = res.consume()
162+
163+
with self.assertRaises(types.DriverError) as exc:
164+
tx.rollback()
165+
166+
session.close()
167+
driver.close()
168+
169+
self._assert_is_transient_exception(exc.exception)
170+
self.assertEqual(summary.server_info.address,
171+
get_dns_resolved_server_address(self._server))
172+
self._server.done()
173+
174+
@driver_feature(types.Feature.TMP_TRANSACTION_CLOSE)
175+
def test_should_error_on_rollback_failure_using_tx_close(self):
176+
uri = "bolt://%s" % self._server.address
177+
self._server.start(
178+
path=self.script_path(self.version_dir,
179+
"writer_yielding_error_on_rollback.script"),
180+
vars=self.get_vars()
181+
)
182+
driver = Driver(self._backend, uri,
183+
types.AuthorizationToken(scheme="basic", principal="p",
184+
credentials="c"),
185+
userAgent="007")
186+
187+
session = driver.session('w', database=self.adb)
188+
tx = session.beginTransaction()
189+
res = tx.run("RETURN 1 as n")
190+
summary = res.consume()
191+
192+
with self.assertRaises(types.DriverError) as exc:
193+
tx.close()
194+
195+
session.close()
196+
driver.close()
197+
198+
self._assert_is_transient_exception(exc.exception)
199+
self.assertEqual(summary.server_info.address,
200+
get_dns_resolved_server_address(self._server))
201+
self._server.done()
202+
203+
def test_should_error_on_rollback_failure_using_session_close(
204+
self):
205+
# TODO There is a pending unification task to fix this.
206+
# Once fixed, this block should be removed.
207+
if get_driver_name() in ["javascript", "go"]:
208+
self.skipTest("There is a pending unification task to fix this.")
209+
# TODO This needs investigation
210+
if get_driver_name() in ["dotnet"]:
211+
self.skipTest("Throws client exception instead.")
212+
uri = "bolt://%s" % self._server.address
213+
self._server.start(
214+
path=self.script_path(self.version_dir,
215+
"writer_yielding_error_on_rollback.script"),
216+
vars=self.get_vars()
217+
)
218+
driver = Driver(self._backend, uri,
219+
types.AuthorizationToken(scheme="basic", principal="p",
220+
credentials="c"),
221+
userAgent="007")
222+
223+
session = driver.session('w', database=self.adb)
224+
tx = session.beginTransaction()
225+
res = tx.run("RETURN 1 as n")
226+
summary = res.consume()
227+
228+
with self.assertRaises(types.DriverError) as exc:
229+
session.close()
230+
231+
driver.close()
232+
233+
self._assert_is_transient_exception(exc.exception)
234+
self.assertEqual(summary.server_info.address,
235+
get_dns_resolved_server_address(self._server))
236+
self._server.done()
237+
238+
@driver_feature(types.Feature.TMP_DRIVER_FETCH_SIZE)
239+
def test_should_accept_custom_fetch_size_using_driver_configuration(
240+
self):
241+
uri = "bolt://%s" % self._server.address
242+
self._server.start(
243+
path=self.script_path(self.version_dir,
244+
"writer_with_custom_fetch_size.script"),
245+
vars=self.get_vars()
246+
)
247+
driver = Driver(self._backend, uri,
248+
types.AuthorizationToken(scheme="basic", principal="p",
249+
credentials="c"),
250+
userAgent="007", fetchSize=2)
251+
252+
session = driver.session('w', database=self.adb)
253+
res = session.run("RETURN 1 as n")
254+
records = list(res)
255+
256+
session.close()
257+
driver.close()
258+
259+
self.assertEqual([types.Record(values=[types.CypherInt(1)]),
260+
types.Record(values=[types.CypherInt(5)]),
261+
types.Record(values=[types.CypherInt(7)])], records)
262+
self._server.done()
263+
264+
def test_should_accept_custom_fetch_size_using_session_configuration(
265+
self):
266+
uri = "bolt://%s" % self._server.address
267+
self._server.start(
268+
path=self.script_path(self.version_dir,
269+
"writer_with_custom_fetch_size.script"),
270+
vars=self.get_vars()
271+
)
272+
driver = Driver(self._backend, uri,
273+
types.AuthorizationToken(scheme="basic", principal="p",
274+
credentials="c"),
275+
userAgent="007")
276+
277+
session = driver.session('w', database=self.adb, fetchSize=2)
278+
res = session.run("RETURN 1 as n")
279+
records = list(res)
280+
281+
session.close()
282+
driver.close()
283+
284+
self.assertEqual([types.Record(values=[types.CypherInt(1)]),
285+
types.Record(values=[types.CypherInt(5)]),
286+
types.Record(values=[types.CypherInt(7)])], records)
287+
self._server.done()
288+
289+
def _assert_is_transient_exception(self, e):
290+
if get_driver_name() in ["java"]:
291+
self.assertEqual("org.neo4j.driver.exceptions.TransientException",
292+
e.errorType)
293+
self.assertEqual("Unable to rollback", e.msg)
294+
self.assertEqual("Neo.TransientError.General.DatabaseUnavailable",
295+
e.code)

0 commit comments

Comments
 (0)