using Microsoft.Extensions.DependencyInjection; using Shouldly; using Tnsu.ApprovalRoute.Application.RequestTypes; using Tnsu.ApprovalRoute.Application.TemplateRoutes; using Tnsu.ApprovalRoute.Domain.Enums; using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure; namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices; [CollectionDefinition(Name)] public sealed class TemplateRouteServiceCollection : ICollectionFixture { 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 DepartmentId = Guid.Parse("00000000-0000-0000-0000-000000000001"); private readonly ServiceProvider _services; private readonly TemplateRouteService _service; private readonly RequestTypeService _requestTypeService; public TemplateRouteServiceTests(PostgresDatabaseFixture fixture) { _services = TestServiceProviderFactory.Build(fixture.ConnectionString); _service = _services.GetRequiredService(); _requestTypeService = _services.GetRequiredService(); } public void Dispose() => _services.Dispose(); private async Task CreateRequestTypeAsync(string name) => (await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}"), CancellationToken.None)).Value.Id; [Fact] public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity() { var requestTypeId = await CreateRequestTypeAsync("Vacation"); var created = await _service.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), CancellationToken.None); created.IsSuccess.ShouldBeTrue(); created.Value.RequestTypeId.ShouldBe(requestTypeId); var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); fetched.IsSuccess.ShouldBeTrue(); fetched.Value.CompanyId.ShouldBe(CompanyId); } [Fact] public async Task CreateAsync_UnknownRequestType_ReturnsFailedResult() { var result = await _service.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, Guid.NewGuid(), DepartmentId, CompanyId, ByProject: false, RequiresControl: false), CancellationToken.None); result.IsFailed.ShouldBeTrue(); } [Fact] public async Task CreateAsync_DuplicateRoute_ReturnsFailedResult() { var requestTypeId = await CreateRequestTypeAsync("Business trip"); var request = new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false); var first = await _service.CreateAsync(request, CancellationToken.None); first.IsSuccess.ShouldBeTrue(); var duplicate = await _service.CreateAsync(request, CancellationToken.None); duplicate.IsFailed.ShouldBeTrue(); } [Fact] public async Task UpdateAsync_ChangesFields() { var requestTypeId = await CreateRequestTypeAsync("Sick leave"); var created = await _service.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), CancellationToken.None); var updated = await _service.UpdateAsync( created.Value.Id, new UpdateTemplateRouteRequest(RouteType.Parallel, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), CancellationToken.None); updated.IsSuccess.ShouldBeTrue(); updated.Value.RouteType.ShouldBe(RouteType.Parallel); updated.Value.RequiresControl.ShouldBeTrue(); } [Fact] public async Task DeleteAsync_RemovesEntity() { var requestTypeId = await CreateRequestTypeAsync("Remote work"); var created = await _service.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), CancellationToken.None); var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None); deleted.IsSuccess.ShouldBeTrue(); var fetched = await _service.GetAsync(created.Value.Id, CancellationToken.None); fetched.IsFailed.ShouldBeTrue(); } [Fact] public async Task ResolveAsync_ExactDepartmentMatch_ReturnsMatchingStepsForStatus() { var requestTypeId = await CreateRequestTypeAsync("Exact match request"); var route = await _service.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: true), CancellationToken.None); var stepService = _services.GetRequiredService(); await stepService.CreateAsync( route.Value.Id, new Application.TemplateRouteSteps.CreateTemplateRouteStepRequest( 1, "approver@tnsu.kz", "Lead", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false), CancellationToken.None); var resolved = await _service.ResolveAsync( new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval), CancellationToken.None); resolved.IsSuccess.ShouldBeTrue(); resolved.Value.RouteId.ShouldBe(route.Value.Id); resolved.Value.Steps.ShouldHaveSingleItem(); resolved.Value.Steps[0].ExecutorEmail.ShouldBe("approver@tnsu.kz"); } [Fact] public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute() { var requestTypeId = await CreateRequestTypeAsync("Company-wide request"); var route = await _service.CreateAsync( new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId: null, CompanyId, ByProject: false, RequiresControl: false), CancellationToken.None); var resolved = await _service.ResolveAsync( new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Execution), CancellationToken.None); resolved.IsSuccess.ShouldBeTrue(); resolved.Value.RouteId.ShouldBe(route.Value.Id); } [Fact] public async Task ResolveAsync_UnknownRequestType_ReturnsFailedResult() { var resolved = await _service.ResolveAsync( new ResolveTemplateRouteRequest(Guid.NewGuid(), "someone@tnsu.kz", null, RouteStepStatus.Approval), CancellationToken.None); resolved.IsFailed.ShouldBeTrue(); } }