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;
+ }
+}