Skip to content

Commit 76b399d

Browse files
authored
1st round of migrating integration tests to testkit (#558)
1 parent 8df3d23 commit 76b399d

File tree

13 files changed

+252
-649
lines changed

13 files changed

+252
-649
lines changed

testkitbackend/backend.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ def _process(self, request):
165165
if isinstance(e, Neo4jError):
166166
payload["code"] = e.code
167167
self.send_response("DriverError", payload)
168+
except requests.FrontendError as e:
169+
self.send_response("FrontendError", {"msg": str(e)})
168170
except Exception:
169171
tb = traceback.format_exc()
170172
log.error(tb)

testkitbackend/fromtestkit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def to_meta_and_timeout(data):
3636
metadata.mark_all_as_read()
3737
timeout = data.get('timeout', None)
3838
if timeout:
39-
timeout = float(timeout) / 1000
39+
timeout = timeout / 1000
4040
return metadata, timeout
4141

4242

testkitbackend/requests.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
from testkitbackend.fromtestkit import to_meta_and_timeout
2424

2525

26+
class FrontendError(Exception):
27+
pass
28+
29+
2630
def load_config():
2731
with open(path.join(path.dirname(__file__), "test_config.json"), "r") as fd:
2832
config = json.load(fd)
@@ -193,7 +197,7 @@ def SessionRun(backend, data):
193197
result = session.run(query, parameters=params)
194198
key = backend.next_key()
195199
backend.results[key] = result
196-
backend.send_response("Result", {"id": key})
200+
backend.send_response("Result", {"id": key, "keys": result.keys()})
197201

198202

199203
def SessionClose(backend, data):
@@ -244,7 +248,7 @@ def func(tx):
244248
if session_tracker.error_id:
245249
raise backend.errors[session_tracker.error_id]
246250
else:
247-
raise Exception("Client said no")
251+
raise FrontendError("Client said no")
248252

249253
if is_read:
250254
session.read_transaction(func)
@@ -270,7 +274,7 @@ def TransactionRun(backend, data):
270274
result = tx.run(cypher, parameters=params)
271275
key = backend.next_key()
272276
backend.results[key] = result
273-
backend.send_response("Result", {"id": key})
277+
backend.send_response("Result", {"id": key, "keys": result.keys()})
274278

275279

276280
def TransactionCommit(backend, data):
@@ -300,13 +304,43 @@ def ResultNext(backend, data):
300304
def ResultConsume(backend, data):
301305
result = backend.results[data["resultId"]]
302306
summary = result.consume()
307+
from neo4j.work.summary import ResultSummary
308+
assert isinstance(summary, ResultSummary)
303309
backend.send_response("Summary", {
304310
"serverInfo": {
311+
"address": ":".join(map(str, summary.server.address)),
312+
"agent": summary.server.agent,
305313
"protocolVersion":
306314
".".join(map(str, summary.server.protocol_version)),
307-
"agent": summary.server.agent,
308-
"address": ":".join(map(str, summary.server.address)),
309-
}
315+
},
316+
"counters": None if not summary.counters else {
317+
"constraintsAdded": summary.counters.constraints_added,
318+
"constraintsRemoved": summary.counters.constraints_removed,
319+
"containsSystemUpdates": summary.counters.contains_system_updates,
320+
"containsUpdates": summary.counters.contains_updates,
321+
"indexesAdded": summary.counters.indexes_added,
322+
"indexesRemoved": summary.counters.indexes_removed,
323+
"labelsAdded": summary.counters.labels_added,
324+
"labelsRemoved": summary.counters.labels_removed,
325+
"nodesCreated": summary.counters.nodes_created,
326+
"nodesDeleted": summary.counters.nodes_deleted,
327+
"propertiesSet": summary.counters.properties_set,
328+
"relationshipsCreated": summary.counters.relationships_created,
329+
"relationshipsDeleted": summary.counters.relationships_deleted,
330+
"systemUpdates": summary.counters.system_updates,
331+
},
332+
"database": summary.database,
333+
"notifications": summary.notifications,
334+
"plan": summary.plan,
335+
"profile": summary.profile,
336+
"query": {
337+
"text": summary.query,
338+
"parameters": {k: totestkit.field(v)
339+
for k, v in summary.parameters.items()},
340+
},
341+
"queryType": summary.query_type,
342+
"resultAvailableAfter": summary.result_available_after,
343+
"resultConsumedAfter": summary.result_consumed_after,
310344
})
311345

312346

testkitbackend/test_config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
"Optimization:ImplicitDefaultArguments": true,
3333
"Optimization:MinimalResets": "Driver resets some clean connections when put back into pool",
3434
"Optimization:ConnectionReuse": true,
35-
"Optimization:PullPipelining": true
35+
"Optimization:PullPipelining": true,
36+
"Temporary:ResultKeys": true,
37+
"Temporary:FullSummary": true,
38+
"Temporary:CypherPathAndRelationship": true
3639
}
3740
}

testkitbackend/totestkit.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
17-
from neo4j.graph import Node
17+
from neo4j.graph import (
18+
Node,
19+
Path,
20+
Relationship,
21+
)
1822

1923

2024
def record(rec):
@@ -55,5 +59,20 @@ def to(name, val):
5559
"props": field(v._properties),
5660
}
5761
return {"name": "Node", "data": node}
62+
if isinstance(v, Relationship):
63+
rel = {
64+
"id": field(v.id),
65+
"startNodeId": field(v.start_node.id),
66+
"endNodeId": field(v.end_node.id),
67+
"type": field(v.type),
68+
"props": field(v._properties),
69+
}
70+
return {"name": "Relationship", "data": rel}
71+
if isinstance(v, Path):
72+
path = {
73+
"nodes": field(list(v.nodes)),
74+
"relationships": field(list(v.relationships)),
75+
}
76+
return {"name": "Path", "data": path}
5877

5978
raise Exception("Unhandled type:" + str(type(v)))

tests/integration/test_autocommit.py

Lines changed: 2 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -19,228 +19,11 @@
1919
# limitations under the License.
2020

2121

22-
import pytest
23-
2422
from neo4j.work.simple import Query
25-
from neo4j.exceptions import Neo4jError, ClientError, TransientError
26-
from neo4j.graph import Node, Relationship
27-
from neo4j.api import Version
28-
29-
30-
def test_can_run_simple_statement(session):
31-
result = session.run("RETURN 1 AS n")
32-
for record in result:
33-
assert record[0] == 1
34-
assert record["n"] == 1
35-
with pytest.raises(KeyError):
36-
_ = record["x"]
37-
assert record["n"] == 1
38-
with pytest.raises(KeyError):
39-
_ = record["x"]
40-
with pytest.raises(TypeError):
41-
_ = record[object()]
42-
assert repr(record)
43-
assert len(record) == 1
44-
45-
46-
def test_can_run_simple_statement_with_params(session):
47-
count = 0
48-
for record in session.run("RETURN $x AS n",
49-
{"x": {"abc": ["d", "e", "f"]}}):
50-
assert record[0] == {"abc": ["d", "e", "f"]}
51-
assert record["n"] == {"abc": ["d", "e", "f"]}
52-
assert repr(record)
53-
assert len(record) == 1
54-
count += 1
55-
assert count == 1
56-
57-
58-
def test_autocommit_transactions_use_bookmarks(neo4j_driver):
59-
bookmarks = []
60-
# Generate an initial bookmark
61-
with neo4j_driver.session() as session:
62-
session.run("CREATE ()").consume()
63-
bookmark = session.last_bookmark()
64-
assert bookmark is not None
65-
bookmarks.append(bookmark)
66-
# Propagate into another session
67-
with neo4j_driver.session(bookmarks=bookmarks) as session:
68-
assert list(session._bookmarks) == bookmarks
69-
session.run("CREATE ()").consume()
70-
bookmark = session.last_bookmark()
71-
assert bookmark is not None
72-
assert bookmark not in bookmarks
73-
74-
75-
def test_fails_on_bad_syntax(session):
76-
with pytest.raises(Neo4jError):
77-
session.run("X").consume()
78-
79-
80-
def test_fails_on_missing_parameter(session):
81-
with pytest.raises(Neo4jError):
82-
session.run("RETURN {x}").consume()
83-
84-
85-
def test_keys_with_an_error(session):
86-
with pytest.raises(Neo4jError):
87-
result = session.run("X")
88-
list(result.keys())
89-
90-
91-
def test_should_not_allow_empty_statements(session):
92-
with pytest.raises(ValueError):
93-
_ = session.run("")
94-
95-
96-
def test_can_run_statement_that_returns_multiple_records(session):
97-
count = 0
98-
for record in session.run("unwind(range(1, 10)) AS z RETURN z"):
99-
assert 1 <= record[0] <= 10
100-
count += 1
101-
assert count == 10
102-
103-
104-
def test_can_use_with_to_auto_close_session(session):
105-
record_list = list(session.run("RETURN 1"))
106-
assert len(record_list) == 1
107-
for record in record_list:
108-
assert record[0] == 1
109-
110-
111-
def test_can_return_node(neo4j_driver):
112-
with neo4j_driver.session() as session:
113-
record_list = list(session.run("CREATE (a:Person {name:'Alice'}) "
114-
"RETURN a"))
115-
assert len(record_list) == 1
116-
for record in record_list:
117-
alice = record[0]
118-
assert isinstance(alice, Node)
119-
assert alice.labels == {"Person"}
120-
assert dict(alice) == {"name": "Alice"}
121-
122-
123-
def test_can_return_relationship(neo4j_driver):
124-
with neo4j_driver.session() as session:
125-
record_list = list(session.run("CREATE ()-[r:KNOWS {since:1999}]->() "
126-
"RETURN r"))
127-
assert len(record_list) == 1
128-
for record in record_list:
129-
rel = record[0]
130-
assert isinstance(rel, Relationship)
131-
assert rel.type == "KNOWS"
132-
assert dict(rel) == {"since": 1999}
133-
134-
135-
# TODO: re-enable after server bug is fixed
136-
# def test_can_return_path(session):
137-
# with self.driver.session() as session:
138-
# record_list = list(session.run("MERGE p=({name:'Alice'})-[:KNOWS]->"
139-
# "({name:'Bob'}) RETURN p"))
140-
# assert len(record_list) == 1
141-
# for record in record_list:
142-
# path = record[0]
143-
# assert isinstance(path, Path)
144-
# assert path.start_node["name"] == "Alice"
145-
# assert path.end_node["name"] == "Bob"
146-
# assert path.relationships[0].type == "KNOWS"
147-
# assert len(path.nodes) == 2
148-
# assert len(path.relationships) == 1
149-
150-
151-
def test_keys_are_available_before_and_after_stream(session):
152-
result = session.run("UNWIND range(1, 10) AS n RETURN n")
153-
assert list(result.keys()) == ["n"]
154-
list(result)
155-
assert list(result.keys()) == ["n"]
15623

15724

25+
# TODO: this test will stay until a uniform behavior for `.single()` across the
26+
# drivers has been specified and tests are created in testkit
15827
def test_result_single_record_value(session):
15928
record = session.run(Query("RETURN $x"), x=1).single()
16029
assert record.value() == 1
161-
162-
163-
@pytest.mark.parametrize(
164-
"test_input, neo4j_version",
165-
[
166-
("CALL dbms.getTXMetaData", Version(3, 0)),
167-
("CALL tx.getMetaData", Version(4, 0)),
168-
]
169-
)
170-
def test_autocommit_transactions_should_support_metadata(session, test_input, neo4j_version):
171-
# python -m pytest tests/integration/test_autocommit.py -s -r fEsxX -k test_autocommit_transactions_should_support_metadata
172-
metadata_in = {"foo": "bar"}
173-
174-
result = session.run("RETURN 1")
175-
value = result.single().value()
176-
summary = result.consume()
177-
server_agent = summary.server.agent
178-
179-
try:
180-
statement = Query(test_input, metadata=metadata_in)
181-
result = session.run(statement)
182-
metadata_out = result.single().value()
183-
except ClientError as e:
184-
if e.code == "Neo.ClientError.Procedure.ProcedureNotFound":
185-
pytest.skip("Cannot assert correct metadata as {} does not support procedure '{}' introduced in Neo4j {}".format(server_agent, test_input, neo4j_version))
186-
else:
187-
raise
188-
else:
189-
assert metadata_in == metadata_out
190-
191-
192-
def test_autocommit_transactions_should_support_timeout(neo4j_driver):
193-
with neo4j_driver.session() as s1:
194-
s1.run("CREATE (a:Node)").consume()
195-
with neo4j_driver.session() as s2:
196-
tx1 = s1.begin_transaction()
197-
tx1.run("MATCH (a:Node) SET a.property = 1").consume()
198-
try:
199-
result = s2.run(Query("MATCH (a:Node) SET a.property = 2", timeout=0.25))
200-
result.consume()
201-
# On 4.0 and older
202-
except TransientError:
203-
pass
204-
# On 4.1 and forward
205-
except ClientError:
206-
pass
207-
else:
208-
raise
209-
210-
211-
def test_regex_in_parameter(session):
212-
matches = []
213-
result = session.run("UNWIND ['A', 'B', 'C', 'A B', 'B C', 'A B C', "
214-
"'A BC', 'AB C'] AS t WITH t "
215-
"WHERE t =~ $re RETURN t", re=r'.*\bB\b.*')
216-
for record in result:
217-
matches.append(record.value())
218-
assert matches == ["B", "A B", "B C", "A B C"]
219-
220-
221-
def test_regex_inline(session):
222-
matches = []
223-
result = session.run(r"UNWIND ['A', 'B', 'C', 'A B', 'B C', 'A B C', "
224-
r"'A BC', 'AB C'] AS t WITH t "
225-
r"WHERE t =~ '.*\\bB\\b.*' RETURN t")
226-
for record in result:
227-
matches.append(record.value())
228-
assert matches == ["B", "A B", "B C", "A B C"]
229-
230-
231-
def test_automatic_reset_after_failure(session):
232-
try:
233-
result = session.run("X")
234-
result.consume()
235-
except Neo4jError:
236-
result = session.run("RETURN 1")
237-
record = next(iter(result))
238-
assert record[0] == 1
239-
else:
240-
assert False, "A Cypher error should have occurred"
241-
242-
243-
def test_large_values(bolt_driver):
244-
for i in range(1, 7):
245-
with bolt_driver.session() as session:
246-
session.run("RETURN '{}'".format("A" * 2 ** 20))

0 commit comments

Comments
 (0)