Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:

- name: Begin scan
if: env.SONAR_TOKEN != null && env.SONAR_TOKEN != ''
run: dotnet sonarscanner begin /k:"GenHTTP" /d:sonar.token="$SONAR_TOKEN" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.exclusions="**/bin/**/*,**/obj/**/*,**/*.css,**/*.js,**/*.html" /o:"kaliumhexacyanoferrat" /k:"GenHTTP" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.branch.name="${GITHUB_REF##*/}" /d:sonar.dotnet.excludeTestProjects=true
run: dotnet sonarscanner begin /k:"GenHTTP" /d:sonar.token="$SONAR_TOKEN" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.exclusions="**/bin/**/*,**/obj/**/*,**/*.css,**/*.js,**/*.html" /d:sonar.coverage.exclusions="**/Engine/Ioxide/Tls/TlsDuplexPipe.cs" /o:"kaliumhexacyanoferrat" /k:"GenHTTP" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.branch.name="${GITHUB_REF##*/}" /d:sonar.dotnet.excludeTestProjects=true

- name: Build project
run: dotnet build GenHTTP.slnx -c Release
Expand Down
4 changes: 4 additions & 0 deletions Engine/Ioxide/GenHTTP.Engine.Ioxide.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
<PackageReference Include="Glyph11.Pico" Version="0.0.1" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="GenHTTP.Testing.Acceptance" />
</ItemGroup>

</Project>
33 changes: 0 additions & 33 deletions Modules/Websockets/Utils/PooledMemoryOwner.cs

This file was deleted.

67 changes: 67 additions & 0 deletions Testing/Acceptance/Engine/Body/ChunkedBodyStreamTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,73 @@ public void TestBasics()
Assert.ThrowsExactly<NotSupportedException>(() => stream.SetLength(0));
Assert.ThrowsExactly<NotSupportedException>(() => stream.Write([], 0, 0));
Assert.ThrowsExactly<NotSupportedException>(() => _ = stream.Length);

Assert.ThrowsExactly<NotSupportedException>(() => _ = stream.Position);
Assert.ThrowsExactly<NotSupportedException>(() => stream.Position = 0);
}

[TestMethod]
public async Task TestReadAsyncByteArrayOverload()
{
var stream = await CreateAsync("5\r\nHello\r\n0\r\n\r\n");

var buffer = new byte[5];
var read = await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None);

Assert.AreEqual(5, read);
Assert.AreEqual("Hello", Encoding.ASCII.GetString(buffer));
}

[TestMethod]
public async Task TestReadIntoEmptyBufferReturnsZero()
{
var stream = await CreateAsync("5\r\nHello\r\n0\r\n\r\n");

Assert.AreEqual(0, await stream.ReadAsync(Memory<byte>.Empty));
}

[TestMethod]
public async Task TestReadOnEmptyCompletedPipeReturnsZero()
{
var stream = await CreateAsync("");

Assert.AreEqual(0, await stream.ReadAsync(new byte[16]));
}

[TestMethod]
public async Task TestTruncatedChunkThrows()
{
var stream = await CreateAsync("5\r\nHel");

#pragma warning disable CA2022 // expected to throw before any byte count is returned
await Assert.ThrowsExactlyAsync<InvalidDataException>(async () => await stream.ReadAsync(new byte[16]));
#pragma warning restore CA2022
}

[TestMethod]
public async Task TestDrainOnEmptyCompletedPipe()
{
var stream = await CreateAsync("");

await stream.DrainAsync();
}

[TestMethod]
public async Task TestDrainOnTruncatedChunkReturnsWithoutThrowing()
{
var stream = await CreateAsync("5\r\nHel");

await stream.DrainAsync();
}

private static async Task<ChunkedBodyStream> CreateAsync(string chunkedBody)
{
var pipe = new Pipe();

await pipe.Writer.WriteAsync(Encoding.ASCII.GetBytes(chunkedBody));
await pipe.Writer.CompleteAsync();

return new ChunkedBodyStream(pipe.Reader);
}

}
27 changes: 27 additions & 0 deletions Testing/Acceptance/Engine/Body/DrainBodyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,33 @@ public async Task TestPartialRead(TestEngine engine)
Assert.AreEqual(3, handler.RequestCount);
}

[TestMethod]
[MultiEngineTest]
public async Task TestPartialReadChunked(TestEngine engine)
{
var handler = new PartialReadHandler(bytesToRead: 4);

await using var runner = await TestHost.RunAsync(handler.Wrap(), engine: engine);

using var client = TestHost.GetClient();

for (var i = 0; i < 3; i++)
{
var request = new HttpRequestMessage(HttpMethod.Post, runner.GetUrl())
{
Content = new StreamContent(new MemoryStream("Hello, body!"u8.ToArray()))
};

request.Headers.TransferEncodingChunked = true;

using var response = await client.SendAsync(request);

await response.AssertStatusAsync(HttpStatusCode.OK);
}

Assert.AreEqual(3, handler.RequestCount);
}

#endregion

#region Supporting types
Expand Down
19 changes: 19 additions & 0 deletions Testing/Acceptance/Engine/HostTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Layouting;

namespace GenHTTP.Testing.Acceptance.Engine;
Expand All @@ -7,6 +8,24 @@ namespace GenHTTP.Testing.Acceptance.Engine;
public sealed class HostTests
{

[TestMethod]
public void TestPortZeroThrows()
{
var host = Host.Create();

Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => host.Port(0));
}

[TestMethod]
public async Task TestRunAsyncReturnsErrorCodeWithoutHandler()
{
var host = Host.Create();

var exitCode = await host.RunAsync();

Assert.AreEqual(-1, exitCode);
}

[TestMethod]
[MultiEngineTest]
public async Task TestStart(TestEngine engine)
Expand Down
68 changes: 68 additions & 0 deletions Testing/Acceptance/Engine/Ioxide/PipeWriterStreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#if NET11_0_OR_GREATER

using System.Buffers;
using System.IO.Pipelines;
using System.Text;

using GenHTTP.Engine.Ioxide.Protocol;

namespace GenHTTP.Testing.Acceptance.Engine.Ioxide;

[TestClass]
public sealed class PipeWriterStreamTests
{

[TestMethod]
public void TestBasics()
{
var pipe = new Pipe();

var stream = new PipeWriterStream(new ArrayBufferWriter<byte>(), pipe.Writer);

Assert.IsFalse(stream.CanRead);
Assert.IsFalse(stream.CanSeek);
Assert.IsTrue(stream.CanWrite);

Assert.ThrowsExactly<NotSupportedException>(() => _ = stream.Length);
Assert.ThrowsExactly<NotSupportedException>(() => _ = stream.Position);
Assert.ThrowsExactly<NotSupportedException>(() => stream.Position = 0);

Assert.ThrowsExactly<NotSupportedException>(() => stream.Read([], 0, 1));
Assert.ThrowsExactly<NotSupportedException>(() => stream.Seek(0, SeekOrigin.Begin));
Assert.ThrowsExactly<NotSupportedException>(() => stream.SetLength(0));

stream.Flush();
}

[TestMethod]
public void TestWriteByte()
{
var writer = new ArrayBufferWriter<byte>();

var stream = new PipeWriterStream(writer, new Pipe().Writer);

stream.WriteByte((byte)'H');
stream.WriteByte((byte)'i');

Assert.AreEqual("Hi", Encoding.ASCII.GetString(writer.WrittenSpan));
}

[TestMethod]
public async Task TestFlushAsyncDrainsUnderlyingPipe()
{
var pipe = new Pipe();

var stream = new PipeWriterStream(pipe.Writer, pipe.Writer);

await stream.WriteAsync("Hello"u8.ToArray());

await stream.FlushAsync();

var read = await pipe.Reader.ReadAsync();

Assert.AreEqual("Hello", Encoding.ASCII.GetString(read.Buffer.ToArray()));
}

}

#endif
31 changes: 31 additions & 0 deletions Testing/Acceptance/Engine/Kestrel/ProtocolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,37 @@ public async Task TestHttp2And3()
}
}

[TestMethod]
public async Task TestHttpsOnSpecificAddress()
{
if (!Engines.KestrelEnabled()) return;

var logic = Inline.Create().Get(() => "Hello");

var certificate = await Utilities.Security.GetCertificateAsync();

var runner = new TestHost(logic.Build(), engine: TestEngine.Kestrel);

var port = TestHost.NextPort();

runner.Host.Bind(IPAddress.Loopback, (ushort)port, certificate);

await runner.StartAsync();

try
{
using var client = TestHost.GetClient(ignoreSecurityErrors: true);

using var response = await client.GetAsync($"https://localhost:{port}");

await response.AssertStatusAsync(HttpStatusCode.OK);
}
finally
{
await runner.DisposeAsync();
}
}

#region Helpers

private static HttpClient GetClient()
Expand Down
56 changes: 56 additions & 0 deletions Testing/Acceptance/Engine/RequestPropertyTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using System.Net;

using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Protocol;
using GenHTTP.Engine.Shared.Types;
using GenHTTP.Modules.Functional;

using NSubstitute;

namespace GenHTTP.Testing.Acceptance.Engine;

[TestClass]
Expand Down Expand Up @@ -35,4 +39,56 @@ public async Task TestRequestProperties(TestEngine engine)
await response.AssertStatusAsync(HttpStatusCode.OK);
}

[TestMethod]
[MultiEngineTest]
public async Task TestGetBodyTwiceThrows(TestEngine engine)
{
var app = Inline.Create().Get((IRequest request) =>
{
request.GetBody();

Assert.ThrowsExactly<InvalidOperationException>(() => request.GetBody());

return true;
});

await using var host = await TestHost.RunAsync(app, engine: engine);

using var response = await host.GetResponseAsync();

await response.AssertStatusAsync(HttpStatusCode.OK);
}

[TestMethod]
[MultiEngineTest]
public async Task TestHeaderAccessibleAfterBodyLoaded(TestEngine engine)
{
var app = Inline.Create().Get((IRequest request) =>
{
request.GetBody();

return request.Header.Target.AsString(decode: false);
});

await using var host = await TestHost.RunAsync(app, engine: engine);

using var response = await host.GetResponseAsync();

await response.AssertStatusAsync(HttpStatusCode.OK);
}

[TestMethod]
public void TestApplyWithoutEndpoint()
{
var request = new Request();

var server = Substitute.For<IServer>();

request.Apply(server);

Assert.AreSame(server, request.Server);
Assert.IsNotNull(request.Header);
Assert.IsNotNull(request.Properties);
}

}
6 changes: 6 additions & 0 deletions Testing/Acceptance/GenHTTP.Testing.Acceptance.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@

<ItemGroup>

<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.6.0" />

<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.10" />

<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.10" />
Expand Down Expand Up @@ -78,6 +80,10 @@
<ProjectReference Include="..\..\Engine\Kestrel\GenHTTP.Engine.Kestrel.csproj" />
<ProjectReference Include="..\..\Engine\Shared\GenHTTP.Engine.Shared.csproj" />

<!-- referenced as a plain library (not as an analyzer) so its generator logic
runs in-process and is picked up by code coverage -->
<ProjectReference Include="..\..\Generators\MemoryView\GenHTTP.Generators.MemoryView.csproj" />

<ProjectReference Include="..\..\Modules\Archives\GenHTTP.Modules.Archives.csproj" />

<ProjectReference Include="..\..\Modules\Files\GenHTTP.Modules.Files.csproj" />
Expand Down
Loading
Loading