diff --git a/tests/CncWebApi.Tests/AlertControllerTests.cs b/tests/CncWebApi.Tests/AlertControllerTests.cs
new file mode 100644
index 0000000..4b4e111
--- /dev/null
+++ b/tests/CncWebApi.Tests/AlertControllerTests.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using CncModels.Dto;
+using CncModels.Dto.Alert;
+using CncService;
+using CncWebApi.Controllers;
+using Xunit;
+
+namespace CncWebApi.Tests
+{
+ ///
+ /// AlertController单元测试
+ /// 告警CRUD + 统计 + 批量处理
+ ///
+ [Collection("Database")]
+ public class AlertControllerTests
+ {
+ private readonly AlertController _controller;
+
+ public AlertControllerTests()
+ {
+ TestDb.TruncateAll();
+ // 预置机床(告警外键依赖)
+ TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
+ VALUES ('测试地址', 'http://192.168.1.1', 1, 5, 1, NOW(), NOW())");
+ TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at)
+ VALUES ('CNC001', '机床1', 1, 1, '192.168.1.100', 1, 1, NOW(), NOW())");
+ _controller = ControllerFactory.CreateAlertController();
+ }
+
+ #region 辅助方法
+
+ ///
+ /// 插入一条告警
+ ///
+ private int InsertAlert(string type = "offline", string title = "告警测试")
+ {
+ TestDb.Execute(@"INSERT INTO cnc_alert (machine_id, alert_type, title, is_resolved, created_at)
+ VALUES (1, @type, @title, 0, NOW())", new { type, title });
+ return TestDb.QuerySingle("SELECT MAX(id) FROM cnc_alert");
+ }
+
+ #endregion
+
+ #region GetList - 告警列表
+
+ ///
+ /// 测试:空数据库返回空列表
+ ///
+ [Fact]
+ public void GetList_EmptyDb_ShouldReturnEmpty()
+ {
+ var result = _controller.GetList(new AlertQuery());
+ var response = ControllerFactory.Extract>(result);
+ ControllerFactory.AssertSuccess(response);
+ Assert.Empty(response.Data.Items);
+ }
+
+ ///
+ /// 测试:有告警数据时返回列表
+ ///
+ [Fact]
+ public void GetList_WithData_ShouldReturnItems()
+ {
+ InsertAlert();
+ var result = _controller.GetList(new AlertQuery());
+ var response = ControllerFactory.Extract>(result);
+ Assert.NotEmpty(response.Data.Items);
+ }
+
+ #endregion
+
+ #region GetStatistics - 告警统计
+
+ ///
+ /// 测试:告警统计数据
+ ///
+ [Fact]
+ public void GetStatistics_ShouldReturnStats()
+ {
+ InsertAlert();
+ var result = _controller.GetStatistics();
+ var response = ControllerFactory.Extract(result);
+ ControllerFactory.AssertSuccess(response);
+ Assert.NotNull(response.Data);
+ }
+
+ #endregion
+
+ #region Resolve - 处理告警
+
+ ///
+ /// 测试:处理单条告警成功
+ ///
+ [Fact]
+ public void Resolve_Existing_ShouldSuccess()
+ {
+ int alertId = InsertAlert();
+ var result = _controller.Resolve(alertId);
+ ControllerFactory.AssertSuccess(ControllerFactory.Extract