Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Advanced HTTP Methods for Users #61

Merged
merged 13 commits into from
Dec 23, 2023
Prev Previous commit
Next Next commit
Implement get user by id
  • Loading branch information
romandykyi committed Dec 23, 2023
commit f4668d38c96e2700cd4e66cd53e5ef809272d4c6
3 changes: 3 additions & 0 deletions Core/Dtos/Users/UserPreviewDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace EUniversity.Core.Dtos.Users;

public record UserPreviewDto(string Id, string Email, string UserName, string FirstName, string LastName, string? MiddleName);
12 changes: 11 additions & 1 deletion Core/Dtos/Users/UserViewDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
namespace EUniversity.Core.Dtos.Users;

public record UserViewDto(string Id, string Email, string UserName, string FirstName, string LastName, string? MiddleName);
public class UserViewDto
{
public string Id { get; set; } = null!;
public string Email { get; set; } = null!;
public string UserName { get; set; } = null!;
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public string? MiddleName { get; set; }
public bool IsDeleted { get; set; }
public IEnumerable<string> Roles { get; set; } = null!;
}
14 changes: 12 additions & 2 deletions Core/Services/Users/IUsersService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface IUsersService
/// <returns>
/// Page with all users with the role.
/// </returns>
Task<Page<UserViewDto>> GetUsersInRoleAsync(string role, PaginationProperties? properties = null,
Task<Page<UserPreviewDto>> GetUsersInRoleAsync(string role, PaginationProperties? properties = null,
IFilter<ApplicationUser>? filter = null);

/// <summary>
Expand All @@ -33,9 +33,19 @@ Task<Page<UserViewDto>> GetUsersInRoleAsync(string role, PaginationProperties? p
/// <returns>
/// Page with all users.
/// </returns>
Task<Page<UserViewDto>> GetAllUsersAsync(PaginationProperties? properties = null,
Task<Page<UserPreviewDto>> GetAllUsersAsync(PaginationProperties? properties = null,
IFilter<ApplicationUser>? filter = null, bool onlyDeleted = false);

/// <summary>
/// Gets a user by its ID.
/// </summary>
/// <param name="id"></param>
/// <returns>
/// A task that represents the asynchronous operation.
/// Returns <see langword="null" /> if user is not found.
/// </returns>
Task<UserViewDto?> GetByIdAsync(string id);

/// <summary>
/// Deletes a user identified by its unique identifier asynchronously.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions EUniversity/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public UsersController(IAuthService authService, IUsersService usersService)
/// <response code="401">Unauthorized user call</response>
/// <response code="403">User lacks 'Administrator' role</response>
[HttpGet]
[ProducesResponseType(typeof(Page<UserViewDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Page<UserPreviewDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAllUsersAsync(
Expand Down Expand Up @@ -76,7 +76,7 @@ public async Task<IActionResult> GetAllUsersAsync(
/// <response code="403">User lacks 'Administrator' role</response>
[HttpGet]
[Route("students")]
[ProducesResponseType(typeof(Page<UserViewDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Page<UserPreviewDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAllStudentsAsync(
Expand Down Expand Up @@ -107,7 +107,7 @@ public async Task<IActionResult> GetAllStudentsAsync(
/// <response code="403">User lacks 'Administrator' role</response>
[HttpGet]
[Route("teachers")]
[ProducesResponseType(typeof(Page<UserViewDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Page<UserPreviewDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAllTeachersAsync(
Expand Down
33 changes: 27 additions & 6 deletions Infrastructure/Services/Users/UsersService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public UsersService(ApplicationDbContext dbContext)
_dbContext = dbContext;
}

private static async Task<Page<UserViewDto>> SelectUsersAsync(
private static async Task<Page<UserPreviewDto>> SelectUsersAsync(
IQueryable<ApplicationUser> query,
PaginationProperties? properties,
IFilter<ApplicationUser>? filter,
Expand All @@ -27,18 +27,18 @@ private static async Task<Page<UserViewDto>> SelectUsersAsync(
query = query.AsNoTracking()
.Where(u => u.IsDeleted == onlyDeleted);
query = filter?.Apply(query) ?? query;
return await query.ToPageAsync<ApplicationUser, UserViewDto>(properties);
return await query.ToPageAsync<ApplicationUser, UserPreviewDto>(properties);
}

/// <inheritdoc />
public async Task<Page<UserViewDto>> GetAllUsersAsync(PaginationProperties? properties,
public async Task<Page<UserPreviewDto>> GetAllUsersAsync(PaginationProperties? properties,
IFilter<ApplicationUser>? filter = null, bool onlyDeleted = false)
{
return await SelectUsersAsync(_dbContext.Users, properties, filter, onlyDeleted);
}

/// <inheritdoc />
public async Task<Page<UserViewDto>> GetUsersInRoleAsync(string role, PaginationProperties? properties,
public async Task<Page<UserPreviewDto>> GetUsersInRoleAsync(string role, PaginationProperties? properties,
IFilter<ApplicationUser>? filter = null)
{
string? roleId = await _dbContext.Roles
Expand All @@ -48,11 +48,32 @@ public async Task<Page<UserViewDto>> GetUsersInRoleAsync(string role, Pagination
throw new InvalidOperationException($"Role {role} doesn't exists");

var users = _dbContext.UserRoles
.Where(r => r.RoleId == roleId)
.Join(_dbContext.Users, r => r.UserId, u => u.Id, (r, u) => u);
.AsNoTracking()
.Where(ur => ur.RoleId == roleId)
.Join(_dbContext.Users, r => r.UserId, u => u.Id, (ur, u) => u);
return await SelectUsersAsync(users, properties, filter, false);
}

/// <inheritdoc />
public async Task<UserViewDto?> GetByIdAsync(string id)
{
var user = await _dbContext.Users
.AsNoTracking()
.Where(u => u.Id == id)
.FirstOrDefaultAsync();
if (user == null) return null;

var dto = user.Adapt<UserViewDto>();
// Select user's roles
dto.Roles = await _dbContext.UserRoles
.AsNoTracking()
.Where(ur => ur.UserId == id)
.Join(_dbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r)
.Select(r => r.Name!)
.ToListAsync();
return dto;
}

/// <inheritdoc />
public async Task<bool> DeleteUserAsync(string userId)
{
Expand Down
12 changes: 6 additions & 6 deletions IntegrationTests/Controllers/UsersControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class UsersControllerTests : ControllersTest
RegisterUser2
});

public UserViewDto[] TestUsers =
public UserPreviewDto[] TestUsers =
{
new("1", "mail1@example.com", "user1", "First1", "Last1", null),
new("2", "mail2@example.com", "user2", "First2", "Last2", "Middle2")
Expand Down Expand Up @@ -56,7 +56,7 @@ public static readonly (string, string)[] RegisterMethodsWithRoles =
(RolesGetMethods[1], Roles.Teacher)
};

private Page<UserViewDto> GetTestPage(PaginationProperties paginationProperties)
private Page<UserPreviewDto> GetTestPage(PaginationProperties paginationProperties)
{
return new(TestUsers, paginationProperties, TestUsers.Length);
}
Expand Down Expand Up @@ -84,7 +84,7 @@ public async Task GetMethods_AdministratorRole_SucceedAndReturnValidType(string

// Assert
result.EnsureSuccessStatusCode();
var users = await result.Content.ReadFromJsonAsync<Page<UserViewDto>>();
var users = await result.Content.ReadFromJsonAsync<Page<UserPreviewDto>>();
Assert.That(users, Is.Not.Null);
}

Expand All @@ -101,7 +101,7 @@ public async Task GetMethods_PaginationQueryParams_SucceedAndApplyPagination(str

// Assert
result.EnsureSuccessStatusCode();
var usersPage = await result.Content.ReadFromJsonAsync<Page<UserViewDto>>();
var usersPage = await result.Content.ReadFromJsonAsync<Page<UserPreviewDto>>();
Assert.That(usersPage, Is.Not.Null);
Assert.Multiple(() =>
{
Expand All @@ -122,7 +122,7 @@ public async Task GetAllUsers_FilterQueryParams_SucceedsAndAppliesFilter()

// Assert
result.EnsureSuccessStatusCode();
var usersPage = await result.Content.ReadFromJsonAsync<Page<UserViewDto>>();
var usersPage = await result.Content.ReadFromJsonAsync<Page<UserPreviewDto>>();
Assert.That(usersPage, Is.Not.Null);
await WebApplicationFactory.UsersServiceMock
.Received()
Expand All @@ -142,7 +142,7 @@ public async Task GetUsersInRoleMethods_FilterQueryParams_SucceedAndApplyFilter(

// Assert
result.EnsureSuccessStatusCode();
var usersPage = await result.Content.ReadFromJsonAsync<Page<UserViewDto>>();
var usersPage = await result.Content.ReadFromJsonAsync<Page<UserPreviewDto>>();
Assert.That(usersPage, Is.Not.Null);
await WebApplicationFactory.UsersServiceMock
.Received()
Expand Down
43 changes: 39 additions & 4 deletions IntegrationTests/Services/UsersServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using EUniversity.Core.Filters;
using EUniversity.Core.Dtos.Users;
using EUniversity.Core.Filters;
using EUniversity.Core.Models;
using EUniversity.Core.Pagination;
using EUniversity.Core.Policy;
Expand Down Expand Up @@ -180,7 +181,7 @@ public async Task GetUsersInRole_ReturnsUsersInRole()
}

[Test]
public virtual async Task DeleteUser_UserExists_Succeeds()
public async Task DeleteUser_UserExists_Succeeds()
{
// Arrange
ApplicationUser user = new()
Expand All @@ -205,7 +206,7 @@ public virtual async Task DeleteUser_UserExists_Succeeds()
}

[Test]
public virtual async Task DeleteUser_UserDoesNotExist_ReturnsFalse()
public async Task DeleteUser_UserDoesNotExist_ReturnsFalse()
{
// Arrange
string fakeId = "null";
Expand All @@ -218,7 +219,7 @@ public virtual async Task DeleteUser_UserDoesNotExist_ReturnsFalse()
}

[Test]
public virtual async Task DeleteUser_UserIsDeleted_ReturnsFalse()
public async Task DeleteUser_UserIsDeleted_ReturnsFalse()
{
// Arrange
ApplicationUser user = new()
Expand All @@ -237,4 +238,38 @@ public virtual async Task DeleteUser_UserIsDeleted_ReturnsFalse()
// Assert
Assert.That(result, Is.False);
}

[Test]
public async Task GetUserById_UserExists_ReturnsUserWithRoles()
{
// Arrange
string[] roles = { Roles.Administrator, Roles.Teacher };
var user = await RegisterTestUserAsync(roles);

// Act
var result = await _usersService.GetByIdAsync(user.Id);

// Assert
Assert.That(result, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(result.Id, Is.EqualTo(user.Id));
Assert.That(result.FirstName, Is.EqualTo(user.FirstName));
Assert.That(result.Email, Is.EqualTo(user.Email));
Assert.That(result.Roles, Is.EquivalentTo(roles));
});
}

[Test]
public async Task GetUserById_UserDoesNotExist_ReturnsNull()
{
// Arrange
string fakeId = "null";

// Act
var result = await _usersService.GetByIdAsync(fakeId);

// Assert
Assert.That(result, Is.Null);
}
}