using Microsoft.Extensions.DependencyInjection; using Shouldly; using Tnsu.ApprovalRoute.Application.RequestTypes; using Tnsu.ApprovalRoute.Application.TemplateRoutes; using Tnsu.ApprovalRoute.Application.TemplateRouteSteps; using Tnsu.ApprovalRoute.Domain.Enums; using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; public sealed class TemplateRouteStepServiceTests : IClassFixture { private const string Company = "ТОО TANSU Construction"; private readonly TemplateRouteStepService _service; private readonly TemplateRouteService _routeService; private readonly RequestTypeService _requestTypeService; public TemplateRouteStepServiceTests(PostgresDatabaseFixture fixture) { var services = TestServiceProviderFactory.Build(fixture.ConnectionString); _service = services.GetRequiredService(); _routeService = services.GetRequiredService(); _requestTypeService = services.GetRequiredService(); } private async Task CreateRouteAsync(string requestTypeName, bool byProject) { var requestTypeId = (await _requestTypeService.CreateAsync( new CreateRequestTypeRequest(requestTypeName, $"/{requestTypeName}"), CancellationToken.None)).Value.Id; var route = await _routeService.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, null, Company, byProject, RequiresControl: false), CancellationToken.None); return route.Value.Id; } [Fact(DisplayName = "Создание шага для несуществующего маршрута возвращает неуспешный результат")] public async Task CreateAsync_UnknownRoute_ReturnsFailedResult() { // Action var result = await _service.CreateAsync( 999999, new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Assert result.IsFailed.ShouldBeTrue(); } [Fact(DisplayName = "Роль в проекте недопустима для шага маршрута без признака «по проекту»")] public async Task CreateAsync_ProjectRoleOnNonProjectRoute_ReturnsFailedResult() { // Arrange var routeId = await CreateRouteAsync("Non project route", byProject: false); // Action var result = await _service.CreateAsync( routeId, new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.ProjectManager, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Assert result.IsFailed.ShouldBeTrue(); } [Fact(DisplayName = "Создание шага маршрута и получение его по Id возвращает сохранённые данные")] public async Task CreateAsync_ThenGetAsync_ReturnsCreatedStep() { // Arrange var routeId = await CreateRouteAsync("Simple route", byProject: false); // Action var created = await _service.CreateAsync( routeId, new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Assert created.IsSuccess.ShouldBeTrue(); var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None); fetched.IsSuccess.ShouldBeTrue(); fetched.Value.ExecutorEmail.ShouldBe("a@b.kz"); } [Fact(DisplayName = "Создание шага без роли в проекте на маршруте «по проекту» возвращает предупреждение")] public async Task CreateAsync_ByProjectRouteWithoutProjectRoleStep_ReturnsWarning() { // Arrange var routeId = await CreateRouteAsync("By-project route without role", byProject: true); // Action var created = await _service.CreateAsync( routeId, new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Assert created.IsSuccess.ShouldBeTrue(); created.Value.Warning.ShouldNotBeNull(); created.Value.Warning.ShouldContain("ролью в проекте"); } [Fact(DisplayName = "Создание шага с ролью в проекте на маршруте «по проекту» не возвращает предупреждение")] public async Task CreateAsync_ByProjectRouteWithProjectRoleStep_HasNoWarning() { // Arrange var routeId = await CreateRouteAsync("By-project route with role", byProject: true); // Action var created = await _service.CreateAsync( routeId, new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.ProjectManager, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Assert created.IsSuccess.ShouldBeTrue(); created.Value.Warning.ShouldBeNull(); } [Fact(DisplayName = "Обновление шага маршрута изменяет исполнителя, статус и разрешение на переназначение")] public async Task UpdateAsync_ChangesFields() { // Arrange var routeId = await CreateRouteAsync("Route to update step", byProject: false); var created = await _service.CreateAsync( routeId, new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Action var updated = await _service.UpdateAsync( routeId, created.Value.Id, new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Execution, true), CancellationToken.None); // Assert updated.IsSuccess.ShouldBeTrue(); updated.Value.ExecutorEmail.ShouldBe("b@b.kz"); updated.Value.Status.ShouldBe(RouteStepStatus.Execution); updated.Value.AllowReassignToOtherApprovers.ShouldBeTrue(); } [Fact(DisplayName = "Удаление шага маршрута убирает его из базы")] public async Task DeleteAsync_RemovesStep() { // Arrange var routeId = await CreateRouteAsync("Route to delete step", byProject: false); var created = await _service.CreateAsync( routeId, new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), CancellationToken.None); // Action var deleted = await _service.DeleteAsync(routeId, created.Value.Id, CancellationToken.None); // Assert deleted.IsSuccess.ShouldBeTrue(); var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None); fetched.IsFailed.ShouldBeTrue(); } }