blob: 0f14c88b84be0855fdf0a484ad8a2c2df8f50a87 [file]
#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);
}
}
}