апи для работы с маршрутами согласования #2

Merged
admin merged 38 commits from initial-api into main 2026-07-27 13:14:32 +00:00
3 changed files with 92 additions and 60 deletions
Showing only changes of commit 0bf5475553 - Show all commits
@@ -5,91 +5,98 @@ using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure;
namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices;
[CollectionDefinition(Name)] public sealed class RequestTypeServiceTests : IClassFixture<PostgresDatabaseFixture>
public sealed class RequestTypeServiceCollection : ICollectionFixture<PostgresDatabaseFixture>
{ {
public const string Name = nameof(RequestTypeServiceCollection);
}
[Collection(RequestTypeServiceCollection.Name)]
public sealed class RequestTypeServiceTests : IDisposable
{
private readonly ServiceProvider _services;
private readonly RequestTypeService _service; private readonly RequestTypeService _service;
public RequestTypeServiceTests(PostgresDatabaseFixture fixture) public RequestTypeServiceTests(PostgresDatabaseFixture fixture)
{ {
_services = TestServiceProviderFactory.Build(fixture.ConnectionString); _service = TestServiceProviderFactory.Build(fixture.ConnectionString).GetRequiredService<RequestTypeService>();
_service = _services.GetRequiredService<RequestTypeService>();
} }
public void Dispose() => _services.Dispose(); [Fact(DisplayName = "Создание типа заявки и получение его по Id возвращает сохранённые данные")]
[Fact]
public async Task CreateAsync_Then_GetAsync_ReturnsCreatedEntity() public async Task CreateAsync_Then_GetAsync_ReturnsCreatedEntity()
{ {
// Action
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Vacation", "/vacation"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Vacation", "/vacation"), CancellationToken.None);
// Assert
created.IsSuccess.ShouldBeTrue(); created.IsSuccess.ShouldBeTrue();
created.Value.Name.ShouldBe("Vacation"); created.Value.Name.ShouldBe("Vacation");
created.Value.PageUrl.ShouldBe("/vacation"); created.Value.PageUrl.ShouldBe("/vacation");
// Action
var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None);
// Assert
fetched.IsSuccess.ShouldBeTrue(); fetched.IsSuccess.ShouldBeTrue();
fetched.Value.Id.ShouldBe(created.Value.Id); fetched.Value.Id.ShouldBe(created.Value.Id);
fetched.Value.Name.ShouldBe("Vacation"); fetched.Value.Name.ShouldBe("Vacation");
} }
[Fact] [Fact(DisplayName = "Получение несуществующего типа заявки возвращает неуспешный результат")]
public async Task GetAsync_UnknownId_ReturnsFailedResult() public async Task GetAsync_UnknownId_ReturnsFailedResult()
{ {
// Action
var result = await _service.GetAsync(Guid.NewGuid(), CancellationToken.None); var result = await _service.GetAsync(Guid.NewGuid(), CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Обновление типа заявки изменяет имя и url страницы")]
public async Task UpdateAsync_ChangesFields() public async Task UpdateAsync_ChangesFields()
{ {
// Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Old name", "/old"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Old name", "/old"), CancellationToken.None);
// Action
var updated = await _service.UpdateAsync( var updated = await _service.UpdateAsync(
created.Value.Id, new UpdateRequestTypeRequest("New name", "/new"), CancellationToken.None); created.Value.Id, new UpdateRequestTypeRequest("New name", "/new"), CancellationToken.None);
// Assert
updated.IsSuccess.ShouldBeTrue(); updated.IsSuccess.ShouldBeTrue();
updated.Value.Name.ShouldBe("New name"); updated.Value.Name.ShouldBe("New name");
updated.Value.PageUrl.ShouldBe("/new"); updated.Value.PageUrl.ShouldBe("/new");
} }
[Fact] [Fact(DisplayName = "Обновление несуществующего типа заявки возвращает неуспешный результат")]
public async Task UpdateAsync_UnknownId_ReturnsFailedResult() public async Task UpdateAsync_UnknownId_ReturnsFailedResult()
{ {
// Action
var result = await _service.UpdateAsync( var result = await _service.UpdateAsync(
Guid.NewGuid(), new UpdateRequestTypeRequest("X", "/x"), CancellationToken.None); Guid.NewGuid(), new UpdateRequestTypeRequest("X", "/x"), CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Удаление типа заявки убирает его из базы")]
public async Task DeleteAsync_RemovesEntity() public async Task DeleteAsync_RemovesEntity()
{ {
// Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Temp", "/temp"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Temp", "/temp"), CancellationToken.None);
// Action
var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None); var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None);
// Assert
deleted.IsSuccess.ShouldBeTrue(); deleted.IsSuccess.ShouldBeTrue();
var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None);
fetched.IsFailed.ShouldBeTrue(); fetched.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Список типов заявок включает ранее созданный тип")]
public async Task ListAsync_IncludesCreatedEntity() public async Task ListAsync_IncludesCreatedEntity()
{ {
// Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Listed type", "/listed"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Listed type", "/listed"), CancellationToken.None);
// Action
var list = await _service.ListAsync(CancellationToken.None); var list = await _service.ListAsync(CancellationToken.None);
// Assert
list.IsSuccess.ShouldBeTrue(); list.IsSuccess.ShouldBeTrue();
list.Value.ShouldContain(x => x.Id == created.Value.Id); list.Value.ShouldContain(x => x.Id == created.Value.Id);
} }
@@ -7,14 +7,7 @@ using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure;
namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices;
[CollectionDefinition(Name)] public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFixture>
public sealed class TemplateRouteServiceCollection : ICollectionFixture<PostgresDatabaseFixture>
{
public const string Name = nameof(TemplateRouteServiceCollection);
}
[Collection(TemplateRouteServiceCollection.Name)]
public sealed class TemplateRouteServiceTests : IDisposable
{ {
private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002"); private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002");
private static readonly Guid DepartmentId = Guid.Parse("00000000-0000-0000-0000-000000000001"); private static readonly Guid DepartmentId = Guid.Parse("00000000-0000-0000-0000-000000000001");
@@ -30,20 +23,21 @@ public sealed class TemplateRouteServiceTests : IDisposable
_requestTypeService = _services.GetRequiredService<RequestTypeService>(); _requestTypeService = _services.GetRequiredService<RequestTypeService>();
} }
public void Dispose() => _services.Dispose();
private async Task<Guid> CreateRequestTypeAsync(string name) => private async Task<Guid> CreateRequestTypeAsync(string name) =>
(await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}"), CancellationToken.None)).Value.Id; (await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}"), CancellationToken.None)).Value.Id;
[Fact] [Fact(DisplayName = "Создание маршрута и получение его по Id возвращает сохранённые данные")]
public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity() public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity()
{ {
// Arrange
var requestTypeId = await CreateRequestTypeAsync("Vacation"); var requestTypeId = await CreateRequestTypeAsync("Vacation");
// Action
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true),
CancellationToken.None); CancellationToken.None);
// Assert
created.IsSuccess.ShouldBeTrue(); created.IsSuccess.ShouldBeTrue();
created.Value.RequestTypeId.ShouldBe(requestTypeId); created.Value.RequestTypeId.ShouldBe(requestTypeId);
@@ -52,65 +46,78 @@ public sealed class TemplateRouteServiceTests : IDisposable
fetched.Value.CompanyId.ShouldBe(CompanyId); fetched.Value.CompanyId.ShouldBe(CompanyId);
} }
[Fact] [Fact(DisplayName = "Создание маршрута с несуществующим типом заявки возвращает неуспешный результат")]
public async Task CreateAsync_UnknownRequestType_ReturnsFailedResult() public async Task CreateAsync_UnknownRequestType_ReturnsFailedResult()
{ {
// Action
var result = await _service.CreateAsync( var result = await _service.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, Guid.NewGuid(), DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, Guid.NewGuid(), DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Повторное создание маршрута с тем же типом заявки, отделом, компанией и признаком «по проекту» возвращает конфликт")]
public async Task CreateAsync_DuplicateRoute_ReturnsFailedResult() public async Task CreateAsync_DuplicateRoute_ReturnsFailedResult()
{ {
// Arrange
var requestTypeId = await CreateRequestTypeAsync("Business trip"); var requestTypeId = await CreateRequestTypeAsync("Business trip");
var request = new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false); var request = new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false);
var first = await _service.CreateAsync(request, CancellationToken.None); var first = await _service.CreateAsync(request, CancellationToken.None);
first.IsSuccess.ShouldBeTrue(); first.IsSuccess.ShouldBeTrue();
// Action
var duplicate = await _service.CreateAsync(request, CancellationToken.None); var duplicate = await _service.CreateAsync(request, CancellationToken.None);
// Assert
duplicate.IsFailed.ShouldBeTrue(); duplicate.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Обновление маршрута изменяет тип маршрута и признак необходимости контроля")]
public async Task UpdateAsync_ChangesFields() public async Task UpdateAsync_ChangesFields()
{ {
// Arrange
var requestTypeId = await CreateRequestTypeAsync("Sick leave"); var requestTypeId = await CreateRequestTypeAsync("Sick leave");
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
// Action
var updated = await _service.UpdateAsync( var updated = await _service.UpdateAsync(
created.Value.Id, created.Value.Id,
new UpdateTemplateRouteRequest(RouteType.Parallel, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), new UpdateTemplateRouteRequest(RouteType.Parallel, DepartmentId, CompanyId, ByProject: false, RequiresControl: true),
CancellationToken.None); CancellationToken.None);
// Assert
updated.IsSuccess.ShouldBeTrue(); updated.IsSuccess.ShouldBeTrue();
updated.Value.RouteType.ShouldBe(RouteType.Parallel); updated.Value.RouteType.ShouldBe(RouteType.Parallel);
updated.Value.RequiresControl.ShouldBeTrue(); updated.Value.RequiresControl.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Удаление маршрута убирает его из базы")]
public async Task DeleteAsync_RemovesEntity() public async Task DeleteAsync_RemovesEntity()
{ {
// Arrange
var requestTypeId = await CreateRequestTypeAsync("Remote work"); var requestTypeId = await CreateRequestTypeAsync("Remote work");
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
// Action
var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None); var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None);
// Assert
deleted.IsSuccess.ShouldBeTrue(); deleted.IsSuccess.ShouldBeTrue();
var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None);
fetched.IsFailed.ShouldBeTrue(); fetched.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Резолв маршрута по точному совпадению отдела возвращает шаги с нужным статусом")]
public async Task ResolveAsync_ExactDepartmentMatch_ReturnsMatchingStepsForStatus() public async Task ResolveAsync_ExactDepartmentMatch_ReturnsMatchingStepsForStatus()
{ {
// Arrange
var requestTypeId = await CreateRequestTypeAsync("Exact match request"); var requestTypeId = await CreateRequestTypeAsync("Exact match request");
var route = await _service.CreateAsync( var route = await _service.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true),
@@ -123,39 +130,46 @@ public sealed class TemplateRouteServiceTests : IDisposable
1, "approver@tnsu.kz", "Lead", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false), 1, "approver@tnsu.kz", "Lead", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Action
var resolved = await _service.ResolveAsync( var resolved = await _service.ResolveAsync(
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval), new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
CancellationToken.None); CancellationToken.None);
// Assert
resolved.IsSuccess.ShouldBeTrue(); resolved.IsSuccess.ShouldBeTrue();
resolved.Value.RouteId.ShouldBe(route.Value.Id); resolved.Value.RouteId.ShouldBe(route.Value.Id);
resolved.Value.Steps.ShouldHaveSingleItem(); resolved.Value.Steps.ShouldHaveSingleItem();
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz"); resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz");
} }
[Fact] [Fact(DisplayName = "Резолв маршрута откатывается на маршрут без отдела, если точного совпадения по отделу нет")]
public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute() public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute()
{ {
// Arrange
var requestTypeId = await CreateRequestTypeAsync("Company-wide request"); var requestTypeId = await CreateRequestTypeAsync("Company-wide request");
var route = await _service.CreateAsync( var route = await _service.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId: null, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId: null, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
// Action
var resolved = await _service.ResolveAsync( var resolved = await _service.ResolveAsync(
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Execution), new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Execution),
CancellationToken.None); CancellationToken.None);
// Assert
resolved.IsSuccess.ShouldBeTrue(); resolved.IsSuccess.ShouldBeTrue();
resolved.Value.RouteId.ShouldBe(route.Value.Id); resolved.Value.RouteId.ShouldBe(route.Value.Id);
} }
[Fact] [Fact(DisplayName = "Резолв маршрута с несуществующим типом заявки возвращает неуспешный результат")]
public async Task ResolveAsync_UnknownRequestType_ReturnsFailedResult() public async Task ResolveAsync_UnknownRequestType_ReturnsFailedResult()
{ {
// Action
var resolved = await _service.ResolveAsync( var resolved = await _service.ResolveAsync(
new ResolveTemplateRouteRequest(Guid.NewGuid(), "someone@tnsu.kz", null, RouteStepStatus.Approval), new ResolveTemplateRouteRequest(Guid.NewGuid(), "someone@tnsu.kz", null, RouteStepStatus.Approval),
CancellationToken.None); CancellationToken.None);
// Assert
resolved.IsFailed.ShouldBeTrue(); resolved.IsFailed.ShouldBeTrue();
} }
} }
@@ -8,32 +8,22 @@ using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure;
namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices;
[CollectionDefinition(Name)] public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDatabaseFixture>
public sealed class TemplateRouteStepServiceCollection : ICollectionFixture<PostgresDatabaseFixture>
{
public const string Name = nameof(TemplateRouteStepServiceCollection);
}
[Collection(TemplateRouteStepServiceCollection.Name)]
public sealed class TemplateRouteStepServiceTests : IDisposable
{ {
private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002"); private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002");
private readonly ServiceProvider _services;
private readonly TemplateRouteStepService _service; private readonly TemplateRouteStepService _service;
private readonly TemplateRouteService _routeService; private readonly TemplateRouteService _routeService;
private readonly RequestTypeService _requestTypeService; private readonly RequestTypeService _requestTypeService;
public TemplateRouteStepServiceTests(PostgresDatabaseFixture fixture) public TemplateRouteStepServiceTests(PostgresDatabaseFixture fixture)
{ {
_services = TestServiceProviderFactory.Build(fixture.ConnectionString); var services = TestServiceProviderFactory.Build(fixture.ConnectionString);
_service = _services.GetRequiredService<TemplateRouteStepService>(); _service = services.GetRequiredService<TemplateRouteStepService>();
_routeService = _services.GetRequiredService<TemplateRouteService>(); _routeService = services.GetRequiredService<TemplateRouteService>();
_requestTypeService = _services.GetRequiredService<RequestTypeService>(); _requestTypeService = services.GetRequiredService<RequestTypeService>();
} }
public void Dispose() => _services.Dispose();
private async Task<Guid> CreateRouteAsync(string requestTypeName, bool byProject) private async Task<Guid> CreateRouteAsync(string requestTypeName, bool byProject)
{ {
var requestTypeId = (await _requestTypeService.CreateAsync( var requestTypeId = (await _requestTypeService.CreateAsync(
@@ -46,40 +36,48 @@ public sealed class TemplateRouteStepServiceTests : IDisposable
return route.Value.Id; return route.Value.Id;
} }
[Fact] [Fact(DisplayName = "Создание шага для несуществующего маршрута возвращает неуспешный результат")]
public async Task CreateAsync_UnknownRoute_ReturnsFailedResult() public async Task CreateAsync_UnknownRoute_ReturnsFailedResult()
{ {
// Action
var result = await _service.CreateAsync( var result = await _service.CreateAsync(
Guid.NewGuid(), Guid.NewGuid(),
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Роль в проекте недопустима для шага маршрута без признака «по проекту»")]
public async Task CreateAsync_ProjectRoleOnNonProjectRoute_ReturnsFailedResult() public async Task CreateAsync_ProjectRoleOnNonProjectRoute_ReturnsFailedResult()
{ {
// Arrange
var routeId = await CreateRouteAsync("Non project route", byProject: false); var routeId = await CreateRouteAsync("Non project route", byProject: false);
// Action
var result = await _service.CreateAsync( var result = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.Manager, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.Manager, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Создание шага маршрута и получение его по Id возвращает сохранённые данные")]
public async Task CreateAsync_ThenGetAsync_ReturnsCreatedStep() public async Task CreateAsync_ThenGetAsync_ReturnsCreatedStep()
{ {
// Arrange
var routeId = await CreateRouteAsync("Simple route", byProject: false); var routeId = await CreateRouteAsync("Simple route", byProject: false);
// Action
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Assert
created.IsSuccess.ShouldBeTrue(); created.IsSuccess.ShouldBeTrue();
var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None); var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None);
@@ -87,65 +85,78 @@ public sealed class TemplateRouteStepServiceTests : IDisposable
fetched.Value.ExecutorEmail.ShouldBe("a@b.kz"); fetched.Value.ExecutorEmail.ShouldBe("a@b.kz");
} }
[Fact] [Fact(DisplayName = "Создание шага без роли в проекте на маршруте «по проекту» возвращает предупреждение")]
public async Task CreateAsync_ByProjectRouteWithoutProjectRoleStep_ReturnsWarning() public async Task CreateAsync_ByProjectRouteWithoutProjectRoleStep_ReturnsWarning()
{ {
// Arrange
var routeId = await CreateRouteAsync("By-project route without role", byProject: true); var routeId = await CreateRouteAsync("By-project route without role", byProject: true);
// Action
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Assert
created.IsSuccess.ShouldBeTrue(); created.IsSuccess.ShouldBeTrue();
created.Reasons.ShouldContain(r => r.Message.Contains("ролью в проекте")); created.Reasons.ShouldContain(r => r.Message.Contains("ролью в проекте"));
} }
[Fact] [Fact(DisplayName = "Создание шага с ролью в проекте на маршруте «по проекту» не возвращает предупреждение")]
public async Task CreateAsync_ByProjectRouteWithProjectRoleStep_HasNoWarning() public async Task CreateAsync_ByProjectRouteWithProjectRoleStep_HasNoWarning()
{ {
// Arrange
var routeId = await CreateRouteAsync("By-project route with role", byProject: true); var routeId = await CreateRouteAsync("By-project route with role", byProject: true);
// Action
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.Manager, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.Manager, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Assert
created.IsSuccess.ShouldBeTrue(); created.IsSuccess.ShouldBeTrue();
created.Reasons.ShouldBeEmpty(); created.Reasons.ShouldBeEmpty();
} }
[Fact] [Fact(DisplayName = "Обновление шага маршрута изменяет исполнителя, статус и разрешение на переназначение")]
public async Task UpdateAsync_ChangesFields() public async Task UpdateAsync_ChangesFields()
{ {
// Arrange
var routeId = await CreateRouteAsync("Route to update step", byProject: false); var routeId = await CreateRouteAsync("Route to update step", byProject: false);
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Action
var updated = await _service.UpdateAsync( var updated = await _service.UpdateAsync(
routeId, routeId,
created.Value.Id, created.Value.Id,
new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Execution, true), new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Execution, true),
CancellationToken.None); CancellationToken.None);
// Assert
updated.IsSuccess.ShouldBeTrue(); updated.IsSuccess.ShouldBeTrue();
updated.Value.ExecutorEmail.ShouldBe("b@b.kz"); updated.Value.ExecutorEmail.ShouldBe("b@b.kz");
updated.Value.Status.ShouldBe(RouteStepStatus.Execution); updated.Value.Status.ShouldBe(RouteStepStatus.Execution);
updated.Value.AllowReassignToOtherApprovers.ShouldBeTrue(); updated.Value.AllowReassignToOtherApprovers.ShouldBeTrue();
} }
[Fact] [Fact(DisplayName = "Удаление шага маршрута убирает его из базы")]
public async Task DeleteAsync_RemovesStep() public async Task DeleteAsync_RemovesStep()
{ {
// Arrange
var routeId = await CreateRouteAsync("Route to delete step", byProject: false); var routeId = await CreateRouteAsync("Route to delete step", byProject: false);
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
CancellationToken.None); CancellationToken.None);
// Action
var deleted = await _service.DeleteAsync(routeId, created.Value.Id, CancellationToken.None); var deleted = await _service.DeleteAsync(routeId, created.Value.Id, CancellationToken.None);
// Assert
deleted.IsSuccess.ShouldBeTrue(); deleted.IsSuccess.ShouldBeTrue();
var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None); var fetched = await _service.GetAsync(routeId, created.Value.Id, CancellationToken.None);