| @@ -4,20 +4,35 @@ namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| public static class ApplicantExtensions | |||
| { | |||
| public static IQueryable<Applicant> FilterByExperience(this IQueryable<Applicant> query, int minExperience, int maxExperience) | |||
| public static async Task<IQueryable<Applicant>> FilterApplicants(this IQueryable<Applicant> query,ApplicantFilterDto applicantFilterDto) | |||
| { | |||
| IQueryable<Applicant> resultQuery; | |||
| resultQuery = query.FilterByExperience(applicantFilterDto.MinExperience, applicantFilterDto.MaxExperience) | |||
| .FilterByEmploymentType(applicantFilterDto.EmploymentType) | |||
| .FilterByDateOfApplication(applicantFilterDto.MinDateOfApplication, applicantFilterDto.MaxDateOfApplication); | |||
| return FilterByTechnologies(await resultQuery.ToListAsync(), applicantFilterDto.Technologies); | |||
| } | |||
| public static IQueryable<Applicant> ApplyPagging(this IQueryable<Applicant> query, Pagination pagination) | |||
| { | |||
| return query.Skip((pagination.CurrentPage - 1) * pagination.PageSize) | |||
| .Take(pagination.PageSize); | |||
| } | |||
| private static IQueryable<Applicant> FilterByExperience(this IQueryable<Applicant> query, int minExperience, int maxExperience) | |||
| { | |||
| if (minExperience == 0 && maxExperience == 0 || minExperience > maxExperience) return query; | |||
| return query.Where(x => x.Experience >= minExperience && x.Experience < maxExperience); | |||
| } | |||
| public static IQueryable<Applicant> FilterByEmploymentType(this IQueryable<Applicant> query, string? employmentType) | |||
| private static IQueryable<Applicant> FilterByEmploymentType(this IQueryable<Applicant> query, string? employmentType) | |||
| { | |||
| if (employmentType == null) return query; | |||
| return query.Where(x => x.TypeOfEmployment == Enum.Parse<TypesOfEmployment>(employmentType)); | |||
| } | |||
| public static IQueryable<Applicant> FilterByDateOfApplication(this IQueryable<Applicant> query, DateTime? minDateOfApplication, DateTime? maxDateOfApplication) | |||
| private static IQueryable<Applicant> FilterByDateOfApplication(this IQueryable<Applicant> query, DateTime? minDateOfApplication, DateTime? maxDateOfApplication) | |||
| { | |||
| if (minDateOfApplication == null) return query; | |||
| if (minDateOfApplication > maxDateOfApplication) return query; | |||
| @@ -25,11 +40,11 @@ namespace Diligent.WebAPI.Business.Extensions | |||
| return query.Where(x => x.DateOfApplication >= minDateOfApplication && x.DateOfApplication < maxDateOfApplication); | |||
| } | |||
| public static List<Applicant> FilterByTechnologies(this List<Applicant> query, string[]? technologies) | |||
| private static IQueryable<Applicant> FilterByTechnologies(this List<Applicant> query, string[]? technologies) | |||
| { | |||
| if (technologies is null) | |||
| { | |||
| return query; | |||
| return query.AsQueryable(); | |||
| } | |||
| List<Applicant> filteredApplicants = new(); | |||
| @@ -55,7 +70,7 @@ namespace Diligent.WebAPI.Business.Extensions | |||
| } | |||
| } | |||
| return filteredApplicants; | |||
| return filteredApplicants.AsQueryable(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,16 +0,0 @@ | |||
| using System.Text.Json; | |||
| using Microsoft.AspNetCore.Http; | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| public static class HttpExtensions | |||
| { | |||
| //extension method for setting header in response | |||
| public static void AddHeader(this HttpResponse response, string headerName, int number) | |||
| { | |||
| var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; //we want to store content inside header of response in CamelCase format | |||
| response.Headers.Add("Pagination", System.Text.Json.JsonSerializer.Serialize(number, options)); | |||
| response.Headers.Add("Access-Control-Expose-Headers", headerName); //giving permission for accessing Pagination header to our client | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| public static class PaginationExtension | |||
| { | |||
| public static IQueryable<T> ApplyPagging<T>(this IQueryable<T> query, Pagination pagination) | |||
| { | |||
| return query.Skip((pagination.CurrentPage - 1) * pagination.PageSize) | |||
| .Take(pagination.PageSize); | |||
| } | |||
| } | |||
| } | |||
| @@ -7,40 +7,34 @@ namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly IHttpContextAccessor _httpContextAccessor; | |||
| public ApplicantService(DatabaseContext context, IMapper mapper, IHttpContextAccessor httpContextAccessor) | |||
| public ApplicantService(DatabaseContext context, IMapper mapper) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _httpContextAccessor = httpContextAccessor; | |||
| } | |||
| public async Task<List<ApplicantViewDto>> GetFilteredApplicants(ApplicantFilterDto applicantFilterDto) | |||
| public async Task<QueryResultDto<ApplicantViewDto>> GetFilteredApplicants(ApplicantFilterDto applicantFilterDto) | |||
| { | |||
| int applicantsToSkip = (applicantFilterDto.CurrentPage - 1) * applicantFilterDto.PageSize; | |||
| var context = _httpContextAccessor.HttpContext; | |||
| var allApplicants = _context.Applicants | |||
| .Include(c => c.Ads) | |||
| .Include(x => x.TechnologyApplicants) | |||
| .ThenInclude(x => x.Technology); | |||
| context.Response.AddHeader("Pagination", allApplicants.ToList().Count); | |||
| var filteredApplicants = await allApplicants | |||
| .FilterByExperience(applicantFilterDto.MinExperience, applicantFilterDto.MaxExperience) | |||
| .FilterByEmploymentType(applicantFilterDto.EmploymentType) | |||
| .FilterByDateOfApplication(applicantFilterDto.MinDateOfApplication, applicantFilterDto.MaxDateOfApplication) | |||
| .Skip(applicantsToSkip) | |||
| .Take(applicantFilterDto.PageSize) | |||
| .ToListAsync(); | |||
| filteredApplicants = filteredApplicants.FilterByTechnologies(applicantFilterDto.Technologies); | |||
| return _mapper.Map<List<ApplicantViewDto>>(filteredApplicants); | |||
| .FilterApplicants(applicantFilterDto); | |||
| filteredApplicants = PaginationExtension.ApplyPagging(filteredApplicants, new Pagination | |||
| { | |||
| CurrentPage = applicantFilterDto.CurrentPage, | |||
| PageSize = applicantFilterDto.PageSize | |||
| }); | |||
| return new QueryResultDto<ApplicantViewDto> | |||
| { | |||
| Items = _mapper.Map<List<ApplicantViewDto>>(filteredApplicants), | |||
| Total = allApplicants.ToList().Count | |||
| }; | |||
| } | |||
| public async Task<ApplicantViewDto> GetById(int id) | |||
| @@ -3,7 +3,7 @@ namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IApplicantService | |||
| { | |||
| Task<List<ApplicantViewDto>> GetFilteredApplicants(ApplicantFilterDto applicantFilterDto); | |||
| Task<QueryResultDto<ApplicantViewDto>> GetFilteredApplicants(ApplicantFilterDto applicantFilterDto); | |||
| Task<List<AdApplicantsViewDto>> GetAllAdsApplicants(); | |||
| Task<ApplicantViewDto> GetById(int id); | |||
| Task<ApplicantViewDto> GetApplicantWithSelectionProcessesById(int id); | |||
| @@ -16,6 +16,5 @@ namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| public int Experience { get; set; } | |||
| public string ApplicationChannel { get; set; } | |||
| public string TypeOfEmployment { get; set; } | |||
| public List<SelectionProcessCreateDto> SelectionProcesses { get; set; } | |||
| } | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| using Diligent.WebAPI.Contracts.Models; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantFilterDto | |||
| public class ApplicantFilterDto : Pagination | |||
| { | |||
| public int MinExperience { get; set; } | |||
| public int MaxExperience { get; set; } | |||
| @@ -8,7 +10,5 @@ | |||
| public string? EmploymentType { get; set; } | |||
| public DateTime? MinDateOfApplication { get; set; } | |||
| public DateTime? MaxDateOfApplication { get; set; } | |||
| public int PageSize { get; set; } | |||
| public int CurrentPage { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs | |||
| { | |||
| public class QueryResultDto<T> where T : class | |||
| { | |||
| public int Total { get; set; } | |||
| public IEnumerable<T> Items { get; set; } | |||
| } | |||
| } | |||
| @@ -2,7 +2,7 @@ | |||
| { | |||
| public class Pagination | |||
| { | |||
| public int CurrentPage { get; set; } | |||
| public int PageSize { get; set; } | |||
| public int CurrentPage { get; set; } = 1; | |||
| public int PageSize { get; set; } = 6; | |||
| } | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| using Diligent.WebAPI.Contracts.DTOs; | |||
| using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionProcess; | |||
| using Diligent.WebAPI.Contracts.Exceptions; | |||
| using NSubstitute; | |||
| @@ -89,7 +90,12 @@ namespace Diligent.WebAPI.Tests.Controllers | |||
| PageSize = 4 | |||
| }; | |||
| _applicantService.GetFilteredApplicants(filterDto).Returns(applicants); | |||
| _applicantService.GetFilteredApplicants(filterDto).Returns(new QueryResultDto<ApplicantViewDto> | |||
| { | |||
| Items = applicants, | |||
| Total = applicants.Count | |||
| }); | |||
| ApplicantsController applicantsController = new(_applicantService); | |||
| var result = await applicantsController.GetFilteredApplicants(filterDto); | |||
| @@ -15,7 +15,6 @@ namespace Diligent.WebAPI.Tests.Services | |||
| public class ApplicantServiceTests | |||
| { | |||
| private readonly IMapper _mapper; | |||
| private readonly IHttpContextAccessor _httpContextAccessor = Substitute.For<IHttpContextAccessor>(); | |||
| private readonly HttpContext _httpContext; | |||
| private readonly List<Applicant> _applicants; | |||
| private readonly List<Ad> _ads; | |||
| @@ -43,27 +42,20 @@ namespace Diligent.WebAPI.Tests.Services | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| _httpContextAccessor.HttpContext.Returns(_httpContext); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| var filterDto = new ApplicantFilterDto | |||
| { | |||
| CurrentPage = 1, | |||
| PageSize = 4 | |||
| }; | |||
| var filterDto = new ApplicantFilterDto(); | |||
| var result = await applicantService.GetFilteredApplicants(filterDto); | |||
| Assert.Equal(_httpContext.Response.Headers["Pagination"], result.Count.ToString()); | |||
| result.Should().BeEquivalentTo(_mapper.Map<List<ApplicantViewDto>>(_applicants)); | |||
| Assert.Equal(_applicants.Count,result.Total); | |||
| } | |||
| [Fact] | |||
| public async Task GetById_ShouldReturnApplicant_WhenApplicantExist() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| var result = await applicantService.GetById(1); | |||
| @@ -74,7 +66,7 @@ namespace Diligent.WebAPI.Tests.Services | |||
| public async Task GetById_ShouldThrowEntityNotFooundException_WhenApplicantDontExist() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| await Assert.ThrowsAsync<EntityNotFoundException>(async () => await applicantService.GetById(1000)); | |||
| } | |||
| @@ -83,7 +75,7 @@ namespace Diligent.WebAPI.Tests.Services | |||
| public async Task CreateApplicant_ShouldAddEntityIntoDatabase_Always() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| ApplicantCreateDto applicantCreateDto = new() | |||
| { | |||
| @@ -98,8 +90,7 @@ namespace Diligent.WebAPI.Tests.Services | |||
| LinkedlnLink = null, | |||
| PhoneNumber = "432424", | |||
| Position = ".NET Developer", | |||
| TypeOfEmployment = TypesOfEmployment.Intership.ToString(), | |||
| SelectionProcesses = _mapper.Map<List<SelectionProcessCreateDto>>(_selectionProcesses) | |||
| TypeOfEmployment = TypesOfEmployment.Intership.ToString() | |||
| }; | |||
| await applicantService.CreateApplicant(applicantCreateDto); | |||
| @@ -112,14 +103,14 @@ namespace Diligent.WebAPI.Tests.Services | |||
| var result = await applicantService.GetFilteredApplicants(filterDto); | |||
| Assert.Equal(2, result.Count); | |||
| Assert.Equal(2, result.Total); | |||
| } | |||
| [Fact] | |||
| public async Task DeleteApplicant_ShouldDeleteApplicant_WhenApplicantExist() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| await applicantService.DeleteApplicant(1); | |||
| @@ -131,14 +122,14 @@ namespace Diligent.WebAPI.Tests.Services | |||
| var applicants = await applicantService.GetFilteredApplicants(filterDto); | |||
| Assert.Empty(applicants); | |||
| Assert.Empty(applicants.Items); | |||
| } | |||
| [Fact] | |||
| public async Task DeleteApplicant_ShouldThrowEntityNotFooundException_WhenApplicantDontExist() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| await Assert.ThrowsAsync<EntityNotFoundException>(async () => await applicantService.DeleteApplicant(1000)); | |||
| } | |||
| @@ -147,7 +138,7 @@ namespace Diligent.WebAPI.Tests.Services | |||
| public async Task UpdateApplicant_ShouldUpdateApplicant_WhenApplicantExist() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| ApplicantUpdateDto applicantUpdateDto = new() | |||
| { | |||
| @@ -174,7 +165,7 @@ namespace Diligent.WebAPI.Tests.Services | |||
| public async Task GetAllAdsApplicants_ShouldReturnListOfAdApplicants_Always() | |||
| { | |||
| var databaseContext = await Helpers<Ad>.GetDatabaseContext(_ads); | |||
| ApplicantService applicantService = new(databaseContext, _mapper, _httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| var result = await applicantService.GetAllAdsApplicants(); | |||
| @@ -185,7 +176,7 @@ namespace Diligent.WebAPI.Tests.Services | |||
| public async Task GetApplicantWithSelectionProcessesById_ShouldReturnApplicant_WhenApplicantExists() | |||
| { | |||
| var databaseContext = await Helpers<Applicant>.GetDatabaseContext(_applicants); | |||
| ApplicantService applicantService = new(databaseContext, _mapper,_httpContextAccessor); | |||
| ApplicantService applicantService = new(databaseContext, _mapper); | |||
| var result = await applicantService.GetApplicantWithSelectionProcessesById(1); | |||
| var processes = result.SelectionProcesses; | |||