Skip to content

Commit 2757dda

Browse files
committed
added more tests
1 parent 7921d01 commit 2757dda

44 files changed

Lines changed: 3901 additions & 1054 deletions

Some content is hidden

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

docs/superpowers/plans/2026-04-29-io-to-transport-migration.md

Lines changed: 0 additions & 803 deletions
This file was deleted.

src/Servus.Akka.Tests/Servus.Akka.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage"/>
1010
<PackageReference Include="xunit.v3.mtp-v2"/>
1111
<PackageReference Include="Akka.TestKit.Xunit"/>
12+
<PackageReference Include="Akka.Streams.TestKit"/>
1213
</ItemGroup>
1314

1415
<ItemGroup>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System.Net;
2+
using System.Net.Security;
3+
using System.Security.Authentication;
4+
using Servus.Akka.Transport;
5+
6+
namespace Servus.Akka.Tests.Transport;
7+
8+
public sealed class ConnectionInfoSpec
9+
{
10+
[Fact(Timeout = 5000)]
11+
public void Should_store_all_properties()
12+
{
13+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
14+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
15+
var sslProtocol = SslProtocols.Tls13;
16+
var appProtocol = SslApplicationProtocol.Http2;
17+
18+
var info = new ConnectionInfo(local, remote, sslProtocol, appProtocol);
19+
20+
Assert.Equal(local, info.Local);
21+
Assert.Equal(remote, info.Remote);
22+
Assert.Equal(sslProtocol, info.NegotiatedSslProtocol);
23+
Assert.Equal(appProtocol, info.NegotiatedApplicationProtocol);
24+
}
25+
26+
[Fact(Timeout = 5000)]
27+
public void Should_support_null_ssl_properties()
28+
{
29+
var local = new IPEndPoint(IPAddress.Loopback, 8080);
30+
var remote = new IPEndPoint(IPAddress.Parse("10.0.0.1"), 80);
31+
32+
var info = new ConnectionInfo(local, remote, NegotiatedSslProtocol: null, NegotiatedApplicationProtocol: null);
33+
34+
Assert.Equal(local, info.Local);
35+
Assert.Equal(remote, info.Remote);
36+
Assert.Null(info.NegotiatedSslProtocol);
37+
Assert.Null(info.NegotiatedApplicationProtocol);
38+
}
39+
40+
[Fact(Timeout = 5000)]
41+
public void Equality_should_work_for_records()
42+
{
43+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
44+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
45+
46+
var info1 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
47+
var info2 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
48+
49+
Assert.Equal(info1, info2);
50+
Assert.Equal(info1.GetHashCode(), info2.GetHashCode());
51+
}
52+
53+
[Fact(Timeout = 5000)]
54+
public void Inequality_should_work_for_different_local_endpoint()
55+
{
56+
var local1 = new IPEndPoint(IPAddress.Loopback, 5000);
57+
var local2 = new IPEndPoint(IPAddress.Loopback, 5001);
58+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
59+
60+
var info1 = new ConnectionInfo(local1, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
61+
var info2 = new ConnectionInfo(local2, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
62+
63+
Assert.NotEqual(info1, info2);
64+
}
65+
66+
[Fact(Timeout = 5000)]
67+
public void Inequality_should_work_for_different_remote_endpoint()
68+
{
69+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
70+
var remote1 = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
71+
var remote2 = new IPEndPoint(IPAddress.Parse("192.168.1.2"), 443);
72+
73+
var info1 = new ConnectionInfo(local, remote1, SslProtocols.Tls13, SslApplicationProtocol.Http2);
74+
var info2 = new ConnectionInfo(local, remote2, SslProtocols.Tls13, SslApplicationProtocol.Http2);
75+
76+
Assert.NotEqual(info1, info2);
77+
}
78+
79+
[Fact(Timeout = 5000)]
80+
public void Inequality_should_work_for_different_ssl_protocol()
81+
{
82+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
83+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
84+
85+
var info1 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
86+
var info2 = new ConnectionInfo(local, remote, SslProtocols.Tls12, SslApplicationProtocol.Http2);
87+
88+
Assert.NotEqual(info1, info2);
89+
}
90+
91+
[Fact(Timeout = 5000)]
92+
public void Inequality_should_work_for_different_app_protocol()
93+
{
94+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
95+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
96+
97+
var info1 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
98+
var info2 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http11);
99+
100+
Assert.NotEqual(info1, info2);
101+
}
102+
103+
[Fact(Timeout = 5000)]
104+
public void Should_support_mixed_null_ssl_fields()
105+
{
106+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
107+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
108+
109+
var info1 = new ConnectionInfo(local, remote, SslProtocols.Tls13, NegotiatedApplicationProtocol: null);
110+
var info2 = new ConnectionInfo(local, remote, NegotiatedSslProtocol: null, SslApplicationProtocol.Http2);
111+
112+
Assert.Equal(SslProtocols.Tls13, info1.NegotiatedSslProtocol);
113+
Assert.Null(info1.NegotiatedApplicationProtocol);
114+
115+
Assert.Null(info2.NegotiatedSslProtocol);
116+
Assert.Equal(SslApplicationProtocol.Http2, info2.NegotiatedApplicationProtocol);
117+
}
118+
119+
[Fact(Timeout = 5000)]
120+
public void Should_work_as_dictionary_key()
121+
{
122+
var local = new IPEndPoint(IPAddress.Loopback, 5000);
123+
var remote = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 443);
124+
125+
var info1 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
126+
var info2 = new ConnectionInfo(local, remote, SslProtocols.Tls13, SslApplicationProtocol.Http2);
127+
128+
var dict = new Dictionary<ConnectionInfo, string> { { info1, "pooled" } };
129+
130+
Assert.True(dict.ContainsKey(info2));
131+
Assert.Equal("pooled", dict[info2]);
132+
}
133+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
using Servus.Akka.Transport;
2+
3+
namespace Servus.Akka.Tests.Transport;
4+
5+
public sealed class PoolConfigRegistrySpec
6+
{
7+
[Fact(Timeout = 5000)]
8+
public void Constructor_should_set_default_config()
9+
{
10+
var defaultConfig = new TcpPoolConfig(
11+
MaxConnectionsPerHost: 10,
12+
IdleTimeout: TimeSpan.FromSeconds(30),
13+
ConnectionLifetime: TimeSpan.FromMinutes(5),
14+
ReuseOnUpstreamFinish: true);
15+
16+
var registry = new PoolConfigRegistry(defaultConfig);
17+
18+
var resolved = registry.Resolve(null);
19+
Assert.Equal(defaultConfig, resolved);
20+
}
21+
22+
[Fact(Timeout = 5000)]
23+
public void Resolve_should_return_default_when_key_is_null()
24+
{
25+
var defaultConfig = new TcpPoolConfig(
26+
MaxConnectionsPerHost: 5,
27+
IdleTimeout: TimeSpan.FromSeconds(60),
28+
ConnectionLifetime: TimeSpan.FromMinutes(10),
29+
ReuseOnUpstreamFinish: false);
30+
31+
var registry = new PoolConfigRegistry(defaultConfig);
32+
33+
var resolved = registry.Resolve(null);
34+
Assert.Equal(defaultConfig, resolved);
35+
}
36+
37+
[Fact(Timeout = 5000)]
38+
public void Resolve_should_return_default_when_key_not_registered()
39+
{
40+
var defaultConfig = new TcpPoolConfig(
41+
MaxConnectionsPerHost: 8,
42+
IdleTimeout: TimeSpan.FromSeconds(45),
43+
ConnectionLifetime: TimeSpan.FromMinutes(3),
44+
ReuseOnUpstreamFinish: true);
45+
46+
var registry = new PoolConfigRegistry(defaultConfig);
47+
48+
var resolved = registry.Resolve("nonexistent-pool");
49+
Assert.Equal(defaultConfig, resolved);
50+
}
51+
52+
[Fact(Timeout = 5000)]
53+
public void Register_should_store_config_for_key()
54+
{
55+
var defaultConfig = new TcpPoolConfig(
56+
MaxConnectionsPerHost: 10,
57+
IdleTimeout: TimeSpan.FromSeconds(30),
58+
ConnectionLifetime: TimeSpan.FromMinutes(5),
59+
ReuseOnUpstreamFinish: true);
60+
61+
var customConfig = new TcpPoolConfig(
62+
MaxConnectionsPerHost: 20,
63+
IdleTimeout: TimeSpan.FromSeconds(15),
64+
ConnectionLifetime: TimeSpan.FromMinutes(2),
65+
ReuseOnUpstreamFinish: false);
66+
67+
var registry = new PoolConfigRegistry(defaultConfig);
68+
registry.Register("custom-pool", customConfig);
69+
70+
var resolved = registry.Resolve("custom-pool");
71+
Assert.Equal(customConfig, resolved);
72+
}
73+
74+
[Fact(Timeout = 5000)]
75+
public void Resolve_should_return_registered_config()
76+
{
77+
var defaultConfig = new TcpPoolConfig(
78+
MaxConnectionsPerHost: 10,
79+
IdleTimeout: TimeSpan.FromSeconds(30),
80+
ConnectionLifetime: TimeSpan.FromMinutes(5),
81+
ReuseOnUpstreamFinish: true);
82+
83+
var poolAConfig = new TcpPoolConfig(
84+
MaxConnectionsPerHost: 5,
85+
IdleTimeout: TimeSpan.FromSeconds(20),
86+
ConnectionLifetime: TimeSpan.FromMinutes(1),
87+
ReuseOnUpstreamFinish: false);
88+
89+
var poolBConfig = new TcpPoolConfig(
90+
MaxConnectionsPerHost: 50,
91+
IdleTimeout: TimeSpan.FromSeconds(60),
92+
ConnectionLifetime: TimeSpan.FromMinutes(10),
93+
ReuseOnUpstreamFinish: true);
94+
95+
var registry = new PoolConfigRegistry(defaultConfig);
96+
registry.Register("pool-a", poolAConfig);
97+
registry.Register("pool-b", poolBConfig);
98+
99+
Assert.Equal(poolAConfig, registry.Resolve("pool-a"));
100+
Assert.Equal(poolBConfig, registry.Resolve("pool-b"));
101+
Assert.Equal(defaultConfig, registry.Resolve("pool-c"));
102+
}
103+
104+
[Fact(Timeout = 5000)]
105+
public void Register_should_overwrite_existing_key()
106+
{
107+
var defaultConfig = new TcpPoolConfig(
108+
MaxConnectionsPerHost: 10,
109+
IdleTimeout: TimeSpan.FromSeconds(30),
110+
ConnectionLifetime: TimeSpan.FromMinutes(5),
111+
ReuseOnUpstreamFinish: true);
112+
113+
var initialConfig = new TcpPoolConfig(
114+
MaxConnectionsPerHost: 15,
115+
IdleTimeout: TimeSpan.FromSeconds(25),
116+
ConnectionLifetime: TimeSpan.FromMinutes(3),
117+
ReuseOnUpstreamFinish: false);
118+
119+
var overwriteConfig = new TcpPoolConfig(
120+
MaxConnectionsPerHost: 25,
121+
IdleTimeout: TimeSpan.FromSeconds(40),
122+
ConnectionLifetime: TimeSpan.FromMinutes(7),
123+
ReuseOnUpstreamFinish: true);
124+
125+
var registry = new PoolConfigRegistry(defaultConfig);
126+
registry.Register("pool", initialConfig);
127+
128+
var resolved1 = registry.Resolve("pool");
129+
Assert.Equal(initialConfig, resolved1);
130+
131+
registry.Register("pool", overwriteConfig);
132+
133+
var resolved2 = registry.Resolve("pool");
134+
Assert.Equal(overwriteConfig, resolved2);
135+
}
136+
137+
[Fact(Timeout = 5000)]
138+
public void Register_should_throw_if_config_is_null()
139+
{
140+
var defaultConfig = new TcpPoolConfig(
141+
MaxConnectionsPerHost: 10,
142+
IdleTimeout: TimeSpan.FromSeconds(30),
143+
ConnectionLifetime: TimeSpan.FromMinutes(5),
144+
ReuseOnUpstreamFinish: true);
145+
146+
var registry = new PoolConfigRegistry(defaultConfig);
147+
148+
Assert.Throws<ArgumentNullException>(() => registry.Register("pool", null!));
149+
}
150+
151+
[Fact(Timeout = 5000)]
152+
public void Constructor_should_throw_if_default_config_is_null()
153+
{
154+
Assert.Throws<ArgumentNullException>(() => new PoolConfigRegistry(null!));
155+
}
156+
157+
[Fact(Timeout = 5000)]
158+
public void Register_should_support_case_insensitive_keys()
159+
{
160+
var defaultConfig = new TcpPoolConfig(
161+
MaxConnectionsPerHost: 10,
162+
IdleTimeout: TimeSpan.FromSeconds(30),
163+
ConnectionLifetime: TimeSpan.FromMinutes(5),
164+
ReuseOnUpstreamFinish: true);
165+
166+
var customConfig = new TcpPoolConfig(
167+
MaxConnectionsPerHost: 20,
168+
IdleTimeout: TimeSpan.FromSeconds(15),
169+
ConnectionLifetime: TimeSpan.FromMinutes(2),
170+
ReuseOnUpstreamFinish: false);
171+
172+
var registry = new PoolConfigRegistry(defaultConfig);
173+
registry.Register("MyPool", customConfig);
174+
175+
var resolved1 = registry.Resolve("mypool");
176+
var resolved2 = registry.Resolve("MYPOOL");
177+
178+
Assert.Equal(customConfig, resolved1);
179+
Assert.Equal(customConfig, resolved2);
180+
}
181+
182+
[Fact(Timeout = 5000)]
183+
public void Register_should_return_self_for_fluent_chaining()
184+
{
185+
var defaultConfig = new TcpPoolConfig(
186+
MaxConnectionsPerHost: 10,
187+
IdleTimeout: TimeSpan.FromSeconds(30),
188+
ConnectionLifetime: TimeSpan.FromMinutes(5),
189+
ReuseOnUpstreamFinish: true);
190+
191+
var config1 = new TcpPoolConfig(
192+
MaxConnectionsPerHost: 5,
193+
IdleTimeout: TimeSpan.FromSeconds(20),
194+
ConnectionLifetime: TimeSpan.FromMinutes(1),
195+
ReuseOnUpstreamFinish: false);
196+
197+
var config2 = new TcpPoolConfig(
198+
MaxConnectionsPerHost: 15,
199+
IdleTimeout: TimeSpan.FromSeconds(40),
200+
ConnectionLifetime: TimeSpan.FromMinutes(4),
201+
ReuseOnUpstreamFinish: true);
202+
203+
var registry = new PoolConfigRegistry(defaultConfig);
204+
var result = registry
205+
.Register("pool1", config1)
206+
.Register("pool2", config2);
207+
208+
Assert.Same(registry, result);
209+
Assert.Equal(config1, registry.Resolve("pool1"));
210+
Assert.Equal(config2, registry.Resolve("pool2"));
211+
}
212+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Servus.Akka.Transport;
2+
using Servus.Akka.Transport.Quic;
3+
4+
namespace Servus.Akka.Tests.Transport.Quic;
5+
6+
internal sealed class InMemoryQuicConnectionFactory : IQuicConnectionFactory
7+
{
8+
public int EstablishCount;
9+
public bool ShouldFail = false;
10+
11+
public Task<QuicConnectionLease> EstablishAsync(QuicTransportOptions options, CancellationToken ct = default)
12+
{
13+
Interlocked.Increment(ref EstablishCount);
14+
if (ShouldFail)
15+
{
16+
return Task.FromException<QuicConnectionLease>(new IOException("Simulated failure"));
17+
}
18+
19+
var handle = CreateMockHandle();
20+
return Task.FromResult(new QuicConnectionLease(handle, options.MaxBidirectionalStreams));
21+
}
22+
23+
private static QuicConnectionHandle CreateMockHandle()
24+
{
25+
return new QuicConnectionHandle(
26+
openStream: (_, _) => Task.FromResult((Stream: (Stream)new MemoryStream(), StreamId: 0L)),
27+
acceptInboundStream: _ => Task.FromResult<(Stream, long)?>(null),
28+
getLocalEndPoint: () => null,
29+
dispose: () => ValueTask.CompletedTask);
30+
}
31+
}

0 commit comments

Comments
 (0)