Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions TelegramSearchBot.Test/Service/Tools/TodoToolServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TelegramSearchBot.Service.Tools;
using TelegramSearchBot.Model;
using Xunit;
using Moq;
using TelegramSearchBot.Interface;

namespace TelegramSearchBot.Test.Service.Tools
{
public class TodoToolServiceTests
{
private readonly Mock<ISendMessageService> _mockSendMessageService;
private readonly TodoToolService _todoToolService;

public TodoToolServiceTests()
{
_mockSendMessageService = new Mock<ISendMessageService>();
_todoToolService = new TodoToolService(_mockSendMessageService.Object, null);
}

[Fact]
public async Task SendTodoToGroup_ValidParameters_Success()
{
// Arrange
long chatId = -123456789; // Group chat ID
string title = "Test Todo";
string description = "This is a test todo item";
string priority = "high";
string dueDate = "2025-12-31";

// Act
var result = await _todoToolService.SendTodoToGroup(chatId, title, description, priority, dueDate);

// Assert
Assert.Contains("✅", result);
Assert.Contains(title, result);
Assert.Contains(chatId.ToString(), result);

// Verify SendMessage was called
_mockSendMessageService.Verify(x => x.SendMessage(It.Is<string>(s => s.Contains(title)), chatId), Times.Once);
}

[Fact]
public async Task SendTodoToGroup_PrivateChatId_LogsWarning()
{
// Arrange
long chatId = 123456789; // Private chat ID (positive)
string title = "Test Todo";
string description = "This is a test todo item";

// Act
var result = await _todoToolService.SendTodoToGroup(chatId, title, description);

// Assert
Assert.Contains("✅", result); // Should still work but with warning
_mockSendMessageService.Verify(x => x.SendMessage(It.Is<string>(s => s.Contains(title)), chatId), Times.Once);
}

[Fact]
public async Task SendTodoToGroup_InvalidPriority_DefaultsToMedium()
{
// Arrange
long chatId = -123456789;
string title = "Test Todo";
string description = "This is a test todo item";
string invalidPriority = "invalid";

// Act
var result = await _todoToolService.SendTodoToGroup(chatId, title, description, invalidPriority);

// Assert
Assert.Contains("✅", result);
_mockSendMessageService.Verify(x => x.SendMessage(It.Is<string>(s => s.Contains("MEDIUM")), chatId), Times.Once);
}

[Fact]
public async Task SendQuickTodo_ValidParameters_Success()
{
// Arrange
long chatId = -123456789;
string message = "Quick todo message";

// Act
var result = await _todoToolService.SendQuickTodo(chatId, message);

// Assert
Assert.Contains("✅", result);
Assert.Contains(chatId.ToString(), result);

// Verify SendMessage was called with formatted message
_mockSendMessageService.Verify(x => x.SendMessage(It.Is<string>(s => s.Contains("📋") && s.Contains("TODO")), chatId), Times.Once);
}

[Fact]
public async Task SendTodoToGroup_SendMessageFails_ReturnsErrorMessage()
{
// Arrange
long chatId = -123456789;
string title = "Test Todo";
string description = "This is a test todo item";

_mockSendMessageService
.Setup(x => x.SendMessage(It.IsAny<string>(), chatId))
.ThrowsAsync(new Exception("Telegram API error"));

// Act
var result = await _todoToolService.SendTodoToGroup(chatId, title, description);

// Assert
Assert.Contains("❌", result);
Assert.Contains("Failed to send todo", result);
Assert.Contains("Telegram API error", result);
}
}
}
154 changes: 154 additions & 0 deletions TelegramSearchBot/Service/Tools/TodoToolService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using TelegramSearchBot.Attributes;
using TelegramSearchBot.Interface;
using TelegramSearchBot.Model;

namespace TelegramSearchBot.Service.Tools
{
/// <summary>
/// LLM tool for sending todo messages to group chats
/// </summary>
[Injectable(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient)]
public class TodoToolService
{
private readonly ISendMessageService _sendMessageService;
private readonly ILogger<TodoToolService> _logger;

public TodoToolService(ISendMessageService sendMessageService, ILogger<TodoToolService> logger)
{
_sendMessageService = sendMessageService;
_logger = logger;
}

/// <summary>
/// Send a todo message to a group chat
/// </summary>
/// <param name="chatId">Target group chat ID</param>
/// <param name="title">Todo title/subject</param>
/// <param name="description">Detailed description of the todo</param>
/// <param name="priority">Priority level (low, medium, high, urgent)</param>
/// <param name="dueDate">Optional due date for the todo</param>
/// <param name="toolContext">Tool execution context</param>
/// <returns>Confirmation message</returns>
[McpTool("Send a todo message to a group chat with structured information including title, description, priority, and optional due date.")]
public async Task<string> SendTodoToGroup(
[McpParameter("Target group chat ID to send the todo message to")]
long chatId,

[McpParameter("Title or subject of the todo item")]
string title,

[McpParameter("Detailed description of the todo item")]
string description,

[McpParameter("Priority level (low, medium, high, urgent)", IsRequired = false)]
string priority = "medium",

[McpParameter("Optional due date for the todo item (format: YYYY-MM-DD or YYYY-MM-DD HH:MM)", IsRequired = false)]
string dueDate = null,

ToolContext toolContext = null)
{
try
{
// Validate chat ID is a group chat (negative chat IDs are groups/channels)
if (chatId > 0)
{
_logger.LogWarning("TodoToolService: Chat ID {ChatId} appears to be a private chat, not a group", chatId);
}

// Validate priority
priority = priority?.ToLowerInvariant() ?? "medium";
if (!new[] { "low", "medium", "high", "urgent" }.Contains(priority))

Check failure on line 64 in TelegramSearchBot/Service/Tools/TodoToolService.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

'string[]' does not contain a definition for 'Contains' and the best extension method overload 'MemoryExtensions.Contains<string>(ReadOnlySpan<string>, string)' requires a receiver of type 'System.ReadOnlySpan<string>'

Check failure on line 64 in TelegramSearchBot/Service/Tools/TodoToolService.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

'string[]' does not contain a definition for 'Contains' and the best extension method overload 'MemoryExtensions.Contains<string>(ReadOnlySpan<string>, string)' requires a receiver of type 'System.ReadOnlySpan<string>'
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing using System.Linq; directive. The Contains method on arrays requires the System.Linq namespace to be imported.

Copilot uses AI. Check for mistakes.
{
priority = "medium";
}

// Build todo message
var message = BuildTodoMessage(title, description, priority, dueDate);

// Send message to group
await _sendMessageService.SendMessage(message, chatId);

_logger.LogInformation("TodoToolService: Successfully sent todo to chat {ChatId}: {Title}", chatId, title);

return $"✅ Todo item '{title}' has been sent to group {chatId} with {priority} priority.";
}
catch (Exception ex)
{
_logger.LogError(ex, "TodoToolService: Failed to send todo to chat {ChatId}: {Title}", chatId, title);
return $"❌ Failed to send todo item '{title}' to group {chatId}: {ex.Message}";
}
}

/// <summary>
/// Send a quick todo message with minimal parameters
/// </summary>
/// <param name="chatId">Target group chat ID</param>
/// <param name="message">Todo message content</param>
/// <param name="toolContext">Tool execution context</param>
/// <returns>Confirmation message</returns>
[McpTool("Send a quick todo message to a group chat with minimal formatting")]
public async Task<string> SendQuickTodo(
[McpParameter("Target group chat ID to send the todo message to")]
long chatId,

[McpParameter("Quick todo message content")]
string message,

ToolContext toolContext = null)
{
try
{
// Validate chat ID is a group chat (negative chat IDs are groups/channels)
if (chatId > 0)
{
_logger.LogWarning("TodoToolService: Chat ID {ChatId} appears to be a private chat, not a group", chatId);
}

// Build quick todo message
var formattedMessage = $"📋 **TODO**\n\n{message}";

// Send message to group
await _sendMessageService.SendMessage(formattedMessage, chatId);

_logger.LogInformation("TodoToolService: Successfully sent quick todo to chat {ChatId}", chatId);

return $"✅ Quick todo has been sent to group {chatId}.";
}
catch (Exception ex)
{
_logger.LogError(ex, "TodoToolService: Failed to send quick todo to chat {ChatId}", chatId);
return $"❌ Failed to send quick todo to group {chatId}: {ex.Message}";
}
}

private string BuildTodoMessage(string title, string description, string priority, string dueDate)
{
var priorityEmojis = new Dictionary<string, string>

Check failure on line 130 in TelegramSearchBot/Service/Tools/TodoToolService.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The type or namespace name 'Dictionary<,>' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 130 in TelegramSearchBot/Service/Tools/TodoToolService.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

The type or namespace name 'Dictionary<,>' could not be found (are you missing a using directive or an assembly reference?)
{
{ "low", "🟢" },
{ "medium", "🟡" },
{ "high", "🟠" },
{ "urgent", "🔴" }
};

var priorityEmoji = priorityEmojis.TryGetValue(priority, out var emoji) ? emoji : "🟡";

var message = $"📋 **TODO: {title}** {priorityEmoji}\n\n";
message += $"**Priority:** {priority.ToUpperInvariant()}\n\n";
message += $"**Description:**\n{description}\n";

if (!string.IsNullOrEmpty(dueDate))
{
message += $"\n**Due Date:** {dueDate}\n";
}

message += $"\n*Created: {DateTimeOffset.Now:yyyy-MM-dd HH:mm})*";
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra closing parenthesis in the message format. Should be *Created: {DateTimeOffset.Now:yyyy-MM-dd HH:mm}* without the double closing parenthesis.

Suggested change
message += $"\n*Created: {DateTimeOffset.Now:yyyy-MM-dd HH:mm})*";
message += $"\n*Created: {DateTimeOffset.Now:yyyy-MM-dd HH:mm}*";

Copilot uses AI. Check for mistakes.

return message;
}
}
}
Loading