Compare commits
No commits in common. '0212ed6afca2fb75107be492364396f2faa20d95' and 'd8f59250d70bae5d7ca4255d28a7f5c8118513cc' have entirely different histories.
0212ed6afc
...
d8f59250d7
@ -1,34 +0,0 @@
|
||||
name: CI-Windows-WindowsServiceStatus
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, feat/windows-service-status-auto ]
|
||||
pull_request:
|
||||
branches: [ main, feat/windows-service-status-auto ]
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
distribution: "windows-hosted"
|
||||
sdk: [5.0.x, 6.0.x]
|
||||
- name: Restore NuGet packages
|
||||
run: dotnet restore
|
||||
- name: Build backend
|
||||
run: dotnet build -c Release
|
||||
- name: Run backend tests (Windows service tests)
|
||||
run: dotnet test tests/CncService.Tests/CncService.Tests.csproj -c Release -v minimal --filter "FullyQualifiedName~WindowsServiceCheckerTests|DashboardServiceTests"
|
||||
- name: Build frontend (optional, if frontend changes)
|
||||
run: |
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build
|
||||
- name: Run frontend tests (optional)
|
||||
if: always()
|
||||
run: |
|
||||
echo "Frontend test steps would run here (optional)"
|
||||
@ -1,92 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using CncModels.Dto.Dashboard;
|
||||
using CncModels.Entity;
|
||||
using CncRepository.Interface;
|
||||
using CncService.Interface;
|
||||
using CncService;
|
||||
using CncService.Impl;
|
||||
using Xunit;
|
||||
|
||||
namespace CncService.Tests
|
||||
{
|
||||
// Fake repositories to isolate DashboardService.GetCollectorStatus tests
|
||||
public class FakeDashboardRepository : IDashboardRepository
|
||||
/// <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_指定天数()
|
||||
{
|
||||
public DashboardSummaryResponse GetSummary() => new DashboardSummaryResponse();
|
||||
public List<WorkshopProductionResponse> GetWorkshopProduction(DateTime startDate, DateTime endDate) => new List<WorkshopProductionResponse>();
|
||||
public List<MachineRankResponse> GetMachineRank(DateTime startDate, DateTime endDate, int top) => new List<MachineRankResponse>();
|
||||
public List<WorkerRankResponse> GetWorkerRank(DateTime startDate, DateTime endDate, int top) => new List<WorkerRankResponse>();
|
||||
public List<dynamic> GetProductionTrend(int days) => new List<dynamic>();
|
||||
public object GetMachineStatusDistribution() => new object();
|
||||
public List<AlertListItem> GetRecentAlerts(int count) => new List<AlertListItem>();
|
||||
var result = _service.GetProductionTrend(30);
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
public class FakeCollectorHeartbeatRepository : ICollectorHeartbeatRepository
|
||||
// ======== GetMachineStatusDistribution ========
|
||||
|
||||
[Fact]
|
||||
public void GetMachineStatusDistribution_无数据_返回结果()
|
||||
{
|
||||
private readonly CollectorHeartbeat _latest;
|
||||
public FakeCollectorHeartbeatRepository(CollectorHeartbeat latest) { _latest = latest; }
|
||||
public long Create(CollectorHeartbeat entity) => 1;
|
||||
public CollectorHeartbeat GetLatest(string serviceId) => _latest;
|
||||
public int DeleteBeforeDate(DateTime date) => 0;
|
||||
var result = _service.GetMachineStatusDistribution();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
public class FakeWindowsServiceChecker : IWindowsServiceChecker
|
||||
// ======== GetRecentAlerts ========
|
||||
|
||||
[Fact]
|
||||
public void GetRecentAlerts_无数据_返回空列表()
|
||||
{
|
||||
private readonly ServiceStatusEnum _status;
|
||||
public FakeWindowsServiceChecker(ServiceStatusEnum status) { _status = status; }
|
||||
public ServiceStatusEnum GetServiceStatus(string serviceName) => _status;
|
||||
public (bool, string) TryStartService(string serviceName, int timeoutSeconds) => (
|
||||
_status == ServiceStatusEnum.NotInstalled ? false : true,
|
||||
_status == ServiceStatusEnum.NotInstalled ? "NotInstalled" : "Started");
|
||||
public (bool, string) TryStopService(string serviceName, int timeoutSeconds) => (
|
||||
true, "Stopped");
|
||||
var result = _service.GetRecentAlerts(5);
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
public class DashboardServiceTests
|
||||
[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_With_NotInstalled_Service_Returns_NotInstalled_State()
|
||||
{
|
||||
// Arrange
|
||||
var latest = new CollectorHeartbeat { Id = 1, ServiceId = "collector-service", Status = "running", UptimeSeconds = 120, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
||||
var dashboardRepo = new FakeDashboardRepository();
|
||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.NotInstalled);
|
||||
|
||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, checker);
|
||||
|
||||
// Act
|
||||
var resultObj = svc.GetCollectorStatus();
|
||||
var t = resultObj.GetType();
|
||||
var statusProp = t.GetProperty("status");
|
||||
var serviceStatusProp = t.GetProperty("serviceStatus");
|
||||
var uptimeProp = t.GetProperty("uptimeSeconds");
|
||||
var lastCollectTimeProp = t.GetProperty("lastCollectTime");
|
||||
Assert.NotNull(statusProp);
|
||||
Assert.NotNull(serviceStatusProp);
|
||||
var statusVal = statusProp.GetValue(resultObj) as string;
|
||||
var serviceStatusVal = serviceStatusProp.GetValue(resultObj) as string;
|
||||
Assert.Equal("stopped", statusVal);
|
||||
Assert.Equal("NotInstalled", serviceStatusVal);
|
||||
public void GetCollectorStatus_无心跳_返回未运行()
|
||||
{
|
||||
var result = _service.GetCollectorStatus();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State()
|
||||
{
|
||||
var latest = new CollectorHeartbeat { Id = 1, ServiceId = "collector-service", Status = "running", UptimeSeconds = 60, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
||||
var dashboardRepo = new FakeDashboardRepository();
|
||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Running);
|
||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, checker);
|
||||
|
||||
var resultObj = svc.GetCollectorStatus();
|
||||
var t = resultObj.GetType();
|
||||
var serviceStatusProp = t.GetProperty("serviceStatus");
|
||||
var statusProp = t.GetProperty("status");
|
||||
var serviceStatusVal = serviceStatusProp.GetValue(resultObj) as string;
|
||||
var statusVal = statusProp.GetValue(resultObj) as string;
|
||||
Assert.Equal("Running", serviceStatusVal);
|
||||
Assert.Equal("running", statusVal);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TestRun id="5ab07c77-7271-4b25-a1e3-31ccefb837c1" name="jiang@DESKTOP-FEAIV5E 2026-05-03 11:09:40" runUser="DESKTOP-FEAIV5E\jiang" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
|
||||
<Times creation="2026-05-03T11:09:40.1401238+08:00" queuing="2026-05-03T11:09:40.1401242+08:00" start="2026-05-03T11:09:37.9060678+08:00" finish="2026-05-03T11:09:40.1486843+08:00" />
|
||||
<TestSettings name="default" id="04bca397-b2d6-4381-9eef-a22546b0f331">
|
||||
<Deployment runDeploymentRoot="jiang_DESKTOP-FEAIV5E_2026-05-03_11_09_40" />
|
||||
</TestSettings>
|
||||
<Results>
|
||||
<UnitTestResult executionId="f53bc1cb-5030-4fef-ab5f-4644a36bd6cb" testId="d78b4668-1844-6210-7271-1e302f64ff27" testName="CncService.Tests.DashboardServiceTests.GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State" computerName="DESKTOP-FEAIV5E" duration="00:00:00.0010000" startTime="2026-05-03T11:09:39.9573096+08:00" endTime="2026-05-03T11:09:39.9573096+08:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="f53bc1cb-5030-4fef-ab5f-4644a36bd6cb" />
|
||||
<UnitTestResult executionId="a730d4b9-63cd-40dd-9d0e-328c4fe3d631" testId="f1da308c-ef1e-1db3-e8bc-9a4b01e12f99" testName="CncService.Tests.DashboardServiceTests.GetCollectorStatus_With_NotInstalled_Service_Returns_NotInstalled_State" computerName="DESKTOP-FEAIV5E" duration="00:00:00.0180000" startTime="2026-05-03T11:09:39.9463357+08:00" endTime="2026-05-03T11:09:39.9463357+08:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="a730d4b9-63cd-40dd-9d0e-328c4fe3d631" />
|
||||
</Results>
|
||||
<TestDefinitions>
|
||||
<UnitTest name="CncService.Tests.DashboardServiceTests.GetCollectorStatus_With_NotInstalled_Service_Returns_NotInstalled_State" storage="e:\opencode\haoliang\tests\cncservice.tests\bin\release\net472\cncservice.tests.dll" id="f1da308c-ef1e-1db3-e8bc-9a4b01e12f99">
|
||||
<Execution id="a730d4b9-63cd-40dd-9d0e-328c4fe3d631" />
|
||||
<TestMethod codeBase="E:\opencode\haoliang\tests\CncService.Tests\bin\Release\net472\CncService.Tests.dll" adapterTypeName="executor://xunit/VsTestRunner2/net" className="CncService.Tests.DashboardServiceTests" name="GetCollectorStatus_With_NotInstalled_Service_Returns_NotInstalled_State" />
|
||||
</UnitTest>
|
||||
<UnitTest name="CncService.Tests.DashboardServiceTests.GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State" storage="e:\opencode\haoliang\tests\cncservice.tests\bin\release\net472\cncservice.tests.dll" id="d78b4668-1844-6210-7271-1e302f64ff27">
|
||||
<Execution id="f53bc1cb-5030-4fef-ab5f-4644a36bd6cb" />
|
||||
<TestMethod codeBase="E:\opencode\haoliang\tests\CncService.Tests\bin\Release\net472\CncService.Tests.dll" adapterTypeName="executor://xunit/VsTestRunner2/net" className="CncService.Tests.DashboardServiceTests" name="GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State" />
|
||||
</UnitTest>
|
||||
</TestDefinitions>
|
||||
<TestEntries>
|
||||
<TestEntry testId="d78b4668-1844-6210-7271-1e302f64ff27" executionId="f53bc1cb-5030-4fef-ab5f-4644a36bd6cb" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
|
||||
<TestEntry testId="f1da308c-ef1e-1db3-e8bc-9a4b01e12f99" executionId="a730d4b9-63cd-40dd-9d0e-328c4fe3d631" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
|
||||
</TestEntries>
|
||||
<TestLists>
|
||||
<TestList name="列表中未列出的结果" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
|
||||
<TestList name="所有已加载的结果" id="19431567-8539-422a-85d7-44ee4e166bda" />
|
||||
</TestLists>
|
||||
<ResultSummary outcome="Completed">
|
||||
<Counters total="2" executed="2" passed="2" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
|
||||
<Output>
|
||||
<StdOut>[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.3+1b45f5407b (64-bit Desktop .NET 4.0.30319.42000)
|
||||
[xUnit.net 00:00:00.39] Discovering: CncService.Tests
|
||||
[xUnit.net 00:00:00.53] Discovered: CncService.Tests
|
||||
[xUnit.net 00:00:00.55] Starting: CncService.Tests
|
||||
[xUnit.net 00:00:00.74] Finished: CncService.Tests
|
||||
</StdOut>
|
||||
</Output>
|
||||
</ResultSummary>
|
||||
</TestRun>
|
||||
@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TestRun id="9439d027-9038-4dab-9e92-a7f3f77ade15" name="jiang@DESKTOP-FEAIV5E 2026-05-03 11:03:38" runUser="DESKTOP-FEAIV5E\jiang" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
|
||||
<Times creation="2026-05-03T11:03:38.5028882+08:00" queuing="2026-05-03T11:03:38.5028885+08:00" start="2026-05-03T11:03:36.1979074+08:00" finish="2026-05-03T11:03:38.5035820+08:00" />
|
||||
<TestSettings name="default" id="66c55dfd-2496-4374-9f81-4dd141d03470">
|
||||
<Deployment runDeploymentRoot="jiang_DESKTOP-FEAIV5E_2026-05-03_11_03_38" />
|
||||
</TestSettings>
|
||||
<TestLists>
|
||||
<TestList name="列表中未列出的结果" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
|
||||
<TestList name="所有已加载的结果" id="19431567-8539-422a-85d7-44ee4e166bda" />
|
||||
</TestLists>
|
||||
<ResultSummary outcome="Completed">
|
||||
<Counters total="0" executed="0" passed="0" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
|
||||
<Output>
|
||||
<StdOut>[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.3+1b45f5407b (64-bit Desktop .NET 4.0.30319.42000)
|
||||
[xUnit.net 00:00:00.41] Discovering: CncService.Tests
|
||||
[xUnit.net 00:00:00.62] Discovered: CncService.Tests
|
||||
[xUnit.net 00:00:00.62] Starting: CncService.Tests
|
||||
[xUnit.net 00:00:00.71] Finished: CncService.Tests
|
||||
</StdOut>
|
||||
</Output>
|
||||
<RunInfos>
|
||||
<RunInfo computerName="DESKTOP-FEAIV5E" outcome="Warning" timestamp="2026-05-03T11:03:38.2081712+08:00">
|
||||
<Text>[xUnit.net 00:00:00.62] CncService.Tests: Exception filtering tests: 筛选器字符串“\*WindowsServiceCheckerTests”包含无法识别的转义序列。</Text>
|
||||
</RunInfo>
|
||||
<RunInfo computerName="DESKTOP-FEAIV5E" outcome="Warning" timestamp="2026-05-03T11:03:38.3543607+08:00">
|
||||
<Text>没有测试匹配 E:\opencode\haoliang\tests\CncService.Tests\bin\Release\net472\CncService.Tests.dll 中的给定用例测试筛选器“\*WindowsServiceCheckerTests”</Text>
|
||||
</RunInfo>
|
||||
</RunInfos>
|
||||
</ResultSummary>
|
||||
</TestRun>
|
||||
Loading…
Reference in New Issue