You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
17 KiB
C#
376 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
using Moq;
|
|
using Haoliang.Core.Services;
|
|
using Haoliang.Data.Repositories;
|
|
using Haoliang.Models.Models.Device;
|
|
using Haoliang.Models.Models.Production;
|
|
using Haoliang.Models.Models.System;
|
|
|
|
namespace Haoliang.Tests.Services
|
|
{
|
|
public class ProductionStatisticsServiceTests
|
|
{
|
|
private readonly Mock<IProductionRepository> _mockProductionRepository;
|
|
private readonly Mock<IDeviceRepository> _mockDeviceRepository;
|
|
private readonly Mock<ISystemRepository> _mockSystemRepository;
|
|
private readonly Mock<IAlarmRepository> _mockAlarmRepository;
|
|
private readonly Mock<ICollectionRepository> _mockCollectionRepository;
|
|
private readonly ProductionStatisticsService _statisticsService;
|
|
|
|
public ProductionStatisticsServiceTests()
|
|
{
|
|
_mockProductionRepository = new Mock<IProductionRepository>();
|
|
_mockDeviceRepository = new Mock<IDeviceRepository>();
|
|
_mockSystemRepository = new Mock<ISystemRepository>();
|
|
_mockAlarmRepository = new Mock<IAlarmRepository>();
|
|
_mockCollectionRepository = new Mock<ICollectionRepository>();
|
|
|
|
_statisticsService = new ProductionStatisticsService(
|
|
_mockProductionRepository.Object,
|
|
_mockDeviceRepository.Object,
|
|
_mockSystemRepository.Object,
|
|
_mockAlarmRepository.Object,
|
|
_mockCollectionRepository.Object
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CalculateProductionTrendsAsync_ValidDeviceAndDates_ReturnsTrendAnalysis()
|
|
{
|
|
// Arrange
|
|
var deviceId = 1;
|
|
var startDate = DateTime.Now.AddDays(-7);
|
|
var endDate = DateTime.Now;
|
|
|
|
var device = new CNCDevice { Id = deviceId, Name = "Test Device" };
|
|
var productionRecords = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = deviceId, Created = startDate, Quantity = 100, TargetQuantity = 120 },
|
|
new ProductionRecord { Id = 2, DeviceId = deviceId, Created = startDate.AddDays(1), Quantity = 110, TargetQuantity = 120 },
|
|
new ProductionRecord { Id = 3, DeviceId = deviceId, Created = startDate.AddDays(2), Quantity = 130, TargetQuantity = 120 }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
|
|
.ReturnsAsync(device);
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(deviceId, startDate, endDate))
|
|
.ReturnsAsync(productionRecords);
|
|
_mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync())
|
|
.ReturnsAsync(new SystemConfiguration { DailyProductionTarget = 100 });
|
|
|
|
// Act
|
|
var result = await _statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(deviceId, result.DeviceId);
|
|
Assert.Equal(device.Name, result.DeviceName);
|
|
Assert.Equal(startDate, result.PeriodStart);
|
|
Assert.Equal(endDate, result.PeriodEnd);
|
|
Assert.Equal(340, result.TotalProduction);
|
|
Assert.Equal(3, result.AverageDailyProduction);
|
|
Assert.Equal(3, result.DailyData.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CalculateProductionTrendsAsync_InvalidDevice_ThrowsKeyNotFoundException()
|
|
{
|
|
// Arrange
|
|
var deviceId = 999;
|
|
var startDate = DateTime.Now.AddDays(-7);
|
|
var endDate = DateTime.Now;
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
|
|
.ReturnsAsync((CNCDevice)null);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<KeyNotFoundException>(() =>
|
|
_statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GenerateProductionReportAsync_ValidFilter_ReturnsProductionReport()
|
|
{
|
|
// Arrange
|
|
var filter = new ReportFilter
|
|
{
|
|
DeviceIds = new List<int> { 1 },
|
|
StartDate = DateTime.Now.AddDays(-7),
|
|
EndDate = DateTime.Now,
|
|
ReportType = ReportType.Daily
|
|
};
|
|
|
|
var device = new CNCDevice { Id = 1, Name = "Test Device" };
|
|
var records = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now, Quantity = 100, TargetQuantity = 120, IsGood = true },
|
|
new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now, Quantity = 50, TargetQuantity = 60, IsGood = true }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
|
|
.ReturnsAsync(device);
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByFilterAsync(filter))
|
|
.ReturnsAsync(records);
|
|
_mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync())
|
|
.ReturnsAsync(new SystemConfiguration { DailyProductionTarget = 100 });
|
|
|
|
// Act
|
|
var result = await _statisticsService.GenerateProductionReportAsync(filter);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(ReportType.Daily, result.ReportType);
|
|
Assert.Equal(2, result.SummaryItems.Count);
|
|
|
|
var summary = result.SummaryItems.First();
|
|
Assert.Equal(1, summary.DeviceId);
|
|
Assert.Equal("Test Device", summary.DeviceName);
|
|
Assert.Equal(150, summary.Quantity);
|
|
Assert.Equal(180, summary.TargetQuantity);
|
|
Assert.Equal(83.33m, summary.Efficiency);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDashboardSummaryAsync_ValidFilter_ReturnsDashboardSummary()
|
|
{
|
|
// Arrange
|
|
var filter = new DashboardFilter
|
|
{
|
|
Date = DateTime.Today,
|
|
IncludeAlerts = true
|
|
};
|
|
|
|
var device = new CNCDevice { Id = 1, Name = "Test Device" };
|
|
var deviceSummaries = new List<DeviceSummary>
|
|
{
|
|
new DeviceSummary { DeviceId = 1, DeviceName = "Test Device", TodayProduction = 100, Efficiency = 85, QualityRate = 98 }
|
|
};
|
|
|
|
var alertSummaries = new List<AlertSummary>
|
|
{
|
|
new AlertSummary { AlertId = 1, DeviceName = "Test Device", AlertType = AlertType.DeviceOffline, Message = "Device offline" }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetAllDevicesAsync())
|
|
.ReturnsAsync(new List<CNCDevice> { device });
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
|
|
.ReturnsAsync(device);
|
|
_mockCollectionRepository.Setup(repo => GetDeviceCurrentStatusAsync(1))
|
|
.ReturnsAsync(new DeviceCurrentStatus { Status = DeviceStatus.Online, Runtime = TimeSpan.FromHours(8) });
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateAsync(1, DateTime.Today))
|
|
.ReturnsAsync(new List<ProductionRecord>());
|
|
_mockAlarmRepository.Setup(repo => GetActiveAlertsByDeviceAsync(1))
|
|
.ReturnsAsync(new List<Alert> { new Alert { Id = 1, DeviceId = 1, AlertType = "DeviceOffline", Message = "Device offline" } });
|
|
|
|
// Act
|
|
var result = await _statisticsService.GetDashboardSummaryAsync(filter);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(DateTime.Now.Date, result.GeneratedAt.Date);
|
|
Assert.Equal(1, result.TotalDevices);
|
|
Assert.Equal(1, result.ActiveDevices);
|
|
Assert.Equal(0, result.OfflineDevices);
|
|
Assert.Equal(1, result.DeviceSummaries.Count);
|
|
Assert.Equal(1, result.ActiveAlerts.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CalculateOeeAsync_ValidDeviceAndDate_ReturnsOeeMetrics()
|
|
{
|
|
// Arrange
|
|
var deviceId = 1;
|
|
var date = DateTime.Today;
|
|
|
|
var device = new CNCDevice { Id = deviceId, Name = "Test Device", IdealCycleTime = 2 };
|
|
var records = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = deviceId, Created = date, Quantity = 50, TargetQuantity = 60, IsGood = true }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
|
|
.ReturnsAsync(device);
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateAsync(deviceId, date))
|
|
.ReturnsAsync(records);
|
|
_mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync())
|
|
.ReturnsAsync(new SystemConfiguration { DailyWorkingHours = TimeSpan.FromHours(8) });
|
|
_mockCollectionRepository.Setup(repo => GetDeviceStatusHistoryAsync(deviceId, date, date))
|
|
.ReturnsAsync(new List<DeviceStatusHistory>());
|
|
|
|
// Act
|
|
var result = await _statisticsService.CalculateOeeAsync(deviceId, date);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(deviceId, result.DeviceId);
|
|
Assert.Equal(device.Name, result.DeviceName);
|
|
Assert.Equal(date, result.Date);
|
|
Assert.True(result.Availability >= 0 && result.Availability <= 100);
|
|
Assert.True(result.Performance >= 0 && result.Performance <= 100);
|
|
Assert.True(result.Quality >= 0 && result.Quality <= 100);
|
|
Assert.True(result.Oee >= 0 && result.Oee <= 100);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GenerateProductionForecastAsync_ValidFilter_ReturnsProductionForecast()
|
|
{
|
|
// Arrange
|
|
var filter = new ForecastFilter
|
|
{
|
|
DeviceId = 1,
|
|
DaysToForecast = 7,
|
|
Model = ForecastModel.Linear,
|
|
HistoricalDataStart = DateTime.Now.AddDays(-30),
|
|
HistoricalDataEnd = DateTime.Now
|
|
};
|
|
|
|
var device = new CNCDevice { Id = 1, Name = "Test Device" };
|
|
var historicalData = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-30), Quantity = 100 },
|
|
new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now.AddDays(-29), Quantity = 110 },
|
|
new ProductionRecord { Id = 3, DeviceId = 1, Created = DateTime.Now.AddDays(-28), Quantity = 105 }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
|
|
.ReturnsAsync(device);
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.HistoricalDataStart, filter.HistoricalDataEnd))
|
|
.ReturnsAsync(historicalData);
|
|
|
|
// Act
|
|
var result = await _statisticsService.GenerateProductionForecastAsync(filter);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(filter.DeviceId, result.DeviceId);
|
|
Assert.Equal(device.Name, result.DeviceName);
|
|
Assert.Equal(filter.Model, result.ModelUsed);
|
|
Assert.Equal(7, result.DailyForecasts.Count);
|
|
|
|
// Check that forecasts have reasonable values
|
|
foreach (var forecast in result.DailyForecasts)
|
|
{
|
|
Assert.True(forecast.ForecastedQuantity >= 0);
|
|
Assert.True(forecast.ConfidenceLower <= forecast.ForecastedQuantity);
|
|
Assert.True(forecast.ConfidenceUpper >= forecast.ForecastedQuantity);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DetectProductionAnomaliesAsync_ValidFilter_ReturnsAnomalyAnalysis()
|
|
{
|
|
// Arrange
|
|
var filter = new AnomalyFilter
|
|
{
|
|
DeviceIds = new List<int> { 1 },
|
|
StartDate = DateTime.Now.AddDays(-7),
|
|
EndDate = DateTime.Now,
|
|
MinSeverity = AnomalySeverity.Medium
|
|
};
|
|
|
|
var device = new CNCDevice { Id = 1, Name = "Test Device" };
|
|
var records = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-2), Quantity = 100 },
|
|
new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now.AddDays(-1), Quantity = 30 }, // Big drop
|
|
new ProductionRecord { Id = 3, DeviceId = 1, Created = DateTime.Now, Quantity = 95 }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
|
|
.ReturnsAsync(device);
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.StartDate, filter.EndDate))
|
|
.ReturnsAsync(records);
|
|
|
|
// Act
|
|
var result = await _statisticsService.DetectProductionAnomaliesAsync(filter);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(1, result.DeviceIds.Count);
|
|
Assert.True(result.Anomalies.Count >= 1); // Should detect the production drop
|
|
|
|
var anomaly = result.Anomalies.FirstOrDefault(a => a.Type == AnomalyType.ProductionDrop);
|
|
Assert.NotNull(anomaly);
|
|
Assert.Equal(AnomalySeverity.High, anomaly.Severity);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEfficiencyMetricsAsync_ValidFilter_ReturnsEfficiencyMetrics()
|
|
{
|
|
// Arrange
|
|
var filter = new EfficiencyFilter
|
|
{
|
|
DeviceIds = new List<int> { 1 },
|
|
StartDate = DateTime.Now.AddDays(-7),
|
|
EndDate = DateTime.Now,
|
|
Metrics = EfficiencyMetric.Oee
|
|
};
|
|
|
|
var device = new CNCDevice { Id = 1, Name = "Test Device" };
|
|
var records = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-1), Quantity = 100, TargetQuantity = 120, IsGood = true }
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
|
|
.ReturnsAsync(device);
|
|
_mockDeviceRepository.Setup(repo => repo.GetAllActiveDevicesAsync())
|
|
.ReturnsAsync(new List<CNCDevice> { device });
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.StartDate, filter.EndDate))
|
|
.ReturnsAsync(records);
|
|
|
|
// Act
|
|
var result = await _statisticsService.CalculateEfficiencyMetricsAsync(filter);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Single(result.DeviceIds);
|
|
Assert.Equal(1, result.DeviceIds.First());
|
|
Assert.True(result.Availability >= 0 && result.Availability <= 100);
|
|
Assert.True(result.Performance >= 0 && result.Performance <= 100);
|
|
Assert.True(result.Quality >= 0 && result.Quality <= 100);
|
|
Assert.True(result.Oee >= 0 && result.Oee <= 100);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PerformQualityAnalysisAsync_ValidFilter_ReturnsQualityAnalysis()
|
|
{
|
|
// Arrange
|
|
var filter = new QualityFilter
|
|
{
|
|
DeviceIds = new List<int> { 1 },
|
|
StartDate = DateTime.Now.AddDays(-7),
|
|
EndDate = DateTime.Now,
|
|
MetricType = QualityMetricType.DefectRate
|
|
};
|
|
|
|
var device = new CNCDevice { Id = 1, Name = "Test Device" };
|
|
var records = new List<ProductionRecord>
|
|
{
|
|
new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now, Quantity = 100, TargetQuantity = 120, IsGood = true },
|
|
new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now, Quantity = 20, TargetQuantity = 30, IsGood = false } // Defects
|
|
};
|
|
|
|
_mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
|
|
.ReturnsAsync(device);
|
|
_mockDeviceRepository.Setup(repo => repo.GetAllActiveDevicesAsync())
|
|
.ReturnsAsync(new List<CNCDevice> { device });
|
|
_mockProductionRepository.Setup(repo => repo.GetProductionRecordsByFilterAsync(It.IsAny<ReportFilter>()))
|
|
.ReturnsAsync(records);
|
|
|
|
// Act
|
|
var result = await _statisticsService.PerformQualityAnalysisAsync(filter);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Single(result.DeviceIds);
|
|
Assert.Equal(1, result.TotalProduced);
|
|
Assert.Equal(0.8m, result.QualityRate); // 80% quality rate
|
|
Assert.Equal(0.2m, result.DefectRate); // 20% defect rate
|
|
Assert.True(result.QualityMetrics.Count > 0);
|
|
}
|
|
}
|
|
} |