Skip to content

Commit 30866c2

Browse files
committed
test: new IntegrationTests
1 parent f5c8fe0 commit 30866c2

152 files changed

Lines changed: 5489 additions & 15617 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
1414
</ItemGroup>
1515
<ItemGroup Label="Testing">
16+
<PackageVersion Include="Testcontainers" Version="4.6.0" />
1617
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
1718
<PackageVersion Include="PublicApiGenerator" Version="11.5.4" />
1819
<PackageVersion Include="Verify.DiffPlex" Version="3.1.2" />

src/TurboHTTP.Benchmarks/Internal/KestrelBaseClass.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ public abstract class KestrelBaseClass : BenchmarkSuiteBase
1212
private static readonly SemaphoreSlim _serverLock = new(1, 1);
1313
private static int _serverRefCount;
1414

15-
/// <summary>Heavy payload: 10 KB deterministic byte array for POST benchmarks.</summary>
16-
protected static readonly byte[] HeavyPayload = GeneratePayload(4 * 1024 * 1024);
15+
/// <summary>Heavy payload: 1 MB deterministic byte array for POST benchmarks.</summary>
16+
protected static readonly byte[] HeavyPayload = GeneratePayload(1 * 1024 * 1024);
1717

1818
/// <summary>Port on which the HTTP/1.1 Kestrel listener is running. Set in GlobalSetup.</summary>
1919
protected int KestrelHttp11Port { get; private set; }
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Net;
2+
using TurboHTTP.IntegrationTests.Container.Shared;
3+
4+
namespace TurboHTTP.IntegrationTests.Container.Features;
5+
6+
public sealed class AuthFeatureSpec : FeatureSpecBase
7+
{
8+
public AuthFeatureSpec(ServerContainerFixture server, ActorSystemFixture systemFixture)
9+
: base(server, systemFixture) { }
10+
11+
[Theory(Timeout = 15000)]
12+
[MemberData(nameof(Protocols))]
13+
public async Task Auth_should_succeed_with_correct_credentials(HttpProtocol protocol)
14+
{
15+
await using var helper = CreateClient(protocol, configureOptions: opts =>
16+
{
17+
opts.Credentials = new NetworkCredential("testuser", "testpass");
18+
opts.PreAuthenticate = true;
19+
});
20+
var ct = TestContext.Current.CancellationToken;
21+
22+
var response = await helper.Client.SendAsync(
23+
new HttpRequestMessage(HttpMethod.Get, "/basic-auth/testuser/testpass"), ct);
24+
25+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
26+
}
27+
28+
[Theory(Timeout = 15000)]
29+
[MemberData(nameof(Protocols))]
30+
public async Task Auth_should_return_401_without_credentials(HttpProtocol protocol)
31+
{
32+
await using var helper = CreateClient(protocol);
33+
var ct = TestContext.Current.CancellationToken;
34+
35+
var response = await helper.Client.SendAsync(
36+
new HttpRequestMessage(HttpMethod.Get, "/basic-auth/testuser/testpass"), ct);
37+
38+
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
39+
}
40+
41+
[Theory(Timeout = 15000)]
42+
[MemberData(nameof(Protocols))]
43+
public async Task Auth_should_return_401_with_wrong_credentials(HttpProtocol protocol)
44+
{
45+
await using var helper = CreateClient(protocol, configureOptions: opts =>
46+
{
47+
opts.Credentials = new NetworkCredential("wrong", "wrong");
48+
opts.PreAuthenticate = true;
49+
});
50+
var ct = TestContext.Current.CancellationToken;
51+
52+
var response = await helper.Client.SendAsync(
53+
new HttpRequestMessage(HttpMethod.Get, "/basic-auth/testuser/testpass"), ct);
54+
55+
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
56+
}
57+
58+
[Theory(Timeout = 15000)]
59+
[MemberData(nameof(Protocols))]
60+
public async Task Auth_should_not_send_header_when_preauthenticate_disabled(HttpProtocol protocol)
61+
{
62+
await using var helper = CreateClient(protocol, configureOptions: opts =>
63+
{
64+
opts.Credentials = new NetworkCredential("testuser", "testpass");
65+
opts.PreAuthenticate = false;
66+
});
67+
var ct = TestContext.Current.CancellationToken;
68+
69+
var response = await helper.Client.SendAsync(
70+
new HttpRequestMessage(HttpMethod.Get, "/basic-auth/testuser/testpass"), ct);
71+
72+
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
73+
}
74+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System.Net;
2+
using System.Text.Json;
3+
using TurboHTTP.IntegrationTests.Container.Shared;
4+
5+
namespace TurboHTTP.IntegrationTests.Container.Features;
6+
7+
public sealed class CacheFeatureSpec : FeatureSpecBase
8+
{
9+
public CacheFeatureSpec(ServerContainerFixture server, ActorSystemFixture systemFixture)
10+
: base(server, systemFixture) { }
11+
12+
[Theory(Timeout = 15000)]
13+
[MemberData(nameof(Protocols))]
14+
public async Task Cache_should_serve_cached_response_on_second_request(HttpProtocol protocol)
15+
{
16+
await using var helper = CreateClient(protocol, b => b.WithCache());
17+
var ct = TestContext.Current.CancellationToken;
18+
19+
var r1 = await helper.Client.SendAsync(
20+
new HttpRequestMessage(HttpMethod.Get, "/cache/60"), ct);
21+
Assert.Equal(HttpStatusCode.OK, r1.StatusCode);
22+
23+
var r2 = await helper.Client.SendAsync(
24+
new HttpRequestMessage(HttpMethod.Get, "/cache/60"), ct);
25+
Assert.Equal(HttpStatusCode.OK, r2.StatusCode);
26+
}
27+
28+
[Theory(Timeout = 15000)]
29+
[MemberData(nameof(Protocols))]
30+
public async Task Cache_should_send_if_none_match_for_etag(HttpProtocol protocol)
31+
{
32+
await using var helper = CreateClient(protocol, b => b.WithCache());
33+
var ct = TestContext.Current.CancellationToken;
34+
35+
var r1 = await helper.Client.SendAsync(
36+
new HttpRequestMessage(HttpMethod.Get, "/etag/test-etag"), ct);
37+
Assert.Equal(HttpStatusCode.OK, r1.StatusCode);
38+
39+
var r2 = await helper.Client.SendAsync(
40+
new HttpRequestMessage(HttpMethod.Get, "/etag/test-etag"), ct);
41+
42+
Assert.True(
43+
r2.StatusCode is HttpStatusCode.OK or HttpStatusCode.NotModified,
44+
$"Expected OK or 304, got {r2.StatusCode}");
45+
}
46+
47+
[Theory(Timeout = 15000)]
48+
[MemberData(nameof(Protocols))]
49+
public async Task Cache_should_return_fresh_response_when_cache_disabled(HttpProtocol protocol)
50+
{
51+
await using var helper = CreateClient(protocol);
52+
var ct = TestContext.Current.CancellationToken;
53+
54+
var r1 = await helper.Client.SendAsync(
55+
new HttpRequestMessage(HttpMethod.Get, "/cache/60"), ct);
56+
var b1 = await r1.Content.ReadAsStringAsync(ct);
57+
58+
var r2 = await helper.Client.SendAsync(
59+
new HttpRequestMessage(HttpMethod.Get, "/cache/60"), ct);
60+
var b2 = await r2.Content.ReadAsStringAsync(ct);
61+
62+
Assert.Equal(HttpStatusCode.OK, r1.StatusCode);
63+
Assert.Equal(HttpStatusCode.OK, r2.StatusCode);
64+
}
65+
66+
[Theory(Timeout = 15000)]
67+
[MemberData(nameof(Protocols))]
68+
public async Task Cache_should_revalidate_with_no_cache(HttpProtocol protocol)
69+
{
70+
await using var helper = CreateClient(protocol, b => b.WithCache());
71+
var ct = TestContext.Current.CancellationToken;
72+
73+
var r1 = await helper.Client.SendAsync(
74+
new HttpRequestMessage(HttpMethod.Get, "/cache"), ct);
75+
Assert.Equal(HttpStatusCode.OK, r1.StatusCode);
76+
77+
var request = new HttpRequestMessage(HttpMethod.Get, "/cache");
78+
request.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue
79+
{
80+
NoCache = true
81+
};
82+
var r2 = await helper.Client.SendAsync(request, ct);
83+
Assert.Equal(HttpStatusCode.OK, r2.StatusCode);
84+
}
85+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Net;
2+
using System.Text.Json;
3+
using TurboHTTP.IntegrationTests.Container.Shared;
4+
5+
namespace TurboHTTP.IntegrationTests.Container.Features;
6+
7+
public sealed class CompressionFeatureSpec : FeatureSpecBase
8+
{
9+
public CompressionFeatureSpec(ServerContainerFixture server, ActorSystemFixture systemFixture)
10+
: base(server, systemFixture) { }
11+
12+
[Theory(Timeout = 15000)]
13+
[MemberData(nameof(Protocols))]
14+
public async Task Decompression_should_transparently_decompress_gzip(HttpProtocol protocol)
15+
{
16+
await using var helper = CreateClient(protocol, b => b.WithDecompression());
17+
var ct = TestContext.Current.CancellationToken;
18+
19+
var response = await helper.Client.SendAsync(
20+
new HttpRequestMessage(HttpMethod.Get, "/gzip"), ct);
21+
22+
var body = await response.Content.ReadAsStringAsync(ct);
23+
var json = JsonDocument.Parse(body);
24+
25+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
26+
Assert.True(json.RootElement.GetProperty("gzipped").GetBoolean());
27+
}
28+
29+
[Theory(Timeout = 15000)]
30+
[MemberData(nameof(Protocols))]
31+
public async Task Decompression_should_transparently_decompress_deflate(HttpProtocol protocol)
32+
{
33+
await using var helper = CreateClient(protocol, b => b.WithDecompression());
34+
var ct = TestContext.Current.CancellationToken;
35+
36+
var response = await helper.Client.SendAsync(
37+
new HttpRequestMessage(HttpMethod.Get, "/deflate"), ct);
38+
39+
var body = await response.Content.ReadAsStringAsync(ct);
40+
var json = JsonDocument.Parse(body);
41+
42+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
43+
Assert.True(json.RootElement.GetProperty("deflated").GetBoolean());
44+
}
45+
46+
[Theory(Timeout = 15000)]
47+
[MemberData(nameof(Protocols))]
48+
public async Task Decompression_should_handle_uncompressed_response(HttpProtocol protocol)
49+
{
50+
await using var helper = CreateClient(protocol, b => b.WithDecompression());
51+
var ct = TestContext.Current.CancellationToken;
52+
53+
var response = await helper.Client.SendAsync(
54+
new HttpRequestMessage(HttpMethod.Get, "/get"), ct);
55+
56+
var body = await response.Content.ReadAsStringAsync(ct);
57+
var json = JsonDocument.Parse(body);
58+
59+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
60+
Assert.True(json.RootElement.TryGetProperty("url", out _));
61+
}
62+
63+
[Theory(Timeout = 15000)]
64+
[MemberData(nameof(Protocols))]
65+
public async Task Decompression_should_decompress_sequentially(HttpProtocol protocol)
66+
{
67+
await using var helper = CreateClient(protocol, b => b.WithDecompression());
68+
var ct = TestContext.Current.CancellationToken;
69+
70+
var r1 = await helper.Client.SendAsync(
71+
new HttpRequestMessage(HttpMethod.Get, "/gzip"), ct);
72+
var b1 = await r1.Content.ReadAsStringAsync(ct);
73+
Assert.True(JsonDocument.Parse(b1).RootElement.GetProperty("gzipped").GetBoolean());
74+
75+
var r2 = await helper.Client.SendAsync(
76+
new HttpRequestMessage(HttpMethod.Get, "/deflate"), ct);
77+
var b2 = await r2.Content.ReadAsStringAsync(ct);
78+
Assert.True(JsonDocument.Parse(b2).RootElement.GetProperty("deflated").GetBoolean());
79+
}
80+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Net;
2+
using System.Text.Json;
3+
using TurboHTTP.IntegrationTests.Container.Shared;
4+
5+
namespace TurboHTTP.IntegrationTests.Container.Features;
6+
7+
public sealed class CookieFeatureSpec : FeatureSpecBase
8+
{
9+
public CookieFeatureSpec(ServerContainerFixture server, ActorSystemFixture systemFixture)
10+
: base(server, systemFixture) { }
11+
12+
[Theory(Timeout = 15000)]
13+
[MemberData(nameof(Protocols))]
14+
public async Task Cookie_should_roundtrip_set_and_echo(HttpProtocol protocol)
15+
{
16+
await using var helper = CreateClient(protocol, b => b.WithCookies().WithRedirect());
17+
var ct = TestContext.Current.CancellationToken;
18+
19+
var setResponse = await helper.Client.SendAsync(
20+
new HttpRequestMessage(HttpMethod.Get, "/cookies/set?session=abc123"), ct);
21+
22+
Assert.Equal(HttpStatusCode.OK, setResponse.StatusCode);
23+
24+
var body = await setResponse.Content.ReadAsStringAsync(ct);
25+
var json = JsonDocument.Parse(body);
26+
Assert.Equal("abc123", json.RootElement.GetProperty("session").GetString());
27+
}
28+
29+
[Theory(Timeout = 15000)]
30+
[MemberData(nameof(Protocols))]
31+
public async Task Cookie_should_persist_across_sequential_requests(HttpProtocol protocol)
32+
{
33+
await using var helper = CreateClient(protocol, b => b.WithCookies().WithRedirect());
34+
var ct = TestContext.Current.CancellationToken;
35+
36+
await helper.Client.SendAsync(
37+
new HttpRequestMessage(HttpMethod.Get, "/cookies/set?token=xyz"), ct);
38+
39+
var echoResponse = await helper.Client.SendAsync(
40+
new HttpRequestMessage(HttpMethod.Get, "/cookies"), ct);
41+
42+
var body = await echoResponse.Content.ReadAsStringAsync(ct);
43+
var json = JsonDocument.Parse(body);
44+
45+
Assert.True(json.RootElement.TryGetProperty("token", out var token),
46+
$"Cookie 'token' not sent on subsequent request. Body: {body}");
47+
Assert.Equal("xyz", token.GetString());
48+
}
49+
50+
[Theory(Timeout = 15000)]
51+
[MemberData(nameof(Protocols))]
52+
public async Task Cookie_should_not_be_sent_when_cookies_disabled(HttpProtocol protocol)
53+
{
54+
await using var helper = CreateClient(protocol, b => b.WithRedirect());
55+
var ct = TestContext.Current.CancellationToken;
56+
57+
await helper.Client.SendAsync(
58+
new HttpRequestMessage(HttpMethod.Get, "/cookies/set?token=secret"), ct);
59+
60+
var response = await helper.Client.SendAsync(
61+
new HttpRequestMessage(HttpMethod.Get, "/cookies"), ct);
62+
63+
var body = await response.Content.ReadAsStringAsync(ct);
64+
var json = JsonDocument.Parse(body);
65+
66+
Assert.Empty(json.RootElement.EnumerateObject());
67+
}
68+
}

0 commit comments

Comments
 (0)