212 lines
9.9 KiB
C#
212 lines
9.9 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using Moq;
|
|
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.Infrastructure.TnsuPortal;
|
|
using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure;
|
|
|
|
namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices;
|
|
|
|
public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFixture>
|
|
{
|
|
private const string Company = "ТОО TANSU Construction";
|
|
private const string Department = "IT";
|
|
|
|
private readonly ServiceProvider _services;
|
|
private readonly TemplateRouteService _service;
|
|
private readonly RequestTypeService _requestTypeService;
|
|
|
|
public TemplateRouteServiceTests(PostgresDatabaseFixture fixture)
|
|
{
|
|
var portalClient = new Mock<ITnsuPortalClient>();
|
|
portalClient
|
|
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new EmployeeDirectoryInfo(Company, Department));
|
|
portalClient
|
|
.Setup(x => x.GetProjectExecutorsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync([new ProjectExecutorInfo(ProjectRole.ProjectManager, "pm@tnsu.kz")]);
|
|
|
|
_services = TestServiceProviderFactory.Build(fixture.ConnectionString, portalClient: portalClient.Object);
|
|
_service = _services.GetRequiredService<TemplateRouteService>();
|
|
_requestTypeService = _services.GetRequiredService<RequestTypeService>();
|
|
}
|
|
|
|
private async Task<int> CreateRequestTypeAsync(string name) =>
|
|
(await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}"), CancellationToken.None)).Value.Id;
|
|
|
|
[Fact(DisplayName = "Создание маршрута и получение его по Id возвращает сохранённые данные")]
|
|
public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Vacation");
|
|
|
|
// Action
|
|
var created = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department, Company, ByProject: false, RequiresControl: true),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
created.IsSuccess.ShouldBeTrue();
|
|
created.Value.RequestTypeId.ShouldBe(requestTypeId);
|
|
|
|
var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None);
|
|
fetched.IsSuccess.ShouldBeTrue();
|
|
fetched.Value.Company.ShouldBe(Company);
|
|
}
|
|
|
|
[Fact(DisplayName = "Создание маршрута с несуществующим типом заявки возвращает неуспешный результат")]
|
|
public async Task CreateAsync_UnknownRequestType_ReturnsFailedResult()
|
|
{
|
|
// Action
|
|
var result = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, 999999, Department, Company, ByProject: false, RequiresControl: false),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
result.IsFailed.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact(DisplayName = "Повторное создание маршрута с тем же типом заявки, отделом, компанией и признаком «по проекту» возвращает конфликт")]
|
|
public async Task CreateAsync_DuplicateRoute_ReturnsFailedResult()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Business trip");
|
|
var request = new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department, Company, ByProject: false, RequiresControl: false);
|
|
var first = await _service.CreateAsync(request, CancellationToken.None);
|
|
first.IsSuccess.ShouldBeTrue();
|
|
|
|
// Action
|
|
var duplicate = await _service.CreateAsync(request, CancellationToken.None);
|
|
|
|
// Assert
|
|
duplicate.IsFailed.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact(DisplayName = "Обновление маршрута изменяет тип маршрута и признак необходимости контроля")]
|
|
public async Task UpdateAsync_ChangesFields()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Sick leave");
|
|
var created = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department, Company, ByProject: false, RequiresControl: false),
|
|
CancellationToken.None);
|
|
|
|
// Action
|
|
var updated = await _service.UpdateAsync(
|
|
created.Value.Id,
|
|
new UpdateTemplateRouteRequest(RouteType.Parallel, Department, Company, ByProject: false, RequiresControl: true),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
updated.IsSuccess.ShouldBeTrue();
|
|
updated.Value.RouteType.ShouldBe(RouteType.Parallel);
|
|
updated.Value.RequiresControl.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact(DisplayName = "Удаление маршрута убирает его из базы")]
|
|
public async Task DeleteAsync_RemovesEntity()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Remote work");
|
|
var created = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department, Company, ByProject: false, RequiresControl: false),
|
|
CancellationToken.None);
|
|
|
|
// Action
|
|
var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None);
|
|
|
|
// Assert
|
|
deleted.IsSuccess.ShouldBeTrue();
|
|
|
|
var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None);
|
|
fetched.IsFailed.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact(DisplayName = "Резолв маршрута по точному совпадению отдела возвращает шаги с нужным статусом")]
|
|
public async Task ResolveAsync_ExactDepartmentMatch_ReturnsMatchingStepsForStatus()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Exact match request");
|
|
var route = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department, Company, ByProject: false, RequiresControl: true),
|
|
CancellationToken.None);
|
|
|
|
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
|
await stepService.CreateAsync(
|
|
route.Value.Id,
|
|
new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", "Lead", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false),
|
|
CancellationToken.None);
|
|
|
|
// Action
|
|
var resolved = await _service.ResolveAsync(
|
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
resolved.IsSuccess.ShouldBeTrue();
|
|
resolved.Value.RouteId.ShouldBe(route.Value.Id);
|
|
resolved.Value.Steps.ShouldHaveSingleItem();
|
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz");
|
|
}
|
|
|
|
[Fact(DisplayName = "Резолв маршрута с ролью в проекте подставляет исполнителя из проекта")]
|
|
public async Task ResolveAsync_ByProjectRole_UsesProjectExecutorEmail()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Project request");
|
|
var route = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department, Company, ByProject: true, RequiresControl: false),
|
|
CancellationToken.None);
|
|
|
|
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
|
await stepService.CreateAsync(
|
|
route.Value.Id,
|
|
new CreateTemplateRouteStepRequest(1, "", "Lead", ProjectRole.ProjectManager, HeadType.None, RouteStepStatus.Approval, false),
|
|
CancellationToken.None);
|
|
|
|
// Action
|
|
var resolved = await _service.ResolveAsync(
|
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", Guid.NewGuid(), RouteStepStatus.Approval),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
resolved.IsSuccess.ShouldBeTrue();
|
|
resolved.Value.Steps.ShouldHaveSingleItem();
|
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("pm@tnsu.kz");
|
|
}
|
|
|
|
[Fact(DisplayName = "Резолв маршрута откатывается на маршрут без отдела, если точного совпадения по отделу нет")]
|
|
public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute()
|
|
{
|
|
// Arrange
|
|
var requestTypeId = await CreateRequestTypeAsync("Company-wide request");
|
|
var route = await _service.CreateAsync(
|
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, Department: null, Company, ByProject: false, RequiresControl: false),
|
|
CancellationToken.None);
|
|
|
|
// Action
|
|
var resolved = await _service.ResolveAsync(
|
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Execution),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
resolved.IsSuccess.ShouldBeTrue();
|
|
resolved.Value.RouteId.ShouldBe(route.Value.Id);
|
|
}
|
|
|
|
[Fact(DisplayName = "Резолв маршрута с несуществующим типом заявки возвращает неуспешный результат")]
|
|
public async Task ResolveAsync_UnknownRequestType_ReturnsFailedResult()
|
|
{
|
|
// Action
|
|
var resolved = await _service.ResolveAsync(
|
|
new ResolveTemplateRouteRequest(999999, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
|
CancellationToken.None);
|
|
|
|
// Assert
|
|
resolved.IsFailed.ShouldBeTrue();
|
|
}
|
|
}
|