| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| import asyncio |
| import os |
| import threading |
| import uuid |
| |
| import pytest |
| from gremlin_python.driver.client import Client |
| from gremlin_python.driver.connection import GremlinServerError |
| from gremlin_python.driver.exceptions import ReadTimeoutError |
| from gremlin_python.driver.request import RequestMessage |
| from gremlin_python.driver.serializer import GraphBinarySerializersV4 |
| from gremlin_python.structure.graph import CompositePDT, PrimitivePDT |
| from gremlin_python.process.graph_traversal import __, GraphTraversalSource |
| from gremlin_python.process.traversal import TraversalStrategies, GValue |
| from gremlin_python.process.strategies import OptionsStrategy |
| from gremlin_python.structure.graph import Graph, Vertex |
| from gremlin_python.driver.aiohttp.transport import AiohttpHTTPTransport |
| from gremlin_python.statics import * |
| |
| __author__ = 'David M. Brown (davebshow@gmail.com)' |
| |
| gremlin_server_url = os.environ.get('GREMLIN_SERVER_URL', 'http://localhost:{}/gremlin') |
| test_no_auth_url = gremlin_server_url.format(45940) |
| |
| |
| def create_basic_request_message(traversal, source='gmodern'): |
| return RequestMessage(fields={'g': source}, gremlin=traversal.gremlin_lang.get_gremlin()) |
| |
| |
| def test_connection(connection): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V() |
| message = create_basic_request_message(t) |
| results_set = connection.write(message).result() |
| future = results_set.all() |
| results = future.result() |
| assert len(results) == 6 |
| assert isinstance(results, list) |
| assert results_set.done.done() |
| |
| def test_client_large_result(client): |
| result_set = client.submit("[\" \".repeat(200000), \" \".repeat(100000)]", request_options={'language': 'gremlin-groovy'}).all().result() |
| assert len(result_set[0]) == 200000 |
| assert len(result_set[1]) == 100000 |
| |
| |
| def test_client_script_submission(client): |
| assert len(client.submit("g.inject(0,0,0,0,0)").all().result()) == 5 |
| |
| |
| def test_client_simple_eval(client): |
| assert client.submit('g.inject(2)').all().result()[0] == 2 |
| |
| |
| def test_client_deflate_compression_round_trip(): |
| # With compression enabled the driver must (1) advertise Accept-Encoding: deflate |
| # on the request and (2) transparently decompress the server's deflate-compressed |
| # response. The interceptor captures a reference to the outgoing headers dict, which |
| # the transport mutates in place to add Accept-Encoding before sending. Use a large, |
| # repetitive payload so the response is actually compressed and spans multiple buffer fills. |
| captured_headers = {} |
| |
| def capture(http_request): |
| # keep a reference; the transport adds Accept-Encoding to this same dict at send time |
| captured_headers['ref'] = http_request.headers |
| |
| client = Client(test_no_auth_url, 'g', compression='deflate', interceptors=capture) |
| try: |
| result = client.submit('[" ".repeat(200000), " ".repeat(100000)]', |
| request_options={'language': 'gremlin-groovy'}).all().result() |
| # (2) decompression succeeded end to end |
| assert len(result[0]) == 200000 |
| assert len(result[1]) == 100000 |
| # (1) the request advertised deflate |
| sent = captured_headers.get('ref', {}) |
| assert sent.get('accept-encoding') == 'deflate', \ |
| "expected Accept-Encoding: deflate on the request, got %r" % sent.get('accept-encoding') |
| finally: |
| client.close() |
| |
| |
| def test_client_simple_eval_parameters(client): |
| assert client.submit('g.V(x).values("age")', {'x': 1}).all().result()[0] == 29 |
| |
| |
| def test_client_eval_traversal(client): |
| assert len(client.submit('g.V()').all().result()) == 6 |
| |
| |
| def test_client_eval_traversal_parameters(client): |
| assert client.submit('g.V(x).values("name")', parameters={'x': 1}).all().result()[0] == 'marko' |
| |
| |
| def test_client_eval_traversal_request_options_parameters(client): |
| assert client.submit('g.V(x).values("name")', request_options={'parameters': {'x': 1}}).all().result()[0] == 'marko' |
| |
| |
| def test_client_eval_traversal_parameters_request_options_parameters(client): |
| # Note that parameters from request_options[parameters] is applied later and will replace parameters if key is the same |
| assert client.submit('g.V(x).values("name")', parameters={'x': 1}, |
| request_options={'parameters': {'x': 2}}).all().result()[0] == 'vadas' |
| |
| |
| def test_client_error(client): |
| try: |
| # should fire an exception |
| client.submit('g.inject(1).math("_/0")').all().result() |
| assert False |
| except GremlinServerError as ex: |
| assert ex.status_message.endswith('Division by zero!') |
| assert ex.status_exception |
| assert str(ex) == f"{ex.status_code}: {ex.status_message}" |
| |
| # still can submit after failure |
| assert client.submit('g.V(x).values("age")', {'x': 1}).all().result()[0] == 29 |
| |
| |
| def test_bad_serialization(client): |
| try: |
| # should timeout |
| client.submit('java.awt.Color.RED', request_options={'language': 'gremlin-groovy'}).all().result() |
| assert False |
| except GremlinServerError as ex: |
| assert ex.status_message |
| assert ex.status_exception |
| assert str(ex) == f"{ex.status_code}: {ex.status_message}" |
| |
| # still can submit after failure |
| assert client.submit('g.V(x).values("age")', {'x': 1}).all().result()[0] == 29 |
| |
| |
| def test_client_connection_pool_after_error(client): |
| # Overwrite fixture with max_connections=1 client |
| client = Client(test_no_auth_url, 'gmodern', max_connections=1) |
| |
| try: |
| # should fire an exception |
| client.submit('g.inject(1).math("_/0")').all().result() |
| assert False |
| except GremlinServerError as gse: |
| # expecting the pool size to be 1 again after query returned |
| assert gse.status_code == 500 |
| assert client.available_pool_size == 1 |
| |
| # still can submit after failure |
| assert client.submit('g.V(x).values("age")', {'x': 1}).all().result()[0] == 29 |
| |
| |
| def test_client_no_hang_if_submit_on_closed(client): |
| assert client.submit('g.inject(2)').all().result()[0] == 2 |
| client.close() |
| try: |
| # should fail since not hang if closed |
| client.submit('g.inject(2)').all().result() |
| assert False |
| except Exception as ex: |
| assert True |
| |
| |
| def test_client_close_all_connection_in_pool(client): |
| client = Client(test_no_auth_url, 'g', max_connections=1) |
| assert client.available_pool_size == 1 |
| client.submit('g.inject(4)').all().result() |
| client.close() |
| assert client.available_pool_size == 0 |
| |
| |
| def test_client_side_timeout_set_for_aiohttp(client): |
| client = Client(test_no_auth_url, 'gmodern', |
| read_timeout_millis=1000, write_timeout=1) |
| |
| try: |
| # should fire an exception |
| client.submit('Thread.sleep(2000);1', request_options={'language': 'gremlin-groovy'}).all().result() |
| assert False |
| except ReadTimeoutError as err: |
| # The driver normalizes a read timeout to a single ReadTimeoutError (a builtin |
| # TimeoutError subclass) with a deterministic message, so we assert on both the |
| # type (still catchable via `except TimeoutError`) and the message. |
| assert isinstance(err, TimeoutError) |
| assert str(err) == "Read timed out after 1.0s waiting for response data." |
| |
| # still can submit after failure |
| assert client.submit('g.V(x).values("age")', {'x': 1}).all().result()[0] == 29 |
| |
| |
| async def async_connect(enable): |
| try: |
| transport = AiohttpHTTPTransport(call_from_event_loop=enable) |
| transport.connect(test_no_auth_url) |
| transport.close() |
| return True |
| except RuntimeError: |
| return False |
| |
| |
| def test_from_event_loop(): |
| assert not asyncio.get_event_loop().run_until_complete(async_connect(False)) |
| assert asyncio.get_event_loop().run_until_complete(async_connect(True)) |
| |
| |
| def test_client_submit(client): |
| result_set = client.submit('g.V(1)') |
| result = result_set.all().result() |
| assert 1 == len(result) |
| vertex = result[0] |
| assert type(vertex) is Vertex |
| assert 1 == vertex.id |
| assert 2 == len(vertex.properties) |
| assert 'name' == vertex.properties[0].key |
| assert 'marko' == vertex.properties[0].value |
| ## |
| result_set = client.submit('g.with("materializeProperties", "tokens").V(1)') |
| result = result_set.all().result() |
| assert 1 == len(result) |
| vertex = result[0] |
| assert 1 == vertex.id |
| assert 0 == len(vertex.properties) |
| ## |
| result_set = client.submit('g.with("materializeProperties", "tokens").E(7)') |
| result = result_set.all().result() |
| assert 1 == len(result) |
| edge = result[0] |
| assert 7 == edge.id |
| assert 0 == len(edge.properties) |
| ## |
| result_set = client.submit('g.with("materializeProperties", "tokens").V(1).properties()') |
| result = result_set.all().result() |
| assert 2 == len(result) |
| for vp in result: |
| assert 0 == len(vp.properties) |
| |
| |
| def test_client_gremlin_lang(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V() |
| message = create_basic_request_message(t) |
| result_set = client.submit(message) |
| assert len(result_set.all().result()) == 6 |
| |
| |
| def test_client_gremlin_lang_options(client): |
| # smoke test to validate serialization of OptionsStrategy. no way to really validate this from an integration |
| # test perspective because there's no way to access the internals of the strategy via bytecode |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.with_strategies(OptionsStrategy(**{"x": "test", "y": True})).V() |
| message = create_basic_request_message(t) |
| result_set = client.submit(message) |
| assert len(result_set.all().result()) == 6 |
| ## |
| t = g.with_("x", "test").with_("y", True).V() |
| message = create_basic_request_message(t) |
| result_set = client.submit(message) |
| assert len(result_set.all().result()) == 6 |
| |
| |
| def test_client_gremlin_lang_request_options_with_parameter(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| # Note that parameters for constructed traversals is done via Parameter only |
| t = g.with_('language', 'gremlin-lang').V(GValue('x', [1, 2, 3])).count() |
| request_opts = {'language': 'gremlin-lang', 'parameters': {'x': [1, 2, 3]}} |
| message = create_basic_request_message(t) |
| result_set = client.submit(message, request_options=request_opts) |
| assert result_set.all().result()[0] == 3 |
| # We can re-use the extracted request options in script submission |
| result_set = client.submit('g.V(x).values("name")', request_options=request_opts) |
| assert result_set.all().result()[0] == 'marko' |
| # For script submission only, we can also add parameters to request options and they will be applied |
| request_opts2 = {'language': 'gremlin-lang', 'parameters': {'y': 4}} |
| result_set = client.submit('g.V(y).values("name")', request_options=request_opts2) |
| assert result_set.all().result()[0] == 'josh' |
| result_set = client.submit('g.V(z).values("name")', parameters={'z': 5}) |
| assert result_set.all().result()[0] == 'ripple' |
| |
| |
| def test_iterate_result_set(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V() |
| message = create_basic_request_message(t) |
| result_set = client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 6 |
| |
| |
| def test_client_async(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V() |
| message = create_basic_request_message(t) |
| future = client.submit_async(message) |
| result_set = future.result() |
| assert len(result_set.all().result()) == 6 |
| |
| |
| def test_connection_share(client): |
| # Overwrite fixture with max_connections=1 client |
| client = Client(test_no_auth_url, 'gmodern', max_connections=1) |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V() |
| message = create_basic_request_message(t) |
| message2 = create_basic_request_message(t) |
| future = client.submit_async(message) |
| future2 = client.submit_async(message2) |
| |
| result_set2 = future2.result() |
| assert len(result_set2.all().result()) == 6 |
| |
| # This future has to finish for the second to yield result - max_connections=1 |
| assert future.done() |
| result_set = future.result() |
| assert len(result_set.all().result()) == 6 |
| |
| |
| def test_multi_conn_pool(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V() |
| message = create_basic_request_message(t) |
| message2 = create_basic_request_message(t) |
| client = Client(test_no_auth_url, 'g', max_connections=1) |
| future = client.submit_async(message) |
| future2 = client.submit_async(message2) |
| |
| result_set2 = future2.result() |
| assert len(result_set2.all().result()) == 6 |
| |
| # with connection pool `future` may or may not be done here |
| result_set = future.result() |
| assert len(result_set.all().result()) == 6 |
| |
| |
| def test_multi_thread_pool(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| traversals = [g.V(), |
| g.V().count(), |
| g.E(), |
| g.E().count() |
| ] |
| results = [[] for _ in traversals] |
| |
| # Use a condition variable to synchronise a group of threads, which should also inject some |
| # non-determinism into the run-time execution order |
| condition = threading.Condition() |
| |
| def thread_run(tr, result_list): |
| message = create_basic_request_message(tr) |
| with condition: |
| condition.wait(5) |
| result_set = client.submit(message) |
| for result in result_set: |
| result_list.append(result) |
| |
| threads = [] |
| for i in range(len(results)): |
| thread = threading.Thread(target=thread_run, |
| args=(traversals[i], results[i]), |
| name="test_multi_thread_pool_%d" % i) |
| thread.daemon = True |
| threads.append(thread) |
| thread.start() |
| with condition: |
| condition.notify_all() |
| |
| for t in threads: |
| t.join(5) |
| |
| assert len(results[0]) == 6 |
| assert results[1][0] == 6 |
| assert len(results[2]) == 6 |
| assert results[3][0] == 6 |
| |
| |
| def test_client_gremlin_lang_with_short(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.with_('language', 'gremlin-lang').V().has('age', short(16)).count() |
| request_opts = {'language': 'gremlin-lang'} |
| message = create_basic_request_message(t) |
| result_set = client.submit(message, request_options=request_opts) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1 |
| |
| |
| def test_client_gremlin_lang_with_long(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.V().has('age', long(851401972585122)).count() |
| request_opts = {} |
| message = create_basic_request_message(t) |
| result_set = client.submit(message, request_options=request_opts) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1 |
| |
| |
| def test_client_gremlin_lang_with_bigint(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.with_('language', 'gremlin-lang').V().has('age', bigint(0x1000_0000_0000_0000_0000)).count() |
| request_opts = {'language': 'gremlin-lang'} |
| message = create_basic_request_message(t) |
| result_set = client.submit(message, request_options=request_opts) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1 |
| |
| |
| def test_big_result_set(client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.inject(1).repeat(__.add_v('person').property('name', __.loops())).times(20000).count() |
| message = create_basic_request_message(t, source='g') |
| result_set = client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1 |
| |
| t = g.V().limit(10) |
| message = create_basic_request_message(t, source='g') |
| result_set = client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 10 |
| |
| t = g.V().limit(100) |
| message = create_basic_request_message(t, source='g') |
| result_set = client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 100 |
| |
| t = g.V().limit(1000) |
| message = create_basic_request_message(t, source='g') |
| result_set = client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1000 |
| |
| t = g.V().limit(10000) |
| message = create_basic_request_message(t, source='g') |
| result_set = client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 10000 |
| |
| |
| def test_big_result_set_secure(authenticated_client): |
| g = GraphTraversalSource(Graph(), TraversalStrategies()) |
| t = g.inject(1).repeat(__.add_v('person').property('name', __.loops())).times(20000).count() |
| message = create_basic_request_message(t, source='g') |
| result_set = authenticated_client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1 |
| |
| t = g.V().limit(10) |
| message = create_basic_request_message(t, source='g') |
| result_set = authenticated_client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 10 |
| |
| t = g.V().limit(100) |
| message = create_basic_request_message(t, source='g') |
| result_set = authenticated_client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 100 |
| |
| t = g.V().limit(1000) |
| message = create_basic_request_message(t, source='g') |
| result_set = authenticated_client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 1000 |
| |
| t = g.V().limit(10000) |
| message = create_basic_request_message(t, source='g') |
| result_set = authenticated_client.submit(message) |
| results = [] |
| for result in result_set: |
| results.append(result) |
| assert len(results) == 10000 |
| |
| |
| async def asyncio_func(): |
| return 1 |
| |
| |
| def test_asyncio(client): |
| try: |
| asyncio.get_event_loop().run_until_complete(asyncio_func()) |
| except RuntimeError: |
| assert False |
| |
| |
| # TODO: tests pass because requestID is now generated on HTTP server and this option gets ignored, tests to be removed |
| # or updated depending on if we still want to use requestID or not |
| @pytest.mark.skip(reason="requestID is generated on server side only, disable for now") |
| def test_client_custom_invalid_request_id_graphbinary_script(client): |
| client = Client(test_no_auth_url, 'gmodern') |
| try: |
| client.submit('g.V()', request_options={"requestId": "malformed"}).all().result() |
| except Exception as ex: |
| assert "badly formed hexadecimal UUID string" in str(ex) |
| |
| |
| @pytest.mark.skip(reason="requestID is generated on server side only, disable for now") |
| def test_client_custom_valid_request_id_script_uuid(client): |
| assert len(client.submit('g.V()', request_options={"requestId": uuid.uuid4()}).all().result()) == 6 |
| |
| |
| @pytest.mark.skip(reason="requestID is generated on server side only, disable for now") |
| def test_client_custom_valid_request_id_script_string(client): |
| assert len(client.submit('g.V()', request_options={"requestId": str(uuid.uuid4())}).all().result()) == 6 |
| |
| |
| @pytest.mark.skip(reason="requestID is generated on server side only, disable for now") |
| def test_client_custom_invalid_request_id_graphbinary_bytecode(client): |
| client = Client(test_no_auth_url, 'gmodern') |
| query = GraphTraversalSource(Graph(), TraversalStrategies()).V().bytecode |
| try: |
| client.submit(query, request_options={"requestId": "malformed"}).all().result() |
| except Exception as ex: |
| assert "badly formed hexadecimal UUID string" in str(ex) |
| |
| |
| @pytest.mark.skip(reason="requestID is generated on server side only, disable for now") |
| def test_client_custom_valid_request_id_bytecode(client): |
| query = GraphTraversalSource(Graph(), TraversalStrategies()).V().bytecode |
| assert len(client.submit(query).all().result()) == 6 |
| |
| def test_response_serializer_never_None(): |
| client = Client('url', 'g', response_serializer=None) |
| resp_ser = client.response_serializer() |
| assert resp_ser is not None |
| |
| |
| def test_serializer_and_interceptor_forwarded(client_with_interceptor): |
| result = client_with_interceptor.submit("g.inject(1)").next() |
| assert 2 == result # interceptor changes request to g.inject(2) |
| |
| def test_simple_pdt_round_trip(client): |
| """Inject and retrieve a simple Point PDT.""" |
| results = client.submit( |
| "g.inject(PDT(\"Point\", [\"x\":1, \"y\":2]))" |
| ).all().result() |
| |
| assert len(results) == 1 |
| pdt = results[0] |
| assert isinstance(pdt, CompositePDT) |
| assert pdt.name == 'Point' |
| assert pdt.fields['x'] == 1 |
| assert pdt.fields['y'] == 2 |
| |
| |
| def test_nested_pdt(client): |
| """Inject and retrieve a nested PDT (Person containing Address).""" |
| results = client.submit( |
| "g.inject(PDT(\"Person\", [\"name\":\"Alice\", \"age\":30, " |
| "\"address\":PDT(\"Address\", [\"street\":\"123 Main St\", \"city\":\"Springfield\", \"zip\":\"12345\"])]))" |
| ).all().result() |
| |
| assert len(results) == 1 |
| pdt = results[0] |
| assert isinstance(pdt, CompositePDT) |
| assert pdt.name == 'Person' |
| assert pdt.fields['name'] == 'Alice' |
| assert pdt.fields['age'] == 30 |
| |
| address = pdt.fields['address'] |
| assert isinstance(address, CompositePDT) |
| assert address.name == 'Address' |
| assert address.fields['street'] == '123 Main St' |
| assert address.fields['city'] == 'Springfield' |
| assert address.fields['zip'] == '12345' |
| |
| |
| def test_pdt_in_collection(client): |
| """Retrieve multiple PDTs as a list.""" |
| results = client.submit( |
| "g.inject([PDT(\"Point\", [\"x\":1, \"y\":2]), PDT(\"Point\", [\"x\":3, \"y\":4])])" |
| ).all().result() |
| |
| assert len(results) == 1 |
| pdt_list = results[0] |
| assert isinstance(pdt_list, list) |
| assert len(pdt_list) == 2 |
| |
| assert isinstance(pdt_list[0], CompositePDT) |
| assert pdt_list[0].name == 'Point' |
| assert pdt_list[0].fields['x'] == 1 |
| assert pdt_list[0].fields['y'] == 2 |
| |
| assert isinstance(pdt_list[1], CompositePDT) |
| assert pdt_list[1].name == 'Point' |
| assert pdt_list[1].fields['x'] == 3 |
| assert pdt_list[1].fields['y'] == 4 |
| |
| |
| def test_auto_serializes_request_message_with_interceptor_mutation(): |
| """Verifies the driver auto-serializes when an interceptor modifies the RequestMessage body.""" |
| from gremlin_python.driver.request import RequestMessage |
| |
| def swap_query(http_request): |
| if isinstance(http_request.body, RequestMessage): |
| http_request.body = RequestMessage(fields={"g": "gmodern"}, gremlin="g.inject(99)") |
| |
| client = Client(test_no_auth_url, 'gmodern', |
| max_connections=1, interceptors=swap_query) |
| try: |
| result = client.submit("g.inject(1)").next() |
| assert 99 == result |
| finally: |
| client.close() |
| |
| |
| def test_interceptor_errors_propagate(): |
| """Verifies that an interceptor error propagates to the caller, the request is not sent, |
| and the client remains usable for subsequent requests.""" |
| call_count = [0] |
| |
| def failing_interceptor(http_request): |
| call_count[0] += 1 |
| if call_count[0] == 1: |
| raise RuntimeError("interceptor broke") |
| |
| client = Client(test_no_auth_url, 'gmodern', |
| max_connections=1, interceptors=failing_interceptor) |
| try: |
| # First request should fail with interceptor error |
| try: |
| client.submit("g.inject(1)").next() |
| assert False, "Should have thrown an exception" |
| except RuntimeError as e: |
| assert "interceptor broke" in str(e) |
| |
| # Subsequent request should succeed, proving the client is still usable |
| result = client.submit("g.inject(2)").next() |
| assert 2 == result |
| finally: |
| client.close() |
| |
| |
| def test_primitive_pdt_round_trip(client): |
| """Inject and retrieve a primitive Uint32 PDT (opaque string value).""" |
| results = client.submit( |
| "g.inject(PDT(\"Uint32\", \"4294967295\"))" |
| ).all().result() |
| |
| assert len(results) == 1 |
| pdt = results[0] |
| assert isinstance(pdt, PrimitivePDT) |
| assert pdt.name == 'Uint32' |
| assert pdt.value == '4294967295' |
| |
| |
| def test_primitive_pdt_in_collection(client): |
| """Retrieve multiple primitive PDTs of different kinds as a list.""" |
| results = client.submit( |
| "g.inject([PDT(\"Uint32\", \"42\"), PDT(\"TinkerId\", \"abc-123\")])" |
| ).all().result() |
| |
| assert len(results) == 1 |
| pdt_list = results[0] |
| assert isinstance(pdt_list, list) |
| assert len(pdt_list) == 2 |
| |
| assert isinstance(pdt_list[0], PrimitivePDT) |
| assert pdt_list[0].name == 'Uint32' |
| assert pdt_list[0].value == '42' |
| |
| assert isinstance(pdt_list[1], PrimitivePDT) |
| assert pdt_list[1].name == 'TinkerId' |
| assert pdt_list[1].value == 'abc-123' |
| |
| |
| def test_primitive_pdt_nested_in_composite(client): |
| """Inject and retrieve a composite PDT containing a nested primitive PDT.""" |
| results = client.submit( |
| "g.inject(PDT(\"Measurement\", [\"unit\":\"meters\", \"quantity\":PDT(\"Uint32\", \"100\")]))" |
| ).all().result() |
| |
| assert len(results) == 1 |
| pdt = results[0] |
| assert isinstance(pdt, CompositePDT) |
| assert pdt.name == 'Measurement' |
| assert pdt.fields['unit'] == 'meters' |
| |
| quantity = pdt.fields['quantity'] |
| assert isinstance(quantity, PrimitivePDT) |
| assert quantity.name == 'Uint32' |
| assert quantity.value == '100' |