diff --git a/tests/CncModels.Tests/ApiResponseTests.cs b/tests/CncModels.Tests/ApiResponseTests.cs
new file mode 100644
index 0000000..11fb089
--- /dev/null
+++ b/tests/CncModels.Tests/ApiResponseTests.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using Xunit;
+using CncModels.Dto;
+
+namespace CncModels.Tests
+{
+ ///
+ /// ApiResponse 单元测试
+ ///
+ public class ApiResponseTests
+ {
+ [Fact]
+ ///
+ /// 成功响应:Code=0, Message=success, Data=传入值
+ ///
+ public void Success_Returns_Code_0_Message_Success_With_Data()
+ {
+ // Arrange
+ var data = "ok";
+ // Act
+ var resp = ApiResponse.Success(data);
+
+ // Assert
+ Assert.Equal(0, resp.Code);
+ Assert.Equal("success", resp.Message);
+ Assert.Equal(data, resp.Data);
+ }
+
+ [Fact]
+ ///
+ /// 成功响应:自定义信息
+ ///
+ public void Success_With_Custom_Message()
+ {
+ var resp = ApiResponse.Success("payload", "custom");
+ Assert.Equal(0, resp.Code);
+ Assert.Equal("custom", resp.Message);
+ Assert.Equal("payload", resp.Data);
+ }
+
+ [Fact]
+ ///
+ /// 失败响应:非零错误码且数据为 null
+ ///
+ public void Fail_Returns_NonZero_Code_And_Null_Data()
+ {
+ var resp = ApiResponse.Fail(400, "bad request");
+ Assert.Equal(400, resp.Code);
+ Assert.Equal("bad request", resp.Message);
+ Assert.Null(resp.Data);
+ }
+
+ [Fact]
+ ///
+ /// 成功响应:数据为 null 的情况
+ ///
+ public void Success_With_Null_Data()
+ {
+ var resp = ApiResponse.Success(null);
+ Assert.Equal(0, resp.Code);
+ Assert.Equal("success", resp.Message);
+ Assert.Null(resp.Data);
+ }
+
+ [Fact]
+ ///
+ /// 成功响应:复杂对象数据
+ ///
+ public void Success_With_Complex_Data()
+ {
+ var data = new List { 1, 2, 3 };
+ var resp = ApiResponse>.Success(data);
+ Assert.Equal(0, resp.Code);
+ Assert.Equal("success", resp.Message);
+ Assert.Equal(data, resp.Data);
+ }
+ }
+}
diff --git a/tests/CncModels.Tests/ConstantsTests.cs b/tests/CncModels.Tests/ConstantsTests.cs
new file mode 100644
index 0000000..1fa45d7
--- /dev/null
+++ b/tests/CncModels.Tests/ConstantsTests.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Reflection;
+using System.Text;
+using Xunit;
+using CncModels.Constants;
+
+namespace CncModels.Tests
+{
+ ///
+ /// 常量类测试:TableName、SensitiveConfigKey、ErrorCode 的常量值应符合命名约定与格式要求。
+ ///
+ public class ConstantsTests
+ {
+ ///
+ /// 验证 TableName 的 20 个表名常量值与数据库实际表名一致
+ /// 覆盖场景:全部20张表的精确值校验
+ ///
+ [Fact]
+ public void TableName_AllConstants_MatchDatabaseTableNames()
+ {
+ // 业务库 17 张表
+ Assert.Equal("cnc_workshop", TableName.Workshop);
+ Assert.Equal("cnc_brand", TableName.Brand);
+ Assert.Equal("cnc_brand_field_mapping", TableName.BrandFieldMapping);
+ Assert.Equal("cnc_collect_address", TableName.CollectAddress);
+ Assert.Equal("cnc_machine", TableName.Machine);
+ Assert.Equal("cnc_worker", TableName.Worker);
+ Assert.Equal("cnc_worker_machine", TableName.WorkerMachine);
+ Assert.Equal("cnc_collect_record", TableName.CollectRecord);
+ Assert.Equal("cnc_production_segment", TableName.ProductionSegment);
+ Assert.Equal("cnc_machine_daily_status", TableName.MachineDailyStatus);
+ Assert.Equal("cnc_daily_production", TableName.DailyProduction);
+ Assert.Equal("cnc_worker_daily_summary", TableName.WorkerDailySummary);
+ Assert.Equal("cnc_production_adjustment", TableName.ProductionAdjustment);
+ Assert.Equal("cnc_alert", TableName.Alert);
+ Assert.Equal("cnc_sys_config", TableName.SysConfig);
+ Assert.Equal("cnc_screen_config", TableName.ScreenConfig);
+ Assert.Equal("cnc_screen_filter", TableName.ScreenFilter);
+ // 日志库 3 张表
+ Assert.Equal("log_collect_raw", TableName.LogCollectRaw);
+ Assert.Equal("log_system", TableName.LogSystem);
+ Assert.Equal("log_collector_heartbeat", TableName.LogCollectorHeartbeat);
+ }
+
+ ///
+ /// AAA:验证 SensitiveConfigKey 的 3 个敏感键常量值应为 snake_case 风格。
+ ///
+ [Fact]
+ public void SensitiveConfigKey_Constants_ShouldFollowSnakeCase()
+ {
+ Type t = typeof(SensitiveConfigKey);
+ var fields = t.GetFields(BindingFlags.Public | BindingFlags.Static);
+ Assert.Equal(3, fields.Length);
+ foreach (var f in fields)
+ {
+ if (f.FieldType != typeof(string)) continue;
+ string value = (string)f.GetValue(null);
+ string expected = ToSnakeCase(f.Name);
+ Assert.Equal(expected, value);
+ }
+ }
+
+ ///
+ /// AAA:验证 ErrorCode 的 6 个错误码常量值应为纯数字字符串。
+ ///
+ [Fact]
+ public void ErrorCode_Constants_ShouldBeNumericStrings()
+ {
+ Type t = typeof(ErrorCode);
+ var fields = t.GetFields(BindingFlags.Public | BindingFlags.Static);
+ // 至少应有 6 个错误码常量,若有更多也应遵循数字字符串规则
+ Assert.True(fields.Length >= 6, "ErrorCode 至少应包含 6 个常量");
+ foreach (var f in fields)
+ {
+ if (f.FieldType != typeof(string)) continue;
+ string value = (string)f.GetValue(null);
+ Assert.Matches("^[0-9]+$", value);
+ }
+ }
+
+ private string ToSnakeCase(string input)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+ var sb = new StringBuilder();
+ for (int i = 0; i < input.Length; i++)
+ {
+ char c = input[i];
+ if (char.IsUpper(c))
+ {
+ if (i > 0) sb.Append('_');
+ sb.Append(char.ToLowerInvariant(c));
+ }
+ else
+ {
+ sb.Append(c);
+ }
+ }
+ return sb.ToString();
+ }
+ }
+}
diff --git a/tests/CncModels.Tests/DtoTests.cs b/tests/CncModels.Tests/DtoTests.cs
new file mode 100644
index 0000000..3756de7
--- /dev/null
+++ b/tests/CncModels.Tests/DtoTests.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+using CncModels.Dto.Login;
+using CncModels.Dto.Machine;
+
+namespace CncModels.Tests
+{
+ ///
+ /// DTO 属性赋值测试(对关键 DTO 的简单结构进行读写回填验证)
+ ///
+ public class DtoTests
+ {
+ [Fact]
+ ///
+ /// LoginRequest 属性赋值读取回填
+ ///
+ public void LoginRequest_Property_Assignment()
+ {
+ // Arrange
+ var req = new LoginRequest
+ {
+ Username = "user1",
+ Password = "pwd",
+ RememberMe = true
+ };
+
+ // Assert
+ Assert.Equal("user1", req.Username);
+ Assert.Equal("pwd", req.Password);
+ Assert.True(req.RememberMe);
+ }
+
+ [Fact]
+ ///
+ /// MachineQuery 继承自 PagedQuery 且字段赋值正确
+ ///
+ public void MachineQuery_Inherits_PagedQuery_And_Sets_Others()
+ {
+ var q = new MachineQuery
+ {
+ Page = 2,
+ PageSize = 5,
+ Keyword = "lathe",
+ WorkshopId = 3,
+ IsOnline = 1,
+ BrandId = 7
+ };
+
+ Assert.Equal(2, q.Page);
+ Assert.Equal(5, q.PageSize);
+ Assert.Equal("lathe", q.Keyword);
+ Assert.Equal(3, q.WorkshopId);
+ Assert.Equal(1, q.IsOnline);
+ Assert.Equal(7, q.BrandId);
+ Assert.Equal(5, q.Offset); // (2-1)*5
+ }
+
+ [Fact]
+ ///
+ /// CreateMachineRequest 属性赋值读取回填
+ ///
+ public void CreateMachineRequest_Property_Assignment()
+ {
+ var req = new CreateMachineRequest
+ {
+ DeviceCode = "D1",
+ Name = "Machine 1",
+ WorkshopId = 2,
+ CollectAddressId = 3,
+ IpAddress = "192.168.1.10",
+ BrandId = 8
+ };
+
+ Assert.Equal("D1", req.DeviceCode);
+ Assert.Equal("Machine 1", req.Name);
+ Assert.Equal(2, req.WorkshopId);
+ Assert.Equal(3, req.CollectAddressId);
+ Assert.Equal("192.168.1.10", req.IpAddress);
+ Assert.Equal(8, req.BrandId);
+ }
+
+ [Fact]
+ ///
+ /// MachineListItem 属性赋值读取回填
+ ///
+ public void MachineListItem_Property_Assignment()
+ {
+ var item = new MachineListItem
+ {
+ Id = 1,
+ DeviceCode = "M1",
+ Name = "Machine 1",
+ WorkshopId = 2,
+ WorkshopName = "Workshop A",
+ CollectAddressId = 4,
+ BrandId = 8,
+ BrandName = "BrandX",
+ IpAddress = "10.0.0.1",
+ IsEnabled = 1,
+ IsOnline = 1,
+ WorkerId = 9,
+ WorkerName = "Worker1",
+ LastProgramName = "P1",
+ LastCollectTime = new DateTime(2026, 4, 1)
+ };
+
+ Assert.Equal(1, item.Id);
+ Assert.Equal("M1", item.DeviceCode);
+ Assert.Equal("Machine 1", item.Name);
+ Assert.Equal(2, item.WorkshopId);
+ Assert.Equal("Workshop A", item.WorkshopName);
+ Assert.Equal(4, item.CollectAddressId);
+ Assert.Equal(8, item.BrandId);
+ Assert.Equal("BrandX", item.BrandName);
+ Assert.Equal("10.0.0.1", item.IpAddress);
+ Assert.Equal(1, item.IsEnabled);
+ Assert.Equal(1, item.IsOnline);
+ Assert.Equal(9, item.WorkerId);
+ Assert.Equal("Worker1", item.WorkerName);
+ Assert.Equal("P1", item.LastProgramName);
+ Assert.Equal(new DateTime(2026, 4, 1), item.LastCollectTime);
+ }
+ }
+}
diff --git a/tests/CncModels.Tests/EntityTests.cs b/tests/CncModels.Tests/EntityTests.cs
new file mode 100644
index 0000000..6d9f401
--- /dev/null
+++ b/tests/CncModels.Tests/EntityTests.cs
@@ -0,0 +1,775 @@
+using System;
+using CncModels.Entity;
+using Xunit;
+
+namespace CncModels.Tests
+{
+ public class EntityTests
+ {
+ // Workshop
+ [Fact]
+ public void Workshop_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ // Arrange: defaults
+ var w0 = new Workshop();
+ Assert.Equal(0, w0.Id);
+ Assert.Null(w0.Name);
+ Assert.Equal(0, w0.SortOrder);
+ Assert.Equal(0, w0.IsEnabled);
+ Assert.Equal(default(DateTime), w0.CreatedAt);
+ Assert.Equal(default(DateTime), w0.UpdatedAt);
+
+ // Act: assign all fields
+ var w = new Workshop
+ {
+ Id = 1,
+ Name = "A栋",
+ SortOrder = 2,
+ IsEnabled = 1,
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+
+ // Assert: values
+ Assert.Equal(1, w.Id);
+ Assert.Equal("A栋", w.Name);
+ Assert.Equal(2, w.SortOrder);
+ Assert.Equal(1, w.IsEnabled);
+ Assert.Equal(new DateTime(2026, 4, 28), w.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), w.UpdatedAt);
+ }
+
+ // Brand
+ [Fact]
+ public void Brand_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var b0 = new Brand();
+ Assert.Equal(0, b0.Id);
+ Assert.Null(b0.BrandName);
+ Assert.Null(b0.DeviceField);
+ Assert.Null(b0.TagsPath);
+ Assert.Equal(0, b0.IsEnabled);
+ Assert.Equal(default(DateTime), b0.CreatedAt);
+ Assert.Equal(default(DateTime), b0.UpdatedAt);
+
+ var b = new Brand
+ {
+ Id = 3,
+ BrandName = "BrandX",
+ DeviceField = "fieldX",
+ TagsPath = "/tags/x",
+ IsEnabled = 1,
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(3, b.Id);
+ Assert.Equal("BrandX", b.BrandName);
+ Assert.Equal("fieldX", b.DeviceField);
+ Assert.Equal("/tags/x", b.TagsPath);
+ Assert.Equal(1, b.IsEnabled);
+ Assert.Equal(new DateTime(2026, 4, 28), b.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), b.UpdatedAt);
+ }
+
+ // BrandFieldMapping
+ [Fact]
+ public void BrandFieldMapping_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var m0 = new BrandFieldMapping();
+ Assert.Equal(0, m0.Id);
+ Assert.Equal(0, m0.BrandId);
+ Assert.Null(m0.StandardField);
+ Assert.Null(m0.FieldName);
+ Assert.Null(m0.MatchBy);
+ Assert.Null(m0.DataType);
+ Assert.Equal(0, m0.IsRequired);
+ Assert.Equal(default(DateTime), m0.CreatedAt);
+
+ var m = new BrandFieldMapping
+ {
+ Id = 1,
+ BrandId = 2,
+ StandardField = "Model",
+ FieldName = "mdl",
+ MatchBy = "equals",
+ DataType = "string",
+ IsRequired = 1,
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(1, m.Id);
+ Assert.Equal(2, m.BrandId);
+ Assert.Equal("Model", m.StandardField);
+ Assert.Equal("mdl", m.FieldName);
+ Assert.Equal("equals", m.MatchBy);
+ Assert.Equal("string", m.DataType);
+ Assert.Equal(1, m.IsRequired);
+ Assert.Equal(new DateTime(2026, 4, 28), m.CreatedAt);
+ }
+
+ // CollectAddress
+ [Fact]
+ public void CollectAddress_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var a0 = new CollectAddress();
+ Assert.Equal(0, a0.Id);
+ Assert.Null(a0.Name);
+ Assert.Null(a0.Url);
+ Assert.Equal(0, a0.BrandId);
+ Assert.Equal(0, a0.CollectInterval);
+ Assert.Equal(0, a0.IsEnabled);
+ Assert.Null(a0.LastCollectTime);
+ Assert.Null(a0.LastCollectStatus);
+ Assert.Equal(0, a0.FailCount);
+ Assert.Equal(default(DateTime), a0.CreatedAt);
+ Assert.Equal(default(DateTime), a0.UpdatedAt);
+
+ var a = new CollectAddress
+ {
+ Id = 10,
+ Name = "地址1",
+ Url = "http://127.0.0.1",
+ BrandId = 5,
+ CollectInterval = 60,
+ IsEnabled = 1,
+ LastCollectTime = new DateTime(2026, 4, 28),
+ LastCollectStatus = "OK",
+ FailCount = 0,
+ CreatedAt = new DateTime(2026, 4, 29),
+ UpdatedAt = new DateTime(2026, 4, 30)
+ };
+ Assert.Equal(10, a.Id);
+ Assert.Equal("地址1", a.Name);
+ Assert.Equal("http://127.0.0.1", a.Url);
+ Assert.Equal(5, a.BrandId);
+ Assert.Equal(60, a.CollectInterval);
+ Assert.Equal(1, a.IsEnabled);
+ Assert.Equal(new DateTime(2026, 4, 28), a.LastCollectTime);
+ Assert.Equal("OK", a.LastCollectStatus);
+ Assert.Equal(0, a.FailCount);
+ Assert.Equal(new DateTime(2026, 4, 29), a.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 30), a. UpdatedAt);
+ }
+
+ // CollectRecord
+ [Fact]
+ public void CollectRecord_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var r0 = new CollectRecord();
+ Assert.Equal(0L, r0.Id);
+ Assert.Equal(default(DateTime), r0.CollectTime);
+ Assert.Equal(0, r0.MachineId);
+ Assert.Null(r0.ProgramName);
+ Assert.Null(r0.DeviceStatus);
+ Assert.Null(r0.RunStatus);
+ Assert.Null(r0.OperateMode);
+ Assert.Null(r0.ExtraData);
+
+ var r = new CollectRecord
+ {
+ Id = 1234567890123,
+ MachineId = 7,
+ CollectTime = new DateTime(2026, 4, 28, 12, 0, 0),
+ DeviceTime = new DateTime(2026, 4, 28, 12, 0, 1),
+ ProgramName = "Prog1",
+ PartCount = 99.5m,
+ DeviceStatus = "OK",
+ RunStatus = "Running",
+ OperateMode = "Auto",
+ SpindleSpeedSet = 1500.0m,
+ FeedSpeedSet = 200.0m,
+ SpindleSpeedActual = 1499.0m,
+ FeedSpeedActual = 198.0m,
+ SpindleLoad = 75.0m,
+ SpindleOverride = 1.0m,
+ PowerOnTime = 3600.0m,
+ RunTime = 7200.0m,
+ CuttingTime = 1800.0m,
+ CycleTime = 300.0m,
+ MachiningStatus = "Machining",
+ ExtraData = "{}",
+ };
+ // Partial assertions to cover types
+ Assert.Equal(1234567890123, r.Id);
+ Assert.Equal(7, r.MachineId);
+ Assert.Equal(new DateTime(2026, 4, 28, 12, 0, 0), r.CollectTime);
+ Assert.Equal("Prog1", r.ProgramName);
+ Assert.Equal(99.5m, r.PartCount);
+ Assert.Equal("OK", r.DeviceStatus);
+ Assert.Equal("Running", r.RunStatus);
+ Assert.Equal(1500.0m, r.SpindleSpeedSet);
+ Assert.Equal(200.0m, r.FeedSpeedSet);
+ Assert.Equal("Machining", r.MachiningStatus);
+ }
+
+ // ProductionSegment
+ [Fact]
+ public void ProductionSegment_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var s0 = new ProductionSegment();
+ Assert.Equal(0L, s0.Id);
+ Assert.Equal(0, s0.MachineId);
+ Assert.Null(s0.ProgramName);
+ Assert.Equal(default(DateTime), s0.ProductionDate);
+ Assert.Equal(default(DateTime), s0.StartTime);
+ Assert.Null(s0.EndTime);
+ Assert.Equal(0m, s0.StartPartCount);
+ Assert.Null(s0.EndPartCount);
+ Assert.Null(s0.Quantity);
+ Assert.Equal(0, s0.IsSettled);
+ Assert.Null(s0.CloseReason);
+ Assert.Equal(default(DateTime), s0.CreatedAt);
+ Assert.Equal(default(DateTime), s0.UpdatedAt);
+
+ var s = new ProductionSegment
+ {
+ Id = 1,
+ MachineId = 2,
+ ProgramName = "Prog",
+ ProductionDate = new DateTime(2026, 4, 28),
+ StartTime = new DateTime(2026, 4, 28, 8, 0, 0),
+ EndTime = new DateTime(2026, 4, 28, 12, 0, 0),
+ StartPartCount = 10.0m,
+ EndPartCount = 20.0m,
+ Quantity = 10.0m,
+ IsSettled = 1,
+ CloseReason = "End",
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(1, s.Id);
+ Assert.Equal(2, s.MachineId);
+ Assert.Equal("Prog", s.ProgramName);
+ Assert.Equal(new DateTime(2026, 4, 28), s.ProductionDate);
+ Assert.Equal(new DateTime(2026, 4, 28, 8, 0, 0), s.StartTime);
+ Assert.Equal(new DateTime(2026, 4, 28, 12, 0, 0), s.EndTime);
+ Assert.Equal(10.0m, s.StartPartCount);
+ Assert.Equal(20.0m, s.EndPartCount);
+ Assert.Equal(10.0m, s.Quantity);
+ Assert.Equal(1, s.IsSettled);
+ Assert.Equal("End", s.CloseReason);
+ Assert.Equal(new DateTime(2026, 4, 28), s.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), s.UpdatedAt);
+ }
+
+ // MachineDailyStatus
+ [Fact]
+ public void MachineDailyStatus_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var m0 = new MachineDailyStatus();
+ Assert.Equal(0, m0.Id);
+ Assert.Equal(0, m0.MachineId);
+ Assert.Null(m0.DataStatus);
+ Assert.Equal(default(DateTime), m0.CreatedAt);
+ Assert.Equal(default(DateTime), m0.UpdatedAt);
+
+ var m = new MachineDailyStatus
+ {
+ Id = 5,
+ MachineId = 9,
+ ProductionDate = new DateTime(2026, 4, 28),
+ DataStatus = "Online",
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(5, m.Id);
+ Assert.Equal(9, m.MachineId);
+ Assert.Equal("Online", m.DataStatus);
+ Assert.Equal(new DateTime(2026, 4, 28), m.ProductionDate);
+ Assert.Equal(new DateTime(2026, 4, 28), m.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), m.UpdatedAt);
+ }
+
+ // DailyProduction
+ [Fact]
+ public void DailyProduction_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var d0 = new DailyProduction();
+ Assert.Equal(0, d0.Id);
+ Assert.Equal(0, d0.MachineId);
+ Assert.Equal(default(DateTime), d0.ProductionDate);
+ Assert.Null(d0.ProgramName);
+ Assert.Equal(0m, d0.TotalQuantity);
+ Assert.Equal(0, d0.SegmentCount);
+ Assert.Null(d0.TotalRunTime);
+ Assert.Null(d0.TotalCuttingTime);
+ Assert.Null(d0.TotalCycleTime);
+ Assert.Equal(0, d0.IsAdjusted);
+ Assert.Null(d0.AdjustedQuantity);
+ Assert.Equal(default(DateTime), d0.CreatedAt);
+ Assert.Equal(default(DateTime), d0.UpdatedAt);
+
+ var d = new DailyProduction
+ {
+ Id = 2,
+ MachineId = 3,
+ ProductionDate = new DateTime(2026, 4, 28),
+ ProgramName = "ProgA",
+ TotalQuantity = 100.5m,
+ SegmentCount = 5,
+ TotalRunTime = 3600.0m,
+ TotalCuttingTime = 1800.0m,
+ TotalCycleTime = 1200.0m,
+ IsAdjusted = 0,
+ AdjustedQuantity = 0.0m,
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(2, d.Id);
+ Assert.Equal(3, d.MachineId);
+ Assert.Equal("ProgA", d.ProgramName);
+ Assert.Equal(new DateTime(2026, 4, 28), d.ProductionDate);
+ Assert.Equal(100.5m, d.TotalQuantity);
+ Assert.Equal(5, d.SegmentCount);
+ Assert.Equal(3600.0m, d.TotalRunTime);
+ Assert.Equal(1800.0m, d.TotalCuttingTime);
+ Assert.Equal(1200.0m, d.TotalCycleTime);
+ Assert.Equal(0, d.IsAdjusted);
+ Assert.Equal(new DateTime(2026, 4, 28), d.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), d.UpdatedAt);
+ }
+
+ // WorkerDailySummary
+ [Fact]
+ public void WorkerDailySummary_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var w0 = new WorkerDailySummary();
+ Assert.Equal(0, w0.Id);
+ Assert.Equal(0, w0.WorkerId);
+ Assert.Equal(default(DateTime), w0.ProductionDate);
+ Assert.Equal(0m, w0.TotalQuantity);
+ Assert.Equal(0, w0.MachineCount);
+ Assert.Equal(0, w0.ProgramCount);
+ Assert.Equal(0, w0.IsAdjusted);
+ Assert.Null(w0.AdjustedQuantity);
+ Assert.Equal(default(DateTime), w0.CreatedAt);
+ Assert.Equal(default(DateTime), w0.UpdatedAt);
+
+ var w = new WorkerDailySummary
+ {
+ Id = 7,
+ WorkerId = 8,
+ ProductionDate = new DateTime(2026, 4, 28),
+ TotalQuantity = 45.0m,
+ MachineCount = 2,
+ ProgramCount = 3,
+ IsAdjusted = 1,
+ AdjustedQuantity = 44.5m,
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(7, w.Id);
+ Assert.Equal(8, w.WorkerId);
+ Assert.Equal(new DateTime(2026, 4, 28), w.ProductionDate);
+ Assert.Equal(45.0m, w.TotalQuantity);
+ Assert.Equal(2, w.MachineCount);
+ Assert.Equal(3, w.ProgramCount);
+ Assert.Equal(1, w.IsAdjusted);
+ Assert.Equal(44.5m, w.AdjustedQuantity);
+ Assert.Equal(new DateTime(2026, 4, 28), w.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), w.UpdatedAt);
+ }
+
+ // ProductionAdjustment
+ [Fact]
+ public void ProductionAdjustment_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var a0 = new ProductionAdjustment();
+ Assert.Equal(0L, a0.Id);
+ Assert.Null(a0.TargetTable);
+ Assert.Equal(0, a0.TargetId);
+ Assert.Null(a0.FieldName);
+ Assert.Null(a0.OldValue);
+ Assert.Equal(0m, a0.NewValue);
+ Assert.Null(a0.Reason);
+ Assert.Null(a0.OperatorIp);
+ Assert.Equal(default(DateTime), a0.CreatedAt);
+
+ var a = new ProductionAdjustment
+ {
+ Id = 1,
+ TargetTable = "cnc_daily_production",
+ TargetId = 2,
+ FieldName = "TotalQuantity",
+ OldValue = 10.0m,
+ NewValue = 12.5m,
+ Reason = "Adjustment",
+ OperatorIp = "127.0.0.1",
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(1, a.Id);
+ Assert.Equal("cnc_daily_production", a.TargetTable);
+ Assert.Equal(2, a.TargetId);
+ Assert.Equal("TotalQuantity", a.FieldName);
+ Assert.Equal(10.0m, a.OldValue);
+ Assert.Equal(12.5m, a.NewValue);
+ Assert.Equal("Adjustment", a.Reason);
+ Assert.Equal("127.0.0.1", a.OperatorIp);
+ Assert.Equal(new DateTime(2026, 4, 28), a.CreatedAt);
+ }
+
+ // Alert
+ [Fact]
+ public void Alert_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var al0 = new Alert();
+ Assert.Equal(0L, al0.Id);
+ Assert.Null(al0.AlertType);
+ Assert.Null(al0.Title);
+ Assert.Null(al0.Detail);
+ Assert.Equal(0, al0.IsResolved);
+ Assert.Equal(default(DateTime), al0.CreatedAt);
+ Assert.Null(al0.ResolvedAt);
+
+ var al = new Alert
+ {
+ Id = 1,
+ AlertType = "Error",
+ MachineId = 5,
+ CollectAddressId = 6,
+ Title = "Alarm",
+ Detail = "Detail",
+ IsResolved = 1,
+ ResolvedAt = new DateTime(2026, 4, 28),
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(1, al.Id);
+ Assert.Equal("Error", al.AlertType);
+ Assert.Equal(5, al.MachineId);
+ Assert.Equal(6, al.CollectAddressId);
+ Assert.Equal("Alarm", al.Title);
+ Assert.Equal("Detail", al.Detail);
+ Assert.Equal(1, al.IsResolved);
+ Assert.Equal(new DateTime(2026, 4, 28), al.ResolvedAt);
+ Assert.Equal(new DateTime(2026, 4, 28), al.CreatedAt);
+ }
+
+ // SysConfig
+ [Fact]
+ public void SysConfig_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var s0 = new SysConfig();
+ Assert.Equal(0, s0.Id);
+ Assert.Null(s0.ConfigKey);
+ Assert.Null(s0.ConfigValue);
+ Assert.Null(s0.ValueType);
+ Assert.Null(s0.Description);
+ Assert.Equal(default(DateTime), s0.UpdatedAt);
+
+ var s = new SysConfig
+ {
+ Id = 1,
+ ConfigKey = "MaxThreads",
+ ConfigValue = "4",
+ ValueType = "int",
+ Description = "最大线程数",
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(1, s.Id);
+ Assert.Equal("MaxThreads", s.ConfigKey);
+ Assert.Equal("4", s.ConfigValue);
+ Assert.Equal("int", s.ValueType);
+ Assert.Equal("最大线程数", s.Description);
+ Assert.Equal(new DateTime(2026, 4, 29), s.UpdatedAt);
+ }
+
+ // ScreenConfig
+ [Fact]
+ public void ScreenConfig_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var sc0 = new ScreenConfig();
+ Assert.Equal(0, sc0.Id);
+ Assert.Null(sc0.CardKey);
+ Assert.Null(sc0.ChartConfig);
+ Assert.Equal(default(DateTime), sc0.CreatedAt);
+ Assert.Equal(default(DateTime), sc0.UpdatedAt);
+
+ var sc = new ScreenConfig
+ {
+ Id = 2,
+ CardKey = "card-1",
+ CardType = "typeA",
+ Title = "Title",
+ Metric = "metric",
+ Dimension = "dim",
+ SortOrder = 1,
+ IsEnabled = 1,
+ ChartConfig = "{...}",
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(2, sc.Id);
+ Assert.Equal("card-1", sc.CardKey);
+ Assert.Equal("typeA", sc.CardType);
+ Assert.Equal("Title", sc.Title);
+ Assert.Equal("metric", sc.Metric);
+ Assert.Equal("dim", sc.Dimension);
+ Assert.Equal(1, sc.SortOrder);
+ Assert.Equal(1, sc.IsEnabled);
+ Assert.Equal("{...}", sc.ChartConfig);
+ Assert.Equal(new DateTime(2026, 4, 28), sc.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), sc.UpdatedAt);
+ }
+
+ // ScreenFilter
+ [Fact]
+ public void ScreenFilter_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var sf0 = new ScreenFilter();
+ Assert.Equal(0, sf0.Id);
+ Assert.Null(sf0.ScreenKey);
+ Assert.Null(sf0.FilterType);
+ Assert.Null(sf0.FilterValue);
+ Assert.Equal(0, sf0.IsDefault);
+ Assert.Equal(0, sf0.SortOrder);
+
+ var sf = new ScreenFilter
+ {
+ Id = 3,
+ ScreenKey = "key1",
+ FilterType = "type",
+ FilterValue = "value",
+ IsDefault = 1,
+ SortOrder = 2
+ };
+ Assert.Equal(3, sf.Id);
+ Assert.Equal("key1", sf.ScreenKey);
+ Assert.Equal("type", sf.FilterType);
+ Assert.Equal("value", sf.FilterValue);
+ Assert.Equal(1, sf.IsDefault);
+ Assert.Equal(2, sf.SortOrder);
+ }
+
+ // CollectRaw
+ [Fact]
+ public void CollectRaw_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var cr0 = new CollectRaw();
+ Assert.Equal(0L, cr0.Id);
+ Assert.Equal(0, cr0.CollectAddressId);
+ Assert.Equal(default(DateTime), cr0.RequestTime);
+ Assert.Equal(default(DateTime), cr0.ResponseTime);
+ Assert.Null(cr0.ResponseDuration);
+ Assert.Equal(0, cr0.IsSuccess);
+ Assert.Null(cr0.StatusCode);
+ Assert.Null(cr0.RawJson);
+ Assert.Null(cr0.ErrorMessage);
+ Assert.Equal(default(DateTime), cr0.CreatedAt);
+
+ var cr = new CollectRaw
+ {
+ Id = 1,
+ CollectAddressId = 2,
+ RequestTime = new DateTime(2026, 4, 28, 10, 0, 0),
+ ResponseTime = new DateTime(2026, 4, 28, 10, 0, 1),
+ ResponseDuration = 100,
+ IsSuccess = 1,
+ StatusCode = 200,
+ RawJson = "{\"a\":1}",
+ ErrorMessage = null,
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(1, cr.Id);
+ Assert.Equal(2, cr.CollectAddressId);
+ Assert.Equal(new DateTime(2026, 4, 28, 10, 0, 0), cr.RequestTime);
+ Assert.Equal(new DateTime(2026, 4, 28, 10, 0, 1), cr.ResponseTime);
+ Assert.Equal(100, cr.ResponseDuration);
+ Assert.Equal(1, cr.IsSuccess);
+ Assert.Equal(200, cr.StatusCode);
+ Assert.Equal("{\"a\":1}", cr.RawJson);
+ Assert.Null(cr.ErrorMessage);
+ Assert.Equal(new DateTime(2026, 4, 28), cr.CreatedAt);
+ }
+
+ // SystemLog
+ [Fact]
+ public void SystemLog_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var sl0 = new SystemLog();
+ Assert.Equal(0L, sl0.Id);
+ Assert.Null(sl0.LogLevel);
+ Assert.Null(sl0.Source);
+ Assert.Null(sl0.Message);
+ Assert.Null(sl0.StackTrace);
+ Assert.Null(sl0.ExtraData);
+ Assert.Equal(default(DateTime), sl0.CreatedAt);
+
+ var sl = new SystemLog
+ {
+ Id = 9,
+ LogLevel = "Info",
+ Source = "SourceA",
+ Message = "msg",
+ StackTrace = "trace",
+ ExtraData = "{}",
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(9, sl.Id);
+ Assert.Equal("Info", sl.LogLevel);
+ Assert.Equal("SourceA", sl.Source);
+ Assert.Equal("msg", sl.Message);
+ Assert.Equal("trace", sl.StackTrace);
+ Assert.Equal("{}", sl.ExtraData);
+ Assert.Equal(new DateTime(2026, 4, 28), sl.CreatedAt);
+ }
+
+ // CollectorHeartbeat
+ [Fact]
+ public void CollectorHeartbeat_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var cb0 = new CollectorHeartbeat();
+ Assert.Equal(0L, cb0.Id);
+ Assert.Null(cb0.ServiceId);
+ Assert.Null(cb0.Status);
+ Assert.Null(cb0.Detail);
+ Assert.Null(cb0.LastCollectTime);
+ Assert.Equal(0, cb0.SuccessCount);
+ Assert.Equal(0, cb0.FailCount);
+ Assert.Null(cb0.UptimeSeconds);
+ Assert.Equal(default(DateTime), cb0.CreatedAt);
+
+ var cb = new CollectorHeartbeat
+ {
+ Id = 11,
+ ServiceId = "svc-1",
+ Status = "Healthy",
+ CollectAddressId = 4,
+ LastCollectTime = new DateTime(2026, 4, 28, 12, 0, 0),
+ SuccessCount = 10,
+ FailCount = 1,
+ UptimeSeconds = 3600,
+ Detail = "{}",
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(11, cb.Id);
+ Assert.Equal("svc-1", cb.ServiceId);
+ Assert.Equal("Healthy", cb.Status);
+ Assert.Equal(4, cb.CollectAddressId);
+ Assert.Equal(new DateTime(2026, 4, 28, 12, 0, 0), cb.LastCollectTime);
+ Assert.Equal(10, cb.SuccessCount);
+ Assert.Equal(1, cb.FailCount);
+ Assert.Equal(3600, cb.UptimeSeconds);
+ Assert.Equal("{}", cb.Detail);
+ Assert.Equal(new DateTime(2026, 4, 28), cb.CreatedAt);
+ }
+
+ // Machine
+ [Fact]
+ public void Machine_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var m0 = new Machine();
+ Assert.Equal(0, m0.Id);
+ Assert.Null(m0.DeviceCode);
+ Assert.Null(m0.Name);
+ Assert.Equal(0, m0.WorkshopId);
+ Assert.Equal(0, m0.CollectAddressId);
+ Assert.Null(m0.IpAddress);
+ Assert.Equal(0, m0.BrandId);
+ Assert.Equal(0, m0.IsEnabled);
+ Assert.Equal(0, m0.IsOnline);
+ Assert.Null(m0.LastPingTime);
+ Assert.Null(m0.LastCollectTime);
+ Assert.Null(m0.LastDeviceStatus);
+ Assert.Null(m0.LastRunStatus);
+ Assert.Null(m0.LastProgramName);
+ Assert.Null(m0.LastPartCount);
+ Assert.Null(m0.LastOperateMode);
+ Assert.Null(m0.LastMachiningStatus);
+ Assert.Equal(default(DateTime), m0.CreatedAt);
+ Assert.Equal(default(DateTime), m0.UpdatedAt);
+
+ var m = new Machine
+ {
+ Id = 10,
+ DeviceCode = "M-10",
+ Name = "Machine10",
+ WorkshopId = 1,
+ CollectAddressId = 2,
+ IpAddress = "192.168.0.10",
+ BrandId = 3,
+ IsEnabled = 1,
+ IsOnline = 1,
+ LastPingTime = new DateTime(2026, 4, 28, 12, 0, 0),
+ LastCollectTime = new DateTime(2026, 4, 28, 12, 5, 0),
+ LastDeviceStatus = "OK",
+ LastRunStatus = "Running",
+ LastProgramName = "Prog1",
+ LastPartCount = 42.0m,
+ LastOperateMode = "Auto",
+ LastMachiningStatus = "Active",
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(10, m.Id);
+ Assert.Equal("M-10", m.DeviceCode);
+ Assert.Equal("Machine10", m.Name);
+ Assert.Equal(1, m.WorkshopId);
+ Assert.Equal(2, m.CollectAddressId);
+ Assert.Equal("192.168.0.10", m.IpAddress);
+ Assert.Equal(3, m.BrandId);
+ Assert.Equal(1, m.IsEnabled);
+ Assert.Equal(1, m.IsOnline);
+ Assert.Equal(new DateTime(2026, 4, 28, 12, 0, 0), m.LastPingTime);
+ Assert.Equal(new DateTime(2026, 4, 28, 12, 5, 0), m.LastCollectTime);
+ Assert.Equal("OK", m.LastDeviceStatus);
+ Assert.Equal("Running", m.LastRunStatus);
+ Assert.Equal("Prog1", m.LastProgramName);
+ Assert.Equal(42.0m, m.LastPartCount);
+ Assert.Equal("Auto", m.LastOperateMode);
+ Assert.Equal("Active", m.LastMachiningStatus);
+ Assert.Equal(new DateTime(2026, 4, 28), m.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), m.UpdatedAt);
+ }
+
+ // Worker
+ [Fact]
+ public void Worker_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var w0 = new Worker();
+ Assert.Equal(0, w0.Id);
+ Assert.Null(w0.Name);
+ Assert.Null(w0.Code);
+ Assert.Equal(0, w0.IsEnabled);
+ Assert.Equal(default(DateTime), w0.CreatedAt);
+ Assert.Equal(default(DateTime), w0.UpdatedAt);
+
+ var w = new Worker
+ {
+ Id = 2,
+ Name = "张三",
+ Code = "W-2",
+ IsEnabled = 1,
+ CreatedAt = new DateTime(2026, 4, 28),
+ UpdatedAt = new DateTime(2026, 4, 29)
+ };
+ Assert.Equal(2, w.Id);
+ Assert.Equal("张三", w.Name);
+ Assert.Equal("W-2", w.Code);
+ Assert.Equal(1, w.IsEnabled);
+ Assert.Equal(new DateTime(2026, 4, 28), w.CreatedAt);
+ Assert.Equal(new DateTime(2026, 4, 29), w.UpdatedAt);
+ }
+
+ // WorkerMachine
+ [Fact]
+ public void WorkerMachine_DefaultsAndAssignments_ReturnsCorrectValues()
+ {
+ var wm0 = new WorkerMachine();
+ Assert.Equal(0, wm0.Id);
+ Assert.Equal(0, wm0.WorkerId);
+ Assert.Equal(0, wm0.MachineId);
+ Assert.Equal(default(DateTime), wm0.CreatedAt);
+
+ var wm = new WorkerMachine
+ {
+ Id = 1,
+ WorkerId = 2,
+ MachineId = 3,
+ CreatedAt = new DateTime(2026, 4, 28)
+ };
+ Assert.Equal(1, wm.Id);
+ Assert.Equal(2, wm.WorkerId);
+ Assert.Equal(3, wm.MachineId);
+ Assert.Equal(new DateTime(2026, 4, 28), wm.CreatedAt);
+ }
+ }
+}
diff --git a/tests/CncModels.Tests/EnumTests.cs b/tests/CncModels.Tests/EnumTests.cs
new file mode 100644
index 0000000..d854c66
--- /dev/null
+++ b/tests/CncModels.Tests/EnumTests.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Reflection;
+using System.Text;
+using Xunit;
+using CncModels.Enum;
+
+namespace CncModels.Tests
+{
+ ///
+ /// 测试所有 CncModels.Enum 下的字符串常量,确保值与命名约定一致。
+ /// 约定:字段名转换为 snake_case 小写字符串后应当与常量值一致。
+ /// 例如 AlertType.CollectFail 应为 "collect_fail"。
+ ///
+ public class EnumTests
+ {
+ ///
+ /// 遍历除 LogLevel 外的所有枚举类,验证 snake_case 命名约定
+ /// 覆盖场景:AlertType/CollectStatus/DataStatus/CardType/DataType/MatchBy/SegmentCloseReason/CollectorServiceStatus/ValueType
+ ///
+ [Fact]
+ public void SnakeCaseEnums_ShouldMatchFieldNames()
+ {
+ Type[] snakeCaseEnums = new Type[]
+ {
+ typeof(AlertType),
+ typeof(CollectStatus),
+ typeof(DataStatus),
+ typeof(CardType),
+ typeof(DataType),
+ typeof(MatchBy),
+ typeof(SegmentCloseReason),
+ typeof(CollectorServiceStatus),
+ typeof(CncModels.Enum.ValueType)
+ };
+
+ foreach (var t in snakeCaseEnums)
+ {
+ var fields = t.GetFields(BindingFlags.Public | BindingFlags.Static);
+ Assert.True(fields.Length > 0, $"类型 {t.Name} 不包含公共静态字段。");
+ foreach (var f in fields)
+ {
+ if (f.FieldType != typeof(string)) continue;
+ string value = (string)f.GetValue(null);
+ string expected = ToSnakeCase(f.Name);
+ Assert.Equal(expected, value);
+ }
+ }
+ }
+
+ ///
+ /// 验证 LogLevel 枚举值为大写(数据库中 log_level 存储为大写)
+ /// 覆盖场景:DEBUG/INFO/WARN/ERROR 全部值
+ ///
+ [Fact]
+ public void LogLevel_ShouldBeUppercase()
+ {
+ Assert.Equal("DEBUG", LogLevel.Debug);
+ Assert.Equal("INFO", LogLevel.Info);
+ Assert.Equal("WARN", LogLevel.Warn);
+ Assert.Equal("ERROR", LogLevel.Error);
+ }
+
+ private string ToSnakeCase(string input)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+ var sb = new StringBuilder();
+ for (int i = 0; i < input.Length; i++)
+ {
+ char c = input[i];
+ if (char.IsUpper(c))
+ {
+ if (i > 0) sb.Append('_');
+ sb.Append(char.ToLowerInvariant(c));
+ }
+ else
+ {
+ sb.Append(c);
+ }
+ }
+ return sb.ToString();
+ }
+ }
+}
diff --git a/tests/CncModels.Tests/PagedQueryTests.cs b/tests/CncModels.Tests/PagedQueryTests.cs
new file mode 100644
index 0000000..0ea54cb
--- /dev/null
+++ b/tests/CncModels.Tests/PagedQueryTests.cs
@@ -0,0 +1,103 @@
+using System;
+using Xunit;
+using CncModels.Dto;
+using CncModels.Dto.Login;
+using CncModels.Dto.Machine;
+
+namespace CncModels.Tests
+{
+ ///
+ /// PagedQuery 单元测试:覆盖默认值、偏移计算与自动修正逻辑
+ ///
+ public class PagedQueryTests
+ {
+ [Fact]
+ ///
+ /// 默认值正确:page=1, pageSize=20, offset=0
+ ///
+ public void Page_Defaults_Are_Correct()
+ {
+ // Arrange/Act
+ var q = new PagedQuery();
+
+ // Assert
+ Assert.Equal(1, q.Page);
+ Assert.Equal(20, q.PageSize);
+ Assert.Equal(0, q.Offset);
+ }
+
+ [Fact]
+ ///
+ /// 标准赋值:Page=3, PageSize=10 -> Offset=20
+ ///
+ public void Page_And_PageSize_Normal_Assignments()
+ {
+ // Arrange
+ var q = new PagedQuery
+ {
+ Page = 3,
+ PageSize = 10
+ };
+
+ // Assert
+ Assert.Equal(3, q.Page);
+ Assert.Equal(10, q.PageSize);
+ Assert.Equal(20, q.Offset);
+ }
+
+ [Fact]
+ ///
+ /// 边界:Page=100, PageSize=20
+ ///
+ public void Page_Boundary_Normal()
+ {
+ var q = new PagedQuery { Page = 100, PageSize = 20 };
+ Assert.Equal(100, q.Page);
+ Assert.Equal(20, q.PageSize);
+ Assert.Equal(1980, q.Offset);
+ }
+
+ [Fact]
+ ///
+ /// 自动修正:Page=0 -> 1
+ ///
+ public void Page_AutoFix_When_Zero()
+ {
+ var q = new PagedQuery { Page = 0 };
+ Assert.Equal(1, q.Page);
+ }
+
+ [Fact]
+ ///
+ /// 自动修正:Page=-5 -> 1
+ ///
+ public void Page_AutoFix_When_Negative()
+ {
+ var q = new PagedQuery { Page = -5 };
+ Assert.Equal(1, q.Page);
+ }
+
+ [Fact]
+ ///
+ /// 自动修正:PageSize=0 -> 20
+ ///
+ public void PageSize_AutoFix_When_Zero()
+ {
+ var q = new PagedQuery { PageSize = 0 };
+ Assert.Equal(20, q.PageSize);
+ }
+
+ [Fact]
+ ///
+ /// 自动修正:PageSize=101 -> 100
+ ///
+ public void PageSize_AutoFix_When_Over_Max()
+ {
+ var q1 = new PagedQuery { PageSize = 101 };
+ Assert.Equal(100, q1.PageSize);
+
+ var q2 = new PagedQuery { PageSize = 999 };
+ Assert.Equal(100, q2.PageSize);
+ }
+ }
+}
diff --git a/tests/CncModels.Tests/PagedResultTests.cs b/tests/CncModels.Tests/PagedResultTests.cs
new file mode 100644
index 0000000..15ca01a
--- /dev/null
+++ b/tests/CncModels.Tests/PagedResultTests.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+using CncModels.Dto;
+
+namespace CncModels.Tests
+{
+ ///
+ /// PagedResult 单元测试
+ ///
+ public class PagedResultTests
+ {
+ [Theory]
+ [InlineData(20, 20, 1)]
+ [InlineData(21, 20, 2)]
+ [InlineData(0, 10, 0)]
+ public void TotalPages_Calculation_Works(int total, int pageSize, int expectedPages)
+ {
+ // Arrange
+ var r = new PagedResult
+ {
+ Total = total,
+ PageSize = pageSize,
+ Page = 1
+ };
+
+ // Act & Assert
+ Assert.Equal(expectedPages, r.TotalPages);
+ }
+
+ [Fact]
+ ///
+ /// Items 属性默认应为空列表而非 null
+ ///
+ public void Items_Should_Be_Empty_List_By_Default()
+ {
+ // Arrange
+ var r = new PagedResult();
+
+ // Assert
+ Assert.NotNull(r.Items);
+ Assert.Empty(r.Items);
+ }
+
+ [Fact]
+ ///
+ /// 属性赋值正确
+ ///
+ public void Properties_Assigned_Correctly()
+ {
+ // Arrange
+ var items = new List { "a", "b" };
+ var r = new PagedResult
+ {
+ Items = items,
+ Total = 2,
+ Page = 1,
+ PageSize = 2
+ };
+
+ // Assert
+ Assert.Equal(items, r.Items);
+ Assert.Equal(2, r.Total);
+ Assert.Equal(1, r.Page);
+ Assert.Equal(2, r.PageSize);
+ Assert.Equal(1, r.TotalPages); // 2/2 = 1
+ }
+ }
+}