Add gRPC server example with file download
This commit is contained in:
233
TestGrpc.Test/UnitTest1.cs
Normal file
233
TestGrpc.Test/UnitTest1.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using Grpc.Core;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace TestGrpc.Test;
|
||||
|
||||
public class UnitTest1 : IntegrationTestBase
|
||||
{
|
||||
public UnitTest1(GrpcTestFixture<Startup> fixture, ITestOutputHelper outputHelper)
|
||||
: base(fixture, outputHelper)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test1()
|
||||
{
|
||||
// Arrange
|
||||
var client = new Greeter.GreeterClient(Channel);
|
||||
|
||||
// Act
|
||||
using var response = client.DownloadFile(new FileRequest { FilePath = "Files/test.txt" });
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
var receivedFileName = string.Empty;
|
||||
await foreach (var chunk in response.ResponseStream.ReadAllAsync())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(chunk.FileName))
|
||||
{
|
||||
receivedFileName = chunk.FileName;
|
||||
}
|
||||
|
||||
memoryStream.Write(chunk.Chunk.Span);
|
||||
}
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
using var reader = new StreamReader(memoryStream);
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = await reader.ReadLineAsync();
|
||||
Console.WriteLine(line);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test.txt", receivedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ForwardingLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly LogMessage _logAction;
|
||||
|
||||
public ForwardingLoggerProvider(LogMessage logAction)
|
||||
{
|
||||
_logAction = logAction;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new ForwardingLogger(categoryName, _logAction);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
internal class ForwardingLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly LogMessage _logAction;
|
||||
|
||||
public ForwardingLogger(string categoryName, LogMessage logAction)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_logAction = logAction;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
_logAction(logLevel, _categoryName, eventId, formatter(state, exception), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class GrpcTestContext<TStartup> : IDisposable where TStartup : class
|
||||
{
|
||||
private readonly Stopwatch _stopwatch;
|
||||
private readonly GrpcTestFixture<TStartup> _fixture;
|
||||
private readonly ITestOutputHelper _outputHelper;
|
||||
|
||||
public GrpcTestContext(GrpcTestFixture<TStartup> fixture, ITestOutputHelper outputHelper)
|
||||
{
|
||||
_stopwatch = Stopwatch.StartNew();
|
||||
_fixture = fixture;
|
||||
_outputHelper = outputHelper;
|
||||
_fixture.LoggedMessage += WriteMessage;
|
||||
}
|
||||
|
||||
private void WriteMessage(LogLevel logLevel, string category, EventId eventId, string message, Exception? exception)
|
||||
{
|
||||
var log = $"{_stopwatch.Elapsed.TotalSeconds:N3}s {category} - {logLevel}: {message}";
|
||||
if (exception != null)
|
||||
{
|
||||
log += Environment.NewLine + exception.ToString();
|
||||
}
|
||||
_outputHelper.WriteLine(log);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_fixture.LoggedMessage -= WriteMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void LogMessage(LogLevel logLevel, string categoryName, EventId eventId, string message, Exception? exception);
|
||||
|
||||
public class GrpcTestFixture<TStartup> : IDisposable where TStartup : class
|
||||
{
|
||||
private TestServer? _server;
|
||||
private IHost? _host;
|
||||
private HttpMessageHandler? _handler;
|
||||
private Action<IWebHostBuilder>? _configureWebHost;
|
||||
|
||||
public event LogMessage? LoggedMessage;
|
||||
|
||||
public GrpcTestFixture()
|
||||
{
|
||||
LoggerFactory = new LoggerFactory();
|
||||
LoggerFactory.AddProvider(new ForwardingLoggerProvider((logLevel, category, eventId, message, exception) =>
|
||||
{
|
||||
LoggedMessage?.Invoke(logLevel, category, eventId, message, exception);
|
||||
}));
|
||||
}
|
||||
|
||||
public void ConfigureWebHost(Action<IWebHostBuilder> configure)
|
||||
{
|
||||
_configureWebHost = configure;
|
||||
}
|
||||
|
||||
private void EnsureServer()
|
||||
{
|
||||
if (_host == null)
|
||||
{
|
||||
var builder = new HostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<ILoggerFactory>(LoggerFactory);
|
||||
})
|
||||
.ConfigureWebHostDefaults(webHost =>
|
||||
{
|
||||
webHost
|
||||
.UseTestServer()
|
||||
.UseStartup<TStartup>();
|
||||
|
||||
_configureWebHost?.Invoke(webHost);
|
||||
});
|
||||
_host = builder.Start();
|
||||
_server = _host.GetTestServer();
|
||||
_handler = _server.CreateHandler();
|
||||
}
|
||||
}
|
||||
|
||||
public LoggerFactory LoggerFactory { get; }
|
||||
|
||||
public HttpMessageHandler Handler
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureServer();
|
||||
return _handler!;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_handler?.Dispose();
|
||||
_host?.Dispose();
|
||||
_server?.Dispose();
|
||||
}
|
||||
|
||||
public IDisposable GetTestContext(ITestOutputHelper outputHelper)
|
||||
{
|
||||
return new GrpcTestContext<TStartup>(this, outputHelper);
|
||||
}
|
||||
}
|
||||
|
||||
public class IntegrationTestBase : IClassFixture<GrpcTestFixture<Startup>>, IDisposable
|
||||
{
|
||||
private GrpcChannel? _channel;
|
||||
private IDisposable? _testContext;
|
||||
|
||||
protected GrpcTestFixture<Startup> Fixture { get; set; }
|
||||
|
||||
protected ILoggerFactory LoggerFactory => Fixture.LoggerFactory;
|
||||
|
||||
protected GrpcChannel Channel => _channel ??= CreateChannel();
|
||||
|
||||
protected GrpcChannel CreateChannel()
|
||||
{
|
||||
return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
|
||||
{
|
||||
LoggerFactory = LoggerFactory,
|
||||
HttpHandler = Fixture.Handler
|
||||
});
|
||||
}
|
||||
|
||||
public IntegrationTestBase(GrpcTestFixture<Startup> fixture, ITestOutputHelper outputHelper)
|
||||
{
|
||||
Fixture = fixture;
|
||||
_testContext = Fixture.GetTestContext(outputHelper);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_testContext?.Dispose();
|
||||
_channel = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user