Fixed some warnings

This commit is contained in:
2026-04-04 05:01:51 +02:00
parent 6d6b2c1166
commit 3138436965
12 changed files with 129 additions and 131 deletions
+4 -1
View File
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using Luna.Agents.Abstractions; using Luna.Agents.Abstractions;
using Luna.Configuration; using Luna.Configuration;
using Luna.Providers.Abstractions; using Luna.Providers.Abstractions;
@@ -38,7 +39,9 @@ public class LibrarianAgent(
.Where(m => m.Role != ChatRole.System); .Where(m => m.Role != ChatRole.System);
} }
public async IAsyncEnumerable<ChatResponseUpdate> ProcessStreamingAsync(IReadOnlyList<ChatMessage> messages, CancellationToken ct = default) public async IAsyncEnumerable<ChatResponseUpdate> ProcessStreamingAsync(
IReadOnlyList<ChatMessage> messages,
[EnumeratorCancellation] CancellationToken ct = default)
{ {
var chatOptions = new ChatOptions var chatOptions = new ChatOptions
{ {
@@ -1,5 +1,4 @@
using Luna.Channels.Abstractions; using Luna.Channels.Abstractions;
using Luna.Channels.Tests.Stubs;
using Luna.Core.Abstractions; using Luna.Core.Abstractions;
using Luna.Shared; using Luna.Shared;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -95,7 +94,7 @@ public class ChannelManagerInterfaceTests
stub.RegisterChannel(mockChannel.Object); stub.RegisterChannel(mockChannel.Object);
var message = new ChannelMessage( var _ = new ChannelMessage(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
"sender-1", "sender-1",
@@ -103,11 +102,11 @@ public class ChannelManagerInterfaceTests
"Hello", "Hello",
DateTimeOffset.UtcNow); DateTimeOffset.UtcNow);
var eventArgs = new ChannelMessageReceivedEventArgs // var eventArgs = new ChannelMessageReceivedEventArgs
{ // {
Message = message, // Message = message,
Channel = mockChannel.Object // Channel = mockChannel.Object
}; // };
Assert.That(capturedArgs, Is.Not.Null); Assert.That(capturedArgs, Is.Not.Null);
} }
-1
View File
@@ -1,6 +1,5 @@
using Luna.Channels.Abstractions; using Luna.Channels.Abstractions;
using Luna.Channels.Cli; using Luna.Channels.Cli;
using Luna.Channels.Tests.Stubs;
using Luna.Core.Abstractions; using Luna.Core.Abstractions;
using Luna.Shared; using Luna.Shared;
using Moq; using Moq;
+20 -20
View File
@@ -10,21 +10,21 @@ namespace Luna.Channels.Tests;
[Category("Unit")] [Category("Unit")]
public class DependencyInjectionTests public class DependencyInjectionTests
{ {
private ServiceCollection _services = null!; private ServiceCollection services = null!;
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
_services = new ServiceCollection(); services = [];
} }
[Test] [Test]
public void AddChannels_RegistersIChannelManager_AsSingleton() public void AddChannels_RegistersIChannelManager_AsSingleton()
{ {
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); // var provider = services.BuildServiceProvider();
var descriptor = _services.FirstOrDefault(d => d.ServiceType == typeof(IChannelManager)); var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IChannelManager));
Assert.That(descriptor, Is.Not.Null); Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor!.Lifetime, Is.EqualTo(ServiceLifetime.Singleton)); Assert.That(descriptor!.Lifetime, Is.EqualTo(ServiceLifetime.Singleton));
@@ -33,8 +33,8 @@ public class DependencyInjectionTests
[Test] [Test]
public void AddChannels_IChannelManager_IsResolvableFromContainer() public void AddChannels_IChannelManager_IsResolvableFromContainer()
{ {
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); var provider = services.BuildServiceProvider();
var manager = provider.GetService<IChannelManager>(); var manager = provider.GetService<IChannelManager>();
@@ -44,10 +44,10 @@ public class DependencyInjectionTests
[Test] [Test]
public void AddChannels_TelegramOptions_IsRegistered() public void AddChannels_TelegramOptions_IsRegistered()
{ {
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); // var provider = services.BuildServiceProvider();
var descriptor = _services.FirstOrDefault(d => var descriptor = services.FirstOrDefault(d =>
d.ServiceType.IsGenericType && d.ServiceType.IsGenericType &&
d.ServiceType.GetGenericTypeDefinition() == typeof(IOptions<>) && d.ServiceType.GetGenericTypeDefinition() == typeof(IOptions<>) &&
d.ServiceType.GetGenericArguments()[0] == typeof(TelegramOptions)); d.ServiceType.GetGenericArguments()[0] == typeof(TelegramOptions));
@@ -59,8 +59,8 @@ public class DependencyInjectionTests
[Test] [Test]
public void AddChannels_CliChannel_IsRegistered() public void AddChannels_CliChannel_IsRegistered()
{ {
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); var provider = services.BuildServiceProvider();
var channels = provider.GetServices<IChannel>().ToList(); var channels = provider.GetServices<IChannel>().ToList();
var channelTypes = channels.Select(c => c.ChannelType).ToList(); var channelTypes = channels.Select(c => c.ChannelType).ToList();
@@ -72,8 +72,8 @@ public class DependencyInjectionTests
[Test] [Test]
public void AddChannels_TelegramChannel_IsRegistered() public void AddChannels_TelegramChannel_IsRegistered()
{ {
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); var provider = services.BuildServiceProvider();
var channels = provider.GetServices<IChannel>().ToList(); var channels = provider.GetServices<IChannel>().ToList();
var channelTypes = channels.Select(c => c.ChannelType).ToList(); var channelTypes = channels.Select(c => c.ChannelType).ToList();
@@ -85,11 +85,11 @@ public class DependencyInjectionTests
[Test] [Test]
public void AddChannels_CalledTwice_DoesNotDuplicateSingletonRegistration() public void AddChannels_CalledTwice_DoesNotDuplicateSingletonRegistration()
{ {
_services.AddChannels(); services.AddChannels();
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); //var provider = services.BuildServiceProvider();
var descriptors = _services.Where(d => d.ServiceType == typeof(IChannelManager)).ToList(); var descriptors = services.Where(d => d.ServiceType == typeof(IChannelManager)).ToList();
Assert.That(descriptors, Has.Count.EqualTo(1), Assert.That(descriptors, Has.Count.EqualTo(1),
"Calling AddChannels() twice must not register IChannelManager twice"); "Calling AddChannels() twice must not register IChannelManager twice");
@@ -98,8 +98,8 @@ public class DependencyInjectionTests
[Test] [Test]
public void AddChannels_SameProviderInstance_ReturnsSameIChannelManager() public void AddChannels_SameProviderInstance_ReturnsSameIChannelManager()
{ {
_services.AddChannels(); services.AddChannels();
var provider = _services.BuildServiceProvider(); var provider = services.BuildServiceProvider();
var first = provider.GetRequiredService<IChannelManager>(); var first = provider.GetRequiredService<IChannelManager>();
var second = provider.GetRequiredService<IChannelManager>(); var second = provider.GetRequiredService<IChannelManager>();
+41 -41
View File
@@ -10,21 +10,21 @@ namespace Luna.Channels.Tests;
[Category("Unit")] [Category("Unit")]
public class IntegrationTests public class IntegrationTests
{ {
private Mock<ISessionManager> _sessionManagerMock = null!; private Mock<ISessionManager> sessionManagerMock = null!;
private Mock<IChannel> _channelMock = null!; private Mock<IChannel> channelMock = null!;
private ChannelManagerStub _manager = null!; private ChannelManagerStub manager = null!;
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
_sessionManagerMock = new Mock<ISessionManager>(); sessionManagerMock = new Mock<ISessionManager>();
_channelMock = new Mock<IChannel>(); channelMock = new Mock<IChannel>();
_channelMock.Setup(c => c.ChannelId).Returns("test-ch-1"); channelMock.Setup(c => c.ChannelId).Returns("test-ch-1");
_channelMock.Setup(c => c.ChannelType).Returns("test"); channelMock.Setup(c => c.ChannelType).Returns("test");
_channelMock.Setup(c => c.SendStreamingMessageAsync( channelMock.Setup(c => c.SendStreamingMessageAsync(
It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(), It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(),
It.IsAny<CancellationToken>())).Returns(Task.CompletedTask); It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
_manager = new ChannelManagerStub(_sessionManagerMock.Object); manager = new ChannelManagerStub(sessionManagerMock.Object);
} }
private static async IAsyncEnumerable<ChatStreamUpdate> BuildStream(params ChatStreamUpdate[] updates) private static async IAsyncEnumerable<ChatStreamUpdate> BuildStream(params ChatStreamUpdate[] updates)
@@ -42,24 +42,24 @@ public class IntegrationTests
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-1", "User One", "Hello Luna!", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-1", "User One", "Hello Luna!", DateTimeOffset.UtcNow);
_sessionManagerMock sessionManagerMock
.Setup(s => s.RouteMessagesAsync( .Setup(s => s.RouteMessagesAsync(
message.Content, message.Content,
message.ConversationId.ToString(), message.ConversationId,
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<CancellationToken>())) It.IsAny<CancellationToken>()))
.Returns(BuildStream(new ChatStreamUpdate(ChatStreamUpdateType.Text, "Hi!", null, false))); .Returns(BuildStream(new ChatStreamUpdate(ChatStreamUpdateType.Text, "Hi!", null, false)));
_manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" }); manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" });
var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = _channelMock.Object }; var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = channelMock.Object };
_channelMock.Raise(c => c.MessageReceived += null, _channelMock.Object, args); channelMock.Raise(c => c.MessageReceived += null, channelMock.Object, args);
await Task.Delay(50); await Task.Delay(50);
_sessionManagerMock.Verify(s => s.RouteMessagesAsync( sessionManagerMock.Verify(s => s.RouteMessagesAsync(
message.Content, message.Content,
message.ConversationId.ToString(), message.ConversationId,
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<CancellationToken>()), Times.Once); It.IsAny<CancellationToken>()), Times.Once);
} }
@@ -75,19 +75,19 @@ public class IntegrationTests
new ChatStreamUpdate(ChatStreamUpdateType.Text, "noon.", null, false), new ChatStreamUpdate(ChatStreamUpdateType.Text, "noon.", null, false),
new ChatStreamUpdate(ChatStreamUpdateType.Complete, null, null, true)); new ChatStreamUpdate(ChatStreamUpdateType.Complete, null, null, true));
_sessionManagerMock sessionManagerMock
.Setup(s => s.RouteMessagesAsync( .Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(responseStream); .Returns(responseStream);
_manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" }); manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" });
var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = _channelMock.Object }; var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = channelMock.Object };
_channelMock.Raise(c => c.MessageReceived += null, _channelMock.Object, args); channelMock.Raise(c => c.MessageReceived += null, channelMock.Object, args);
await Task.Delay(50); await Task.Delay(50);
_channelMock.Verify(c => c.SendStreamingMessageAsync( channelMock.Verify(c => c.SendStreamingMessageAsync(
It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(), It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(),
It.IsAny<CancellationToken>()), Times.Once); It.IsAny<CancellationToken>()), Times.Once);
} }
@@ -98,14 +98,14 @@ public class IntegrationTests
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-3", "User Three", "/start", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-3", "User Three", "/start", DateTimeOffset.UtcNow);
_manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" }); manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" });
var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = _channelMock.Object, IsHandled = true }; var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = channelMock.Object, IsHandled = true };
_channelMock.Raise(c => c.MessageReceived += null, _channelMock.Object, args); channelMock.Raise(c => c.MessageReceived += null, channelMock.Object, args);
await Task.Delay(50); await Task.Delay(50);
_sessionManagerMock.Verify(s => s.RouteMessagesAsync( sessionManagerMock.Verify(s => s.RouteMessagesAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()),
Times.Never); Times.Never);
} }
@@ -120,25 +120,25 @@ public class IntegrationTests
It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(), It.IsAny<CancellationToken>())) It.IsAny<IAsyncEnumerable<ChatStreamUpdate>>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask); .Returns(Task.CompletedTask);
_sessionManagerMock sessionManagerMock
.Setup(s => s.RouteMessagesAsync( .Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(BuildStream(new ChatStreamUpdate(ChatStreamUpdateType.Text, "ok", null, true))); .Returns(BuildStream(new ChatStreamUpdate(ChatStreamUpdateType.Text, "ok", null, true)));
_manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" }); manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" });
_manager.RegisterChannel(channel2Mock.Object); manager.RegisterChannel(channel2Mock.Object);
var msg1 = new ChannelMessage(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "u1", "U1", "Msg1", DateTimeOffset.UtcNow); var msg1 = new ChannelMessage(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "u1", "U1", "Msg1", DateTimeOffset.UtcNow);
var msg2 = new ChannelMessage(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "u2", "U2", "Msg2", DateTimeOffset.UtcNow); var msg2 = new ChannelMessage(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "u2", "U2", "Msg2", DateTimeOffset.UtcNow);
_channelMock.Raise(c => c.MessageReceived += null, _channelMock.Object, channelMock.Raise(c => c.MessageReceived += null, channelMock.Object,
new ChannelMessageReceivedEventArgs { Message = msg1, Channel = _channelMock.Object }); new ChannelMessageReceivedEventArgs { Message = msg1, Channel = channelMock.Object });
channel2Mock.Raise(c => c.MessageReceived += null, channel2Mock.Object, channel2Mock.Raise(c => c.MessageReceived += null, channel2Mock.Object,
new ChannelMessageReceivedEventArgs { Message = msg2, Channel = channel2Mock.Object }); new ChannelMessageReceivedEventArgs { Message = msg2, Channel = channel2Mock.Object });
await Task.Delay(100); await Task.Delay(100);
_sessionManagerMock.Verify(s => s.RouteMessagesAsync( sessionManagerMock.Verify(s => s.RouteMessagesAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()),
Times.Exactly(2)); Times.Exactly(2));
} }
@@ -150,21 +150,21 @@ public class IntegrationTests
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-4", "User Four", "Cancel me", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-4", "User Four", "Cancel me", DateTimeOffset.UtcNow);
_sessionManagerMock sessionManagerMock
.Setup(s => s.RouteMessagesAsync( .Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), cts.Token)) It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), cts.Token))
.Returns(BuildStream(new ChatStreamUpdate(ChatStreamUpdateType.Text, "x", null, true))); .Returns(BuildStream(new ChatStreamUpdate(ChatStreamUpdateType.Text, "x", null, true)));
_manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" }); manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" });
var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = _channelMock.Object }; var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = channelMock.Object };
await _channelMock.RaiseAsync(c => c.MessageReceived += null, _channelMock.Object, args); await channelMock.RaiseAsync(c => c.MessageReceived += null, channelMock.Object, args);
await Task.Delay(50, cts.Token); await Task.Delay(50, cts.Token);
_sessionManagerMock.Verify(s => s.RouteMessagesAsync( sessionManagerMock.Verify(s => s.RouteMessagesAsync(
message.Content, message.Content,
message.ConversationId.ToString(), message.ConversationId,
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<CancellationToken>()), Times.Once); It.IsAny<CancellationToken>()), Times.Once);
} }
@@ -175,17 +175,17 @@ public class IntegrationTests
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-5", "User Five", "Boom", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "user-5", "User Five", "Boom", DateTimeOffset.UtcNow);
_sessionManagerMock sessionManagerMock
.Setup(s => s.RouteMessagesAsync( .Setup(s => s.RouteMessagesAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Throws(new InvalidOperationException("Simulated routing failure")); .Throws(new InvalidOperationException("Simulated routing failure"));
_manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" }); manager.RegisterChannel(new CliChannelStub { ChannelId = "test-1" });
Assert.DoesNotThrowAsync(async () => Assert.DoesNotThrowAsync(async () =>
{ {
var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = _channelMock.Object }; var args = new ChannelMessageReceivedEventArgs { Message = message, Channel = channelMock.Object };
await _channelMock.RaiseAsync(c => c.MessageReceived += null, _channelMock.Object, args); await channelMock.RaiseAsync(c => c.MessageReceived += null, channelMock.Object, args);
await Task.Delay(50); await Task.Delay(50);
}); });
} }
@@ -14,7 +14,7 @@ public class ChannelManagerStub : IChannelManager
public void UnregisterChannel(string channelId) => throw new NotImplementedException(); public void UnregisterChannel(string channelId) => throw new NotImplementedException();
public IChannel? GetChannel(string channelId) => throw new NotImplementedException(); public IChannel GetChannel(string channelId) => throw new NotImplementedException();
public IReadOnlyList<IChannel> GetAllChannels() => throw new NotImplementedException(); public IReadOnlyList<IChannel> GetAllChannels() => throw new NotImplementedException();
} }
+50 -50
View File
@@ -14,43 +14,43 @@ namespace Luna.Channels.Tests;
[Category("Unit")] [Category("Unit")]
public class TelegramChannelTests public class TelegramChannelTests
{ {
private Mock<ITelegramBotClient> _mockBotClient = null!; private Mock<ITelegramBotClient> mockBotClient = null!;
private Mock<ILogger<TelegramChannel>> _mockLogger = null!; private Mock<ILogger<TelegramChannel>> mockLogger = null!;
private TelegramOptions _options = null!; private TelegramOptions options = null!;
private TelegramChannel _channel = null!; private TelegramChannel channel = null!;
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
_mockBotClient = new Mock<ITelegramBotClient>(); mockBotClient = new Mock<ITelegramBotClient>();
_mockLogger = new Mock<ILogger<TelegramChannel>>(); mockLogger = new Mock<ILogger<TelegramChannel>>();
_options = new TelegramOptions { BotToken = "test-bot-token:ABCDEF" }; options = new TelegramOptions { BotToken = "test-bot-token:ABCDEF" };
_channel = new TelegramChannel("100", _mockBotClient.Object, _mockLogger.Object, _options); channel = new TelegramChannel("100", mockBotClient.Object, mockLogger.Object, options);
} }
[TearDown] [TearDown]
public void TearDown() public void TearDown()
{ {
_channel.Dispose(); channel.Dispose();
} }
[Test] [Test]
public void ChannelType_Returns_Telegram() public void ChannelType_Returns_Telegram()
{ {
Assert.That(_channel.ChannelType, Is.EqualTo("telegram")); Assert.That(channel.ChannelType, Is.EqualTo("telegram"));
} }
[Test] [Test]
public void ChannelId_IsSet_From_Constructor() public void ChannelId_IsSet_From_Constructor()
{ {
Assert.That(_channel.ChannelId, Is.EqualTo("100")); Assert.That(channel.ChannelId, Is.EqualTo("100"));
} }
[Test] [Test]
public void ChannelId_IsUnique_Per_Instance() public void ChannelId_IsUnique_Per_Instance()
{ {
var channel1 = new TelegramChannel("111", _mockBotClient.Object, _mockLogger.Object, _options); var channel1 = new TelegramChannel("111", mockBotClient.Object, mockLogger.Object, options);
var channel2 = new TelegramChannel("222", _mockBotClient.Object, _mockLogger.Object, _options); var channel2 = new TelegramChannel("222", mockBotClient.Object, mockLogger.Object, options);
Assert.That(channel1.ChannelId, Is.Not.EqualTo(channel2.ChannelId)); Assert.That(channel1.ChannelId, Is.Not.EqualTo(channel2.ChannelId));
@@ -61,36 +61,36 @@ public class TelegramChannelTests
[Test] [Test]
public void DisplayName_Contains_Telegram() public void DisplayName_Contains_Telegram()
{ {
Assert.That(_channel.DisplayName, Does.Contain("Telegram")); Assert.That(channel.DisplayName, Does.Contain("Telegram"));
} }
[Test] [Test]
public void IsConnected_Is_True_Initially() public void IsConnected_Is_True_Initially()
{ {
Assert.That(_channel.IsConnected, Is.True); Assert.That(channel.IsConnected, Is.True);
} }
[Test] [Test]
public void IsConnected_Is_False_After_Dispose() public void IsConnected_Is_False_After_Dispose()
{ {
_channel.Dispose(); channel.Dispose();
Assert.That(_channel.IsConnected, Is.False); Assert.That(channel.IsConnected, Is.False);
} }
[Test] [Test]
public async Task SendMessageAsync_Calls_BotClient_SendMessage() public async Task SendMessageAsync_Calls_BotClient_SendMessage()
{ {
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 1, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 1, Chat = new Chat { Id = 100 } });
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), "100", "42", "Alice", "Hello!", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), "100", "42", "Alice", "Hello!", DateTimeOffset.UtcNow);
await _channel.SendMessageAsync(message); await channel.SendMessageAsync(message);
_mockBotClient.Verify( mockBotClient.Verify(
c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()), c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()),
Times.Once); Times.Once);
} }
@@ -98,33 +98,33 @@ public class TelegramChannelTests
[Test] [Test]
public async Task SendMessageAsync_Handles_Network_Error_Without_Crash() public async Task SendMessageAsync_Handles_Network_Error_Without_Crash()
{ {
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new HttpRequestException("Network error")); .ThrowsAsync(new HttpRequestException("Network error"));
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), "100", "42", "Alice", "Hello!", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), "100", "42", "Alice", "Hello!", DateTimeOffset.UtcNow);
Assert.DoesNotThrowAsync(async () => await _channel.SendMessageAsync(message)); Assert.DoesNotThrowAsync(async () => await channel.SendMessageAsync(message));
} }
[Test] [Test]
public void RaiseMessageReceived_Fires_Event_With_Correct_Mapping() public void RaiseMessageReceived_Fires_Event_With_Correct_Mapping()
{ {
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
_channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), "999", "77", "Bob", "Hello Luna", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), "999", "77", "Bob", "Hello Luna", DateTimeOffset.UtcNow);
_channel.RaiseMessageReceived(message); channel.RaiseMessageReceived(message);
Assert.That(captured, Is.Not.Null); Assert.That(captured, Is.Not.Null);
Assert.That(captured!.Message.ConversationId, Is.EqualTo("999")); Assert.That(captured!.Message.ConversationId, Is.EqualTo("999"));
Assert.That(captured.Message.SenderId, Is.EqualTo("77")); Assert.That(captured.Message.SenderId, Is.EqualTo("77"));
Assert.That(captured.Message.SenderName, Is.EqualTo("Bob")); Assert.That(captured.Message.SenderName, Is.EqualTo("Bob"));
Assert.That(captured.Message.Content, Is.EqualTo("Hello Luna")); Assert.That(captured.Message.Content, Is.EqualTo("Hello Luna"));
Assert.That(captured.Channel, Is.SameAs(_channel)); Assert.That(captured.Channel, Is.SameAs(channel));
} }
[Test] [Test]
@@ -133,12 +133,12 @@ public class TelegramChannelTests
var sentAt = new DateTimeOffset(2025, 6, 1, 12, 0, 0, TimeSpan.Zero); var sentAt = new DateTimeOffset(2025, 6, 1, 12, 0, 0, TimeSpan.Zero);
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
_channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), "123", "55", "Carol", "Hi", sentAt); Guid.NewGuid().ToString(), "123", "55", "Carol", "Hi", sentAt);
_channel.RaiseMessageReceived(message); channel.RaiseMessageReceived(message);
Assert.That(captured, Is.Not.Null); Assert.That(captured, Is.Not.Null);
Assert.That(captured!.Message.SenderName, Is.EqualTo("Carol")); Assert.That(captured!.Message.SenderName, Is.EqualTo("Carol"));
@@ -148,11 +148,11 @@ public class TelegramChannelTests
[Test] [Test]
public async Task SendStreamingMessageAsync_Sends_Initial_Then_Edits_In_Place() public async Task SendStreamingMessageAsync_Sends_Initial_Then_Edits_In_Place()
{ {
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 10, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 10, Chat = new Chat { Id = 100 } });
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 10, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 10, Chat = new Chat { Id = 100 } });
@@ -163,14 +163,14 @@ public class TelegramChannelTests
new(ChatStreamUpdateType.Complete, null, null, true), new(ChatStreamUpdateType.Complete, null, null, true),
}; };
await _channel.SendStreamingMessageAsync(updates.ToAsyncEnumerable()); await channel.SendStreamingMessageAsync(updates.ToAsyncEnumerable());
// First chunk sends a message, second chunk edits it // First chunk sends a message, second chunk edits it
_mockBotClient.Verify( mockBotClient.Verify(
c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()), c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()),
Times.Once); Times.Once);
_mockBotClient.Verify( mockBotClient.Verify(
c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>()), c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>()),
Times.AtLeastOnce); Times.AtLeastOnce);
} }
@@ -185,18 +185,18 @@ public class TelegramChannelTests
new(ChatStreamUpdateType.Complete, null, null, true), new(ChatStreamUpdateType.Complete, null, null, true),
}; };
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 20, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 20, Chat = new Chat { Id = 100 } });
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 20, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 20, Chat = new Chat { Id = 100 } });
await _channel.SendStreamingMessageAsync(updates.ToAsyncEnumerable()); await channel.SendStreamingMessageAsync(updates.ToAsyncEnumerable());
// Content exceeds 4096 — needs at least 2 SendMessage calls (initial + overflow) // Content exceeds 4096 — needs at least 2 SendMessage calls (initial + overflow)
_mockBotClient.Verify( mockBotClient.Verify(
c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()), c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()),
Times.AtLeast(2)); Times.AtLeast(2));
} }
@@ -204,11 +204,11 @@ public class TelegramChannelTests
[Test] [Test]
public async Task SendStreamingMessageAsync_Handles_IsComplete_True_With_Final_Edit() public async Task SendStreamingMessageAsync_Handles_IsComplete_True_With_Final_Edit()
{ {
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 30, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 30, Chat = new Chat { Id = 100 } });
_mockBotClient mockBotClient
.Setup(c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>())) .Setup(c => c.SendRequest(It.IsAny<EditMessageTextRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Message { Id = 30, Chat = new Chat { Id = 100 } }); .ReturnsAsync(new Message { Id = 30, Chat = new Chat { Id = 100 } });
@@ -218,10 +218,10 @@ public class TelegramChannelTests
new(ChatStreamUpdateType.Complete, null, null, true), new(ChatStreamUpdateType.Complete, null, null, true),
}; };
await _channel.SendStreamingMessageAsync(updates.ToAsyncEnumerable()); await channel.SendStreamingMessageAsync(updates.ToAsyncEnumerable());
// Initial message sent // Initial message sent
_mockBotClient.Verify( mockBotClient.Verify(
c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()), c => c.SendRequest(It.IsAny<SendMessageRequest>(), It.IsAny<CancellationToken>()),
Times.Once); Times.Once);
} }
@@ -235,7 +235,7 @@ public class TelegramChannelTests
AllowedUserIds = ["111", "222"] AllowedUserIds = ["111", "222"]
}; };
var restrictedChannel = new TelegramChannel( var restrictedChannel = new TelegramChannel(
"500", _mockBotClient.Object, _mockLogger.Object, restrictedOptions); "500", mockBotClient.Object, mockLogger.Object, restrictedOptions);
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
restrictedChannel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; restrictedChannel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
@@ -259,7 +259,7 @@ public class TelegramChannelTests
AllowedUserIds = ["111", "222"] AllowedUserIds = ["111", "222"]
}; };
var restrictedChannel = new TelegramChannel( var restrictedChannel = new TelegramChannel(
"500", _mockBotClient.Object, _mockLogger.Object, restrictedOptions); "500", mockBotClient.Object, mockLogger.Object, restrictedOptions);
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
restrictedChannel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; restrictedChannel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
@@ -283,7 +283,7 @@ public class TelegramChannelTests
AllowedUserIds = [] AllowedUserIds = []
}; };
var openChannel = new TelegramChannel( var openChannel = new TelegramChannel(
"600", _mockBotClient.Object, _mockLogger.Object, openOptions); "600", mockBotClient.Object, mockLogger.Object, openOptions);
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
openChannel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; openChannel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
@@ -302,12 +302,12 @@ public class TelegramChannelTests
public void Malformed_Message_With_Null_Text_Is_Skipped() public void Malformed_Message_With_Null_Text_Is_Skipped()
{ {
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
_channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), "700", "88", "Ghost", null!, DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), "700", "88", "Ghost", null!, DateTimeOffset.UtcNow);
_channel.RaiseMessageReceived(message); channel.RaiseMessageReceived(message);
Assert.That(captured, Is.Null, "Message with null text should be silently skipped"); Assert.That(captured, Is.Null, "Message with null text should be silently skipped");
} }
@@ -316,12 +316,12 @@ public class TelegramChannelTests
public void Malformed_Message_With_Empty_Text_Is_Skipped() public void Malformed_Message_With_Empty_Text_Is_Skipped()
{ {
ChannelMessageReceivedEventArgs? captured = null; ChannelMessageReceivedEventArgs? captured = null;
_channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; }; channel.MessageReceived += (_, args) => { captured = args; return Task.CompletedTask; };
var message = new ChannelMessage( var message = new ChannelMessage(
Guid.NewGuid().ToString(), "700", "88", "Ghost", "", DateTimeOffset.UtcNow); Guid.NewGuid().ToString(), "700", "88", "Ghost", "", DateTimeOffset.UtcNow);
_channel.RaiseMessageReceived(message); channel.RaiseMessageReceived(message);
Assert.That(captured, Is.Null, "Message with empty text should be silently skipped"); Assert.That(captured, Is.Null, "Message with empty text should be silently skipped");
} }
@@ -330,16 +330,16 @@ public class TelegramChannelTests
public void Dispose_Fires_ConnectionStateChanged() public void Dispose_Fires_ConnectionStateChanged()
{ {
ChannelConnectionEventArgs? captured = null; ChannelConnectionEventArgs? captured = null;
_channel.ConnectionStateChanged += (_, args) => channel.ConnectionStateChanged += (_, args) =>
{ {
captured = args; captured = args;
return Task.CompletedTask; return Task.CompletedTask;
}; };
_channel.Dispose(); channel.Dispose();
Assert.That(captured, Is.Not.Null); Assert.That(captured, Is.Not.Null);
Assert.That(captured!.IsConnected, Is.False); Assert.That(captured!.IsConnected, Is.False);
Assert.That(captured.Channel, Is.SameAs(_channel)); Assert.That(captured.Channel, Is.SameAs(channel));
} }
} }
+1 -2
View File
@@ -42,10 +42,9 @@ public class TelegramChannel(
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var targetChatId = long.Parse(ChannelId); var targetChatId = long.Parse(ChannelId);
int? currentMessageId = null;
var typingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var typingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var typingTask = KeepTypingAsync(targetChatId, typingCts.Token); var _ = KeepTypingAsync(targetChatId, typingCts.Token);
var messageBuilder = new StringBuilder(); var messageBuilder = new StringBuilder();
await foreach (var update in messageStream.WithCancellation(cancellationToken)) await foreach (var update in messageStream.WithCancellation(cancellationToken))
-2
View File
@@ -1,5 +1,3 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels; using System.Threading.Channels;
using Luna.Shared; using Luna.Shared;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
@@ -6,12 +6,12 @@ namespace Luna.Configuration.Extensions;
public static class ConfigurationBuilderExtensions public static class ConfigurationBuilderExtensions
{ {
private static string[] AgentConfigSearchPaths = private static readonly string[] AgentConfigSearchPaths =
[ [
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".luna", "agents") Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".luna", "agents")
]; ];
private static string[] ProviderConfigSearchPaths = private static readonly string[] ProviderConfigSearchPaths =
[ [
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".luna", "providers") Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".luna", "providers")
]; ];
+2 -1
View File
@@ -1,5 +1,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using Luna.Agents.Abstractions; using Luna.Agents.Abstractions;
using Luna.Core.Abstractions; using Luna.Core.Abstractions;
@@ -31,7 +32,7 @@ public class SessionManager(
string content, string content,
string conversationId, string conversationId,
string connectionId, string connectionId,
CancellationToken ct = default) [EnumeratorCancellation] CancellationToken ct = default)
{ {
if (!connectedClients.ContainsKey(connectionId)) if (!connectedClients.ContainsKey(connectionId))
connectedClients[connectionId] = conversationId; connectedClients[connectionId] = conversationId;
@@ -19,9 +19,8 @@ public static class ServiceCollectionExtensions
} }
/// <summary> /// <summary>
/// Registers the <see cref="MistralProvider"/> and maps <see cref="Provider"/> to it as a singleton. /// Registers the <see cref="MistralProvider"/> and maps <see cref="IProvider"/> to it as a singleton.
/// </summary> /// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The updated service collection.</returns> /// <returns>The updated service collection.</returns>
private IServiceCollection AddMistralProvider() private IServiceCollection AddMistralProvider()
{ {
@@ -42,7 +41,7 @@ public static class ServiceCollectionExtensions
} }
/// <summary> /// <summary>
/// Registers the <see cref="OllamaProvider"/> and maps <see cref="Provider"/> to it as a singleton. /// Registers the <see cref="OllamaProvider"/> and maps <see cref="IProvider"/> to it as a singleton.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private IServiceCollection AddOllamaProvider() private IServiceCollection AddOllamaProvider()