Skip to content

Commit 812b07f

Browse files
committed
feat(csharp-bdd): add leader_redirection scenario support
1 parent 3b95b25 commit 812b07f

File tree

5 files changed

+264
-2
lines changed

5 files changed

+264
-2
lines changed

bdd/docker-compose.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,18 @@ services:
249249
context: ..
250250
dockerfile: bdd/csharp/Dockerfile
251251
depends_on:
252-
- iggy-server
252+
iggy-server:
253+
condition: service_healthy
254+
iggy-leader:
255+
condition: service_healthy
256+
iggy-follower:
257+
condition: service_healthy
253258
environment:
254259
- IGGY_ROOT_USERNAME=iggy
255260
- IGGY_ROOT_PASSWORD=iggy
256261
- IGGY_TCP_ADDRESS=iggy-server:8090
262+
- IGGY_TCP_ADDRESS_LEADER=iggy-leader:8091
263+
- IGGY_TCP_ADDRESS_FOLLOWER=iggy-follower:8092
257264
command: [ "dotnet", "test" ]
258265
networks:
259266
- iggy-bdd-network

foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ public class TestContext
2525
{
2626
public IIggyClient IggyClient { get; set; } = null!;
2727
public string TcpUrl { get; set; } = string.Empty;
28+
public string LeaderTcpUrl { get; set; } = string.Empty;
29+
public string FollowerTcpUrl { get; set; } = string.Empty;
30+
public Dictionary<string, IIggyClient> Clients { get; set; } = [];
2831
public StreamResponse? CreatedStream { get; set; }
2932
public TopicResponse? CreatedTopic { get; set; }
3033
public List<MessageResponse> PolledMessages { get; set; } = new();
3134
public Message? LastSendMessage { get; set; }
35+
public string? InitialAddress { get; set; }
3236
}

foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestHooks.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,29 @@ public TestHooks(TestContext context)
3333
public void BeforeScenario()
3434
{
3535
_context.TcpUrl = Environment.GetEnvironmentVariable("IGGY_TCP_ADDRESS") ?? "127.0.0.1:8090";
36+
_context.LeaderTcpUrl = Environment.GetEnvironmentVariable("IGGY_TCP_ADDRESS_LEADER") ?? "127.0.0.1:8091";
37+
_context.FollowerTcpUrl = Environment.GetEnvironmentVariable("IGGY_TCP_ADDRESS_FOLLOWER") ?? "127.0.0.1:8092";
38+
_context.Clients.Clear();
39+
_context.InitialAddress = null;
40+
_context.CreatedStream = null;
3641
}
3742

3843
[AfterScenario]
3944
public void AfterScenario()
4045
{
41-
//
46+
var clients = _context.Clients.Values
47+
.Distinct()
48+
.ToList();
49+
50+
if (_context.IggyClient is not null && !clients.Contains(_context.IggyClient))
51+
{
52+
clients.Add(_context.IggyClient);
53+
}
54+
55+
foreach (var client in clients)
56+
{
57+
client.Dispose();
58+
}
4259
}
4360

4461
[BeforeFeature]

foreign/csharp/Iggy_SDK.Tests.BDD/Iggy_SDK.Tests.BDD.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
<Link>Features\basic_messaging.feature</Link>
3838
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3939
</Content>
40+
<Content Include="..\..\..\bdd\scenarios\leader_redirection.feature">
41+
<Link>Features\leader_redirection.feature</Link>
42+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
43+
</Content>
4044
</ItemGroup>
4145

4246
<ItemGroup>
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
using Apache.Iggy.Configuration;
19+
using Apache.Iggy.Contracts;
20+
using Apache.Iggy.Factory;
21+
using Apache.Iggy.IggyClient;
22+
using Reqnroll;
23+
using Shouldly;
24+
using TestContext = Apache.Iggy.Tests.BDD.Context.TestContext;
25+
26+
namespace Apache.Iggy.Tests.BDD.StepDefinitions;
27+
28+
[Binding]
29+
public class LeaderRedirectionSteps
30+
{
31+
private readonly TestContext _context;
32+
33+
public LeaderRedirectionSteps(TestContext context)
34+
{
35+
_context = context;
36+
}
37+
38+
[Given(@"I have cluster configuration enabled with (\d+) nodes")]
39+
public void GivenIHaveClusterConfigurationEnabledWithNodes(int nodeCount)
40+
{
41+
nodeCount.ShouldBe(2);
42+
}
43+
44+
[Given(@"node (\d+) is configured on port (\d+)")]
45+
public void GivenNodeIsConfiguredOnPort(int nodeId, int port)
46+
{
47+
_ = nodeId;
48+
ResolveAddressForPort(port).ShouldNotBeNullOrEmpty();
49+
}
50+
51+
[Given(@"I start server (\d+) on port (\d+) as (leader|follower)")]
52+
public void GivenIStartServerOnPortAs(int nodeId, int port, string role)
53+
{
54+
_ = nodeId;
55+
var address = ResolveAddressForRole(role);
56+
address.ShouldEndWith($":{port}");
57+
}
58+
59+
[Given(@"I start a single server on port (\d+) without clustering enabled")]
60+
public void GivenIStartASingleServerOnPortWithoutClusteringEnabled(int port)
61+
{
62+
_context.TcpUrl.ShouldEndWith($":{port}");
63+
}
64+
65+
[When(@"I create a client connecting to (follower|leader) on port (\d+)")]
66+
public async Task WhenICreateAClientConnectingToOnPort(string role, int port)
67+
{
68+
var address = ResolveAddressForRole(role);
69+
address.ShouldEndWith($":{port}");
70+
71+
await CreateAndConnectClient("main", address);
72+
_context.InitialAddress = address;
73+
}
74+
75+
[When(@"I create a client connecting directly to leader on port (\d+)")]
76+
public async Task WhenICreateAClientConnectingDirectlyToLeaderOnPort(int port)
77+
{
78+
var address = _context.LeaderTcpUrl;
79+
address.ShouldEndWith($":{port}");
80+
81+
await CreateAndConnectClient("main", address);
82+
_context.InitialAddress = address;
83+
}
84+
85+
[When(@"I create a client connecting to port (\d+)")]
86+
public async Task WhenICreateAClientConnectingToPort(int port)
87+
{
88+
var address = ResolveAddressForPort(port);
89+
await CreateAndConnectClient("main", address);
90+
_context.InitialAddress = address;
91+
}
92+
93+
[When(@"I create client ([A-Z]) connecting to port (\d+)")]
94+
public async Task WhenICreateClientConnectingToPort(string clientName, int port)
95+
{
96+
var address = ResolveAddressForPort(port);
97+
await CreateAndConnectClient(clientName, address);
98+
}
99+
100+
[When(@"I authenticate as root user")]
101+
public async Task WhenIAuthenticateAsRootUser()
102+
{
103+
var client = GetClient("main");
104+
var loginResult = await client.LoginUser("iggy", "iggy");
105+
106+
loginResult.ShouldNotBeNull();
107+
loginResult.UserId.ShouldBe(0);
108+
}
109+
110+
[When(@"both clients authenticate as root user")]
111+
public async Task WhenBothClientsAuthenticateAsRootUser()
112+
{
113+
foreach (var clientName in _context.Clients.Keys.OrderBy(name => name).ToList())
114+
{
115+
var loginResult = await _context.Clients[clientName].LoginUser("iggy", "iggy");
116+
loginResult.ShouldNotBeNull();
117+
loginResult.UserId.ShouldBe(0);
118+
}
119+
}
120+
121+
[When(@"I create a stream named ""([^""]+)""")]
122+
public async Task WhenICreateAStreamNamed(string streamName)
123+
{
124+
_context.CreatedStream = await GetClient("main").CreateStreamAsync(streamName);
125+
}
126+
127+
[Then(@"the client should automatically redirect to leader on port (\d+)")]
128+
public async Task ThenTheClientShouldAutomaticallyRedirectToLeaderOnPort(int port)
129+
{
130+
await AssertClientAddress("main", port);
131+
GetClient("main").GetCurrentAddress().ShouldNotBe(_context.InitialAddress);
132+
}
133+
134+
[Then(@"the stream should be created successfully on the leader")]
135+
public void ThenTheStreamShouldBeCreatedSuccessfullyOnTheLeader()
136+
{
137+
_context.CreatedStream.ShouldNotBeNull();
138+
_context.CreatedStream.Name.ShouldNotBeNullOrEmpty();
139+
}
140+
141+
[Then(@"the client should not perform any redirection")]
142+
public void ThenTheClientShouldNotPerformAnyRedirection()
143+
{
144+
GetClient("main").GetCurrentAddress().ShouldBe(_context.InitialAddress);
145+
}
146+
147+
[Then(@"the connection should remain on port (\d+)")]
148+
public async Task ThenTheConnectionShouldRemainOnPort(int port)
149+
{
150+
await AssertClientAddress("main", port);
151+
}
152+
153+
[Then(@"the client should connect successfully without redirection")]
154+
public void ThenTheClientShouldConnectSuccessfullyWithoutRedirection()
155+
{
156+
GetClient("main").ShouldNotBeNull();
157+
GetClient("main").GetCurrentAddress().ShouldBe(_context.InitialAddress);
158+
}
159+
160+
[Then(@"client ([A-Z]) should stay connected to port (\d+)")]
161+
public async Task ThenClientShouldStayConnectedToPort(string clientName, int port)
162+
{
163+
await AssertClientAddress(clientName, port);
164+
}
165+
166+
[Then(@"client ([A-Z]) should redirect to port (\d+)")]
167+
public async Task ThenClientShouldRedirectToPort(string clientName, int port)
168+
{
169+
await AssertClientAddress(clientName, port);
170+
}
171+
172+
[Then(@"both clients should be using the same server")]
173+
public void ThenBothClientsShouldBeUsingTheSameServer()
174+
{
175+
GetClient("A").GetCurrentAddress().ShouldBe(GetClient("B").GetCurrentAddress());
176+
}
177+
178+
private async Task CreateAndConnectClient(string name, string address)
179+
{
180+
var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
181+
{
182+
BaseAddress = address,
183+
Protocol = Protocol.Tcp,
184+
ReconnectionSettings = new ReconnectionSettings { Enabled = true },
185+
AutoLoginSettings = new AutoLoginSettings { Enabled = false }
186+
});
187+
188+
await client.ConnectAsync();
189+
await client.PingAsync();
190+
191+
_context.Clients[name] = client;
192+
if (name == "main")
193+
{
194+
_context.IggyClient = client;
195+
}
196+
}
197+
198+
private IIggyClient GetClient(string name)
199+
{
200+
return _context.Clients[name];
201+
}
202+
203+
private async Task AssertClientAddress(string clientName, int expectedPort)
204+
{
205+
var client = GetClient(clientName);
206+
await client.PingAsync();
207+
client.GetCurrentAddress().ShouldEndWith($":{expectedPort}");
208+
}
209+
210+
private string ResolveAddressForRole(string role)
211+
{
212+
return role switch
213+
{
214+
"leader" => _context.LeaderTcpUrl,
215+
"follower" => _context.FollowerTcpUrl,
216+
_ => throw new ArgumentOutOfRangeException(nameof(role), role, "Unsupported server role")
217+
};
218+
}
219+
220+
private string ResolveAddressForPort(int port)
221+
{
222+
return port switch
223+
{
224+
8090 => _context.TcpUrl,
225+
8091 => _context.LeaderTcpUrl,
226+
8092 => _context.FollowerTcpUrl,
227+
_ => throw new ArgumentOutOfRangeException(nameof(port), port, "Unsupported test port")
228+
};
229+
}
230+
}

0 commit comments

Comments
 (0)