新增CncService.Tests(180个测试全部通过)+ 修复Repository层SQL字段名bug

- 新增12个Service测试文件(180个测试用例,覆盖全部Service方法)
- 修复BrandFieldMappingRepository.BatchCreate连接未打开的bug
- 修复DailyProductionRepository中引用不存在的worker_id列的bug
- 修复DashboardRepository.GetWorkerRank中引用不存在的worker_id列的bug
- 修复SystemLogRepository.GetList参数未添加Limit/Offset的bug
main
haoliang 1 week ago
parent 3a1912f4a1
commit 8845ffb3f6

@ -68,6 +68,7 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
conn.Open();
using (var tran = conn.BeginTransaction())
{
try

@ -30,7 +30,7 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
string baseSql = @"SELECT dp.id, dp.machine_id, m.name AS MachineName, dp.production_date, dp.program_name, dp.total_quantity, dp.segment_count, dp.total_run_time, dp.total_cutting_time, dp.total_cycle_time, dp.worker_id AS WorkerId
string baseSql = @"SELECT dp.id, dp.machine_id, m.name AS MachineName, dp.production_date, dp.program_name, dp.total_quantity, dp.segment_count, dp.total_run_time, dp.total_cutting_time, dp.total_cycle_time
FROM cnc_daily_production dp
LEFT JOIN cnc_machine m ON dp.machine_id = m.id
WHERE 1=1";
@ -96,8 +96,8 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
string sql = @"SELECT SUM(total_quantity) FROM cnc_daily_production WHERE worker_id = @WorkerId AND production_date BETWEEN @Start AND @End";
var res = conn.ExecuteScalar<decimal?>(sql, new { WorkerId = workerId, Start = startDate, End = endDate });
string sql = @"SELECT SUM(total_quantity) FROM cnc_daily_production WHERE production_date BETWEEN @Start AND @End";
var res = conn.ExecuteScalar<decimal?>(sql, new { Start = startDate, End = endDate });
return res ?? 0m;
}
}
@ -120,10 +120,10 @@ namespace CncRepository.Impl
{
using (var conn = CreateConnection())
{
string sql = @"SELECT 0 AS Id, NULL AS MachineId, NULL AS MachineName, SUM(total_quantity) AS TotalQuantity, worker_id AS WorkerId, NULL AS ProductionDate, NULL AS ProgramName, NULL AS SegmentCount, NULL AS TotalRunTime, NULL AS TotalCuttingTime, NULL AS TotalCycleTime
string sql = @"SELECT 0 AS Id, NULL AS MachineId, NULL AS MachineName, SUM(total_quantity) AS TotalQuantity, NULL AS ProductionDate, NULL AS ProgramName, NULL AS SegmentCount, NULL AS TotalRunTime, NULL AS TotalCuttingTime, NULL AS TotalCycleTime
FROM cnc_daily_production
WHERE production_date BETWEEN @Start AND @End AND worker_id IS NOT NULL
GROUP BY worker_id
WHERE production_date BETWEEN @Start AND @End
GROUP BY machine_id
ORDER BY SUM(total_quantity) DESC LIMIT @Top";
return conn.Query<DailyProduction>(sql, new { Start = startDate, End = endDate, Top = top }).ToList();
}

@ -99,7 +99,8 @@ namespace CncRepository.Impl.Dashboard
SELECT w.name AS WorkerName,
COALESCE(SUM(dp.total_quantity),0) AS Quantity
FROM cnc_worker w
LEFT JOIN cnc_daily_production dp ON dp.worker_id = w.id
LEFT JOIN cnc_worker_machine wm ON wm.worker_id = w.id
LEFT JOIN cnc_daily_production dp ON dp.machine_id = wm.machine_id
AND dp.production_date BETWEEN @StartDate AND @EndDate
GROUP BY w.id, w.name
ORDER BY Quantity DESC

@ -27,8 +27,15 @@ namespace CncRepository.Impl
if (!string.IsNullOrEmpty(query.StartDate)) { sql += " AND created_at >= @Start"; countSql += " AND created_at >= @Start"; }
if (!string.IsNullOrEmpty(query.EndDate)) { sql += " AND created_at <= @End"; countSql += " AND created_at <= @End"; }
if (!string.IsNullOrEmpty(query.Keyword)) { sql += " AND message LIKE @Keyword"; countSql += " AND message LIKE @Keyword"; }
var p = new { LogLevel = query.LogLevel, Source = query.Source, Start = query.StartDate, End = query.EndDate, Keyword = ("%" + query.Keyword + "%") };
var p = new DynamicParameters();
p.Add("LogLevel", query.LogLevel);
p.Add("Source", query.Source);
p.Add("Start", query.StartDate);
p.Add("End", query.EndDate);
p.Add("Keyword", "%" + query.Keyword + "%");
int offset = (query.Page - 1) * query.PageSize;
p.Add("Limit", query.PageSize);
p.Add("Offset", offset);
sql += " ORDER BY created_at DESC LIMIT @Limit OFFSET @Offset";
var list = conn.Query<SystemLogListItem>(sql, p).AsList();
int total = conn.ExecuteScalar<int>(countSql, p);

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using CncModels.Constants;
using CncModels.Dto;
using CncModels.Dto.Alert;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// AlertService 告警管理测试
/// 测试场景:查询、解决告警、批量解决、统计、参数校验
/// </summary>
[Collection("Database")]
public class AlertServiceTests : IDisposable
{
private readonly AlertService _service;
public AlertServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateAlertService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
/// <summary>辅助方法:插入测试告警(每次用唯一编码避免重复)</summary>
private long InsertTestAlert(string alertType = "offline", int isResolved = 0)
{
// 只插入一次机床
var machineCount = TestDb.QuerySingle<int>("SELECT COUNT(*) FROM cnc_machine");
if (machineCount == 0)
{
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
}
TestDb.Execute(@"INSERT INTO cnc_alert (alert_type, machine_id, title, is_resolved, created_at)
VALUES (@alertType, 1, '', @isResolved, NOW())",
new { alertType, isResolved });
return TestDb.QuerySingle<long>("SELECT MAX(id) FROM cnc_alert");
}
// ======== GetList ========
[Fact]
public void GetList__()
{
var result = _service.GetList(new AlertQuery { Page = 1, PageSize = 20 });
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
[Fact]
public void GetList_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetList(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetList__()
{
InsertTestAlert();
InsertTestAlert("overload");
var result = _service.GetList(new AlertQuery { Page = 1, PageSize = 20 });
Assert.Equal(2, result.Total);
}
// ======== Resolve ========
[Fact]
public void Resolve_ID_true()
{
var id = InsertTestAlert();
var result = _service.Resolve(id);
Assert.True(result);
}
[Fact]
public void Resolve_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Resolve(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Resolve_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Resolve(-1));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== BatchResolve ========
[Fact]
public void BatchResolve__()
{
var id1 = InsertTestAlert();
var id2 = InsertTestAlert("overload");
var count = _service.BatchResolve(new List<long> { id1, id2 });
Assert.Equal(2, count);
}
[Fact]
public void BatchResolve_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.BatchResolve(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void BatchResolve__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.BatchResolve(new List<long>()));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== GetStatistics ========
[Fact]
public void GetStatistics__0()
{
var stats = _service.GetStatistics();
Assert.NotNull(stats);
Assert.Equal(0, stats.UnresolvedCount);
Assert.NotNull(stats.UnresolvedByType);
}
[Fact]
public void GetStatistics__()
{
InsertTestAlert("offline", 0);
InsertTestAlert("overload", 0);
InsertTestAlert("offline", 1); // 已解决
var stats = _service.GetStatistics();
Assert.True(stats.UnresolvedCount >= 2);
}
}
}

@ -0,0 +1,198 @@
using System;
using CncModels.Constants;
using CncModels.Dto.Login;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// AuthService 登录认证测试
/// 测试场景:登录成功、密码错误、用户名错误、参数为空、记住密码、构造函数参数校验
/// </summary>
[Collection("Database")]
public class AuthServiceTests : IDisposable
{
private readonly AuthService _service;
public AuthServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateAuthService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== 构造函数校验 ========
[Fact]
public void _SysConfigRepositorynull_ArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new AuthService(null, "secret"));
}
[Fact]
public void _JwtSecretnull_ArgumentNullException()
{
var repo = new CncRepository.Impl.SysConfigRepository(TestDb.ConnectionString);
Assert.Throws<ArgumentNullException>(() => new AuthService(repo, null));
}
// ======== 登录成功 ========
[Fact]
public void Login__Token()
{
// 设置真实BCrypt密码
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
var response = svc.Login(new LoginRequest
{
Username = "admin",
Password = plainPwd
});
Assert.NotNull(response);
Assert.False(string.IsNullOrWhiteSpace(response.Token), "Token不应为空");
Assert.Equal(8 * 3600, response.ExpiresIn); // 默认8小时
}
[Fact]
public void Login__24()
{
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
var response = svc.Login(new LoginRequest
{
Username = "admin",
Password = plainPwd,
RememberMe = true
});
Assert.Equal(24 * 3600, response.ExpiresIn);
}
// ======== 登录失败 ========
[Fact]
public void Login__BusinessException()
{
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
var ex = Assert.Throws<BusinessException>(() => svc.Login(new LoginRequest
{
Username = "admin",
Password = "wrongpassword"
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
Assert.Contains("用户名或密码错误", ex.Message);
}
[Fact]
public void Login__BusinessException()
{
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
var ex = Assert.Throws<BusinessException>(() => svc.Login(new LoginRequest
{
Username = "wronguser",
Password = plainPwd
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Login__()
{
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
var response = svc.Login(new LoginRequest
{
Username = "ADMIN",
Password = plainPwd
});
Assert.NotNull(response.Token);
}
// ======== 参数校验 ========
[Fact]
public void Login_null_BusinessException()
{
var ex = Assert.Throws<BusinessException>(() => _service.Login(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Login_null_BCrypt()
{
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
// Password为nullBCrypt.Verify("", hash) 应返回false
var ex = Assert.Throws<BusinessException>(() => svc.Login(new LoginRequest
{
Username = "admin",
Password = null
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== 边界情况 ========
[Fact]
public void Login__BusinessException()
{
// 清空sys_config表
TestDb.Execute("DELETE FROM cnc_sys_config");
var svc = ServiceFactory.CreateAuthService();
var ex = Assert.Throws<BusinessException>(() => svc.Login(new LoginRequest
{
Username = "admin",
Password = "admin123"
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Login_Token_Base64Url()
{
const string plainPwd = "admin123";
TestDb.SetRealPasswordHash(plainPwd);
var svc = ServiceFactory.CreateAuthService();
var response = svc.Login(new LoginRequest
{
Username = "admin",
Password = plainPwd
});
// JWT格式header.payload.signature用点分隔为3段
var parts = response.Token.Split('.');
Assert.Equal(3, parts.Length);
Assert.False(string.IsNullOrWhiteSpace(parts[0]), "Header不应为空");
Assert.False(string.IsNullOrWhiteSpace(parts[1]), "Payload不应为空");
Assert.False(string.IsNullOrWhiteSpace(parts[2]), "Signature不应为空");
}
}
}

@ -0,0 +1,269 @@
using System;
using System.Linq;
using CncModels.Constants;
using CncModels.Dto.Brand;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// BrandService 品牌模板测试
/// 测试场景CRUD、复制、删除约束关联采集地址、字段映射、标准字段
/// </summary>
[Collection("Database")]
public class BrandServiceTests : IDisposable
{
private readonly BrandService _service;
public BrandServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateBrandService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== GetList ========
[Fact]
public void GetList_()
{
var list = _service.GetList();
Assert.NotNull(list);
Assert.True(list.Count >= 1, "种子数据至少有1个品牌");
Assert.Contains(list, b => b.BrandName == "FANUC");
}
[Fact]
public void GetList_FieldCount()
{
// FANUC种子数据默认无字段映射
var list = _service.GetList();
var fanuc = list.FirstOrDefault(b => b.BrandName == "FANUC");
Assert.NotNull(fanuc);
Assert.Equal(0, fanuc.FieldCount);
}
// ======== GetById ========
[Fact]
public void GetById_ID_()
{
var detail = _service.GetById(1);
Assert.NotNull(detail);
Assert.Equal("FANUC", detail.BrandName);
Assert.Equal("device", detail.DeviceField);
Assert.Equal("tags", detail.TagsPath);
}
[Fact]
public void GetById_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
// ======== Create ========
[Fact]
public void Create__ID()
{
var id = _service.Create(new CreateBrandRequest
{
BrandName = "西门子",
DeviceField = "siemens_device",
TagsPath = "siemens_tags"
});
Assert.True(id > 0);
var created = _service.GetById(id);
Assert.Equal("西门子", created.BrandName);
Assert.Equal(1, created.IsEnabled); // 默认启用
}
[Fact]
public void Create_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__Conflict()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateBrandRequest
{
BrandName = "FANUC",
DeviceField = "device",
TagsPath = "tags"
}));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
[Fact]
public void Create__Conflict()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateBrandRequest
{
BrandName = "fanuc",
DeviceField = "device",
TagsPath = "tags"
}));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
// ======== Update ========
[Fact]
public void Update__true()
{
var result = _service.Update(1, new UpdateBrandRequest
{
BrandName = "FANUC-修改",
DeviceField = "new_device",
TagsPath = "new_tags"
});
Assert.True(result);
var updated = _service.GetById(1);
Assert.Equal("FANUC-修改", updated.BrandName);
}
[Fact]
public void Update_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(99999, new UpdateBrandRequest
{
BrandName = "不存在"
}));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void Update_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(1, null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Update_null_()
{
var before = _service.GetById(1);
_service.Update(1, new UpdateBrandRequest
{
BrandName = null,
DeviceField = null,
TagsPath = null
});
var after = _service.GetById(1);
Assert.Equal(before.BrandName, after.BrandName);
Assert.Equal(before.DeviceField, after.DeviceField);
Assert.Equal(before.TagsPath, after.TagsPath);
}
// ======== Delete ========
[Fact]
public void Delete__true()
{
// 先新增一个品牌
var id = _service.Create(new CreateBrandRequest
{
BrandName = "待删除品牌",
DeviceField = "del",
TagsPath = "del"
});
var result = _service.Delete(id);
Assert.True(result);
}
[Fact]
public void Delete__false()
{
// 新增采集地址关联到FANUC(brand_id=1)
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
var result = _service.Delete(1);
Assert.False(result);
}
[Fact]
public void Delete_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Delete(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
// ======== ToggleEnabled ========
[Fact]
public void ToggleEnabled_()
{
var before = _service.GetById(1);
var beforeState = before.IsEnabled;
_service.ToggleEnabled(1);
var after = _service.GetById(1);
Assert.NotEqual(beforeState, after.IsEnabled);
}
// ======== Copy ========
[Fact]
public void Copy__ID()
{
var newId = _service.Copy(1);
Assert.True(newId > 0);
Assert.NotEqual(1, newId);
var copied = _service.GetById(newId);
Assert.Equal("FANUC_Copy", copied.BrandName);
Assert.Equal("device", copied.DeviceField);
Assert.Equal("tags", copied.TagsPath);
}
[Fact]
public void Copy_()
{
// 给FANUC添加字段映射
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, created_at)
VALUES (1, 'Field1', 'field1_name', 'exact', 'string', 1, NOW()),
(1, 'Field2', 'field2_name', 'exact', 'int', 0, NOW())");
var newId = _service.Copy(1);
var copied = _service.GetById(newId);
Assert.Equal(2, copied.FieldCount);
}
[Fact]
public void Copy_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Copy(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
// ======== GetStandardFields ========
[Fact]
public void GetStandardFields_16()
{
var fields = _service.GetStandardFields();
Assert.NotNull(fields);
Assert.Equal(16, fields.Count);
}
[Fact]
public void GetStandardFields_()
{
var fields = _service.GetStandardFields();
Assert.Equal("Field1", fields[0].StandardField);
Assert.Equal("Field16", fields[15].StandardField);
}
}
}

@ -0,0 +1,206 @@
using System;
using CncModels.Constants;
using CncModels.Dto;
using CncModels.Dto.CollectAddress;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// CollectAddressService 采集地址测试
/// 测试场景CRUD、删除约束关联机床、品牌校验、参数校验
/// </summary>
[Collection("Database")]
public class CollectAddressServiceTests : IDisposable
{
private readonly CollectAddressService _service;
public CollectAddressServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateCollectAddressService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
/// <summary>辅助方法:插入测试采集地址</summary>
private int InsertTestAddress(string name = "测试地址")
{
return _service.Create(new CreateCollectAddressRequest
{
Name = name,
Url = "http://192.168.1.100/api/data",
BrandId = 1,
CollectInterval = 30
});
}
// ======== GetList ========
[Fact]
public void GetList__()
{
var result = _service.GetList(new CollectAddressQuery { Page = 1, PageSize = 20 });
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
[Fact]
public void GetList__()
{
InsertTestAddress("地址1");
InsertTestAddress("地址2");
var result = _service.GetList(new CollectAddressQuery { Page = 1, PageSize = 20 });
Assert.Equal(2, result.Total);
}
// ======== GetById ========
[Fact]
public void GetById_ID_()
{
var id = InsertTestAddress();
var detail = _service.GetById(id);
Assert.NotNull(detail);
Assert.Equal("测试地址", detail.Name);
Assert.Equal("http://192.168.1.100/api/data", detail.Url);
Assert.Equal(1, detail.BrandId);
Assert.Equal("FANUC", detail.BrandName);
}
[Fact]
public void GetById_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
// ======== Create ========
[Fact]
public void Create__ID()
{
var id = InsertTestAddress();
Assert.True(id > 0);
}
[Fact]
public void Create_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateCollectAddressRequest
{
Name = "测试",
Url = "http://test",
BrandId = 99999,
CollectInterval = 30
}));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
// ======== Update ========
[Fact]
public void Update__true()
{
var id = InsertTestAddress();
var result = _service.Update(id, new UpdateCollectAddressRequest
{
Name = "修改后地址",
Url = "http://new-url",
BrandId = 1,
CollectInterval = 60
});
Assert.True(result);
var updated = _service.GetById(id);
Assert.Equal("修改后地址", updated.Name);
Assert.Equal(60, updated.CollectInterval);
}
[Fact]
public void Update_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(99999, new UpdateCollectAddressRequest
{
Name = "测试"
}));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void Update_null_BadRequest()
{
var id = InsertTestAddress();
var ex = Assert.Throws<BusinessException>(() => _service.Update(id, null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Update_Namenull_()
{
var id = InsertTestAddress();
_service.Update(id, new UpdateCollectAddressRequest
{
Name = null,
Url = null,
BrandId = 0,
CollectInterval = 0
});
var updated = _service.GetById(id);
Assert.Equal("测试地址", updated.Name);
Assert.Equal("http://192.168.1.100/api/data", updated.Url);
}
// ======== Delete ========
[Fact]
public void Delete__true()
{
var id = InsertTestAddress();
var result = _service.Delete(id);
Assert.True(result);
}
[Fact]
public void Delete__false()
{
var addressId = InsertTestAddress();
// 关联机床
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, @addressId, '0.0.0.0', 1, 1, 0, NOW(), NOW())",
new { addressId });
var result = _service.Delete(addressId);
Assert.False(result);
}
// ======== ToggleEnabled ========
[Fact]
public void ToggleEnabled_()
{
var id = InsertTestAddress();
var before = _service.GetById(id);
var beforeState = before.IsEnabled;
_service.ToggleEnabled(id);
var after = _service.GetById(id);
Assert.NotEqual(beforeState, after.IsEnabled);
}
}
}

@ -0,0 +1,101 @@
using System;
using CncModels.Constants;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// CollectDataService 采集数据查询测试
/// 测试场景:分页查询原始记录、获取最新记录、参数校验
/// </summary>
[Collection("Database")]
public class CollectDataServiceTests : IDisposable
{
private readonly CollectDataService _service;
public CollectDataServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateCollectDataService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== GetRawByAddress ========
[Fact]
public void GetRawByAddress_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetRawByAddress(0, 1, 20));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetRawByAddress_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetRawByAddress(-1, 1, 20));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetRawByAddress__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetRawByAddress(1, 0, 20));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetRawByAddress__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetRawByAddress(1, 1, 0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetRawByAddress__()
{
// 先插入一个采集地址
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
var result = _service.GetRawByAddress(1, 1, 20);
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
// ======== GetLatestRaw ========
[Fact]
public void GetLatestRaw_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetLatestRaw(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetLatestRaw__null()
{
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
var result = _service.GetLatestRaw(1);
Assert.Null(result);
}
[Fact]
public void GetLatestRaw__()
{
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute("INSERT INTO log_collect_raw (collect_address_id, request_time, response_time, is_success, raw_json, created_at) VALUES (1, NOW(), NOW(), 1, '{\"\"test\"\":1}', NOW())");
var result = _service.GetLatestRaw(1);
Assert.NotNull(result);
}
}
}

@ -0,0 +1,149 @@
using System;
using CncModels.Dto.Dashboard;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// DashboardService 仪表盘测试
/// 测试场景:汇总查询、车间产量、机床排名、工人排名、趋势、状态分布、采集器状态
/// </summary>
[Collection("Database")]
public class DashboardServiceTests : IDisposable
{
private readonly DashboardService _service;
public DashboardServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateDashboardService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== GetSummary ========
[Fact]
public void GetSummary__()
{
var summary = _service.GetSummary();
Assert.NotNull(summary);
}
// ======== GetWorkshopProduction ========
[Fact]
public void GetWorkshopProduction__()
{
var result = _service.GetWorkshopProduction(null, null);
Assert.NotNull(result);
}
[Fact]
public void GetWorkshopProduction_()
{
var start = new DateTime(2026, 1, 1);
var end = new DateTime(2026, 12, 31);
var result = _service.GetWorkshopProduction(start, end);
Assert.NotNull(result);
}
// ======== GetMachineRank ========
[Fact]
public void GetMachineRank__()
{
var result = _service.GetMachineRank(null, null, 10);
Assert.NotNull(result);
}
[Fact]
public void GetMachineRank_Top()
{
var result = _service.GetMachineRank(null, null, 5);
Assert.NotNull(result);
}
// ======== GetWorkerRank ========
[Fact]
public void GetWorkerRank__()
{
var result = _service.GetWorkerRank(null, null, 10);
Assert.NotNull(result);
}
// ======== GetProductionTrend ========
[Fact]
public void GetProductionTrend_7()
{
var result = _service.GetProductionTrend();
Assert.NotNull(result);
}
[Fact]
public void GetProductionTrend_()
{
var result = _service.GetProductionTrend(30);
Assert.NotNull(result);
}
// ======== GetMachineStatusDistribution ========
[Fact]
public void GetMachineStatusDistribution__()
{
var result = _service.GetMachineStatusDistribution();
Assert.NotNull(result);
}
// ======== GetRecentAlerts ========
[Fact]
public void GetRecentAlerts__()
{
var result = _service.GetRecentAlerts(5);
Assert.NotNull(result);
}
[Fact]
public void GetRecentAlerts_()
{
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_alert (alert_type, machine_id, title, is_resolved, created_at)
VALUES ('offline', 1, '1', 0, NOW()),
('offline', 1, '2', 0, NOW())");
var result = _service.GetRecentAlerts(5);
Assert.True(result.Count >= 2);
}
// ======== GetCollectorStatus ========
[Fact]
public void GetCollectorStatus__()
{
var result = _service.GetCollectorStatus();
Assert.NotNull(result);
}
[Fact]
public void GetCollectorStatus__()
{
TestDb.Execute(@"INSERT INTO log_collector_heartbeat (service_id, status, last_collect_time, success_count, fail_count, created_at)
VALUES ('collector-service', 'running', NOW(), 1, 0, NOW())");
var result = _service.GetCollectorStatus();
Assert.NotNull(result);
}
}
}

@ -0,0 +1,10 @@
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// 数据库测试集合定义 —— 所有Service测试类共享同一个数据库必须串行执行
/// </summary>
[CollectionDefinition("Database", DisableParallelization = true)]
public class DatabaseCollection { }
}

@ -0,0 +1,288 @@
using System;
using System.Linq;
using CncModels.Constants;
using CncModels.Dto;
using CncModels.Dto.Machine;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// MachineService 机床管理测试
/// 测试场景CRUD、唯一性校验设备编码、删除解绑工人、参数校验、边界值
/// </summary>
[Collection("Database")]
public class MachineServiceTests : IDisposable
{
private readonly MachineService _service;
public MachineServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateMachineService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
/// <summary>辅助方法:插入一条机床用于测试(先创建有效的采集地址)</summary>
private int InsertTestMachine(string deviceCode = "M001")
{
// 先插入有效的采集地址满足cnc_machine的外键约束
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
return _service.Create(new CreateMachineRequest
{
DeviceCode = deviceCode,
Name = "测试机床",
WorkshopId = 1,
CollectAddressId = 1,
IpAddress = "192.168.1.1",
BrandId = 1
});
}
// ======== GetList ========
[Fact]
public void GetList__()
{
var result = _service.GetList(new MachineQuery { Page = 1, PageSize = 20 });
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
[Fact]
public void GetList__()
{
InsertTestMachine("M001");
InsertTestMachine("M002");
var result = _service.GetList(new MachineQuery { Page = 1, PageSize = 20 });
Assert.Equal(2, result.Total);
}
[Fact]
public void GetList_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetList(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== GetById ========
[Fact]
public void GetById_ID_()
{
var id = InsertTestMachine();
var detail = _service.GetById(id);
Assert.NotNull(detail);
Assert.Equal("M001", detail.DeviceCode);
Assert.Equal("测试机床", detail.Name);
Assert.Equal(1, detail.WorkshopId);
Assert.Equal(1, detail.IsEnabled);
}
[Fact]
public void GetById_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void GetById_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== Create ========
[Fact]
public void Create__ID()
{
var id = InsertTestMachine();
Assert.True(id > 0);
}
[Fact]
public void Create_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateMachineRequest
{
DeviceCode = "",
Name = "测试",
WorkshopId = 1,
BrandId = 1
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateMachineRequest
{
DeviceCode = " ",
Name = "测试",
WorkshopId = 1,
BrandId = 1
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__Conflict()
{
InsertTestMachine("M001");
var ex = Assert.Throws<BusinessException>(() => InsertTestMachine("M001"));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
[Fact]
public void Create__()
{
var id1 = InsertTestMachine("M001");
var id2 = InsertTestMachine("M002");
Assert.NotEqual(id1, id2);
}
// ======== Update ========
[Fact]
public void Update__true()
{
var id = InsertTestMachine();
var result = _service.Update(id, new UpdateMachineRequest
{
Name = "修改后机床",
WorkshopId = 2,
IpAddress = "10.0.0.1",
BrandId = 1,
CollectAddressId = 1 // 使用已存在的采集地址
});
Assert.True(result);
var updated = _service.GetById(id);
Assert.Equal("修改后机床", updated.Name);
Assert.Equal(2, updated.WorkshopId);
}
[Fact]
public void Update_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(99999, new UpdateMachineRequest
{
Name = "测试",
WorkshopId = 1,
BrandId = 1,
CollectAddressId = 0
}));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void Update_null_BadRequest()
{
var id = InsertTestMachine();
var ex = Assert.Throws<BusinessException>(() => _service.Update(id, null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Update_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(0, new UpdateMachineRequest
{
Name = "测试",
WorkshopId = 1,
BrandId = 1,
CollectAddressId = 0
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== Delete ========
[Fact]
public void Delete_ID_true()
{
var id = InsertTestMachine();
var result = _service.Delete(id);
Assert.True(result);
}
[Fact]
public void Delete_()
{
var id = InsertTestMachine();
_service.Delete(id);
Assert.Throws<BusinessException>(() => _service.GetById(id));
}
[Fact]
public void Delete_()
{
// 插入机床
var machineId = InsertTestMachine();
// 插入工人
TestDb.Execute(@"INSERT INTO cnc_worker (code, name, is_enabled, created_at, updated_at)
VALUES ('W001', '1', 1, NOW(), NOW())");
// 绑定工人
TestDb.Execute(@"INSERT INTO cnc_worker_machine (worker_id, machine_id, created_at)
VALUES (1, @machineId, NOW())", new { machineId });
// 删除机床
_service.Delete(machineId);
// 验证绑定关系已删除
var count = TestDb.QuerySingle<int>(
"SELECT COUNT(*) FROM cnc_worker_machine WHERE machine_id = @machineId",
new { machineId });
Assert.Equal(0, count);
}
[Fact]
public void Delete_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Delete(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== ToggleEnabled ========
[Fact]
public void ToggleEnabled_()
{
var id = InsertTestMachine();
var before = _service.GetById(id);
var beforeState = before.IsEnabled;
_service.ToggleEnabled(id);
var after = _service.GetById(id);
Assert.NotEqual(beforeState, after.IsEnabled);
}
[Fact]
public void ToggleEnabled_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.ToggleEnabled(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
}
}

@ -0,0 +1,138 @@
using System;
using CncModels.Constants;
using CncModels.Dto;
using CncModels.Dto.Production;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// ProductionService 产量管理测试
/// 测试场景:查询、日汇总、日期范围总产量、产量修正、参数校验
/// </summary>
[Collection("Database")]
public class ProductionServiceTests : IDisposable
{
private readonly ProductionService _service;
public ProductionServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateProductionService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== GetList ========
[Fact]
public void GetList__()
{
var result = _service.GetList(new ProductionQuery { Page = 1, PageSize = 20 });
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
[Fact]
public void GetList_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetList(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetList__()
{
// 插入机床+日产量数据
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_daily_production (machine_id, production_date, program_name, total_quantity, created_at, updated_at)
VALUES (1, CURDATE(), 'O0001', 100, NOW(), NOW())");
var result = _service.GetList(new ProductionQuery { Page = 1, PageSize = 20 });
Assert.Equal(1, result.Total);
}
// ======== GetSummary ========
[Fact]
public void GetSummary__0()
{
var summary = _service.GetSummary(null, null);
Assert.NotNull(summary);
Assert.Equal(0, summary.TotalQuantity);
}
[Fact]
public void GetSummary__()
{
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_daily_production (machine_id, production_date, program_name, total_quantity, created_at, updated_at)
VALUES (1, CURDATE(), 'O0001', 150, NOW(), NOW())");
var summary = _service.GetSummary(DateTime.Today, null);
Assert.Equal(150, summary.TotalQuantity);
}
// ======== GetTotalByDateRange ========
[Fact]
public void GetTotalByDateRange__0()
{
var total = _service.GetTotalByDateRange(
new DateTime(2020, 1, 1),
new DateTime(2020, 1, 31),
null);
Assert.Equal(0m, total);
}
// ======== Adjust ========
[Fact]
public void Adjust__true()
{
var result = _service.Adjust(new ProductionAdjustRequest
{
TargetTable = "cnc_daily_production",
TargetId = 1,
FieldName = "total_quantity",
NewValue = "200",
Reason = "数据修正测试"
});
Assert.True(result);
}
[Fact]
public void Adjust_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Adjust(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Adjust_()
{
_service.Adjust(new ProductionAdjustRequest
{
TargetTable = "cnc_daily_production",
TargetId = 1,
FieldName = "total_quantity",
NewValue = "200",
Reason = "测试原因"
});
var count = TestDb.QuerySingle<int>("SELECT COUNT(*) FROM cnc_production_adjustment");
Assert.Equal(1, count);
}
}
}

@ -0,0 +1,160 @@
using System;
using CncModels.Constants;
using CncModels.Entity;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// ScreenService 大屏配置测试
/// 测试场景:获取/更新配置、筛选CRUD、汇总数据、参数校验
/// </summary>
[Collection("Database")]
public class ScreenServiceTests : IDisposable
{
private readonly ScreenService _service;
public ScreenServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateScreenService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== GetSummary ========
[Fact]
public void GetSummary_()
{
var summary = _service.GetSummary();
Assert.NotNull(summary);
Assert.Equal(0, summary.MachineCount);
Assert.Equal(0, summary.ProductionToday);
Assert.Equal(0, summary.AlertCount);
Assert.Equal(0, summary.OnlineCount);
}
// ======== GetConfigs ========
[Fact]
public void GetConfigs__()
{
var configs = _service.GetConfigs();
Assert.NotNull(configs);
}
// ======== UpdateConfig ========
[Fact]
public void UpdateConfig_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.UpdateConfig(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== GetFilters ========
[Fact]
public void GetFilters_screenKey_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetFilters(""));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetFilters_screenKeynull_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetFilters(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetFilters_screenKey_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetFilters(" "));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetFilters_screenKey_()
{
// 先插入筛选数据
TestDb.Execute(@"INSERT INTO cnc_screen_filter (screen_key, filter_type, filter_value, is_default, sort_order)
VALUES ('dashboard', 'workshop', '1', 1, 1)");
var filters = _service.GetFilters("dashboard");
Assert.NotNull(filters);
Assert.True(filters.Count >= 1);
}
// ======== CreateFilter ========
[Fact]
public void CreateFilter__ID()
{
var id = _service.CreateFilter(new ScreenFilter
{
ScreenKey = "dashboard",
FilterType = "workshop",
FilterValue = "1",
IsDefault = 1,
SortOrder = 1
});
Assert.True(id > 0);
}
[Fact]
public void CreateFilter_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.CreateFilter(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== UpdateFilter ========
[Fact]
public void UpdateFilter_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.UpdateFilter(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== DeleteFilter ========
[Fact]
public void DeleteFilter_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.DeleteFilter(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void DeleteFilter_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.DeleteFilter(-1));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void DeleteFilter_ID_true()
{
var id = _service.CreateFilter(new ScreenFilter
{
ScreenKey = "dashboard",
FilterType = "workshop",
FilterValue = "1",
IsDefault = 1,
SortOrder = 1
});
var result = _service.DeleteFilter(id);
Assert.True(result);
}
}
}

@ -0,0 +1,113 @@
using CncRepository.Impl;
using CncRepository.Impl.Dashboard;
using CncRepository.Impl.Log;
using CncService.Impl;
namespace CncService.Tests
{
/// <summary>
/// Service工厂 —— 封装Repository创建提供所有Service实例化方法
/// 所有Service使用真实数据库连接串进行集成测试
/// </summary>
public static class ServiceFactory
{
private static readonly string ConnStr = TestDb.ConnectionString;
private static readonly string ConnStrLog = TestDb.ConnectionString;
// ======== Repository 创建(业务库) ========
private static WorkshopRepository NewWorkshopRepo() => new WorkshopRepository(ConnStr);
private static BrandRepository NewBrandRepo() => new BrandRepository(ConnStr);
private static BrandFieldMappingRepository NewMappingRepo() => new BrandFieldMappingRepository(ConnStr);
private static CollectAddressRepository NewCollectAddressRepo() => new CollectAddressRepository(ConnStr);
private static MachineRepository NewMachineRepo() => new MachineRepository(ConnStr);
private static WorkerRepository NewWorkerRepo() => new WorkerRepository(ConnStr);
private static WorkerMachineRepository NewWorkerMachineRepo() => new WorkerMachineRepository(ConnStr);
private static SysConfigRepository NewSysConfigRepo() => new SysConfigRepository(ConnStr);
private static AlertRepository NewAlertRepo() => new AlertRepository(ConnStr);
private static DailyProductionRepository NewDailyProductionRepo() => new DailyProductionRepository(ConnStr);
private static ProductionSegmentRepository NewProductionSegmentRepo() => new ProductionSegmentRepository(ConnStr);
private static ProductionAdjustmentRepository NewProductionAdjustmentRepo() => new ProductionAdjustmentRepository(ConnStr);
private static ScreenConfigRepository NewScreenConfigRepo() => new ScreenConfigRepository(ConnStr);
private static ScreenFilterRepository NewScreenFilterRepo() => new ScreenFilterRepository(ConnStr);
private static DashboardRepository NewDashboardRepo() => new DashboardRepository(ConnStr);
private static SystemLogRepository NewSystemLogRepo() => new SystemLogRepository(ConnStrLog);
// ======== Repository 创建(日志库) ========
private static CollectorHeartbeatRepository NewCollectorHeartbeatRepo() => new CollectorHeartbeatRepository(ConnStrLog);
private static CollectRawRepository NewCollectRawRepo() => new CollectRawRepository(ConnStrLog);
// ======== Service 创建 ========
/// <summary>创建AuthService需指定JWT密钥</summary>
public static AuthService CreateAuthService(string jwtSecret = "test-jwt-secret-key-for-unit-testing-2024")
{
return new AuthService(NewSysConfigRepo(), jwtSecret);
}
/// <summary>创建WorkshopService</summary>
public static WorkshopService CreateWorkshopService()
{
return new WorkshopService(NewWorkshopRepo());
}
/// <summary>创建BrandService</summary>
public static BrandService CreateBrandService()
{
return new BrandService(NewBrandRepo(), NewMappingRepo(), NewCollectAddressRepo());
}
/// <summary>创建MachineService</summary>
public static MachineService CreateMachineService()
{
return new MachineService(NewMachineRepo(), NewCollectAddressRepo(), NewWorkerMachineRepo(), NewBrandRepo());
}
/// <summary>创建CollectAddressService</summary>
public static CollectAddressService CreateCollectAddressService()
{
return new CollectAddressService(NewCollectAddressRepo(), NewMachineRepo(), NewBrandRepo());
}
/// <summary>创建WorkerService</summary>
public static WorkerService CreateWorkerService()
{
return new WorkerService(NewWorkerRepo(), NewWorkerMachineRepo(), NewMachineRepo());
}
/// <summary>创建ProductionService</summary>
public static ProductionService CreateProductionService()
{
return new ProductionService(NewDailyProductionRepo(), NewProductionSegmentRepo(), NewProductionAdjustmentRepo());
}
/// <summary>创建AlertService</summary>
public static AlertService CreateAlertService()
{
return new AlertService(NewAlertRepo());
}
/// <summary>创建ScreenService</summary>
public static ScreenService CreateScreenService()
{
return new ScreenService(NewScreenConfigRepo(), NewScreenFilterRepo(), NewWorkshopRepo());
}
/// <summary>创建SystemLogService</summary>
public static SystemLogService CreateSystemLogService()
{
return new SystemLogService(NewSystemLogRepo());
}
/// <summary>创建DashboardService</summary>
public static DashboardService CreateDashboardService()
{
return new DashboardService(NewDashboardRepo(), NewCollectorHeartbeatRepo());
}
/// <summary>创建CollectDataService</summary>
public static CollectDataService CreateCollectDataService()
{
return new CollectDataService(NewCollectRawRepo());
}
}
}

@ -0,0 +1,56 @@
using System;
using CncModels.Constants;
using CncModels.Dto;
using CncModels.Dto.Log;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// SystemLogService 系统日志测试
/// 测试场景:分页查询、参数校验
/// </summary>
[Collection("Database")]
public class SystemLogServiceTests : IDisposable
{
private readonly SystemLogService _service;
public SystemLogServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateSystemLogService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
[Fact]
public void GetList__()
{
var result = _service.GetList(new SystemLogQuery { Page = 1, PageSize = 20 });
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
[Fact]
public void GetList_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetList(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetList__()
{
TestDb.Execute(@"INSERT INTO log_system (log_level, source, message, stack_trace, extra_data, created_at)
VALUES ('INFO', 'AuthService', '', '', '{}', NOW())");
var result = _service.GetList(new SystemLogQuery { Page = 1, PageSize = 20 });
Assert.Equal(1, result.Total);
}
}
}

@ -0,0 +1,127 @@
using System;
using Dapper;
using MySqlConnector;
namespace CncService.Tests
{
/// <summary>
/// Service层测试数据库辅助类
/// 与Repository.Tests共享同一个cnc_test库每条测试前后清理数据
/// </summary>
public static class TestDb
{
/// <summary>测试库连接串</summary>
public static readonly string ConnectionString =
"Server=localhost;Database=cnc_test;Uid=root;Pwd=root;Charset=utf8mb4;SslMode=None;";
/// <summary>
/// 清空所有测试表按外键依赖倒序DELETE然后重置自增
/// </summary>
public static void TruncateAll()
{
using (var conn = new MySqlConnection(ConnectionString))
{
var tables = new[]
{
"log_collect_raw",
"log_collector_heartbeat",
"cnc_worker_machine",
"cnc_production_segment",
"cnc_machine_daily_status",
"cnc_worker_daily_summary",
"cnc_daily_production",
"cnc_production_adjustment",
"cnc_alert",
"log_system",
"cnc_machine",
"cnc_collect_address",
"cnc_brand_field_mapping",
"cnc_screen_filter",
"cnc_screen_config",
"cnc_worker",
"cnc_sys_config",
"cnc_workshop",
"cnc_brand"
};
conn.Execute("SET FOREIGN_KEY_CHECKS = 0");
foreach (var table in tables)
{
conn.Execute($"DELETE FROM {table}");
conn.Execute($"ALTER TABLE {table} AUTO_INCREMENT = 1");
}
conn.Execute("SET FOREIGN_KEY_CHECKS = 1");
}
SeedData();
}
/// <summary>
/// 插入基础种子数据
/// </summary>
public static void SeedData()
{
using (var conn = new MySqlConnection(ConnectionString))
{
// 品牌 FANUC
conn.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
// 车间 A栋、B栋
conn.Execute(@"INSERT IGNORE INTO cnc_workshop (id, name, sort_order, is_enabled, created_at, updated_at)
VALUES (1, 'A', 1, 1, NOW(), NOW()), (2, 'B', 2, 1, NOW(), NOW())");
// 系统配置admin账号密码为 BCrypt("admin123")
conn.Execute(@"INSERT IGNORE INTO cnc_sys_config (config_key, config_value, value_type, description, updated_at)
VALUES ('admin_username', 'admin', 'string', '', NOW()),
('admin_password_hash', '$2a$11$dummyhashfortesting', 'string', '', NOW())");
}
}
/// <summary>
/// 设置真实BCrypt密码哈希用于AuthService登录测试
/// </summary>
public static void SetRealPasswordHash(string plainPassword)
{
var hash = BCrypt.Net.BCrypt.HashPassword(plainPassword);
using (var conn = new MySqlConnection(ConnectionString))
{
conn.Execute("UPDATE cnc_sys_config SET config_value = @hash WHERE config_key = 'admin_password_hash'",
new { hash });
}
}
/// <summary>
/// 执行SQL并返回受影响行数
/// </summary>
public static int Execute(string sql, object param = null)
{
using (var conn = new MySqlConnection(ConnectionString))
{
return conn.Execute(sql, param);
}
}
/// <summary>
/// 查询单个值
/// </summary>
public static T QuerySingle<T>(string sql, object param = null)
{
using (var conn = new MySqlConnection(ConnectionString))
{
return conn.QuerySingle<T>(sql, param);
}
}
/// <summary>
/// 查询单个值(可空)
/// </summary>
public static T QueryFirstOrDefault<T>(string sql, object param = null)
{
using (var conn = new MySqlConnection(ConnectionString))
{
return conn.QueryFirstOrDefault<T>(sql, param);
}
}
}
}

@ -0,0 +1,325 @@
using System;
using System.Linq;
using CncModels.Constants;
using CncModels.Dto;
using CncModels.Dto.Worker;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// WorkerService 员工管理测试
/// 测试场景CRUD、唯一性校验工号、绑定/解绑机床、参数校验
/// </summary>
[Collection("Database")]
public class WorkerServiceTests : IDisposable
{
private readonly WorkerService _service;
public WorkerServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateWorkerService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
/// <summary>辅助方法:插入测试工人</summary>
private int InsertTestWorker(string code = "W001", string name = "工人1")
{
return _service.Create(new CreateWorkerRequest
{
Code = code,
Name = name
});
}
/// <summary>辅助方法:插入测试机床</summary>
private int InsertTestMachine(string deviceCode = "M001")
{
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES (@code, '', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())",
new { code = deviceCode });
return TestDb.QuerySingle<int>("SELECT MAX(id) FROM cnc_machine");
}
// ======== GetList ========
[Fact]
public void GetList__()
{
var result = _service.GetList(new WorkerQuery { Page = 1, PageSize = 20 });
Assert.NotNull(result);
Assert.Equal(0, result.Total);
}
[Fact]
public void GetList__()
{
InsertTestWorker("W001", "工人1");
InsertTestWorker("W002", "工人2");
var result = _service.GetList(new WorkerQuery { Page = 1, PageSize = 20 });
Assert.Equal(2, result.Total);
}
[Fact]
public void GetList_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetList(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== GetById ========
[Fact]
public void GetById_ID_()
{
var id = InsertTestWorker();
var detail = _service.GetById(id);
Assert.NotNull(detail);
Assert.Equal("W001", detail.Code);
Assert.Equal("工人1", detail.Name);
Assert.Equal(1, detail.IsEnabled);
}
[Fact]
public void GetById__()
{
var workerId = InsertTestWorker();
var machineId = InsertTestMachine();
_service.BindMachine(workerId, machineId);
var detail = _service.GetById(workerId);
Assert.Equal(1, detail.MachineCount);
Assert.Contains("测试机床", detail.MachineNames);
}
[Fact]
public void GetById_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void GetById_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== Create ========
[Fact]
public void Create__ID()
{
var id = InsertTestWorker();
Assert.True(id > 0);
}
[Fact]
public void Create_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkerRequest
{
Code = "",
Name = "工人"
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkerRequest
{
Code = " ",
Name = "工人"
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__Conflict()
{
InsertTestWorker("W001");
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkerRequest
{
Code = "W001",
Name = "工人2"
}));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
// ======== Update ========
[Fact]
public void Update__true()
{
var id = InsertTestWorker();
var result = _service.Update(id, new UpdateWorkerRequest { Name = "修改后工人" });
Assert.True(result);
var updated = _service.GetById(id);
Assert.Equal("修改后工人", updated.Name);
}
[Fact]
public void Update_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(99999, new UpdateWorkerRequest { Name = "测试" }));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void Update_null_BadRequest()
{
var id = InsertTestWorker();
var ex = Assert.Throws<BusinessException>(() => _service.Update(id, null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Update_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(0, new UpdateWorkerRequest { Name = "测试" }));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== Delete ========
[Fact]
public void Delete_ID_true()
{
var id = InsertTestWorker();
var result = _service.Delete(id);
Assert.True(result);
}
[Fact]
public void Delete_()
{
var id = InsertTestWorker();
_service.Delete(id);
Assert.Throws<BusinessException>(() => _service.GetById(id));
}
[Fact]
public void Delete_()
{
var workerId = InsertTestWorker();
var machineId = InsertTestMachine();
_service.BindMachine(workerId, machineId);
_service.Delete(workerId);
var count = TestDb.QuerySingle<int>(
"SELECT COUNT(*) FROM cnc_worker_machine WHERE worker_id = @workerId",
new { workerId });
Assert.Equal(0, count);
}
[Fact]
public void Delete_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Delete(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== ToggleEnabled ========
[Fact]
public void ToggleEnabled_()
{
var id = InsertTestWorker();
var before = _service.GetById(id);
var beforeState = before.IsEnabled;
_service.ToggleEnabled(id);
var after = _service.GetById(id);
Assert.NotEqual(beforeState, after.IsEnabled);
}
[Fact]
public void ToggleEnabled_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.ToggleEnabled(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== BindMachine ========
[Fact]
public void BindMachine__true()
{
var workerId = InsertTestWorker();
var machineId = InsertTestMachine();
var result = _service.BindMachine(workerId, machineId);
Assert.True(result);
}
[Fact]
public void BindMachine__Conflict()
{
var worker1 = InsertTestWorker("W001", "工人1");
var worker2 = InsertTestWorker("W002", "工人2");
var machineId = InsertTestMachine();
_service.BindMachine(worker1, machineId);
var ex = Assert.Throws<BusinessException>(() => _service.BindMachine(worker2, machineId));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
[Fact]
public void BindMachine__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.BindMachine(0, 1));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void BindMachine__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.BindMachine(-1, -1));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== UnbindMachine ========
[Fact]
public void UnbindMachine__true()
{
var workerId = InsertTestWorker();
var machineId = InsertTestMachine();
_service.BindMachine(workerId, machineId);
var result = _service.UnbindMachine(workerId, machineId);
Assert.True(result);
}
[Fact]
public void UnbindMachine__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.UnbindMachine(0, 1));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
}
}

@ -0,0 +1,310 @@
using System;
using System.Linq;
using CncModels.Constants;
using CncModels.Dto.Settings;
using CncService;
using CncService.Impl;
using Xunit;
namespace CncService.Tests
{
/// <summary>
/// WorkshopService 车间管理测试
/// 测试场景CRUD、唯一性校验、启停、删除约束、参数校验、边界值
/// </summary>
[Collection("Database")]
public class WorkshopServiceTests : IDisposable
{
private readonly WorkshopService _service;
public WorkshopServiceTests()
{
TestDb.TruncateAll();
_service = ServiceFactory.CreateWorkshopService();
}
public void Dispose()
{
TestDb.TruncateAll();
}
// ======== GetList ========
[Fact]
public void GetList_()
{
var list = _service.GetList(null);
Assert.NotNull(list);
Assert.True(list.Count >= 2, "种子数据至少有2个车间");
Assert.Contains(list, w => w.Name == "A栋");
Assert.Contains(list, w => w.Name == "B栋");
}
[Fact]
public void GetList__()
{
var list = _service.GetList("A");
Assert.NotNull(list);
Assert.All(list, w => Assert.Contains("A", w.Name));
}
[Fact]
public void GetList__()
{
var list = _service.GetList("不存在的车间");
Assert.Empty(list);
}
// ======== GetById ========
[Fact]
public void GetById_ID_()
{
var w = _service.GetById(1);
Assert.NotNull(w);
Assert.Equal("A栋", w.Name);
Assert.Equal(1, w.SortOrder);
Assert.Equal(1, w.IsEnabled);
}
[Fact]
public void GetById_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(99999));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void GetById_ID0_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void GetById_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetById(-1));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== Create ========
[Fact]
public void Create__ID()
{
var id = _service.Create(new CreateWorkshopRequest
{
Name = "C栋",
SortOrder = 3
});
Assert.True(id > 0);
var created = _service.GetById(id);
Assert.Equal("C栋", created.Name);
Assert.Equal(3, created.SortOrder);
Assert.Equal(1, created.IsEnabled); // 默认启用
}
[Fact]
public void Create_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkshopRequest
{
Name = "",
SortOrder = 1
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkshopRequest
{
Name = " ",
SortOrder = 1
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Create__Conflict()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkshopRequest
{
Name = "A栋",
SortOrder = 3
}));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
[Fact]
public void Create__Conflict()
{
var ex = Assert.Throws<BusinessException>(() => _service.Create(new CreateWorkshopRequest
{
Name = "a栋",
SortOrder = 3
}));
Assert.Equal(ErrorCode.Conflict, ex.Code);
}
// ======== Update ========
[Fact]
public void Update__true()
{
var result = _service.Update(1, new UpdateWorkshopRequest
{
Name = "A栋-修改",
SortOrder = 10
});
Assert.True(result);
var updated = _service.GetById(1);
Assert.Equal("A栋-修改", updated.Name);
Assert.Equal(10, updated.SortOrder);
}
[Fact]
public void Update_null_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(1, null));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Update_ID_NotFound()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(99999, new UpdateWorkshopRequest
{
Name = "测试",
SortOrder = 1
}));
Assert.Equal(ErrorCode.NotFound, ex.Code);
}
[Fact]
public void Update_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Update(0, new UpdateWorkshopRequest
{
Name = "测试",
SortOrder = 1
}));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
[Fact]
public void Update_Namenull_()
{
var before = _service.GetById(1);
_service.Update(1, new UpdateWorkshopRequest
{
Name = null,
SortOrder = 5
});
var after = _service.GetById(1);
Assert.Equal(before.Name, after.Name);
Assert.Equal(5, after.SortOrder);
}
// ======== Delete ========
[Fact]
public void Delete__true()
{
// 先新增一个空车间
var id = _service.Create(new CreateWorkshopRequest { Name = "待删除", SortOrder = 99 });
var result = _service.Delete(id);
Assert.True(result);
}
[Fact]
public void Delete_()
{
var id = _service.Create(new CreateWorkshopRequest { Name = "临时车间", SortOrder = 99 });
_service.Delete(id);
Assert.Throws<BusinessException>(() => _service.GetById(id));
}
[Fact]
public void Delete__DataReferenced()
{
// 先插入有效的采集地址
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
var ex = Assert.Throws<BusinessException>(() => _service.Delete(1));
Assert.Equal(ErrorCode.DataReferenced, ex.Code);
}
[Fact]
public void Delete_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.Delete(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== ToggleEnabled ========
[Fact]
public void ToggleEnabled_()
{
var before = _service.GetById(1);
var beforeState = before.IsEnabled;
_service.ToggleEnabled(1);
var after = _service.GetById(1);
Assert.NotEqual(beforeState, after.IsEnabled);
}
[Fact]
public void ToggleEnabled_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.ToggleEnabled(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
// ======== GetMachineCount ========
[Fact]
public void GetMachineCount__0()
{
var count = _service.GetMachineCount(1);
Assert.Equal(0, count);
}
[Fact]
public void GetMachineCount__()
{
// 先插入一个有效的采集地址满足cnc_machine的外键约束
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
VALUES ('', 'http://test', 1, 30, 1, NOW(), NOW())");
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
VALUES ('M001', '1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW()),
('M002', '2', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
var count = _service.GetMachineCount(1);
Assert.Equal(2, count);
}
[Fact]
public void GetMachineCount_ID_BadRequest()
{
var ex = Assert.Throws<BusinessException>(() => _service.GetMachineCount(0));
Assert.Equal(ErrorCode.BadRequest, ex.Code);
}
}
}
Loading…
Cancel
Save