From c6f5783db3cba9ea6ff3ca791a372977b531a483 Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 21:02:24 +0200 Subject: [PATCH 01/15] unit test tests certs --- .../CHttp.Tests/Http/CHttpFunctionalTests.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs b/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs index a18b4af..43f4f9a 100644 --- a/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs +++ b/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.IO.Compression; using System.Text; using CHttp.Abstractions; @@ -350,8 +349,51 @@ public async Task Diff_FunctionalTest() Assert.Contains("probability, the base session's true mean latency is", console.Text); } + [Fact] + public async Task CertificateValidationError_WithProposedSuggestion() + { + using var host = HttpServer.CreateHostBuilder(context => context.Response.WriteAsync("test"), HttpProtocols.Http2); + await host.StartAsync(TestContext.Current.CancellationToken); + var console = new TestConsolePerWrite(); + var writer = new SilentConsoleWriter(new TextBufferedProcessor(), console); + + var client = await CommandFactory.CreateRootCommand(writer) + .Parse("--method GET --uri https://localhost:5011 -v 2") + .InvokeAsync(cancellationToken: TestContext.Current.CancellationToken); + + await writer.CompleteAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); + Assert.Contains($"SSL", console.Text); + } + private class Request { public string? Data { get; set; } } } + + +//Request Error System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. +// ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot +// at System.Net.Security.SslStream.SendAuthResetSignal(ReadOnlySpan`1, ExceptionDispatchInfo) + 0x6e +// at System.Net.Security.SslStream.CompleteHandshake(SslAuthenticationOptions) + 0x18b +// at System.Net.Security.SslStream.d__157`1.MoveNext() + 0x942 +//--- End of stack trace from previous location --- +// at System.Net.Http.ConnectHelper.d__2.MoveNext() + 0xd0 +// --- End of inner exception stack trace --- +// at System.Net.Http.ConnectHelper.d__2.MoveNext() + 0x441 +//--- End of stack trace from previous location --- +// at System.Net.Http.HttpConnectionPool.d__51.MoveNext() + 0xa10 +//--- End of stack trace from previous location --- +// at System.Net.Http.HttpConnectionPool.d__101.MoveNext() + 0x857 +//--- End of stack trace from previous location --- +// at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.d__1.MoveNext() + 0xef +//--- End of stack trace from previous location --- +// at System.Net.Http.HttpConnectionPool.d__50.MoveNext() + 0x692 +//--- End of stack trace from previous location --- +// at System.Net.Http.RedirectHandler.d__4.MoveNext() + 0x1e5 +//--- End of stack trace from previous location --- +// at System.Net.Http.DecompressionHandler.d__16.MoveNext() + 0x2fd +//--- End of stack trace from previous location --- +// at System.Net.Http.HttpClient.<g__Core|83_0>d.MoveNext() + 0x3a1 +//--- End of stack trace from previous location --- +// at CHttp.Http.HttpMessageSender.d__7.MoveNext() + 0x30b From 3e0186d54fd521004d4616dd9cce7fef85054eb8 Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 22:14:32 +0200 Subject: [PATCH 02/15] User friendly error messages for SSL errors --- src/CHttp/Binders/HttpBehaviorBinder.cs | 6 +- src/CHttp/CommandFactory.cs | 6 +- src/CHttp/Data/Summary.cs | 14 +-- src/CHttp/Http/HttpMessageSender.cs | 25 ++++- .../CHttp.Tests/Http/CHttpFunctionalTests.cs | 100 +++++++++++++----- tests/CHttp.Tests/HttpServer.cs | 54 +++++----- 6 files changed, 134 insertions(+), 71 deletions(-) diff --git a/src/CHttp/Binders/HttpBehaviorBinder.cs b/src/CHttp/Binders/HttpBehaviorBinder.cs index 2b250a6..76f1277 100644 --- a/src/CHttp/Binders/HttpBehaviorBinder.cs +++ b/src/CHttp/Binders/HttpBehaviorBinder.cs @@ -5,14 +5,14 @@ namespace CHttp.Binders; internal sealed class HttpBehaviorBinder( Option redirectBinder, - Option enableCertificateValidationBinder, + Option validateCertificateValidationBinder, Option timeout, Option cookieContainerOption, Option kerberosAuthOption, Option decompressResponse) { private readonly Option _redirectBinder = redirectBinder; - private readonly Option _enableCertificateValidationBinder = enableCertificateValidationBinder; + private readonly Option _validateCertificateValidationBinder = validateCertificateValidationBinder; private readonly Option _timeoutOption = timeout; private readonly Option _cookieContainerOption = cookieContainerOption; private readonly Option _kerberosAuthOption = kerberosAuthOption; @@ -21,7 +21,7 @@ internal sealed class HttpBehaviorBinder( internal HttpBehavior Bind(ParseResult parseResult) { var redirects = parseResult.GetValue(_redirectBinder); - var enableCertificateValidation = parseResult.GetValue(_enableCertificateValidationBinder); + var enableCertificateValidation = !parseResult.GetValue(_validateCertificateValidationBinder); var timeout = parseResult.GetValue(_timeoutOption); var cookieContainer = parseResult.GetValue(_cookieContainerOption)?.FullName ?? string.Empty; var kerberosAuth = parseResult.GetValue(_kerberosAuthOption); diff --git a/src/CHttp/CommandFactory.cs b/src/CHttp/CommandFactory.cs index df6505a..7dea335 100644 --- a/src/CHttp/CommandFactory.cs +++ b/src/CHttp/CommandFactory.cs @@ -20,8 +20,6 @@ public static Command CreateRootCommand( IFileSystem? fileSystem = null) { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - // kerberos auth - // certificates // TLS // proxy @@ -122,9 +120,9 @@ public static Command CreateRootCommand( { var value = parseResult.Tokens.FirstOrDefault()?.Value; if (value == null) - return false; + return true; if (bool.TryParse(value, out var result)) - return !result; // Invert the value to match the option name + return result; // Invert the value to match the option name parseResult.AddError("Invalid value for --no-certificate-validation. Expected 'true' or 'false' or no value."); return false; }, diff --git a/src/CHttp/Data/Summary.cs b/src/CHttp/Data/Summary.cs index 3ba6497..e8951ca 100644 --- a/src/CHttp/Data/Summary.cs +++ b/src/CHttp/Data/Summary.cs @@ -37,7 +37,7 @@ internal Summary(string url, DateTime startTime, TimeSpan duration) private long _endTime; public long EndTime { - get => _endTime; + readonly get => _endTime; set { if (_endTime != default || StartTime == default) @@ -59,7 +59,7 @@ public void RequestCompleted(HttpStatusCode statusCode) HttpStatusCode = (int)statusCode; } - public override string ToString() + public readonly override string ToString() { if (!string.IsNullOrEmpty(Error)) return Error; @@ -69,19 +69,19 @@ public override string ToString() inputs.Url.CopyTo(buffer); buffer = buffer.Slice(inputs.Url.Length); buffer[0] = ' '; - buffer = buffer.Slice(1); + buffer = buffer[1..]; var responseSize = inputs.Length; if (SizeFormatter.TryFormatSize(responseSize, buffer, out var count)) { - buffer = buffer.Slice(count); + buffer = buffer[count..]; buffer[0] = ' '; - buffer = buffer.Slice(1); + buffer = buffer[1..]; } if (!inputs.Duration.TryFormat(buffer, out count, "c")) ThrowInvalidOperationException(); - buffer = buffer.Slice(count); + buffer = buffer[count..]; buffer[0] = 's'; - buffer = buffer.Slice(1); + buffer = buffer[1..]; buffer.Fill(' '); }); } diff --git a/src/CHttp/Http/HttpMessageSender.cs b/src/CHttp/Http/HttpMessageSender.cs index 5774894..1e9ee56 100644 --- a/src/CHttp/Http/HttpMessageSender.cs +++ b/src/CHttp/Http/HttpMessageSender.cs @@ -1,5 +1,7 @@ -using System.IO.Pipelines; +using System.Drawing; +using System.IO.Pipelines; using System.Net.Http.Headers; +using System.Security.Authentication; using System.Text; using CHttp.Data; using CHttp.Writers; @@ -46,7 +48,7 @@ public async Task SendRequestAsync(HttpRequestDetails requestData, CancellationT private async Task SendRequestAsync(HttpClient client, HttpRequestMessage request, CancellationToken token = default) { - Summary summary = new Summary(request.RequestUri?.ToString() ?? string.Empty); + Summary summary = new(request.RequestUri?.ToString() ?? string.Empty); HttpResponseHeaders? trailers = null; { try @@ -61,7 +63,7 @@ private async Task SendRequestAsync(HttpClient client, HttpRequestMessage reques } catch (HttpRequestException requestException) { - summary = summary with { Error = $"Request Error {requestException}", ErrorCode = ErrorType.HttpRequestException }; + summary = summary with { Error = HandleRequestException(requestException), ErrorCode = ErrorType.HttpRequestException }; } catch (HttpProtocolException protocolException) { @@ -112,4 +114,21 @@ private void SetHeaders(HttpRequestDetails requestData, HttpRequestMessage reque } } } + + private string HandleRequestException(HttpRequestException requestException) + { + if (requestException.InnerException is AuthenticationException authException) + { + if (authException.Message.StartsWith("The remote certificate is invalid")) + return $"Enable flag --no-certificate-validation true'. SSL error: {authException.Message}"; + if (authException.Message.StartsWith("Cannot determine the frame size or a corrupted frame was received")) + return $"Invalid http(s) schema: {authException.Message}"; + } + + if (requestException.InnerException is HttpIOException ioException) + { + return $"Invalid http(s) schema: {ioException.Message}"; + } + return $"Request Error {requestException}"; + } } diff --git a/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs b/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs index 43f4f9a..c5d4730 100644 --- a/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs +++ b/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs @@ -1,12 +1,19 @@ +using System.Collections; using System.IO.Compression; +using System.Net.Security; +using System.Runtime.Intrinsics.X86; using System.Text; using CHttp.Abstractions; +using CHttp.Http; using CHttp.Writers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Primitives; +using OpenTelemetry.Trace; +using static System.Runtime.InteropServices.JavaScript.JSType; +using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext; namespace CHttp.Tests.Http; @@ -362,7 +369,71 @@ public async Task CertificateValidationError_WithProposedSuggestion() .InvokeAsync(cancellationToken: TestContext.Current.CancellationToken); await writer.CompleteAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); - Assert.Contains($"SSL", console.Text); + Assert.Contains("Enable flag --no-certificate-validation true'. SSL error: The remote certificate is invalid because of errors in the certificate chain", console.Text); + } + + [Fact] + public async Task CertificateValidationError_HttpsServer_HttpRequest_H2() + { + using var host = HttpServer.CreateHostBuilder(context => context.Response.WriteAsync("test"), HttpProtocols.Http2); + await host.StartAsync(TestContext.Current.CancellationToken); + var console = new TestConsolePerWrite(); + var writer = new SilentConsoleWriter(new TextBufferedProcessor(), console); + + var client = await CommandFactory.CreateRootCommand(writer) + .Parse("--method GET --uri http://localhost:5011 -v 2") + .InvokeAsync(cancellationToken: TestContext.Current.CancellationToken); + + await writer.CompleteAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); + Assert.Contains("Invalid http(s) schema: An HTTP/2 connection could not be established", console.Text); + } + + [Fact] + public async Task CertificateValidationError_HttpsServer_HttpRequest_H3() + { + using var host = HttpServer.CreateHostBuilder(context => context.Response.WriteAsync("test"), HttpProtocols.Http3); + await host.StartAsync(TestContext.Current.CancellationToken); + var console = new TestConsolePerWrite(); + var writer = new SilentConsoleWriter(new TextBufferedProcessor(), console); + + var client = await CommandFactory.CreateRootCommand(writer) + .Parse("--method GET --uri http://localhost:5011 -v 3") + .InvokeAsync(cancellationToken: TestContext.Current.CancellationToken); + + await writer.CompleteAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); + Assert.Contains("Requesting HTTP version 3.0 with version policy RequestVersionExact while unable to establish HTTP/3 connection.", console.Text); + } + + [Fact] + public async Task CertificateValidationError_HttpsServer_HttpRequest_H1() + { + using var host = HttpServer.CreateHostBuilder(context => context.Response.WriteAsync("test"), HttpProtocols.Http1); + await host.StartAsync(TestContext.Current.CancellationToken); + var console = new TestConsolePerWrite(); + var writer = new SilentConsoleWriter(new TextBufferedProcessor(), console); + + var client = await CommandFactory.CreateRootCommand(writer) + .Parse("--method GET --uri http://localhost:5011 -v 1.1") + .InvokeAsync(cancellationToken: TestContext.Current.CancellationToken); + + await writer.CompleteAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); + Assert.Contains("Invalid http(s) schema: The response ended prematurely.", console.Text); + } + + [Fact] + public async Task CertificateValidationError_HttpServer_HttpsRequest() + { + using var host = HttpServer.CreateHostBuilder(context => context.Response.WriteAsync("test"), HttpProtocols.Http1, withHttps: false); + await host.StartAsync(TestContext.Current.CancellationToken); + var console = new TestConsolePerWrite(); + var writer = new SilentConsoleWriter(new TextBufferedProcessor(), console); + + var client = await CommandFactory.CreateRootCommand(writer) + .Parse("--method GET --uri https://localhost:5011 -v 1.1") + .InvokeAsync(cancellationToken: TestContext.Current.CancellationToken); + + await writer.CompleteAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); + Assert.Contains("Invalid http(s) schema: Cannot determine the frame size or a corrupted frame was received.", console.Text); } private class Request @@ -370,30 +441,3 @@ private class Request public string? Data { get; set; } } } - - -//Request Error System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. -// ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot -// at System.Net.Security.SslStream.SendAuthResetSignal(ReadOnlySpan`1, ExceptionDispatchInfo) + 0x6e -// at System.Net.Security.SslStream.CompleteHandshake(SslAuthenticationOptions) + 0x18b -// at System.Net.Security.SslStream.d__157`1.MoveNext() + 0x942 -//--- End of stack trace from previous location --- -// at System.Net.Http.ConnectHelper.d__2.MoveNext() + 0xd0 -// --- End of inner exception stack trace --- -// at System.Net.Http.ConnectHelper.d__2.MoveNext() + 0x441 -//--- End of stack trace from previous location --- -// at System.Net.Http.HttpConnectionPool.d__51.MoveNext() + 0xa10 -//--- End of stack trace from previous location --- -// at System.Net.Http.HttpConnectionPool.d__101.MoveNext() + 0x857 -//--- End of stack trace from previous location --- -// at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.d__1.MoveNext() + 0xef -//--- End of stack trace from previous location --- -// at System.Net.Http.HttpConnectionPool.d__50.MoveNext() + 0x692 -//--- End of stack trace from previous location --- -// at System.Net.Http.RedirectHandler.d__4.MoveNext() + 0x1e5 -//--- End of stack trace from previous location --- -// at System.Net.Http.DecompressionHandler.d__16.MoveNext() + 0x2fd -//--- End of stack trace from previous location --- -// at System.Net.Http.HttpClient.<g__Core|83_0>d.MoveNext() + 0x3a1 -//--- End of stack trace from previous location --- -// at CHttp.Http.HttpMessageSender.d__7.MoveNext() + 0x30b diff --git a/tests/CHttp.Tests/HttpServer.cs b/tests/CHttp.Tests/HttpServer.cs index 325a227..5b24c78 100644 --- a/tests/CHttp.Tests/HttpServer.cs +++ b/tests/CHttp.Tests/HttpServer.cs @@ -9,32 +9,34 @@ namespace CHttp.Tests; public static class HttpServer { - public static WebApplication CreateHostBuilder(RequestDelegate? requestDelegate = null, - HttpProtocols? protocol = null, - Action? configureKestrel = null, - Action? configureServices = null, - Action? configureApp = null, - int port = 5011, - string path = "/") - { - var builder = WebApplication.CreateBuilder(); - builder.WebHost.UseKestrel(kestrel => - { - kestrel.ListenAnyIP(port, options => - { - options.UseHttps(X509CertificateLoader.LoadPkcs12FromFile("testCert.pfx", "testPassword")); - options.Protocols = protocol ?? HttpProtocols.Http3; - }); - configureKestrel?.Invoke(kestrel); - }); + public static WebApplication CreateHostBuilder(RequestDelegate? requestDelegate = null, + HttpProtocols? protocol = null, + Action? configureKestrel = null, + Action? configureServices = null, + Action? configureApp = null, + int port = 5011, + string path = "/", + bool withHttps = true) + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseKestrel(kestrel => + { + kestrel.ListenAnyIP(port, options => + { + if (withHttps) + options.UseHttps(X509CertificateLoader.LoadPkcs12FromFile("testCert.pfx", "testPassword")); + options.Protocols = protocol ?? HttpProtocols.Http3; + }); + configureKestrel?.Invoke(kestrel); + }); - configureServices?.Invoke(builder.Services); - var app = builder.Build(); + configureServices?.Invoke(builder.Services); + var app = builder.Build(); - if (requestDelegate != null) - app.Map(path, requestDelegate); - else if (configureApp != null) - configureApp.Invoke(app); - return app; - } + if (requestDelegate != null) + app.Map(path, requestDelegate); + else if (configureApp != null) + configureApp.Invoke(app); + return app; + } } \ No newline at end of file From 89a2968192fedd465c035147d8f37689f59bb5a5 Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 22:47:59 +0200 Subject: [PATCH 03/15] TestContext.Current.CancellationToken --- tests/CHttp.Tests/AwaiterTests.cs | 2 +- .../Writers/StreamBufferedProcessorTests.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/CHttp.Tests/AwaiterTests.cs b/tests/CHttp.Tests/AwaiterTests.cs index c5119bd..9337767 100644 --- a/tests/CHttp.Tests/AwaiterTests.cs +++ b/tests/CHttp.Tests/AwaiterTests.cs @@ -14,6 +14,6 @@ public async Task WaitAsync_Waits50Ms() timeProvider.Advance(TimeSpan.FromMilliseconds(49)); Assert.False(waiting.IsCompleted); timeProvider.Advance(TimeSpan.FromMilliseconds(1)); - await waiting.WaitAsync(TimeSpan.FromSeconds(1)); + await waiting.WaitAsync(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); } } diff --git a/tests/CHttp.Tests/Writers/StreamBufferedProcessorTests.cs b/tests/CHttp.Tests/Writers/StreamBufferedProcessorTests.cs index c221c9a..14b5b4e 100644 --- a/tests/CHttp.Tests/Writers/StreamBufferedProcessorTests.cs +++ b/tests/CHttp.Tests/Writers/StreamBufferedProcessorTests.cs @@ -17,7 +17,7 @@ public async Task Run_CopiesDataTo_OutputStream() var input = Enumerable.Range(0, 100).Select(x => (byte)x).ToArray(); var sut = new StreamBufferedProcessor(outputStream); var sutRun = sut.RunAsync(_ => Task.CompletedTask); - await sut.Pipe.WriteAsync(input); + await sut.Pipe.WriteAsync(input, TestContext.Current.CancellationToken); sut.Pipe.Complete(); await sutRun; Assert.True(input.SequenceEqual(outputStream.ToArray())); @@ -31,7 +31,7 @@ public async Task EmptyArrayRun_CopiesDataTo_OutputStream() var input = new byte[0]; var sut = new StreamBufferedProcessor(outputStream); var sutRun = sut.RunAsync(_ => Task.CompletedTask); - await sut.Pipe.WriteAsync(input); + await sut.Pipe.WriteAsync(input, TestContext.Current.CancellationToken); sut.Pipe.Complete(); await sutRun; Assert.True(input.SequenceEqual(outputStream.ToArray())); @@ -44,7 +44,7 @@ public async Task LargeArrayRun_CopiesDataTo_OutputStream() var input = Enumerable.Range(0, 100000).Select(x => (byte)x).ToArray(); var sut = new StreamBufferedProcessor(outputStream); var sutRun = sut.RunAsync(_ => Task.CompletedTask); - await sut.Pipe.WriteAsync(input); + await sut.Pipe.WriteAsync(input, TestContext.Current.CancellationToken); sut.Pipe.Complete(); await sutRun; Assert.True(input.SequenceEqual(outputStream.ToArray())); @@ -63,7 +63,7 @@ public async Task MultiWrittenArrayRun_CopiesDataTo_OutputStream(int segmentLeng while (remainderToWrite.Length > 0) { - await sut.Pipe.WriteAsync(remainderToWrite.Slice(0, segmentLength)); + await sut.Pipe.WriteAsync(remainderToWrite.Slice(0, segmentLength), TestContext.Current.CancellationToken); remainderToWrite = remainderToWrite.Slice(segmentLength); } @@ -79,7 +79,7 @@ public async Task WhenCancelled_Run_Completes() var sut = new StreamBufferedProcessor(outputStream); var sutRun = sut.RunAsync(_ => Task.CompletedTask); sut.Cancel(); - await sutRun.WaitAsync(TimeSpan.FromSeconds(1)); + await sutRun.WaitAsync(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); Assert.Equal(0, outputStream.Length); Assert.Equal(0, sut.Position); } From 35a491bc15dccb0957a240663e12b6ffaa3d51be Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 22:52:06 +0200 Subject: [PATCH 04/15] Remove unused usings --- src/CHttp/Http/HttpMessageSender.cs | 3 +-- tests/CHttp.Tests/Http/CHttpFunctionalTests.cs | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/CHttp/Http/HttpMessageSender.cs b/src/CHttp/Http/HttpMessageSender.cs index 1e9ee56..9df1a15 100644 --- a/src/CHttp/Http/HttpMessageSender.cs +++ b/src/CHttp/Http/HttpMessageSender.cs @@ -1,5 +1,4 @@ -using System.Drawing; -using System.IO.Pipelines; +using System.IO.Pipelines; using System.Net.Http.Headers; using System.Security.Authentication; using System.Text; diff --git a/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs b/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs index c5d4730..7073cd7 100644 --- a/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs +++ b/tests/CHttp.Tests/Http/CHttpFunctionalTests.cs @@ -1,19 +1,12 @@ -using System.Collections; using System.IO.Compression; -using System.Net.Security; -using System.Runtime.Intrinsics.X86; using System.Text; using CHttp.Abstractions; -using CHttp.Http; using CHttp.Writers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Primitives; -using OpenTelemetry.Trace; -using static System.Runtime.InteropServices.JavaScript.JSType; -using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext; namespace CHttp.Tests.Http; From adb4fbec00e54ec4c6bf7ed4938738e989de594f Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 23:08:50 +0200 Subject: [PATCH 05/15] .NET 10 tests --- tests/CHttpServer.Tests/CHttpServer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CHttpServer.Tests/CHttpServer.Tests.csproj b/tests/CHttpServer.Tests/CHttpServer.Tests.csproj index 0390301..e0175c0 100644 --- a/tests/CHttpServer.Tests/CHttpServer.Tests.csproj +++ b/tests/CHttpServer.Tests/CHttpServer.Tests.csproj @@ -1,7 +1,7 @@  - net11.0 + net10.0 enable enable false From 4145d724c2e31dd3c9c1dee8ef3c4027088b09ce Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 23:11:42 +0200 Subject: [PATCH 06/15] nuget packages --- tests/CHttpServer.Tests/CHttpServer.Tests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/CHttpServer.Tests/CHttpServer.Tests.csproj b/tests/CHttpServer.Tests/CHttpServer.Tests.csproj index e0175c0..a12f565 100644 --- a/tests/CHttpServer.Tests/CHttpServer.Tests.csproj +++ b/tests/CHttpServer.Tests/CHttpServer.Tests.csproj @@ -19,13 +19,13 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From f754c7da911881dd2c677adc84643cc592494cdc Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 23:16:47 +0200 Subject: [PATCH 07/15] .net 10 chttp tests --- src/CHttp/CHttp.csproj | 2 +- tests/CHttp.Tests/CHttp.Tests.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CHttp/CHttp.csproj b/src/CHttp/CHttp.csproj index 75d3c08..b5e5880 100644 --- a/src/CHttp/CHttp.csproj +++ b/src/CHttp/CHttp.csproj @@ -2,7 +2,7 @@ Exe - net9.0;net11.0 + net9.0;net10.0;net11.0 enable enable true diff --git a/tests/CHttp.Tests/CHttp.Tests.csproj b/tests/CHttp.Tests/CHttp.Tests.csproj index c0fc1a4..e36cae6 100644 --- a/tests/CHttp.Tests/CHttp.Tests.csproj +++ b/tests/CHttp.Tests/CHttp.Tests.csproj @@ -1,7 +1,7 @@  - net11.0 + net10.0 enable enable false @@ -9,15 +9,15 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From f3b16fa4782bef8a6981f34bd87921ffae6d9149 Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 23:21:44 +0200 Subject: [PATCH 08/15] Server tests to 11 --- tests/CHttpServer.Tests/CHttpServer.Tests.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CHttpServer.Tests/CHttpServer.Tests.csproj b/tests/CHttpServer.Tests/CHttpServer.Tests.csproj index a12f565..0390301 100644 --- a/tests/CHttpServer.Tests/CHttpServer.Tests.csproj +++ b/tests/CHttpServer.Tests/CHttpServer.Tests.csproj @@ -1,7 +1,7 @@  - net10.0 + net11.0 enable enable false @@ -19,13 +19,13 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From c3a3c76134d535f64cdbfa5bba1e86ab51bca5bb Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 15 Jun 2026 23:31:49 +0200 Subject: [PATCH 09/15] CHttp tests to 11 --- tests/CHttp.Tests/CHttp.Tests.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CHttp.Tests/CHttp.Tests.csproj b/tests/CHttp.Tests/CHttp.Tests.csproj index e36cae6..c0fc1a4 100644 --- a/tests/CHttp.Tests/CHttp.Tests.csproj +++ b/tests/CHttp.Tests/CHttp.Tests.csproj @@ -1,7 +1,7 @@  - net10.0 + net11.0 enable enable false @@ -9,15 +9,15 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From ba3b8a82ae3b052b2fcf375210f1ad8b82840709 Mon Sep 17 00:00:00 2001 From: ladeak Date: Tue, 16 Jun 2026 08:22:29 +0200 Subject: [PATCH 10/15] logging + individual test CIs steps --- .github/workflows/CI.yml | 6 +++++- tests/CHttp.Tests/HttpServer.cs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 32d790c..0b21a0d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -33,7 +33,11 @@ jobs: run: dotnet build CHttpTools.slnx -c ${{ env.CONFIGURATION }} - name: Test run: | - dotnet test --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./src/tests/CHttp.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./src/tests/CHttpExecutor.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./src/tests/TestWebApplication.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./src/tests/CHttp.Api.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./src/tests/CHttpServer.Tests --no-build -c ${{ env.CONFIGURATION }} - name: Publish VSCE run: | pushd ./src/VSCodeExt/ diff --git a/tests/CHttp.Tests/HttpServer.cs b/tests/CHttp.Tests/HttpServer.cs index 5b24c78..d52f5ae 100644 --- a/tests/CHttp.Tests/HttpServer.cs +++ b/tests/CHttp.Tests/HttpServer.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace CHttp.Tests; @@ -19,6 +20,7 @@ public static WebApplication CreateHostBuilder(RequestDelegate? requestDelegate bool withHttps = true) { var builder = WebApplication.CreateBuilder(); + builder.Logging.ClearProviders(); builder.WebHost.UseKestrel(kestrel => { kestrel.ListenAnyIP(port, options => From db6d5e0d49af68c7356bffa5a444ac37b1eef193 Mon Sep 17 00:00:00 2001 From: ladeak Date: Tue, 16 Jun 2026 08:23:41 +0200 Subject: [PATCH 11/15] Moving tests --- .github/workflows/CI.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0b21a0d..27da76e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -33,11 +33,11 @@ jobs: run: dotnet build CHttpTools.slnx -c ${{ env.CONFIGURATION }} - name: Test run: | - dotnet test ./src/tests/CHttp.Tests --no-build -c ${{ env.CONFIGURATION }} - dotnet test ./src/tests/CHttpExecutor.Tests --no-build -c ${{ env.CONFIGURATION }} - dotnet test ./src/tests/TestWebApplication.Tests --no-build -c ${{ env.CONFIGURATION }} - dotnet test ./src/tests/CHttp.Api.Tests --no-build -c ${{ env.CONFIGURATION }} - dotnet test ./src/tests/CHttpServer.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./tests/CHttp.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./tests/CHttpExecutor.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./tests/TestWebApplication.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./tests/CHttp.Api.Tests --no-build -c ${{ env.CONFIGURATION }} + dotnet test ./tests/CHttpServer.Tests --no-build -c ${{ env.CONFIGURATION }} - name: Publish VSCE run: | pushd ./src/VSCodeExt/ From f6c2388a09e3e0d6d5dff4b87974f344b911be59 Mon Sep 17 00:00:00 2001 From: ladeak Date: Tue, 16 Jun 2026 19:39:21 +0200 Subject: [PATCH 12/15] More CancellationToken in tests --- tests/CHttpExecutor.Tests/StringLinesContentTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/CHttpExecutor.Tests/StringLinesContentTests.cs b/tests/CHttpExecutor.Tests/StringLinesContentTests.cs index 5383002..26fcf83 100644 --- a/tests/CHttpExecutor.Tests/StringLinesContentTests.cs +++ b/tests/CHttpExecutor.Tests/StringLinesContentTests.cs @@ -10,7 +10,7 @@ public async Task SingleWrite() List input = ["test"]; var sut = new StringLinesContent(input); using var ms = new MemoryStream(); - await sut.CopyToAsync(ms); + await sut.CopyToAsync(ms, TestContext.Current.CancellationToken); ms.Seek(0, SeekOrigin.Begin); Assert.Equal(Encoding.UTF8.GetBytes("test"), ms.ToArray()); } @@ -21,7 +21,7 @@ public async Task TwoWrites() List input = ["test", "test"]; var sut = new StringLinesContent(input); using var ms = new MemoryStream(); - await sut.CopyToAsync(ms); + await sut.CopyToAsync(ms, TestContext.Current.CancellationToken); ms.Seek(0, SeekOrigin.Begin); Assert.Equal(Encoding.UTF8.GetBytes("testtest"), ms.ToArray()); } @@ -32,7 +32,7 @@ public async Task LargeContent() List input = ["test", new string('a', 8 * 1024), "test2"]; var sut = new StringLinesContent(input); using var ms = new MemoryStream(); - await sut.CopyToAsync(ms); + await sut.CopyToAsync(ms, TestContext.Current.CancellationToken); ms.Seek(0, SeekOrigin.Begin); StringBuilder sb = new(); sb.Append(input[0]); From bb0512e455c40cc3a44d69a9ac67321d0808d095 Mon Sep 17 00:00:00 2001 From: ladeak Date: Tue, 16 Jun 2026 20:40:47 +0200 Subject: [PATCH 13/15] Increase delay in integration test sample to fail asserts --- tests/CHttpExecutor.Tests/IntegrationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CHttpExecutor.Tests/IntegrationTests.cs b/tests/CHttpExecutor.Tests/IntegrationTests.cs index bf126b2..31805c8 100644 --- a/tests/CHttpExecutor.Tests/IntegrationTests.cs +++ b/tests/CHttpExecutor.Tests/IntegrationTests.cs @@ -360,7 +360,7 @@ public async Task FailingAssert_ReturnsErrors() { using var host = HttpServer.CreateHostBuilder(async context => { - await Task.Delay(1); + await Task.Delay(2); await context.Response.WriteAsync("test"); }, HttpProtocols.Http2, port: Port); await host.StartAsync(TestContext.Current.CancellationToken); From 817fdf11646d1a35e491484383a8b61ce6751749 Mon Sep 17 00:00:00 2001 From: ladeak Date: Tue, 16 Jun 2026 22:49:46 +0200 Subject: [PATCH 14/15] Logging only errors in TestServer --- src/CHttpServer/CHttpServer/ManualResetValueTaskSource.cs | 6 +++++- tests/CHttp.Tests/HttpServer.cs | 6 ++---- tests/CHttpServer.Tests/TestServer.cs | 7 +++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/CHttpServer/CHttpServer/ManualResetValueTaskSource.cs b/src/CHttpServer/CHttpServer/ManualResetValueTaskSource.cs index abd8199..e08d6de 100644 --- a/src/CHttpServer/CHttpServer/ManualResetValueTaskSource.cs +++ b/src/CHttpServer/CHttpServer/ManualResetValueTaskSource.cs @@ -8,7 +8,11 @@ internal class ManualResetValueTaskSource : IValueTaskSource, IValueTaskSo public bool RunContinuationsAsynchronously { get => _core.RunContinuationsAsynchronously; set => _core.RunContinuationsAsynchronously = value; } public short Version => _core.Version; public void Reset() => _core.Reset(); - public void SetException(Exception error) => _core.SetException(error); + public void SetException(Exception error) + { + if (_core.GetStatus(_core.Version) == ValueTaskSourceStatus.Pending) + _core.SetException(error); + } void IValueTaskSource.GetResult(short token) => _core.GetResult(token); public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags); diff --git a/tests/CHttp.Tests/HttpServer.cs b/tests/CHttp.Tests/HttpServer.cs index d52f5ae..e1f8ce8 100644 --- a/tests/CHttp.Tests/HttpServer.cs +++ b/tests/CHttp.Tests/HttpServer.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace CHttp.Tests; @@ -20,7 +19,6 @@ public static WebApplication CreateHostBuilder(RequestDelegate? requestDelegate bool withHttps = true) { var builder = WebApplication.CreateBuilder(); - builder.Logging.ClearProviders(); builder.WebHost.UseKestrel(kestrel => { kestrel.ListenAnyIP(port, options => @@ -37,8 +35,8 @@ public static WebApplication CreateHostBuilder(RequestDelegate? requestDelegate if (requestDelegate != null) app.Map(path, requestDelegate); - else if (configureApp != null) - configureApp.Invoke(app); + else + configureApp?.Invoke(app); return app; } } \ No newline at end of file diff --git a/tests/CHttpServer.Tests/TestServer.cs b/tests/CHttpServer.Tests/TestServer.cs index cf2526a..b6815ec 100644 --- a/tests/CHttpServer.Tests/TestServer.cs +++ b/tests/CHttpServer.Tests/TestServer.cs @@ -1,10 +1,8 @@ -using System.Net; -using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace CHttpServer.Tests; @@ -18,6 +16,7 @@ public Task RunAsync(int port = 7222, bool usePriority = false, bool useHttp3 = if (_app != null) return Task.CompletedTask; var builder = WebApplication.CreateBuilder(); + builder.Logging.SetMinimumLevel(LogLevel.Error); builder.UseCHttpServer(o => { o.Port = port; From 590a7685f67c6d1e1f121d2f8916163cbbe4761b Mon Sep 17 00:00:00 2001 From: ladeak Date: Wed, 17 Jun 2026 08:29:47 +0200 Subject: [PATCH 15/15] Handle ObjectDisposedException in StopAsync cancellation Wrap _abruptCts.Cancel() in a try-catch block within StopAsync to safely ignore ObjectDisposedException if the cancellation token source has already been disposed. This prevents unhandled exceptions during graceful shutdown. --- src/CHttpServer/CHttpServer/Http3/Http3Connection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs b/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs index bd7c7f9..3b8c935 100644 --- a/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs +++ b/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs @@ -143,7 +143,7 @@ private async Task TryCloseConnection() /// Graceful shutdown (until token gets cancelled). internal async Task StopAsync(CancellationToken token) { - using var _ = token.Register(() => _abruptCts.Cancel()); // Registers for immediate abort. + using var _ = token.Register(() => { try { _abruptCts.Cancel(); } catch (ObjectDisposedException) { } }); // Registers for immediate abort. _context.ConnectionCancellation.Cancel(); // Graceful shutdown await _processingCompleted.Task; }