blob: e00f2bd48dc632cb376774f3e78c95bc748207c7 [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.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Gremlin.Net.Driver;
using Gremlin.Net.Driver.Exceptions;
using Xunit;
namespace Gremlin.Net.IntegrationTest.Driver
{
public class ClientBehaviorIntegrationTests : IAsyncLifetime
{
private static readonly string Host = GetHost();
private GremlinClient? _client;
private bool _serverAvailable;
private static string GetHost()
{
var url = Environment.GetEnvironmentVariable("GREMLIN_SOCKET_SERVER_URL");
if (string.IsNullOrEmpty(url)) return "localhost";
try
{
return new Uri(url).Host;
}
catch
{
return "localhost";
}
}
public async Task InitializeAsync()
{
var server = new GremlinServer(Host, SocketServerConstants.Port);
_client = new GremlinClient(server);
try
{
var resultSet = await _client.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
await resultSet.ToListAsync();
_serverAvailable = true;
}
catch
{
_serverAvailable = false;
}
}
public Task DisposeAsync()
{
_client?.Dispose();
return Task.CompletedTask;
}
private void SkipIfServerUnavailable()
{
if (!_serverAvailable)
throw new Exception("$XunitDynamicSkip$Socket server not available");
}
private GremlinClient CreateClient()
{
return new GremlinClient(new GremlinServer(Host, SocketServerConstants.Port));
}
[Fact]
public async Task ShouldReceiveSingleVertex()
{
SkipIfServerUnavailable();
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleServerClosingConnectionBeforeResponse()
{
SkipIfServerUnavailable();
var ex = await Assert.ThrowsAsync<HttpRequestException>(async () =>
{
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinCloseConnection);
await resultSet.ToListAsync();
});
Assert.Contains("error occurred while sending the request", ex.Message);
// Recovery
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleServerClosingConnectionAfterResponse()
{
SkipIfServerUnavailable();
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinVertexThenClose);
var results = await resultSet.ToListAsync();
Assert.NotEmpty(results);
await Task.Delay(TimeSpan.FromSeconds(3));
// Recovery
resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleServerErrorAfterDelay()
{
SkipIfServerUnavailable();
var ex = await Assert.ThrowsAsync<ResponseException>(async () =>
{
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinFailAfterDelay);
await resultSet.ToListAsync();
});
Assert.Equal(500, ex.StatusCode);
// Recovery
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandlePartialContentClose()
{
SkipIfServerUnavailable();
var ex = await Assert.ThrowsAsync<HttpIOException>(async () =>
{
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinPartialContentClose);
await resultSet.ToListAsync();
});
Assert.Contains("response ended prematurely", ex.Message);
// Recovery
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleMalformedResponse()
{
SkipIfServerUnavailable();
var ex = await Assert.ThrowsAsync<ResponseException>(async () =>
{
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinMalformedResponse);
await resultSet.ToListAsync();
});
Assert.Equal(ResponseException.NoStatusCode, ex.StatusCode);
Assert.NotNull(ex.InnerException);
// Recovery
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleEmptyResponseBody()
{
SkipIfServerUnavailable();
var ex = await Assert.ThrowsAsync<ResponseException>(async () =>
{
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinEmptyBody);
await resultSet.ToListAsync();
});
Assert.Equal(ResponseException.NoStatusCode, ex.StatusCode);
Assert.IsType<System.IO.IOException>(ex.InnerException);
Assert.Contains("Unexpected end of stream", ex.InnerException!.Message);
// Recovery
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleSlowResponse()
{
SkipIfServerUnavailable();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSlowResponse,
cancellationToken: cts.Token);
var results = await resultSet.ToListAsync(cts.Token);
Assert.NotEmpty(results);
}
[Fact]
public async Task ShouldTimeoutWhenServerNeverResponds()
{
SkipIfServerUnavailable();
using var timeoutClient = CreateClient();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
{
var resultSet = await timeoutClient.SubmitAsync<dynamic>(
SocketServerConstants.GremlinNoResponse, cancellationToken: cts.Token);
await resultSet.ToListAsync(cts.Token);
});
Assert.Contains("operation was canceled", ex.Message);
// Recovery with main client
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldTimeoutWhenServerNeverRespondsUsingReadTimeout()
{
SkipIfServerUnavailable();
// A short ReadTimeout must bound the wait for the initial server response even
// when the caller supplies no cancellation token.
var settings = new ConnectionSettings { ReadTimeout = TimeSpan.FromSeconds(2) };
using var timeoutClient = new GremlinClient(
new GremlinServer(Host, SocketServerConstants.Port), connectionSettings: settings);
var ex = await Assert.ThrowsAsync<TimeoutException>(async () =>
{
var resultSet = await timeoutClient.SubmitAsync<dynamic>(
SocketServerConstants.GremlinNoResponse);
await resultSet.ToListAsync();
});
Assert.Contains("waiting for the initial server response", ex.Message);
// Recovery with main client
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var results = await resultSet.ToListAsync();
Assert.Single(results);
}
[Fact]
public async Task ShouldHandleAsyncRequestsDuringConnectionClose()
{
SkipIfServerUnavailable();
var task1 = Task.Run(async () =>
{
var rs = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinCloseConnection);
await rs.ToListAsync();
});
var task2 = Task.Run(async () =>
{
var rs = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinCloseConnection);
await rs.ToListAsync();
});
var results = await Task.WhenAll(
Task.Run(async () => { try { await task1; return true; } catch { return false; } }),
Task.Run(async () => { try { await task2; return true; } catch { return false; } }));
Assert.Contains(false, results);
// Recovery
var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
var list = await resultSet.ToListAsync();
Assert.Single(list);
}
[Fact]
public async Task ShouldHandleConcurrentMixedRequests()
{
SkipIfServerUnavailable();
var goodTasks = Enumerable.Range(0, 5).Select(_ => Task.Run(async () =>
{
var rs = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
return await rs.ToListAsync();
})).ToList();
var badTasks = Enumerable.Range(0, 5).Select(_ => Task.Run(async () =>
{
var rs = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinCloseConnection);
await rs.ToListAsync();
})).ToList();
var goodResults = await Task.WhenAll(goodTasks.Select(async t =>
{
try { return await t; }
catch { return null; }
}));
var badResults = await Task.WhenAll(badTasks.Select(async t =>
{
try { await t; return false; }
catch { return true; }
}));
Assert.Contains(goodResults, r => r != null && r.Count > 0);
Assert.All(badResults, failed => Assert.True(failed));
}
}
}