blob: e77453a225c120af2041ce2fbcb1d905a3116626 [file]
#
# 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 json
from gremlin_python.driver.http_request import HttpRequest
from gremlin_python.driver.request import RequestMessage
def make_request(gremlin="g.V()", fields=None):
msg = RequestMessage(fields=fields or {"g": "g"}, gremlin=gremlin)
return HttpRequest(method="POST", url="http://localhost:8182/gremlin",
headers={"accept": "application/vnd.graphbinary-v4.0"}, body=msg)
class TestBasicInterceptorExecution:
def test_interceptor_receives_request_message_in_body(self):
request = make_request()
captured = []
def interceptor(req):
captured.append(req.body)
interceptor(request)
assert len(captured) == 1
assert isinstance(captured[0], RequestMessage)
def test_interceptor_can_read_and_modify_headers(self):
request = make_request()
request.headers["x-existing"] = "original"
def interceptor(req):
assert req.headers["x-existing"] == "original"
req.headers["x-existing"] = "modified"
req.headers["x-new"] = "added"
interceptor(request)
assert request.headers["x-existing"] == "modified"
assert request.headers["x-new"] == "added"
def test_interceptor_can_modify_uri(self):
request = make_request()
def interceptor(req):
req.url = "http://other-host:9999/gremlin"
interceptor(request)
assert request.url == "http://other-host:9999/gremlin"
def test_interceptors_run_in_registration_order(self):
request = make_request()
order = []
interceptors = [
lambda req: order.append(1),
lambda req: order.append(2),
lambda req: order.append(3),
]
for i in interceptors:
i(request)
assert order == [1, 2, 3]
class TestSerializeBody:
def test_converts_request_message_to_json_bytes(self):
request = make_request(gremlin="g.V().count()")
result = request.serialize_body()
parsed = json.loads(result)
assert parsed["gremlin"] == "g.V().count()"
assert parsed["g"] == "g"
def test_sets_content_type_header(self):
request = make_request()
request.serialize_body()
assert request.headers["content-type"] == "application/json"
def test_sets_content_length_header(self):
request = make_request()
result = request.serialize_body()
assert request.headers["content-length"] == str(len(result))
def test_is_idempotent_with_pre_serialized_bytes(self):
existing = b'{"gremlin":"g.V()"}'
request = HttpRequest(method="POST", url="http://localhost:8182/gremlin",
headers={}, body=existing)
first = request.serialize_body()
second = request.serialize_body()
assert first is existing
assert first is second
def test_is_idempotent_with_request_message(self):
request = make_request()
first = request.serialize_body()
second = request.serialize_body()
assert first is second
def test_includes_all_fields_in_json(self):
msg = RequestMessage(
fields={"g": "g", "language": "gremlin-lang", "timeoutMillis": 5000},
gremlin="g.V()"
)
request = HttpRequest(method="POST", url="http://localhost:8182/gremlin",
headers={}, body=msg)
result = request.serialize_body()
parsed = json.loads(result)
assert parsed["gremlin"] == "g.V()"
assert parsed["g"] == "g"
assert parsed["language"] == "gremlin-lang"
assert parsed["timeoutMillis"] == 5000
def test_raises_on_unsupported_body_type(self):
request = HttpRequest(method="POST", url="http://localhost:8182/gremlin",
headers={}, body=42)
try:
request.serialize_body()
assert False, "expected TypeError for unsupported body type"
except TypeError as e:
assert "int" in str(e)
def test_raises_on_none_body(self):
request = HttpRequest(method="POST", url="http://localhost:8182/gremlin",
headers={}, body=None)
try:
request.serialize_body()
assert False, "expected TypeError for None body"
except TypeError as e:
assert "NoneType" in str(e)
class TestFieldMutation:
def test_interceptor_can_replace_body_before_serialization(self):
request = make_request()
def interceptor(req):
req.body = RequestMessage(fields={"g": "gmodern"}, gremlin="g.E()")
interceptor(request)
request.serialize_body()
parsed = json.loads(request.body)
assert parsed["gremlin"] == "g.E()"
assert parsed["g"] == "gmodern"
class TestAuthInterceptorOrdering:
def test_auth_interceptor_is_always_last(self):
"""Auth interceptor should always be appended to the end of the interceptor list."""
from unittest.mock import MagicMock
from gremlin_python.driver.connection import Connection
order = []
def interceptor1(req):
order.append(1)
def interceptor2(req):
order.append(2)
def auth_interceptor(req):
order.append(3)
conn = Connection(
url="http://localhost:8182/gremlin",
traversal_source="g",
executor=MagicMock(),
pool=MagicMock(),
auth=auth_interceptor,
interceptors=[interceptor1, interceptor2],
enable_user_agent_on_connect=False
)
# Verify the internal interceptor list has auth at the end
assert len(conn._interceptors) == 3
assert conn._interceptors[0] is interceptor1
assert conn._interceptors[1] is interceptor2
assert conn._interceptors[2] is auth_interceptor
def test_auth_interceptor_is_last_even_without_other_interceptors(self):
"""Auth interceptor works when no other interceptors are provided."""
from unittest.mock import MagicMock
from gremlin_python.driver.connection import Connection
def auth_interceptor(req):
pass
conn = Connection(
url="http://localhost:8182/gremlin",
traversal_source="g",
executor=MagicMock(),
pool=MagicMock(),
auth=auth_interceptor,
enable_user_agent_on_connect=False
)
assert len(conn._interceptors) == 1
assert conn._interceptors[0] is auth_interceptor