mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 16:46:08 +02:00
- Implemented IrcCommandHandlerTests to cover various IRC commands including PING, JOIN, PART, and authentication scenarios. - Added IrcMessageFormatterTests to validate message formatting for different message types such as text, images, files, and audio. - Created IrcMessageTests to ensure correct parsing of IRC messages and handling of various command formats. - Introduced TestHelpers to facilitate testing with mock connections and streams, including a FakeChatService and FakeEncryptionService for simulating chat behavior.
214 lines
6.6 KiB
C#
214 lines
6.6 KiB
C#
using EchoHub.Server.Irc;
|
|
using Xunit;
|
|
|
|
namespace EchoHub.Tests.Irc;
|
|
|
|
public class IrcClientConnectionTests
|
|
{
|
|
// ── Connection identity ──────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ConnectionId_StartsWithIrcPrefix()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
Assert.StartsWith("irc-", conn.ConnectionId);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConnectionId_IsUnique()
|
|
{
|
|
var (conn1, _) = TestIrcConnectionFactory.Create();
|
|
var (conn2, _) = TestIrcConnectionFactory.Create();
|
|
Assert.NotEqual(conn1.ConnectionId, conn2.ConnectionId);
|
|
}
|
|
|
|
// ── Hostmask ─────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Hostmask_WithNicknameAndUsername_FormatsCorrectly()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
conn.Nickname = "alice";
|
|
conn.Username = "alice_user";
|
|
|
|
Assert.Equal("alice!alice_user@echohub", conn.Hostmask);
|
|
}
|
|
|
|
[Fact]
|
|
public void Hostmask_WithoutUsername_FallsBackToNickname()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
conn.Nickname = "alice";
|
|
|
|
Assert.Equal("alice!alice@echohub", conn.Hostmask);
|
|
}
|
|
|
|
// ── I/O ──────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task ReadLineAsync_ReturnsInputLines()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create("PING", "PONG");
|
|
|
|
var line1 = await conn.ReadLineAsync(CancellationToken.None);
|
|
var line2 = await conn.ReadLineAsync(CancellationToken.None);
|
|
|
|
Assert.Equal("PING", line1);
|
|
Assert.Equal("PONG", line2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadLineAsync_EndOfStream_ReturnsNull()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create("PING");
|
|
|
|
await conn.ReadLineAsync(CancellationToken.None); // consume "PING"
|
|
var result = await conn.ReadLineAsync(CancellationToken.None);
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAsync_WritesToOutput()
|
|
{
|
|
var (conn, stream) = TestIrcConnectionFactory.Create();
|
|
|
|
await conn.SendAsync(":server 001 alice :Welcome!");
|
|
|
|
var output = stream.GetOutputLines();
|
|
Assert.Single(output);
|
|
Assert.Equal(":server 001 alice :Welcome!", output[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendAsync_MultipleLines_AllCaptured()
|
|
{
|
|
var (conn, stream) = TestIrcConnectionFactory.Create();
|
|
|
|
await conn.SendAsync("line1");
|
|
await conn.SendAsync("line2");
|
|
await conn.SendAsync("line3");
|
|
|
|
var output = stream.GetOutputLines();
|
|
Assert.Equal(3, output.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendNumericAsync_FormatsCorrectly()
|
|
{
|
|
var (conn, stream) = TestIrcConnectionFactory.Create();
|
|
conn.Nickname = "alice";
|
|
|
|
await conn.SendNumericAsync("testserver", "001", ":Welcome!");
|
|
|
|
var output = stream.GetOutputLines();
|
|
Assert.Single(output);
|
|
Assert.Equal(":testserver 001 alice :Welcome!", output[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendNumericAsync_NoNickname_UsesStar()
|
|
{
|
|
var (conn, stream) = TestIrcConnectionFactory.Create();
|
|
|
|
await conn.SendNumericAsync("testserver", "451", ":Not registered");
|
|
|
|
var output = stream.GetOutputLines();
|
|
Assert.Contains("*", output[0]);
|
|
}
|
|
|
|
// ── Thread-safe channel operations ───────────────────────────────────
|
|
|
|
[Fact]
|
|
public void JoinChannel_AddsChannel()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
conn.JoinChannel("general");
|
|
|
|
Assert.True(conn.IsInChannel("general"));
|
|
}
|
|
|
|
[Fact]
|
|
public void LeaveChannel_RemovesChannel()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
conn.JoinChannel("general");
|
|
conn.LeaveChannel("general");
|
|
|
|
Assert.False(conn.IsInChannel("general"));
|
|
}
|
|
|
|
[Fact]
|
|
public void IsInChannel_CaseInsensitive()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
conn.JoinChannel("General");
|
|
|
|
Assert.True(conn.IsInChannel("general"));
|
|
Assert.True(conn.IsInChannel("GENERAL"));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetJoinedChannels_ReturnsSnapshot()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
conn.JoinChannel("general");
|
|
conn.JoinChannel("random");
|
|
|
|
var channels = conn.GetJoinedChannels();
|
|
Assert.Equal(2, channels.Count);
|
|
Assert.Contains("general", channels);
|
|
Assert.Contains("random", channels);
|
|
|
|
// Modifying the returned list shouldn't affect the connection state
|
|
channels.Clear();
|
|
Assert.True(conn.IsInChannel("general"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JoinedChannels_ConcurrentAccess_DoesNotThrow()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
|
|
// Simulate concurrent reads and writes (broadcaster reads while handler writes)
|
|
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
|
|
var writerTask = Task.Run(async () =>
|
|
{
|
|
for (int i = 0; i < 1000 && !cts.IsCancellationRequested; i++)
|
|
{
|
|
conn.JoinChannel($"channel-{i}");
|
|
await Task.Yield();
|
|
if (i % 3 == 0) conn.LeaveChannel($"channel-{i}");
|
|
}
|
|
}, cts.Token);
|
|
|
|
var readerTask = Task.Run(async () =>
|
|
{
|
|
for (int i = 0; i < 1000 && !cts.IsCancellationRequested; i++)
|
|
{
|
|
_ = conn.IsInChannel($"channel-{i}");
|
|
_ = conn.GetJoinedChannels();
|
|
await Task.Yield();
|
|
}
|
|
}, cts.Token);
|
|
|
|
// Should complete without exceptions
|
|
await Task.WhenAll(writerTask, readerTask);
|
|
}
|
|
|
|
// ── Default state ────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void NewConnection_IsNotRegistered()
|
|
{
|
|
var (conn, _) = TestIrcConnectionFactory.Create();
|
|
|
|
Assert.False(conn.IsRegistered);
|
|
Assert.False(conn.IsAuthenticated);
|
|
Assert.Null(conn.Nickname);
|
|
Assert.Null(conn.Username);
|
|
Assert.Null(conn.UserId);
|
|
Assert.Null(conn.AwayMessage);
|
|
}
|
|
}
|