Skip to content

Commit

Permalink
add a simple pagination endpoint for employees
Browse files Browse the repository at this point in the history
  • Loading branch information
Barsonax committed May 25, 2024
1 parent 0b96067 commit 21e1d85
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 2 deletions.
90 changes: 90 additions & 0 deletions CleanAspCore.Api.Tests/Features/Employees/GetEmployeesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using CleanAspCore.Api.Tests.Fakers;

namespace CleanAspCore.Api.Tests.Features.Employees;

public class GetEmployees : TestBase
{
[Test]
public async Task? GetEmployees_NoEmployees_ReturnsEmptyPage()
{
//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployees(1);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
await response.AssertJsonBodyIsEquivalentTo(new
{
TotalPages = 0,
TotalRecords = 0,
PageNumber = 0,
Data = new List<Guid>()
});
}

[Test]
public async Task GetEmployees_FirstPage_ReturnsExpectedEmployees()
{
//Arrange
var department = new DepartmentFaker().Generate();
var job = new JobFaker().Generate();
var employees = new EmployeeFaker()
.RuleFor(x => x.Department, department)
.RuleFor(x => x.Job, job)
.Generate(15);
Sut.SeedData(context =>
{
context.Employees.AddRange(employees);
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployees(1);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
await response.AssertJsonBodyIsEquivalentTo(new
{
TotalPages = 2,
TotalRecords = 15,
PageNumber = 1,
Data = employees
.OrderBy(x => x.FirstName)
.Select(x => new { x.Id })
.Take(10)
.ToList()
});
}

[Test]
public async Task GetEmployees_SecondPage_ReturnsExpectedEmployees()
{
//Arrange
var department = new DepartmentFaker().Generate();
var job = new JobFaker().Generate();
var employees = new EmployeeFaker()
.RuleFor(x => x.Department, department)
.RuleFor(x => x.Job, job)
.Generate(15);
Sut.SeedData(context =>
{
context.Employees.AddRange(employees);
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployees(2);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
await response.AssertJsonBodyIsEquivalentTo(new
{
TotalPages = 2,
TotalRecords = 15,
PageNumber = 2,
Data = employees
.OrderBy(x => x.FirstName)
.Select(x => new { x.Id })
.Skip(10)
.Take(10)
.ToList()
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public interface IEmployeeApiClient
[Get("/employees/{id}")]
Task<HttpResponseMessage> GetEmployeeById(Guid id);

[Get("/employees")]
Task<HttpResponseMessage> GetEmployees();
[Get("/employees?page={page}")]
Task<HttpResponseMessage> GetEmployees(int page);

[Post("/employees")]
Task<HttpResponseMessage> CreateEmployee(CreateEmployeeRequest createEmployeeRequest);
Expand Down
42 changes: 42 additions & 0 deletions CleanAspCore/Data/Extensions/PagedListQueryableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections;
using Microsoft.EntityFrameworkCore;

namespace CleanAspCore.Data.Extensions;

public static class PagedListQueryableExtensions
{
public static async Task<PagedList<T>> ToPagedListAsync<T>(
this IQueryable<T> source,
int page,
int pageSize,
CancellationToken token = default)
{
var count = await source.CountAsync(token);
if (count > 0)
{
var items = await source
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(token);
return new PagedList<T>(items, count, page, pageSize);
}

return new(Enumerable.Empty<T>(), 0, 0, pageSize);
}
}

public class PagedList<T>
{
public PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize)
{
PageNumber = pageNumber;
TotalRecords = count;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
Data = items as IList<T> ?? new List<T>(items);
}

public IList<T> Data { get; init; }
public int PageNumber { get; init; }
public int TotalPages { get; init; }
public int TotalRecords { get; init; }
}
4 changes: 4 additions & 0 deletions CleanAspCore/Endpoints/Employees/EmployeeEndpointConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ internal static void AddEmployeesRoutes(this IEndpointRouteBuilder host)
.RequireAuthorization(ReadEmployeesPolicy)
.WithName(nameof(GetEmployeeById));

employeeGroup.MapGet("/", GetEmployees.Handle)
.RequireAuthorization(ReadEmployeesPolicy)
.WithName(nameof(GetEmployees));

employeeGroup.MapDelete("/{id:guid}", DeleteEmployeeById.Handle)
.RequireAuthorization(WriteEmployeesPolicy);

Expand Down
29 changes: 29 additions & 0 deletions CleanAspCore/Endpoints/Employees/GetEmployees.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using CleanAspCore.Data;
using CleanAspCore.Data.Extensions;
using CleanAspCore.Data.Models.Employees;
using Microsoft.AspNetCore.Http.HttpResults;

namespace CleanAspCore.Endpoints.Employees;

internal static class GetEmployees
{
internal static async Task<Ok<PagedList<GetEmployeeResponse>>> Handle(HrContext context, [FromQuery] int page, CancellationToken cancellationToken)
{
var result = await context.Employees
.OrderBy(x => x.FirstName)
.Select(x => x.ToDto())
.ToPagedListAsync(page, 10, cancellationToken);
return TypedResults.Ok(result);
}

private static GetEmployeeResponse ToDto(this Employee employee) => new()
{
Id = employee.Id,
FirstName = employee.FirstName,
LastName = employee.LastName,
Email = employee.Email.ToString(),
Gender = employee.Gender,
DepartmentId = employee.DepartmentId,
JobId = employee.JobId
};
}

0 comments on commit 21e1d85

Please sign in to comment.