diff --git a/AsbCloudApp/Data/WellOperationDto.cs b/AsbCloudApp/Data/WellOperationDto.cs
index 03f55957..725debfd 100644
--- a/AsbCloudApp/Data/WellOperationDto.cs
+++ b/AsbCloudApp/Data/WellOperationDto.cs
@@ -114,11 +114,6 @@ namespace AsbCloudApp.Data
///
public IEnumerable Validate(ValidationContext validationContext)
{
- if (!(DateStart is DateTimeOffset dateTimeOffset))
- yield return new ValidationResult(
- $"{nameof(DateStart)}: дата DateStart указана не в формате DateTimeOffset",
- new[] { nameof(DateStart) });
-
var gtDate = new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero);
if (DateStart <= gtDate)
yield return new ValidationResult(
diff --git a/AsbCloudApp/Requests/ReportParametersRequest.cs b/AsbCloudApp/Requests/ReportParametersRequest.cs
index 7a9061a5..57b83e4d 100644
--- a/AsbCloudApp/Requests/ReportParametersRequest.cs
+++ b/AsbCloudApp/Requests/ReportParametersRequest.cs
@@ -1,32 +1,43 @@
-using AsbCloudApp.Validation;
using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
namespace AsbCloudApp.Requests;
///
/// Параметры для создания отчёта и получения прогнозируемого количества страниц будущего отчета
///
-public class ReportParametersRequest
+public class ReportParametersRequest: IValidatableObject
{
- ///
- /// Шаг интервала
- ///
- public int StepSeconds { get; set; }
+ ///
+ /// Шаг интервала
+ ///
+ [Range(1, 86400)]
+ public int StepSeconds { get; set; }
///
/// формат отчета (0-PDF, 1-LAS)
///
+ [Range(0, 1)]
public int Format { get; set; }
///
/// Дата начала интервала
///
- [DateValidation(GtDate ="2000-01-01")]
public DateTime Begin { get; set; } = default;
///
/// Дата окончания интервала
///
- [DateValidation(GtDate ="2000-01-01")]
public DateTime End { get; set; } = default;
+
+ ///
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (End < Begin)
+ yield return new("End mast be less then begin");
+
+ if (Begin < new DateTime(2000, 1, 1))
+ yield return new("Begin mast be > 2000-1-1");
+ }
}
\ No newline at end of file
diff --git a/AsbCloudApp/Validation/DateValidationAttribute.cs b/AsbCloudApp/Validation/DateValidationAttribute.cs
deleted file mode 100644
index 925739a4..00000000
--- a/AsbCloudApp/Validation/DateValidationAttribute.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-
-namespace AsbCloudApp.Validation
-{
- ///
- /// Атрибут валидации даты-времени
- ///
- [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
- public class DateValidationAttribute : ValidationAttribute
- {
- private DateTime? gtDate;
-
- ///
- /// null разрешен
- ///
- public bool AllowNull { get; set; } = true;
-
- ///
- /// Разрешена только дат.
- /// При наличии времени в DateTime инвалидирует.
- /// При наличии UTC тоже инвалидирует.
- ///
- public bool IsDateOnly { get; set; } = false;
-
- ///
- /// Допустима дата-время в UTC
- ///
- public bool AllowUtc { get; set; } = true;
-
- ///
- /// Дата больше которой должно быть проверяемое значение.
- /// Формат строки - любой поддерживаемый DateTime.Parse.
- /// Желательно использовать ISO 8601 формат
- ///
- public string? GtDate {
- get => gtDate.ToString();
- set
- {
- if(value is null)
- gtDate = null;
- else
- gtDate = DateTime.Parse(value);
- }
- }
-
- ///
- /// Проверка значения
- ///
- ///
- ///
- public override bool IsValid(object? value)
- {
- if (value is null)
- return AllowNull;
-
- if (value is not DateTime dateTime)
- return false;
-
- if (IsDateOnly)
- {
- if (dateTime.Hour > 0 ||
- dateTime.Minute > 0 ||
- dateTime.Second > 0 ||
- dateTime.Millisecond > 0 ||
- dateTime.Kind == DateTimeKind.Utc)
- return false;
- }
-
- if (!AllowUtc && dateTime.Kind == DateTimeKind.Utc)
- return false;
-
- if (gtDate.HasValue && dateTime <= gtDate)
- return false;
-
- return true;
- }
-
- }
-}
diff --git a/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj b/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj
index b538cf94..66a2f309 100644
--- a/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj
+++ b/AsbCloudWebApi.Tests/AsbCloudWebApi.Tests.csproj
@@ -9,13 +9,13 @@
-
-
+
+
-
-
+
+
diff --git a/AsbCloudWebApi.Tests/Extensions/AspExtensions.cs b/AsbCloudWebApi.Tests/AspExtensions.cs
similarity index 100%
rename from AsbCloudWebApi.Tests/Extensions/AspExtensions.cs
rename to AsbCloudWebApi.Tests/AspExtensions.cs
diff --git a/AsbCloudWebApi.Tests/AttributeTest/DateValidationAttributeTest.cs b/AsbCloudWebApi.Tests/AttributeTest/DateValidationAttributeTest.cs
deleted file mode 100644
index 35c446e0..00000000
--- a/AsbCloudWebApi.Tests/AttributeTest/DateValidationAttributeTest.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using AsbCloudApp.Validation;
-using System;
-using Xunit;
-
-namespace AsbCloudWebApi.Tests.AttributeTest
-{
- public class DateValidationAttributeTest
- {
- [Fact]
- public void AllowNull_true_on_null_valid()
- {
- var attribute = new DateValidationAttribute { AllowNull = true };
- var result = attribute.IsValid(null);
- Assert.True(result);
- }
-
- [Fact]
- public void AllowNull_false_on_null_invalid()
- {
- var attribute = new DateValidationAttribute { AllowNull = false };
- var result = attribute.IsValid(null);
- Assert.False(result);
- }
-
- [Fact]
- public void IsDateOnly_true_on_empty_timePart_valid()
- {
- var attribute = new DateValidationAttribute { IsDateOnly = true };
- var date = new DateTime(2023, 01, 01, 00, 00, 00);
- var result = attribute.IsValid(date);
- Assert.True(result);
- }
-
- [Fact]
- public void IsDateOnly_true_on_timePart_invalid()
- {
- var attribute = new DateValidationAttribute { IsDateOnly = true };
- var date = new DateTime(2023, 01, 01, 01, 01, 01);
- var result = attribute.IsValid(date);
- Assert.False(result);
- }
-
- [Fact]
- public void IsDateOnly_true_on_utc_invalid()
- {
- var attribute = new DateValidationAttribute { IsDateOnly = true };
- var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Utc);
- var result = attribute.IsValid(date);
- Assert.False(result);
- }
-
- [Fact]
- public void AllowUtc_true_on_unspecified_valid()
- {
- var attribute = new DateValidationAttribute { AllowUtc = true };
- var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Unspecified);
- var result = attribute.IsValid(date);
- Assert.True(result);
- }
-
- [Fact]
- public void AllowUtc_true_on_utc_valid()
- {
- var attribute = new DateValidationAttribute { AllowUtc = true };
- var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Utc);
- var result = attribute.IsValid(date);
- Assert.True(result);
- }
-
- [Fact]
- public void AllowUtc_false_on_utc_invalid()
- {
- var attribute = new DateValidationAttribute { AllowUtc = false };
- var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Utc);
- var result = attribute.IsValid(date);
- Assert.False(result);
- }
-
- [Fact]
- public void AllowUtc_false_on_unspecified_valid()
- {
- var attribute = new DateValidationAttribute { AllowUtc = false };
- var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Unspecified);
- var result = attribute.IsValid(date);
- Assert.True(result);
- }
-
- [Fact]
- public void GtDate_on_same_date_invalid()
- {
- var gtDate = "2023-01-01T00:00:00";
- var date = DateTime.Parse(gtDate);
- var attribute = new DateValidationAttribute { GtDate = gtDate };
- var result = attribute.IsValid(date);
- Assert.False(result);
- }
-
- [Fact]
- public void GtDate_on_greater_date_valid()
- {
- var gtDate = "2023-01-01T00:00:00";
- var date = DateTime.Parse(gtDate).AddMilliseconds(1);
- var attribute = new DateValidationAttribute { GtDate = gtDate };
- var result = attribute.IsValid(date);
- Assert.True(result);
- }
- }
-}
diff --git a/AsbCloudWebApi.Tests/Background/BackgroundWorkerTest.cs b/AsbCloudWebApi.Tests/Background/BackgroundWorkerTest.cs
new file mode 100644
index 00000000..912b4806
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Background/BackgroundWorkerTest.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudInfrastructure.Background;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Background;
+
+public class BackgroundWorkerTest
+{
+ private readonly IServiceProvider serviceProviderMock = Substitute.For();
+ private readonly IServiceScope serviceScopeMock = Substitute.For();
+ private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For();
+
+ private readonly BackgroundWorker backgroundWorker;
+
+ public BackgroundWorkerTest()
+ {
+ serviceScopeFactoryMock.CreateScope().Returns(serviceScopeMock);
+ ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock);
+
+ backgroundWorker = new BackgroundWorker(serviceProviderMock);
+ typeof(BackgroundWorker)
+ .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)
+ ?.SetValue(backgroundWorker, TimeSpan.FromMilliseconds(1));
+ }
+
+ [Fact]
+ public async Task Enqueue_ShouldReturn_WorkCount()
+ {
+ //arrange
+ const int workCount = 10;
+ var result = 0;
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result++;
+ return Task.Delay(1);
+ }
+
+ //act
+ for (int i = 0; i < workCount; i++)
+ {
+ var work = Work.CreateByDelegate(i.ToString(), workAction);
+ backgroundWorker.Enqueue(work);
+ }
+
+ await backgroundWorker.ExecuteTask;
+
+ //assert
+ Assert.Equal(workCount, result);
+ }
+
+ [Fact]
+ public async Task Enqueue_Continues_AfterExceptions()
+ {
+ //arrange
+ const int expectadResult = 42;
+ var result = 0;
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result = expectadResult;
+ return Task.CompletedTask;
+ }
+
+ var goodWork = Work.CreateByDelegate("", workAction);
+
+ Task failAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ => throw new Exception();
+
+ var badWork = Work.CreateByDelegate("", failAction);
+ badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
+
+ //act
+ backgroundWorker.Enqueue(badWork);
+ backgroundWorker.Enqueue(goodWork);
+
+ await backgroundWorker.ExecuteTask;
+
+ //assert
+ Assert.Equal(expectadResult, result);
+ Assert.Equal(1, backgroundWorker.Felled.Count);
+ Assert.Equal(1, backgroundWorker.Done.Count);
+ }
+
+ [Fact]
+ public async Task TryRemoveFromQueue_ShouldReturn_True()
+ {
+ //arrange
+ const int workCount = 5;
+ var result = 0;
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ result++;
+ return Task.Delay(10);
+ }
+
+ //act
+ for (int i = 0; i < workCount; i++)
+ {
+ var work = Work.CreateByDelegate(i.ToString(), workAction);
+ backgroundWorker.Enqueue(work);
+ }
+
+ var removed = backgroundWorker.TryRemoveFromQueue((workCount - 1).ToString());
+
+ await backgroundWorker.ExecuteTask;
+
+ //assert
+ Assert.True(removed);
+ Assert.Equal(workCount - 1, result);
+ Assert.Equal(workCount - 1, backgroundWorker.Done.Count);
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudWebApi.Tests/UnitTests/Background/PeriodicBackgroundWorkerTest.cs b/AsbCloudWebApi.Tests/Background/PeriodicBackgroundWorkerTest.cs
similarity index 93%
rename from AsbCloudWebApi.Tests/UnitTests/Background/PeriodicBackgroundWorkerTest.cs
rename to AsbCloudWebApi.Tests/Background/PeriodicBackgroundWorkerTest.cs
index fced6864..938d8106 100644
--- a/AsbCloudWebApi.Tests/UnitTests/Background/PeriodicBackgroundWorkerTest.cs
+++ b/AsbCloudWebApi.Tests/Background/PeriodicBackgroundWorkerTest.cs
@@ -8,7 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Xunit;
-namespace AsbCloudWebApi.Tests.UnitTests.Background;
+namespace AsbCloudWebApi.Tests.Background;
//TODO: нужно поправить тесты, иногда они не проходят
public class PeriodicBackgroundWorkerTest
@@ -55,7 +55,7 @@ public class PeriodicBackgroundWorkerTest
var stopwatch = Stopwatch.StartNew();
service.Add(work, period);
- var delay = (periodMs / 20) + (periodMs * workCount) - stopwatch.ElapsedMilliseconds;
+ var delay = periodMs / 20 + periodMs * workCount - stopwatch.ElapsedMilliseconds;
await Task.Delay(TimeSpan.FromMilliseconds(delay));
//assert
@@ -86,7 +86,7 @@ public class PeriodicBackgroundWorkerTest
service.Add(badWork, TimeSpan.FromSeconds(2));
service.Add(goodWork, TimeSpan.FromSeconds(2));
- await Task.Delay(TimeSpan.FromMilliseconds(128));
+ await Task.Delay(TimeSpan.FromMilliseconds(256));
//assert
Assert.Equal(expectadResult, result);
diff --git a/AsbCloudWebApi.Tests/Background/WorkTest.cs b/AsbCloudWebApi.Tests/Background/WorkTest.cs
new file mode 100644
index 00000000..41fc486d
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Background/WorkTest.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudInfrastructure.Background;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Background;
+
+public class WorkTest
+{
+ private readonly IServiceProvider serviceProviderMock = Substitute.For();
+ private readonly IServiceScope serviceScopeMock = Substitute.For();
+ private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For();
+
+ public WorkTest()
+ {
+ serviceScopeFactoryMock.CreateScope().Returns(serviceScopeMock);
+ ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock);
+ }
+
+ [Fact]
+ public async Task Start_ShouldReturn_Success()
+ {
+ //arrange
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ => Task.CompletedTask;
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ await work.Start(serviceProviderMock, CancellationToken.None);
+ var done = DateTime.Now;
+ var executionTime = done - begin;
+
+ //assert
+ Assert.Equal(1, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(0, work.CountErrors);
+ Assert.Null(work.CurrentState);
+ Assert.Null(work.LastError);
+
+ var lastState = work.LastComplete;
+ Assert.NotNull(lastState);
+ Assert.InRange(lastState.Start, begin, done - 0.5 * executionTime);
+ Assert.InRange(lastState.End, done - 0.5 * executionTime, done);
+ Assert.InRange(lastState.ExecutionTime, TimeSpan.Zero, executionTime);
+ }
+
+ [Fact]
+ public async Task ExecutionWork_Invokes_Callback()
+ {
+ //arrange
+ const string expectedState = "42";
+ const double expectedProgress = 42d;
+
+ var timeout = TimeSpan.FromMilliseconds(80);
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ callback.Invoke(expectedState, expectedProgress);
+ return Task.Delay(timeout);
+ }
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ _ = work.Start(serviceProviderMock, CancellationToken.None);
+ await Task.Delay(timeout / 3);
+
+ //assert
+ Assert.Equal(0, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(0, work.CountErrors);
+ Assert.NotNull(work.CurrentState);
+ Assert.Null(work.LastComplete);
+ Assert.Null(work.LastError);
+
+ var currentState = work.CurrentState;
+ Assert.NotNull(currentState);
+ Assert.InRange(currentState.Start, begin, begin + timeout);
+ Assert.InRange(currentState.StateUpdate, begin, begin + timeout);
+ Assert.Equal(expectedState, currentState.State);
+ Assert.Equal(expectedProgress, currentState.Progress);
+ }
+
+ [Fact]
+ public async Task ExecutionWork_ShouldReturn_FailsWithInfo()
+ {
+ //arrange
+ const string expectedState = "41";
+ const string expectedErrorText = "42";
+ var minWorkTime = TimeSpan.FromMilliseconds(10);
+
+ async Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ {
+ await Task.Delay(minWorkTime);
+ callback(expectedState, 0);
+ throw new Exception(expectedErrorText);
+ }
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ await work.Start(serviceProviderMock, CancellationToken.None);
+
+ //assert
+ Assert.Equal(0, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(1, work.CountErrors);
+ Assert.Null(work.CurrentState);
+ Assert.Null(work.LastComplete);
+
+ var error = work.LastError;
+ Assert.NotNull(error);
+ Assert.InRange(error.Start, begin, DateTime.Now);
+ Assert.InRange(error.End, begin, DateTime.Now);
+ Assert.InRange(error.ExecutionTime, minWorkTime, DateTime.Now - begin);
+ Assert.Contains(expectedErrorText, error.ErrorText, StringComparison.InvariantCultureIgnoreCase);
+ Assert.Equal(expectedState, error.State);
+ }
+
+ [Fact]
+ public async Task Stop_ShouldReturn_Success()
+ {
+ //arrange
+ var workTime = TimeSpan.FromMilliseconds(1_000);
+
+ Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token)
+ => Task.Delay(workTime, token);
+
+ var work = Work.CreateByDelegate("", workAction);
+
+ //act
+ var begin = DateTime.Now;
+ _ = work.Start(serviceProviderMock, CancellationToken.None);
+ await Task.Delay(10);
+ work.Stop();
+ await Task.Delay(10);
+
+ //assert
+ Assert.Equal(0, work.CountComplete);
+ Assert.Equal(1, work.CountStart);
+ Assert.Equal(1, work.CountErrors);
+ Assert.Null(work.LastComplete);
+ Assert.NotNull(work.LastError);
+ }
+}
\ No newline at end of file
diff --git a/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs b/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs
new file mode 100644
index 00000000..d9665e4d
--- /dev/null
+++ b/AsbCloudWebApi.Tests/Services/DailyReportServiceTest.cs
@@ -0,0 +1,583 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AsbCloudApp.Data;
+using AsbCloudApp.Data.DailyReport;
+using AsbCloudApp.Data.DailyReport.Blocks.Sign;
+using AsbCloudApp.Data.DailyReport.Blocks.Subsystems;
+using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance;
+using AsbCloudApp.Data.DetectedOperation;
+using AsbCloudApp.Data.ProcessMaps.Report;
+using AsbCloudApp.Data.Subsystems;
+using AsbCloudApp.Data.Trajectory;
+using AsbCloudApp.Exceptions;
+using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
+using AsbCloudApp.Services.ProcessMaps.WellDrilling;
+using AsbCloudInfrastructure.Services.DailyReport;
+using NSubstitute;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.Services;
+
+public class DailyReportServiceTest
+{
+ private const int idDailyReport = 1;
+ private const int idUser = 3;
+ private const int idWell = 2;
+
+ private readonly SubsystemBlockDto fakeSubsystemBlock = new()
+ {
+ IdUser = idUser,
+ Wellbore = 999,
+ MeasurementsPerDay = 999,
+ TotalRopPlan = 999,
+ Comment = "Увеличить обороты",
+ Subsystems = new[]
+ {
+ new SubsystemRecordDto
+ {
+ Name = "АвтоСПО",
+ UsagePerDay = new SubsystemParametersDto
+ {
+ UsedTimeHours = 24,
+ SumDepthInterval = 1500,
+ KUsage = 15
+ },
+ UsagePerWell = new SubsystemParametersDto
+ {
+ UsedTimeHours = 500,
+ SumDepthInterval = 3000,
+ KUsage = 100
+ }
+ }
+ }
+ };
+
+ private readonly SignBlockDto fakeSignBlock = new()
+ {
+ IdUser = idUser,
+ DrillingMaster = new SignRecordDto
+ {
+ Name = "Иван",
+ Patronymic = "Иванович",
+ Surname = "Иванов"
+ },
+ Supervisor = new SignRecordDto()
+ {
+ Name = "Илья",
+ Patronymic = "Ильич",
+ Surname = "Бурилов"
+ }
+ };
+
+ private readonly TimeBalanceBlockDto fakeTimeBalanceBlock = new()
+ {
+ IdUser = idUser,
+ IdSection = 1,
+ WellDepth = new PlanFactDto
+ {
+ Plan = 2000
+ },
+ WellOperations = new[]
+ {
+ new TimeBalanceRecordDto
+ {
+ IdWellOperation = 1,
+ DurationHours = new PlanFactDto
+ {
+ Fact = 100,
+ Plan = 150,
+ },
+ DrillingDeviationPerSection = 90,
+ DrillingDeviationPerDay = 100,
+ ReasonDeviation = "Отклонение"
+ }
+ }
+ };
+
+ private readonly DetectedOperationListDto fakeWellOperationSlipsTime = new()
+ {
+ Stats = new[]
+ {
+ new DetectedOperationDrillersStatDto
+ {
+ Count = 40
+ }
+ }
+ };
+
+ private readonly ProcessMapReportWellDrillingDto fakeProcessMapReportWellDrilling = new()
+ {
+ DrillingMode = "Ротор",
+ DateStart = new DateTime(2023, 10, 26),
+ DeltaDepth = 500,
+ Rop = new PlanFactDto
+ {
+ Plan = 300,
+ Fact = 500
+ },
+ MechDrillingHours = 100
+ };
+
+ private readonly WellSectionTypeDto fakeSectionType = new()
+ {
+ Id = 1,
+ Caption = "Пилотный ствол",
+ };
+
+ private readonly TrajectoryGeoFactDto fakeLastFactTrajectory = new()
+ {
+ WellboreDepth = 100,
+ VerticalDepth = 150,
+ ZenithAngle = 3,
+ AzimuthGeo = 5
+ };
+
+ private readonly CompanyDto fakeCustomer = new()
+ {
+ Caption = "Тестовый заказчик",
+ IdCompanyType = 1
+ };
+
+ private readonly CompanyDto fakeContractor = new()
+ {
+ Caption = "Тестовый подрядчик",
+ IdCompanyType = 2
+ };
+
+ private readonly WellOperationDto fakeFirstFactWellOperation = new()
+ {
+ IdWell = idWell,
+ IdParentCategory = 4001,
+ IdWellSectionType = 1,
+ CategoryName = "Механическое. бурение",
+ DateStart = new DateTime(2023, 10, 26),
+ DepthStart = 80,
+ DepthEnd = 150,
+ DurationHours = 8,
+ };
+
+ private readonly WellOperationDto fakeLastFactWellOperation = new()
+ {
+ IdWell = idWell,
+ CategoryName = "Механическое. бурение",
+ IdWellSectionType = 1,
+ IdParentCategory = 4001,
+ DateStart = new DateTime(2023, 10, 26),
+ DepthStart = 150,
+ DepthEnd = 200,
+ DurationHours = 8,
+ };
+
+ private readonly ScheduleDto fakeShedule = new()
+ {
+ IdWell = idWell,
+ ShiftStart = new TimeDto(1),
+ ShiftEnd = new TimeDto(5),
+ DrillStart = new DateTime(2023, 01, 26),
+ DrillEnd = new DateTime(2023, 12, 26),
+ Driller = new()
+ {
+ Name = "Иван",
+ Surname = "Иванов",
+ Patronymic = "Бурила"
+ }
+ };
+
+ private readonly SubsystemStatDto fakeSubsystemsStat = new()
+ {
+ SubsystemName = "АПД",
+ SumDepthInterval = 250,
+ UsedTimeHours = 200,
+ KUsage = 30
+ };
+
+ private readonly SimpleTimezoneDto fakeWellTimezone = new()
+ {
+ Hours = 5,
+ };
+
+ private readonly IWellService wellServiceMock = Substitute.For();
+ private readonly ITrajectoryNnbRepository trajectoryFactNnbRepositoryMock = Substitute.For();
+ private readonly IDailyReportRepository dailyReportRepositoryMock = Substitute.For();
+ private readonly IScheduleRepository scheduleRepositoryMock = Substitute.For();
+ private readonly IWellOperationRepository wellOperationRepositoryMock = Substitute.For();
+ private readonly ISubsystemService subsystemServiceMock = Substitute.For();
+ private readonly IProcessMapReportWellDrillingService processMapReportWellDrillingServiceMock = Substitute.For();
+ private readonly IDetectedOperationService detectedOperationServiceMock = Substitute.For();
+
+ private readonly DailyReportService dailyReportService;
+
+ private readonly DailyReportDto fakeDailyReport;
+ private readonly WellDto fakeWell;
+ private readonly DatesRangeDto fakeDatesRange;
+
+ public DailyReportServiceTest()
+ {
+ fakeDailyReport = new DailyReportDto
+ {
+ Id = idDailyReport,
+ IdWell = idWell,
+ Date = new(2023, 10, 26),
+ DateLastUpdate = null
+ };
+
+ fakeWell = new WellDto
+ {
+ Id = idWell,
+ Caption = "Тестовое название",
+ WellType = "Горизонтальная",
+ Cluster = "Тестовый куст",
+ Deposit = "Тестовое месторождение",
+ Companies = new[] { fakeCustomer, fakeContractor }
+ };
+
+ fakeDatesRange = new DatesRangeDto
+ {
+ From = fakeFirstFactWellOperation.DateStart.DateTime,
+ To = fakeLastFactWellOperation.DateStart.DateTime
+ };
+
+ dailyReportService = new DailyReportService(wellServiceMock,
+ trajectoryFactNnbRepositoryMock,
+ dailyReportRepositoryMock,
+ scheduleRepositoryMock,
+ wellOperationRepositoryMock,
+ subsystemServiceMock,
+ processMapReportWellDrillingServiceMock,
+ detectedOperationServiceMock);
+
+ dailyReportRepositoryMock.InsertAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(idDailyReport);
+
+ dailyReportRepositoryMock.GetOrDefaultAsync(Arg.Any(), Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(fakeDailyReport);
+
+ dailyReportRepositoryMock.UpdateAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(idDailyReport);
+
+ wellServiceMock.GetOrDefaultAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(fakeWell);
+
+ trajectoryFactNnbRepositoryMock.GetByRequestAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(new[] { fakeLastFactTrajectory });
+
+ wellOperationRepositoryMock.GetAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(new[] { fakeFirstFactWellOperation, fakeLastFactWellOperation });
+
+ wellOperationRepositoryMock.GetDatesRangeAsync(Arg.Any(), Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(fakeDatesRange);
+
+ wellOperationRepositoryMock.GetSectionTypes()
+ .ReturnsForAnyArgs(new[] { fakeSectionType });
+
+ detectedOperationServiceMock.GetAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(fakeWellOperationSlipsTime);
+
+ subsystemServiceMock.GetStatAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(new[] { fakeSubsystemsStat });
+
+ scheduleRepositoryMock.GetAsync(Arg.Any(), Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(new[] { fakeShedule });
+
+ processMapReportWellDrillingServiceMock.GetAsync(Arg.Any(), Arg.Any())
+ .ReturnsForAnyArgs(new[] { fakeProcessMapReportWellDrilling });
+
+ wellServiceMock.GetTimezone(Arg.Any())
+ .ReturnsForAnyArgs(fakeWellTimezone);
+ }
+
+ [Fact]
+ public async Task UpdateOrInsertAsync_ShouldReturn_UpdatedSubsystemBlock()
+ {
+ //act
+ var result = await dailyReportService.UpdateOrInsertAsync(idWell, fakeDailyReport.Date, idUser, fakeSubsystemBlock, CancellationToken.None);
+
+ //assert
+ Assert.NotNull(fakeSubsystemBlock.LastUpdateDate);
+ Assert.NotNull(fakeDailyReport.DateLastUpdate);
+ Assert.Equal(fakeSubsystemBlock.IdUser, idUser);
+ Assert.Equal(fakeDailyReport.SubsystemBlock, fakeSubsystemBlock);
+ Assert.Equal(idDailyReport, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(DateDailyReport))]
+ public async Task UpdateOrInsertAsync_ShouldReturn_UnableToUpdateDailyReport(DateOnly dateDailyReport)
+ {
+ //act
+ var result = await Assert.ThrowsAsync(() => dailyReportService.UpdateOrInsertAsync(
+ idWell,
+ dateDailyReport,
+ idUser,
+ fakeSignBlock,
+ CancellationToken.None));
+
+ //assert
+ Assert.Contains("Невозможно обновить суточный отчёт", result.Message);
+ }
+
+ [Fact]
+ public async Task UpdateOrInsertAsync_ShouldReturn_UpdatedSignBlock()
+ {
+ //act
+ var result = await dailyReportService.UpdateOrInsertAsync(idWell, fakeDailyReport.Date, idUser, fakeSignBlock, CancellationToken.None);
+
+ //assert
+ Assert.NotNull(fakeSignBlock.LastUpdateDate);
+ Assert.NotNull(fakeDailyReport.DateLastUpdate);
+ Assert.Equal(fakeSignBlock.IdUser, idUser);
+ Assert.Equal(fakeDailyReport.SignBlock, fakeSignBlock);
+ Assert.Equal(idDailyReport, result);
+ }
+
+ [Fact]
+ public async Task UpdateOrInsertAsync_ShouldReturn_UpdatedTimeBalanceBlock()
+ {
+ //act
+ var result = await dailyReportService.UpdateOrInsertAsync(idWell, fakeDailyReport.Date, idUser, fakeTimeBalanceBlock,
+ CancellationToken.None);
+
+ //assert
+ Assert.NotNull(fakeTimeBalanceBlock.LastUpdateDate);
+ Assert.NotNull(fakeDailyReport.DateLastUpdate);
+ Assert.Equal(fakeTimeBalanceBlock.IdUser, idUser);
+ Assert.Equal(fakeDailyReport.TimeBalanceBlock, fakeTimeBalanceBlock);
+ Assert.Equal(idDailyReport, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(DateDailyReport))]
+ public async Task GetAsync_ShouldReturn_UnableToGetDailyReport(DateOnly dateDailyReport)
+ {
+ //act
+ var result = await Assert.ThrowsAsync(() => dailyReportService.GetAsync(idWell,
+ dateDailyReport,
+ CancellationToken.None));
+
+ //assert
+ Assert.Contains("Невозможно получить суточный отчёт", result.Message);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_AddedWellInfo()
+ {
+ //act
+ var result = await dailyReportService.GetAsync(idWell, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.Equal(result.IdWell, fakeWell.Id);
+ Assert.Equal(result.WellCaption, fakeWell.Caption);
+ Assert.Equal(result.WellType, fakeWell.WellType);
+ Assert.Equal(result.Cluster, fakeWell.Cluster);
+ Assert.Equal(result.Deposit, fakeWell.Deposit);
+ Assert.Equal(result.Customer, fakeCustomer.Caption);
+ Assert.Equal(result.Contractor, fakeContractor.Caption);
+ Assert.Equal(result.DepthStart, fakeFirstFactWellOperation.DepthStart);
+ Assert.Equal(result.DepthEnd, fakeLastFactWellOperation.DepthEnd);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_AddedTrajectoryBlock()
+ {
+ //act
+ var result = await dailyReportService.GetAsync(idWell, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.Equal(fakeLastFactTrajectory.WellboreDepth, result.TrajectoryBlock.WellboreDepth);
+ Assert.Equal(fakeLastFactTrajectory.VerticalDepth, result.TrajectoryBlock.VerticalDepth);
+ Assert.Equal(fakeLastFactTrajectory.ZenithAngle, result.TrajectoryBlock.ZenithAngle);
+ Assert.Equal(fakeLastFactTrajectory.AzimuthGeo, result.TrajectoryBlock.AzimuthGeo);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_AddedFactWellOperationBlock()
+ {
+ //act
+ var result = await dailyReportService.GetAsync(idWell, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.Equal(16, result.FactWellOperationBlock.SectionDrillingHours);
+ Assert.Single(result.FactWellOperationBlock.WellOperations);
+
+ var wellOperation = result.FactWellOperationBlock.WellOperations.Single();
+
+ Assert.Equal("Механическое. бурение", wellOperation.CategoryName);
+ Assert.Equal(16, wellOperation.DurationHours);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_AddedScheduleBlock()
+ {
+ //act
+ var result = await dailyReportService.GetAsync(idWell, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.Single(result.ScheduleBlock);
+
+ var sheduleRecord = result.ScheduleBlock.Single();
+
+ Assert.Equal(fakeShedule.ShiftStart, sheduleRecord.ShiftStart);
+ Assert.Equal(fakeShedule.ShiftEnd, sheduleRecord.ShiftEnd);
+ Assert.Equal(fakeShedule.Driller?.Name, sheduleRecord.Name);
+ Assert.Equal(fakeShedule.Driller?.Surname, sheduleRecord.Surname);
+ Assert.Equal(fakeShedule.Driller?.Patronymic, sheduleRecord.Patronymic);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_UpdatedTimeBalanceBlock()
+ {
+ //arrange
+ fakeDailyReport.TimeBalanceBlock = fakeTimeBalanceBlock;
+
+ //act
+ var result = await dailyReportService.GetAsync(idWell, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.NotNull(result.TimeBalanceBlock);
+ Assert.Equal(fakeSectionType.Id, result.TimeBalanceBlock.IdSection);
+ Assert.Equal(fakeSectionType.Caption, result.TimeBalanceBlock.SectionName);
+ Assert.Equal(2000, result.TimeBalanceBlock?.WellDepth.Plan);
+ Assert.Equal(120, result.TimeBalanceBlock?.WellDepth.Fact);
+ Assert.Equal(40, result.TimeBalanceBlock?.WellOperationSlipsTimeCount);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_AddedProcessMapWellDrillingBlock()
+ {
+ //act
+ var result = await dailyReportService.GetAsync(idWell, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.Single(result.ProcessMapWellDrillingBlock);
+
+ var processMapWellDrillingRecord = result.ProcessMapWellDrillingBlock.Single();
+
+ Assert.Equal(fakeProcessMapReportWellDrilling.DrillingMode, processMapWellDrillingRecord.DrillingMode);
+ Assert.Equal(fakeProcessMapReportWellDrilling.Rop.Plan, processMapWellDrillingRecord.Rop.Plan);
+ Assert.Equal(fakeProcessMapReportWellDrilling.Rop.Fact, processMapWellDrillingRecord.Rop.Fact);
+ Assert.Equal(fakeProcessMapReportWellDrilling.DeltaDepth, processMapWellDrillingRecord.WellBoreDepth);
+ Assert.Equal(fakeProcessMapReportWellDrilling.MechDrillingHours, processMapWellDrillingRecord.MechDrillingHours);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_UpdatedSubsystemBlock()
+ {
+ //arrange
+ fakeDailyReport.SubsystemBlock = fakeSubsystemBlock;
+
+ //act
+ var result = await dailyReportService.GetAsync(idDailyReport, fakeDailyReport.Date, CancellationToken.None);
+
+ //assert
+ Assert.NotNull(result.SubsystemBlock);
+ Assert.Equal(2, result.SubsystemBlock?.Subsystems.Count());
+
+ var subsystemRecord0 = result.SubsystemBlock?.Subsystems.ElementAt(0);
+
+ Assert.Equal("АвтоСПО", subsystemRecord0?.Name);
+ Assert.Equal(24, subsystemRecord0?.UsagePerDay?.UsedTimeHours);
+ Assert.Equal(1500, subsystemRecord0?.UsagePerDay?.SumDepthInterval);
+ Assert.Equal(15, subsystemRecord0?.UsagePerDay?.KUsage);
+
+ Assert.Equal(500, subsystemRecord0?.UsagePerWell?.UsedTimeHours);
+ Assert.Equal(3000, subsystemRecord0?.UsagePerWell?.SumDepthInterval);
+ Assert.Equal(100, subsystemRecord0?.UsagePerWell?.KUsage);
+
+ var subsystemRecord1 = result.SubsystemBlock?.Subsystems.ElementAt(1);
+
+ Assert.Equal("АПД", subsystemRecord1?.Name);
+ Assert.Equal(200, subsystemRecord1?.UsagePerDay?.UsedTimeHours);
+ Assert.Equal(250, subsystemRecord1?.UsagePerDay?.SumDepthInterval);
+ Assert.Equal(30, subsystemRecord1?.UsagePerDay?.KUsage);
+
+ Assert.Equal(200, subsystemRecord1?.UsagePerWell?.UsedTimeHours);
+ Assert.Equal(250, subsystemRecord1?.UsagePerWell?.SumDepthInterval);
+ Assert.Equal(30, subsystemRecord1?.UsagePerWell?.KUsage);
+ }
+
+ [Fact]
+ public async Task GetAsync_ShouldReturn_FictiveDailyReport()
+ {
+ //arrange
+ var expectedCount = (fakeLastFactWellOperation.DateStart - fakeFirstFactWellOperation.DateStart).TotalDays + 1;
+
+ //act
+ var result = await dailyReportService.GetAsync(idWell, new FileReportRequest(), CancellationToken.None);
+
+ //assert
+ Assert.Equal(expectedCount, result.Count);
+ }
+
+ [Theory]
+ [MemberData(nameof(FactWellOperationDatesRange))]
+ public async Task GetDatesRangeAsync_ShouldReturn_DateRangeByFactWellOperations(DatesRangeDto datesRange)
+ {
+ //arrange
+ wellOperationRepositoryMock.GetDatesRangeAsync(Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns(datesRange);
+
+ //act
+ var result = await dailyReportService.GetDatesRangeAsync(idWell, CancellationToken.None);
+
+ //assert
+ Assert.NotNull(result);
+ Assert.True(result.From <= result.To);
+ Assert.True(result.To < DateTime.UtcNow.Date);
+ }
+
+ public static IEnumerable