diff --git a/HttpClientToCurlGenerator.sln b/HttpClientToCurlGenerator.sln index 34648e8..769f653 100644 --- a/HttpClientToCurlGenerator.sln +++ b/HttpClientToCurlGenerator.sln @@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpRequestMessageToCurlTes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpClientToCurl.Sample.InSpecific", "examples\HttpClientToCurl.Sample.InSpecific\HttpClientToCurl.Sample.InSpecific.csproj", "{9D56718F-C9E6-4C45-926D-97599072DA35}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpMessageHandlerTest", "tests\HttpMessageHandlerTest\HttpMessageHandlerTest.csproj", "{EF2591EB-8810-433B-BAD6-A41540801342}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +71,10 @@ Global {9D56718F-C9E6-4C45-926D-97599072DA35}.Debug|Any CPU.Build.0 = Debug|Any CPU {9D56718F-C9E6-4C45-926D-97599072DA35}.Release|Any CPU.ActiveCfg = Release|Any CPU {9D56718F-C9E6-4C45-926D-97599072DA35}.Release|Any CPU.Build.0 = Release|Any CPU + {EF2591EB-8810-433B-BAD6-A41540801342}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF2591EB-8810-433B-BAD6-A41540801342}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF2591EB-8810-433B-BAD6-A41540801342}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF2591EB-8810-433B-BAD6-A41540801342}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -82,6 +88,7 @@ Global {8CC76F1F-5845-D81E-5E9A-113F913A444B} = {E36BF269-7F5D-4DE7-99B0-14567F9CD6B3} {69E31075-F14E-1DE2-1D6E-D934A5C0480F} = {E36BF269-7F5D-4DE7-99B0-14567F9CD6B3} {9D56718F-C9E6-4C45-926D-97599072DA35} = {A8574DB9-8411-4F81-A82E-F97AD00EF8AF} + {EF2591EB-8810-433B-BAD6-A41540801342} = {E36BF269-7F5D-4DE7-99B0-14567F9CD6B3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E5E0FFF6-54C3-4BA1-91F3-EF3513A18D5D} diff --git a/tests/HttpMessageHandlerTest/HttpMessageHandlerTest.csproj b/tests/HttpMessageHandlerTest/HttpMessageHandlerTest.csproj new file mode 100644 index 0000000..8b0e002 --- /dev/null +++ b/tests/HttpMessageHandlerTest/HttpMessageHandlerTest.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/tests/HttpMessageHandlerTest/UnitTest/Builders/CompositConfigBuilder.cs b/tests/HttpMessageHandlerTest/UnitTest/Builders/CompositConfigBuilder.cs new file mode 100644 index 0000000..96607d1 --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/Builders/CompositConfigBuilder.cs @@ -0,0 +1,35 @@ +using HttpClientToCurl.Config; + +namespace HttpMessageHandlerTest.UnitTest.Builders; + +public class CompositConfigBuilder +{ + private readonly CompositConfig _config; + public CompositConfigBuilder() + { + _config = new CompositConfig(); + } + + public CompositConfigBuilder SetTurnOnAll(bool turnOnAll) + { + _config.TurnOnAll = turnOnAll; + return this; + } + + public CompositConfigBuilder SetShowOnConsole(ConsoleConfig? consoleConfig) + { + _config.ShowOnConsole = consoleConfig; + return this; + } + + public CompositConfigBuilder SetSaveToFile(FileConfig? fileConfig) + { + _config.SaveToFile = fileConfig; + return this; + } + + public CompositConfig Build() + { + return _config; + } +} diff --git a/tests/HttpMessageHandlerTest/UnitTest/Builders/HttpMessageHandlerBuilder.cs b/tests/HttpMessageHandlerTest/UnitTest/Builders/HttpMessageHandlerBuilder.cs new file mode 100644 index 0000000..e3b6676 --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/Builders/HttpMessageHandlerBuilder.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace HttpMessageHandlerTest.UnitTest.Builders; + +public class HttpMessageHandlerBuilder : Microsoft.Extensions.Http.HttpMessageHandlerBuilder +{ + private readonly IList _additional = []; + public override string Name { get; set; } + public override HttpMessageHandler PrimaryHandler { get; set; } + public override IList AdditionalHandlers => _additional; + public override IServiceProvider Services => new ServiceCollection().BuildServiceProvider(); + public override HttpMessageHandler Build() => PrimaryHandler; + + public HttpMessageHandlerBuilder() + { + Name = "test"; + PrimaryHandler = new HttpClientHandler(); + } +} \ No newline at end of file diff --git a/tests/HttpMessageHandlerTest/UnitTest/CurlGeneratorHttpMessageHandlerTests.cs b/tests/HttpMessageHandlerTest/UnitTest/CurlGeneratorHttpMessageHandlerTests.cs new file mode 100644 index 0000000..68b521f --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/CurlGeneratorHttpMessageHandlerTests.cs @@ -0,0 +1,258 @@ +using System.Net; +using HttpClientToCurl.Config; +using HttpClientToCurl.HttpMessageHandlers; +using HttpMessageHandlerTest.UnitTest.Fakes; +using HttpMessageHandlerTest.UnitTest.Builders; +using FluentAssertions; + +namespace HttpMessageHandlerTest.UnitTest; + +public class CurlGeneratorHttpMessageHandlerTests +{ + [Fact] + public async Task CurlGeneratorHttpMessageHandler_ReturnsResponse_When_TurnOffAll() + { + // Arrange + var config = new CompositConfigBuilder() + .SetTurnOnAll(false) + .Build(); + var handler = new CurlGeneratorHttpMessageHandler(new FakeOptionsMonitor(config)) + { + InnerHandler = new FakeHttpMessageHandler() + }; + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test"); + + // Act + using var invoker = new HttpMessageInvoker(handler); + var response = await invoker.SendAsync(request, CancellationToken.None); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + [Fact] + public async Task CurlGeneratorHttpMessageHandler_ReturnsResponse_When_TurnOnAll_But_ShowOnConsole_And_SaveToFile_AreNot_Configured() + { + // Arrange + var config = new CompositConfigBuilder() + .SetTurnOnAll(true) + .SetShowOnConsole(null) + .SetSaveToFile(null) + .Build(); + var handler = new CurlGeneratorHttpMessageHandler(new FakeOptionsMonitor(config)) + { + InnerHandler = new FakeHttpMessageHandler() + }; + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test"); + + // Act + using var invoker = new HttpMessageInvoker(handler); + var response = await invoker.SendAsync(request, CancellationToken.None); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + [Fact] + public async Task CurlGeneratorHttpMessageHandler_ReturnsResponse_When_TurnOnAll_But_ShowOnConsole_And_SaveToFile_TurnOff() + { + // Arrange + var config = new CompositConfigBuilder() + .SetTurnOnAll(true) + .SetShowOnConsole(new ConsoleConfig + { + TurnOn = false, + }) + .SetSaveToFile(new FileConfig() + { + TurnOn = false + }) + .Build(); + var handler = new CurlGeneratorHttpMessageHandler(new FakeOptionsMonitor(config)) + { + InnerHandler = new FakeHttpMessageHandler() + }; + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test"); + + // Act + using var invoker = new HttpMessageInvoker(handler); + var response = await invoker.SendAsync(request, CancellationToken.None); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + [Fact] + public async Task CurlGeneratorHttpMessageHandler_WritesToConsole_When_ShowOnConsole_TurnOn() + { + // Arrange + var config = new CompositConfigBuilder() + .SetTurnOnAll(true) + .SetShowOnConsole(new ConsoleConfig + { + TurnOn = true, + EnableCodeBeautification = false + }) + .Build(); + + var handler = new CurlGeneratorHttpMessageHandler(new FakeOptionsMonitor(config)) + { + InnerHandler = new FakeHttpMessageHandler() + }; + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/test"); + + var sw = new StringWriter(); + var originalOut = Console.Out; + try + { + Console.SetOut(sw); + + // Act + using var invoker = new HttpMessageInvoker(handler); + var response = await invoker.SendAsync(request, CancellationToken.None); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var output = sw.ToString(); + output.Should().Contain("curl"); + } + finally + { + Console.SetOut(originalOut); + } + } + + [Fact] + public async Task CurlGeneratorHttpMessageHandler_WritesToFile_When_SaveToFile_TurnOn() + { + // Arrange + var tempPath = Path.GetTempPath(); + var filename = Guid.NewGuid().ToString("N"); + + var config = new CompositConfigBuilder() + .SetTurnOnAll(true) + .SetSaveToFile(new FileConfig + { + TurnOn = true, + Path = tempPath, + Filename = filename + }) + .Build(); + + var monitor = new FakeOptionsMonitor(config); + var handler = new CurlGeneratorHttpMessageHandler(monitor) + { + InnerHandler = new FakeHttpMessageHandler() + }; + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/test") { Content = new StringContent("hello") }; + + var filePath = Path.Combine(tempPath.TrimEnd(Path.DirectorySeparatorChar), filename + ".curl"); + + try + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + + // Act + using var invoker = new HttpMessageInvoker(handler); + var response = await invoker.SendAsync(request, CancellationToken.None); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + + File.Exists(filePath).Should().BeTrue(); + var content = File.ReadAllText(filePath); + content.Should().Contain("curl"); + } + finally + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + } + + [Fact] + public async Task CurlGeneratorHttpMessageHandler_WritesToConsole_And_WritesToFile_When_ShowOnConsole_And_SaveToFile_TurnOn() + { + // Arrange + var tempPath = Path.GetTempPath(); + var filename = Guid.NewGuid().ToString("N"); + + var config = new CompositConfigBuilder() + .SetTurnOnAll(true) + .SetShowOnConsole(new ConsoleConfig + { + TurnOn = true, + EnableCodeBeautification = false + }) + .SetSaveToFile(new FileConfig + { + TurnOn = true, + Path = tempPath, + Filename = filename + }) + .Build(); + + var handler = new CurlGeneratorHttpMessageHandler(new FakeOptionsMonitor(config)) + { + InnerHandler = new FakeHttpMessageHandler() + }; + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/test"); + + var sw = new StringWriter(); + var originalOut = Console.Out; + var filePath = Path.Combine(tempPath.TrimEnd(Path.DirectorySeparatorChar), filename + ".curl"); + + try + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + + Console.SetOut(sw); + + // Act + using var invoker = new HttpMessageInvoker(handler); + var response = await invoker.SendAsync(request, CancellationToken.None); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var output = sw.ToString(); + output.Should().Contain("curl"); + + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + + File.Exists(filePath).Should().BeTrue(); + var content = File.ReadAllText(filePath); + content.Should().Contain("curl"); + } + finally + { + Console.SetOut(originalOut); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + } +} diff --git a/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeHttpMessageHandler.cs b/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeHttpMessageHandler.cs new file mode 100644 index 0000000..e0efd76 --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeHttpMessageHandler.cs @@ -0,0 +1,11 @@ +using System.Net; + +namespace HttpMessageHandlerTest.UnitTest.Fakes; + +public class FakeHttpMessageHandler : HttpMessageHandler +{ + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + } +} diff --git a/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeOptionsMonitor.cs b/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeOptionsMonitor.cs new file mode 100644 index 0000000..007b715 --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeOptionsMonitor.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.Options; + +namespace HttpMessageHandlerTest.UnitTest.Fakes; + +public class FakeOptionsMonitor(T value) : IOptionsMonitor where T : class +{ + public T CurrentValue { get; } = value; + public T Get(string? name) => CurrentValue; + public IDisposable OnChange(Action listener) => new Disposable(); + private sealed class Disposable : IDisposable { public void Dispose() { } } +} \ No newline at end of file diff --git a/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeServiceProvider.cs b/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeServiceProvider.cs new file mode 100644 index 0000000..2826803 --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/Fakes/FakeServiceProvider.cs @@ -0,0 +1,10 @@ +using HttpClientToCurl.HttpMessageHandlers; + +namespace HttpMessageHandlerTest.UnitTest.Fakes; + +public class FakeServiceProvider(object service) : IServiceProvider +{ + private readonly object _service = service; + + public object GetService(Type serviceType) => serviceType == typeof(CurlGeneratorHttpMessageHandler) ? _service : null; +} \ No newline at end of file diff --git a/tests/HttpMessageHandlerTest/UnitTest/HttpMessageHandlerAppenderTests.cs b/tests/HttpMessageHandlerTest/UnitTest/HttpMessageHandlerAppenderTests.cs new file mode 100644 index 0000000..8b72e3a --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/HttpMessageHandlerAppenderTests.cs @@ -0,0 +1,29 @@ +using FluentAssertions; +using HttpClientToCurl.Config; +using HttpClientToCurl.HttpMessageHandlers; +using HttpMessageHandlerTest.UnitTest.Builders; +using HttpMessageHandlerTest.UnitTest.Fakes; + +namespace HttpMessageHandlerTest.UnitTest; +public class HttpMessageHandlerAppenderTests +{ + [Fact] + public void HttpMessageHandlerAppender_Adds_Handler_To_Builder() + { + // Arrange + var config = new CompositConfig { TurnOnAll = false }; + var handler = new CurlGeneratorHttpMessageHandler(new FakeOptionsMonitor(config)); + var sp = new FakeServiceProvider(handler); + var appender = new HttpMessageHandlerAppender(sp); + + var builder = new HttpMessageHandlerBuilder(); + + // Act + var configure = appender.Configure(next => { }); + configure(builder); + + // Assert + builder.AdditionalHandlers.Should().ContainSingle() + .Which.Should().Be(handler); + } +} diff --git a/tests/HttpMessageHandlerTest/UnitTest/ServiceCollectionExtensionsTests.cs b/tests/HttpMessageHandlerTest/UnitTest/ServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..75c8f37 --- /dev/null +++ b/tests/HttpMessageHandlerTest/UnitTest/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,117 @@ +using System.Reflection; +using FluentAssertions; +using HttpClientToCurl.Config; +using HttpClientToCurl.Extensions; +using HttpClientToCurl.HttpMessageHandlers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Options; + +namespace HttpMessageHandlerTest.UnitTest; + +public class ServiceCollectionExtensionsTests +{ + [Fact] + public void ServiceCollectionExtensions_Register_Services_In_GeneralMode() + { + // Arrange + var services = new ServiceCollection(); + var configuration = new Microsoft.Extensions.Configuration.ConfigurationBuilder() + .Build(); + + // Act + services.AddHttpClientToCurlInGeneralMode(configuration); + var provider = services.BuildServiceProvider(); + + // Assert + var filter = provider.GetService(); + filter.Should().NotBeNull(); + filter.Should().BeOfType(); + + var handler = provider.GetService(); + handler.Should().NotBeNull(); + + var options = provider.GetService>(); + options.Should().NotBeNull(); + } + + [Fact] + public void ServiceCollectionExtensions_Register_Services_In_SpecificMode() + { + // Arrange + var services = new ServiceCollection(); + var configuration = new Microsoft.Extensions.Configuration.ConfigurationBuilder() + .Build(); + + // Act + services.AddHttpClientToCurl(configuration); + var provider = services.BuildServiceProvider(); + + // Assert + var filter = provider.GetService(); + filter.Should().BeNull(); + + var handler = provider.GetService(); + handler.Should().NotBeNull(); + + var options = provider.GetService>(); + options.Should().NotBeNull(); + } + + [Fact] + public void ServiceCollectionExtensions_AddHttpMessageHandler_When_ShowCurl_IsEnabled() + { + // Arrange + var services = new ServiceCollection(); + + services.AddTransient(); + services.AddHttpClient("TestClient", true); + + var serviceProvider = services.BuildServiceProvider(); + var httpClientFactory = serviceProvider.GetRequiredService(); + + // Act + var httpClient = httpClientFactory.CreateClient("TestClient"); + + // Assert + var hasHandler = HasCurlGeneratorHttpMessageHandler(httpClient); + hasHandler.Should().BeTrue(); + } + + [Fact] + public void ServiceCollectionExtensions_AddHttpMessageHandler_When_ShowCurl_IsNotEnabled() + { + // Arrange + var services = new ServiceCollection(); + + services.AddTransient(); + services.AddHttpClient("TestClient", false); + + var serviceProvider = services.BuildServiceProvider(); + var httpClientFactory = serviceProvider.GetRequiredService(); + + // Act + var httpClient = httpClientFactory.CreateClient("TestClient"); + + // Assert + var hasHandler = HasCurlGeneratorHttpMessageHandler(httpClient); + hasHandler.Should().BeFalse(); + } + + private static bool HasCurlGeneratorHttpMessageHandler(HttpClient client) + { + var field = typeof(HttpMessageInvoker).GetField("_handler", BindingFlags.NonPublic | BindingFlags.Instance); + var handler = field?.GetValue(client); + + while (handler is DelegatingHandler delegatingHandler) + { + if (delegatingHandler.GetType() == typeof(CurlGeneratorHttpMessageHandler)) + { + return true; + } + handler = delegatingHandler.InnerHandler; + } + + return false; + } +}