| #region License |
| |
| /* |
| * 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. |
| */ |
| |
| #endregion |
| |
| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using System.Threading.Tasks; |
| using Gremlin.Net.Driver; |
| using Gremlin.Net.Driver.Exceptions; |
| using Gremlin.Net.Driver.Messages; |
| using Gremlin.Net.IntegrationTest.Util; |
| using Gremlin.Net.Process.Traversal; |
| using Gremlin.Net.Structure; |
| using Microsoft.Extensions.Logging; |
| using NSubstitute; |
| using Xunit; |
| |
| namespace Gremlin.Net.IntegrationTest.Driver |
| { |
| public class GremlinClientTests |
| { |
| private readonly RequestMessageProvider _requestMessageProvider = new(); |
| private static readonly string TestHost = ConfigProvider.Configuration["TestServerIpAddress"]!; |
| private static readonly int TestPort = Convert.ToInt32(ConfigProvider.Configuration["TestServerPort"]); |
| |
| [Theory] |
| [InlineData("g.inject('justAString')", "justAString")] |
| [InlineData("g.inject('HelloWorld')", "HelloWorld")] |
| public async Task ShouldSendScriptForEvaluationAndReturnCorrectResult(string requestMsg, string expectedResponse) |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var response = await gremlinClient.SubmitWithSingleResultAsync<string>(requestMsg); |
| |
| Assert.Equal(expectedResponse, response); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldHandleBigResponse() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var requestMsg = "g.inject(1,2,3,4,5,6,7,8,9,10)"; |
| |
| var response = await gremlinClient.SubmitAsync<int>(requestMsg); |
| var results = await response.ToListAsync(); |
| |
| Assert.Equal(10, results.Count); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldHandleResponseWithoutContent() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var gremlinScript = "g.V().has('name','unknownTestName')"; |
| |
| var response = |
| await gremlinClient.SubmitWithSingleResultAsync<object>(gremlinScript); |
| |
| Assert.Null(response); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldThrowExceptionForInvalidScript() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var requestMsg = "invalid"; |
| |
| var exception = |
| await Assert.ThrowsAsync<ResponseException>(async () => |
| { |
| var resultSet = await gremlinClient.SubmitAsync<object>(requestMsg); |
| await resultSet.ToListAsync(); |
| }); |
| |
| Assert.Equal(typeof(ResponseException), exception.GetType()); |
| Assert.Contains("Failed to interpret Gremlin query", exception.Message); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldReassembleResponseBatches() |
| { |
| var expectedResult = new List<int> {1, 2, 3, 4, 5}; |
| var requestScript = "g.inject(1,2,3,4,5)"; |
| var requestMessage = RequestMessage.Build(requestScript).Create(); |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var response = await gremlinClient.SubmitAsync<int>(requestMessage); |
| |
| Assert.Equal(expectedResult, await response.ToListAsync()); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldReturnEnumerableResult() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var expectedResult = new List<int> {1, 2, 3, 4, 5}; |
| var requestMsg = "g.inject(1,2,3,4,5)"; |
| |
| var response = await gremlinClient.SubmitAsync<int>(requestMsg); |
| |
| Assert.Equal(expectedResult, await response.ToListAsync()); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldReturnRawValuesWithBulkResultsFalse() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| // bulkResults=false: the ResultSet contains raw values (no Traverser wrapping). |
| var requestMessage = RequestMessage.Build("g.inject(1,2,3,2,1)") |
| .AddBulkResults(false).Create(); |
| |
| var response = await gremlinClient.SubmitAsync<object>(requestMessage); |
| var results = await response.ToListAsync(); |
| |
| Assert.Equal(5, results.Count); |
| Assert.All(results, r => Assert.IsNotType<Traverser>(r)); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldReturnTraversersWithBulkResultsTrue() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| // bulkResults=true: values are bulked into Traversers, read as Traverser. |
| var requestMessage = RequestMessage.Build("g.inject(1,2,3,2,1)") |
| .AddBulkResults(true).Create(); |
| |
| var response = await gremlinClient.SubmitAsync<Traverser>(requestMessage); |
| var results = await response.ToListAsync(); |
| |
| // 3 unique values with bulk counts summing to the 5 injected. |
| Assert.Equal(3, results.Count); |
| Assert.Equal(5L, results.Sum(t => t.Bulk)); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldThrowOnExecutionOfSimpleInvalidScript() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var invalidRequestScript = "invalid"; |
| |
| await Assert.ThrowsAsync<ResponseException>(async () => |
| { |
| var resultSet = await gremlinClient.SubmitAsync<object>(invalidRequestScript); |
| await resultSet.ToListAsync(); |
| }); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldHandleSimpleScriptWithoutErrors() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var requestMsg = _requestMessageProvider.GetDummyMessage(); |
| |
| await gremlinClient.SubmitAsync(requestMsg); |
| } |
| } |
| |
| [Fact] |
| public async Task ShouldUseParametersForScript() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using (var gremlinClient = new GremlinClient(gremlinServer)) |
| { |
| var requestMsg = "g.inject(3)"; |
| |
| var response = |
| await gremlinClient.SubmitWithSingleResultAsync<int>(requestMsg); |
| |
| Assert.Equal(3, response); |
| } |
| } |
| |
| [Fact] |
| public void ShouldLogWithProvidedLoggerFactory() |
| { |
| var loggerFactory = Substitute.For<ILoggerFactory>(); |
| var logger = Substitute.For<ILogger>(); |
| logger.IsEnabled(Arg.Any<LogLevel>()).Returns(true); |
| loggerFactory.CreateLogger(Arg.Any<string>()).Returns(logger); |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| |
| using var gremlinClient = new GremlinClient(gremlinServer, loggerFactory: loggerFactory); |
| |
| logger.VerifyMessageWasLogged(LogLevel.Information, "connections"); |
| } |
| |
| [Fact] |
| public void ShouldNotLogForDisabledLogLevel() |
| { |
| var loggerFactory = Substitute.For<ILoggerFactory>(); |
| var logger = Substitute.For<ILogger>(); |
| logger.IsEnabled(Arg.Any<LogLevel>()).Returns(false); |
| loggerFactory.CreateLogger(Arg.Any<string>()).Returns(logger); |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| |
| using var gremlinClient = new GremlinClient(gremlinServer, loggerFactory: loggerFactory); |
| |
| logger.VerifyNothingWasLogged(); |
| } |
| |
| [Fact] |
| public async Task ShouldRoundTripSimplePointPdt() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject(PDT(\"Point\", [\"x\":1, \"y\":2]))"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var pdt = Assert.IsType<CompositePDT>(results[0]); |
| Assert.Equal("Point", pdt.Name); |
| Assert.Equal(2, pdt.Fields.Count); |
| Assert.Equal(1, pdt.Fields["x"]); |
| Assert.Equal(2, pdt.Fields["y"]); |
| } |
| |
| [Fact] |
| public async Task ShouldRoundTripNestedPdt() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject(PDT(\"Person\", [\"name\":\"Alice\", \"age\":30, " + |
| "\"address\":PDT(\"Address\", [\"street\":\"123 Main St\", \"city\":\"Springfield\", \"zip\":\"12345\"])]))"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var pdt = Assert.IsType<CompositePDT>(results[0]); |
| Assert.Equal("Person", pdt.Name); |
| Assert.Equal("Alice", pdt.Fields["name"]); |
| Assert.Equal(30, pdt.Fields["age"]); |
| |
| var address = Assert.IsType<CompositePDT>(pdt.Fields["address"]); |
| Assert.Equal("Address", address.Name); |
| Assert.Equal("123 Main St", address.Fields["street"]); |
| Assert.Equal("Springfield", address.Fields["city"]); |
| Assert.Equal("12345", address.Fields["zip"]); |
| } |
| |
| [Fact] |
| public async Task ShouldHandlePdtInCollection() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject([PDT(\"Point\", [\"x\":1, \"y\":2]), PDT(\"Point\", [\"x\":3, \"y\":4])])"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var list = Assert.IsType<List<object>>(results[0]); |
| Assert.Equal(2, list.Count); |
| |
| var p1 = Assert.IsType<CompositePDT>(list[0]); |
| Assert.Equal("Point", p1.Name); |
| Assert.Equal(1, p1.Fields["x"]); |
| Assert.Equal(2, p1.Fields["y"]); |
| |
| var p2 = Assert.IsType<CompositePDT>(list[1]); |
| Assert.Equal("Point", p2.Name); |
| Assert.Equal(3, p2.Fields["x"]); |
| Assert.Equal(4, p2.Fields["y"]); |
| } |
| |
| [Fact] |
| public async Task ShouldAutoSerializeRequestMessageWithInterceptorMutation() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| var interceptors = new List<Func<HttpRequestContext, Task>> |
| { |
| ctx => |
| { |
| if (ctx.Body is RequestMessage msg) |
| { |
| var g = msg.Fields.ContainsKey("g") ? (string)msg.Fields["g"] : "g"; |
| ctx.Body = RequestMessage.Build("g.inject(99)").AddG(g).Create(); |
| } |
| return Task.CompletedTask; |
| } |
| }; |
| |
| using var gremlinClient = new GremlinClient(gremlinServer, interceptors: interceptors); |
| |
| var response = await gremlinClient.SubmitWithSingleResultAsync<int>("g.inject(1)"); |
| Assert.Equal(99, response); |
| } |
| |
| [Fact] |
| public async Task ShouldPropagateExceptionThrownDuringInterceptor() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| var callCount = 0; |
| var interceptors = new List<Func<HttpRequestContext, Task>> |
| { |
| ctx => |
| { |
| callCount++; |
| if (callCount == 1) |
| { |
| throw new InvalidOperationException("interceptor broke"); |
| } |
| return Task.CompletedTask; |
| } |
| }; |
| |
| using var gremlinClient = new GremlinClient(gremlinServer, interceptors: interceptors); |
| |
| // First request should fail with interceptor error |
| var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => |
| { |
| var resultSet = await gremlinClient.SubmitAsync<int>("g.inject(1)"); |
| await resultSet.ToListAsync(); |
| }); |
| Assert.Contains("interceptor broke", ex.Message); |
| |
| // Subsequent request should succeed, proving connection recovery |
| var response = await gremlinClient.SubmitWithSingleResultAsync<int>("g.inject(2)"); |
| Assert.Equal(2, response); |
| } |
| |
| [Fact] |
| public async Task ShouldPropagateErrorWhenInterceptorSetsUnsupportedBodyType() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| var callCount = 0; |
| var interceptors = new List<Func<HttpRequestContext, Task>> |
| { |
| ctx => |
| { |
| callCount++; |
| if (callCount == 1) |
| { |
| ctx.Body = 42; |
| } |
| return Task.CompletedTask; |
| } |
| }; |
| |
| using var gremlinClient = new GremlinClient(gremlinServer, interceptors: interceptors); |
| |
| // First request should fail with serialization error |
| var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => |
| { |
| var resultSet = await gremlinClient.SubmitAsync<int>("g.inject(1)"); |
| await resultSet.ToListAsync(); |
| }); |
| Assert.Contains("Cannot serialize body", ex.Message); |
| |
| // Subsequent request should succeed, proving connection recovery |
| var response = await gremlinClient.SubmitWithSingleResultAsync<int>("g.inject(2)"); |
| Assert.Equal(2, response); |
| } |
| |
| [Fact] |
| public async Task ShouldRoundTripSimplePrimitivePdt() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject(PDT(\"Uint32\", \"42\"))"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var pdt = Assert.IsType<PrimitivePDT>(results[0]); |
| Assert.Equal("Uint32", pdt.Name); |
| Assert.Equal("42", pdt.Value); |
| } |
| |
| [Fact] |
| public async Task ShouldRoundTripPrimitivePdtWithOpaqueValue() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject(PDT(\"Token\", \"007-abc\"))"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var pdt = Assert.IsType<PrimitivePDT>(results[0]); |
| Assert.Equal("Token", pdt.Name); |
| Assert.Equal("007-abc", pdt.Value); |
| } |
| |
| [Fact] |
| public async Task ShouldHandlePrimitivePdtInCollection() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject([PDT(\"Uint32\", \"1\"), PDT(\"Uint32\", \"2\")])"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var list = Assert.IsType<List<object>>(results[0]); |
| Assert.Equal(2, list.Count); |
| |
| var p1 = Assert.IsType<PrimitivePDT>(list[0]); |
| Assert.Equal("1", p1.Value); |
| |
| var p2 = Assert.IsType<PrimitivePDT>(list[1]); |
| Assert.Equal("2", p2.Value); |
| } |
| |
| [Fact] |
| public async Task ShouldRoundTripPrimitivePdtNestedInComposite() |
| { |
| var gremlinServer = new GremlinServer(TestHost, TestPort); |
| using var gremlinClient = new GremlinClient(gremlinServer); |
| |
| var response = await gremlinClient.SubmitAsync<object>( |
| "g.inject(PDT(\"Measurement\", [\"unit\":\"kg\", \"value\":PDT(\"Uint32\", \"100\")]))"); |
| var results = await response.ToListAsync(); |
| |
| Assert.Single(results); |
| var pdt = Assert.IsType<CompositePDT>(results[0]); |
| Assert.Equal("Measurement", pdt.Name); |
| Assert.Equal("kg", pdt.Fields["unit"]); |
| var inner = Assert.IsType<PrimitivePDT>(pdt.Fields["value"]); |
| Assert.Equal("Uint32", inner.Name); |
| Assert.Equal("100", inner.Value); |
| } |
| } |
| } |