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...");