Files
Luna/Luna.Channels.Tests/ChannelManagerTests.cs
darman 79492eac16 Add channel system unit and integration tests
Comprehensive NUnit test suite covering ChannelManager, CliChannel,
TelegramChannel, DI registration, and integration scenarios. Uses
Moq for mocking with test stubs for isolated unit testing.
2026-04-04 04:12:24 +02:00

241 lines
7.9 KiB
C#

using Luna.Channels.Abstractions;
using Luna.Channels.Tests.Stubs;
using Luna.Core.Abstractions;
using Luna.Shared;
using Microsoft.Extensions.Logging;
using Moq;
namespace Luna.Channels.Tests;
[TestFixture]
[Category("Unit")]
public class ChannelManagerTests
{
private readonly Mock<ILogger<ChannelManager>> loggerMock = new();
private static Mock<IChannel> BuildChannelMock(string id)
{
var mock = new Mock<IChannel>();
mock.Setup(c => c.ChannelId).Returns(id);
mock.Setup(c => c.ChannelType).Returns("test");
mock.Setup(c => c.DisplayName).Returns($"Channel {id}");
mock.Setup(c => c.IsConnected).Returns(true);
mock.Setup(c => c.SendStreamingMessageAsync(
It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(),
It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
return mock;
}
private static ChannelMessage BuildMessage(string content = "Hello") =>
new ChannelMessage(
Guid.NewGuid().ToString(),
Guid.NewGuid().ToString(),
"sender-1",
"Sender Name",
content,
DateTimeOffset.UtcNow);
[Test]
public void RegisterChannel_AddsToRegistry_GetChannel_ReturnsIt()
{
var sessionManagerMock = new Mock<ISessionManager>();
var sut = new ChannelManager(sessionManagerMock.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-reg-1");
sut.RegisterChannel(mockChannel.Object);
var result = sut.GetChannel("ch-reg-1");
Assert.That(result, Is.SameAs(mockChannel.Object));
}
[Test]
public void RegisterChannel_DuplicateId_Throws_InvalidOperationException()
{
var sessionManagerMock = new Mock<ISessionManager>();
var sut = new ChannelManager(sessionManagerMock.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-dup-1");
sut.RegisterChannel(mockChannel.Object);
Assert.That(
() => sut.RegisterChannel(mockChannel.Object),
Throws.TypeOf<InvalidOperationException>());
}
[Test]
public void UnregisterChannel_RemovesChannel_GetChannel_ReturnsNull()
{
var sessionManagerMock = new Mock<ISessionManager>();
var sut = new ChannelManager(sessionManagerMock.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-rem-1");
sut.RegisterChannel(mockChannel.Object);
sut.UnregisterChannel("ch-rem-1");
var result = sut.GetChannel("ch-rem-1");
Assert.That(result, Is.Null);
}
[Test]
public void UnregisterChannel_UnknownId_NoOp()
{
var sessionManagerMock = new Mock<ISessionManager>();
var sut = new ChannelManager(sessionManagerMock.Object, loggerMock.Object);
Assert.That(() => sut.UnregisterChannel("does-not-exist"), Throws.Nothing);
}
[Test]
public void GetAllChannels_ReturnsAllRegistered()
{
var sessionManagerMock = new Mock<ISessionManager>();
var sut = new ChannelManager(sessionManagerMock.Object, loggerMock.Object);
for (var i = 0; i < 3; i++)
sut.RegisterChannel(new CliChannelStub { ChannelId = i.ToString() });
var all = sut.GetAllChannels();
Assert.That(all.Count, Is.EqualTo(3));
}
[Test]
public async Task MessageReceived_Routes_To_SessionManager()
{
var mockSession = new Mock<ISessionManager>();
mockSession
.Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.Returns(AsyncEnumerable.Empty<ChatStreamUpdate>());
var sut = new ChannelManager(mockSession.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-route-1");
sut.RegisterChannel(mockChannel.Object);
var message = BuildMessage("Hello from channel");
mockChannel.Raise(c => c.MessageReceived += null, mockChannel.Object,
new ChannelMessageReceivedEventArgs
{
Message = message,
Channel = mockChannel.Object
});
await Task.Delay(100);
mockSession.Verify(
s => s.RouteMessagesAsync(
message.Content,
message.ConversationId,
It.IsAny<string>(),
It.IsAny<CancellationToken>()),
Times.Once);
}
[Test]
public async Task MessageReceived_Pipes_StreamingResponse_To_Channel()
{
async IAsyncEnumerable<ChatStreamUpdate> FakeStreamAsync()
{
yield return new ChatStreamUpdate(ChatStreamUpdateType.Text, "AI reply", null, false);
yield return new ChatStreamUpdate(ChatStreamUpdateType.Complete, null, null, true);
}
var mockSession = new Mock<ISessionManager>();
mockSession
.Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.Returns(FakeStreamAsync());
var sut = new ChannelManager(mockSession.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-stream-1");
sut.RegisterChannel(mockChannel.Object);
await mockChannel.RaiseAsync(c => c.MessageReceived += null, mockChannel.Object,
new ChannelMessageReceivedEventArgs
{
Message = BuildMessage("stream test"),
Channel = mockChannel.Object
});
await Task.Delay(200);
mockChannel.Verify(
c => c.SendStreamingMessageAsync(
It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(),
It.IsAny<CancellationToken>()),
Times.Once);
}
[Test]
public async Task MessageReceived_IsHandled_True_SkipsRouting()
{
var mockSession = new Mock<ISessionManager>();
var sut = new ChannelManager(mockSession.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-handled-1");
sut.RegisterChannel(mockChannel.Object);
await mockChannel.RaiseAsync(c => c.MessageReceived += null, mockChannel.Object,
new ChannelMessageReceivedEventArgs
{
Message = BuildMessage("already handled"),
Channel = mockChannel.Object,
IsHandled = true
});
await Task.Delay(100);
mockSession.Verify(
s => s.RouteMessagesAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()),
Times.Never);
}
[Test]
public async Task RouteMessagesAsync_Throws_ChannelNotDisconnected()
{
var mockSession = new Mock<ISessionManager>();
mockSession
.Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.Throws(new Exception("Simulated routing failure"));
var sut = new ChannelManager(mockSession.Object, loggerMock.Object);
var mockChannel = BuildChannelMock("ch-err-1");
mockChannel.Setup(c => c.IsConnected).Returns(true);
sut.RegisterChannel(mockChannel.Object);
Assert.That(
async () =>
{
await mockChannel.RaiseAsync(c => c.MessageReceived += null, mockChannel.Object,
new ChannelMessageReceivedEventArgs
{
Message = BuildMessage("error trigger"),
Channel = mockChannel.Object
});
await Task.Delay(200);
},
Throws.Nothing);
Assert.That(mockChannel.Object.IsConnected, Is.True);
}
}