diff --git a/TelegramSearchBot.Test/Controller/Manage/UpdateControllerTests.cs b/TelegramSearchBot.Test/Controller/Manage/UpdateControllerTests.cs index 5fe454a5..81858383 100644 --- a/TelegramSearchBot.Test/Controller/Manage/UpdateControllerTests.cs +++ b/TelegramSearchBot.Test/Controller/Manage/UpdateControllerTests.cs @@ -6,35 +6,25 @@ using Telegram.Bot; using Telegram.Bot.Requests.Abstractions; using Telegram.Bot.Types; +using TelegramSearchBot.Common; using TelegramSearchBot.Controller.Manage; using TelegramSearchBot.Model; -using TelegramSearchBot.Service.Common; -using TelegramSearchBot.Service.Manage; -using TelegramSearchBot.Service.Scheduler; using Xunit; namespace TelegramSearchBot.Test.Controller.Manage; /// -/// RED-phase tests for UpdateController bot command handling. +/// Tests for UpdateController bot command handling. /// /// These tests validate the command routing logic, null-safety guards, -/// admin permission checks, and the full ExecuteAsync flow. +/// admin permission checks (global admin via Env.AdminId), and the full ExecuteAsync flow. /// -/// NOTE: Many integration-style tests are EXPECTED to fail at runtime -/// in the RED phase because: -/// 1. AdminService.IsNormalAdmin is non-virtual and cannot be mocked. -/// 2. SelfUpdateBootstrap is a static class with platform-dependent partial methods. -/// 3. Telegram.Bot v22 API methods are extension methods (not interface methods) -/// and cannot be mocked by Moq. -/// -/// The test structure is correct and compiles; the GREEN phase (Task 10) -/// will make the system-under-test fully mockable. +/// NOTE: Some integration-style tests call SelfUpdateBootstrap static methods +/// which depend on network access and platform-specific functionality. /// public class UpdateControllerTests { private readonly Mock _mockBotClient; - private readonly Mock _mockAdminService; private readonly Mock _mockAppLifetime; private readonly UpdateController _controller; @@ -43,19 +33,6 @@ public UpdateControllerTests() _mockBotClient = new Mock(); _mockAppLifetime = new Mock(); - // AdminService constructor requires several dependencies. - // IsNormalAdmin is now virtual — mock returns true for admin permission. - _mockAdminService = new Mock( - Mock.Of>(), - null!, // DataDbContext — not needed when IsNormalAdmin is mocked - Mock.Of(), - Mock.Of(), - Mock.Of()); - - _mockAdminService - .Setup(x => x.IsNormalAdmin(It.IsAny())) - .ReturnsAsync(true); - // SendMessage in Telegram.Bot v22 is an extension method that calls // ITelegramBotClient.SendRequest() internally. Mock the underlying // interface method to avoid real HTTP calls to Telegram API. @@ -67,7 +44,6 @@ public UpdateControllerTests() _controller = new UpdateController( _mockBotClient.Object, - _mockAdminService.Object, _mockAppLifetime.Object); } diff --git a/TelegramSearchBot/Controller/Manage/UpdateController.cs b/TelegramSearchBot/Controller/Manage/UpdateController.cs index 54c208e5..a8063f2d 100644 --- a/TelegramSearchBot/Controller/Manage/UpdateController.cs +++ b/TelegramSearchBot/Controller/Manage/UpdateController.cs @@ -6,25 +6,22 @@ using Microsoft.Extensions.Hosting; using Telegram.Bot; using Telegram.Bot.Types; +using TelegramSearchBot.Common; using TelegramSearchBot.Interface.Controller; using TelegramSearchBot.Model; using TelegramSearchBot.Service.AppUpdate; -using TelegramSearchBot.Service.Manage; namespace TelegramSearchBot.Controller.Manage { public class UpdateController : IOnUpdate { private readonly ITelegramBotClient _botClient; - private readonly AdminService _adminService; private readonly IHostApplicationLifetime _applicationLifetime; public List Dependencies => new List() { typeof(AdminController) }; public UpdateController( ITelegramBotClient botClient, - AdminService adminService, IHostApplicationLifetime applicationLifetime) { _botClient = botClient; - _adminService = adminService; _applicationLifetime = applicationLifetime; } @@ -39,8 +36,9 @@ public async Task ExecuteAsync(PipelineContext p) { return; } - if (!await _adminService.IsNormalAdmin(message.From.Id)) { - await ReplyAsync(message, "❌ 权限不足:只有管理员才能使用更新指令。"); + // 仅允许全局管理员使用更新指令 + if (message.From.Id != Env.AdminId) { + await ReplyAsync(message, "❌ 权限不足:只有全局管理员才能使用更新指令。"); return; } @@ -70,50 +68,58 @@ private static bool IsUpdateCommand(string text) { } private async Task HandleCheckUpdateAsync(Message message) { - var result = await SelfUpdateBootstrap.GetUpdateStatusAsync(); - var sb = new StringBuilder(); - sb.AppendLine("更新状态"); - sb.AppendLine($"当前版本: {result.CurrentVersion}"); - sb.AppendLine($"运行位置: {( result.RunningManagedInstall ? "独立安装目录" : "桥接启动实例" )}"); - sb.AppendLine($"独立安装目录: {( result.ManagedInstallExists ? "已存在" : "尚未建立" )}"); - - if (!string.IsNullOrWhiteSpace(result.LatestVersion)) { - sb.AppendLine($"最新版本: {result.LatestVersion}"); + try { + var result = await SelfUpdateBootstrap.GetUpdateStatusAsync(); + var sb = new StringBuilder(); + sb.AppendLine("更新状态"); + sb.AppendLine($"当前版本: {result.CurrentVersion}"); + sb.AppendLine($"运行位置: {( result.RunningManagedInstall ? "独立安装目录" : "桥接启动实例" )}"); + sb.AppendLine($"独立安装目录: {( result.ManagedInstallExists ? "已存在" : "尚未建立" )}"); + + if (!string.IsNullOrWhiteSpace(result.LatestVersion)) { + sb.AppendLine($"最新版本: {result.LatestVersion}"); + } + + switch (result.State) { + case ManagedUpdateState.UpToDate: + sb.AppendLine("状态: 已是最新版本。"); + break; + case ManagedUpdateState.UpdateAvailable: + sb.AppendLine($"状态: 可更新到 {result.TargetVersion ?? result.LatestVersion}。"); + sb.AppendLine("发送「更新」或 /update 立即开始升级。"); + break; + case ManagedUpdateState.UpdateUnavailable: + case ManagedUpdateState.NoPathFound: + sb.AppendLine($"状态: {result.Message ?? "当前没有可用更新路径。"}"); + break; + default: + sb.AppendLine($"状态: {result.Message ?? result.State.ToString()}"); + break; + } + + await ReplyAsync(message, sb.ToString()); + } catch (Exception ex) { + await ReplyAsync(message, $"❌ 检查更新时发生异常:{ex.Message}"); } - - switch (result.State) { - case ManagedUpdateState.UpToDate: - sb.AppendLine("状态: 已是最新版本。"); - break; - case ManagedUpdateState.UpdateAvailable: - sb.AppendLine($"状态: 可更新到 {result.TargetVersion ?? result.LatestVersion}。"); - sb.AppendLine("发送“更新”或 /update 立即开始升级。"); - break; - case ManagedUpdateState.UpdateUnavailable: - case ManagedUpdateState.NoPathFound: - sb.AppendLine($"状态: {result.Message ?? "当前没有可用更新路径。"}"); - break; - default: - sb.AppendLine($"状态: {result.Message ?? result.State.ToString()}"); - break; - } - - await ReplyAsync(message, sb.ToString()); } private async Task HandleStartUpdateAsync(Message message) { - var result = await SelfUpdateBootstrap.StartUpdateAsync(); - string responseText = result.State switch { - ManagedUpdateState.UpdateScheduled => $"✅ 已开始准备更新到 {result.TargetVersion ?? result.LatestVersion},机器人将退出并在更新后自动重启。", - ManagedUpdateState.UpToDate => "✅ 当前已经是最新版本,无需更新。", - ManagedUpdateState.UpdateAvailable => $"ℹ️ 已检测到新版本 {result.TargetVersion ?? result.LatestVersion},但暂未成功调度更新。", - _ => $"❌ 无法启动更新:{result.Message ?? result.State.ToString()}" - }; - - await ReplyAsync(message, responseText); - - if (result.ShouldStopApplication) { - _applicationLifetime.StopApplication(); + try { + var result = await SelfUpdateBootstrap.StartUpdateAsync(); + string responseText = result.State switch { + ManagedUpdateState.UpdateScheduled => $"✅ 已开始准备更新到 {result.TargetVersion ?? result.LatestVersion},机器人将退出并在更新后自动重启。", + ManagedUpdateState.UpToDate => "✅ 当前已经是最新版本,无需更新。", + ManagedUpdateState.UpdateAvailable => $"ℹ️ 已检测到新版本 {result.TargetVersion ?? result.LatestVersion},但暂未成功调度更新。", + _ => $"❌ 无法启动更新:{result.Message ?? result.State.ToString()}" + }; + + await ReplyAsync(message, responseText); + + if (result.ShouldStopApplication) { + _applicationLifetime.StopApplication(); + } + } catch (Exception ex) { + await ReplyAsync(message, $"❌ 启动更新时发生异常:{ex.Message}"); } } diff --git a/TelegramSearchBot/Service/BotAPI/TelegramCommandRegistryService.cs b/TelegramSearchBot/Service/BotAPI/TelegramCommandRegistryService.cs index 69be3024..2d8cb301 100644 --- a/TelegramSearchBot/Service/BotAPI/TelegramCommandRegistryService.cs +++ b/TelegramSearchBot/Service/BotAPI/TelegramCommandRegistryService.cs @@ -24,7 +24,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var commands = new List { new BotCommand { Command = "resolveurls", Description = "解析文本中的链接并存储原始链接与解析后链接的映射。" }, - // 可以根据需要添加更多命令 + new BotCommand { Command = "checkupdate", Description = "检查系统更新状态。" }, + new BotCommand { Command = "update", Description = "执行系统更新(如果存在新版本)。" }, }; _logger.LogInformation($"Registering {commands.Count} commands...");