Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions TickAPI/TickAPI.Tests/Admins/Controllers/AdminsControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TickAPI.Admins.Abstractions;
using TickAPI.Admins.Controllers;
using TickAPI.Admins.DTOs.Request;
using TickAPI.Admins.DTOs.Response;
using TickAPI.Admins.Models;
using TickAPI.Common.Auth.Abstractions;
using TickAPI.Common.Auth.Enums;
using TickAPI.Common.Auth.Responses;
using TickAPI.Common.Claims.Abstractions;
using TickAPI.Common.Results.Generic;
using TickAPI.Customers.DTOs.Request;
using Xunit;

namespace TickAPI.Tests.Admins.Controllers;

public class AdminsControllerTests
{
private readonly Mock<IGoogleAuthService> _mockGoogleAuthService;
private readonly Mock<IJwtService> _mockJwtService;
private readonly Mock<IAdminService> _mockAdminService;
private readonly Mock<IClaimsService> _mockClaimsService;
private readonly AdminsController _controller;

public AdminsControllerTests()
{
_mockGoogleAuthService = new Mock<IGoogleAuthService>();
_mockJwtService = new Mock<IJwtService>();
_mockAdminService = new Mock<IAdminService>();
_mockClaimsService = new Mock<IClaimsService>();

_controller = new AdminsController(
_mockGoogleAuthService.Object,
_mockJwtService.Object,
_mockAdminService.Object,
_mockClaimsService.Object
);
}

[Fact]
public async Task GoogleLogin_WithValidAccessToken_ReturnsJwtToken()
{
// Arrange
const string email = "existing@test.com";
const string accessToken = "valid-google-token";
const string jwtToken = "valid-jwt-token";

_mockGoogleAuthService
.Setup(x => x.GetUserDataFromAccessToken(accessToken))
.ReturnsAsync(Result<GoogleUserData>.Success(new GoogleUserData(email, "First", "Last")));

_mockAdminService
.Setup(x => x.GetAdminByEmailAsync(email))
.ReturnsAsync(Result<Admin>.Success(new Admin{Email = email}));

_mockJwtService
.Setup(x => x.GenerateJwtToken(email, UserRole.Admin))
.Returns(Result<string>.Success(jwtToken));

// Act
var result = await _controller.GoogleLogin(new GoogleAdminLoginDto(accessToken));

// Assert
var actionResult = Assert.IsType<ActionResult<GoogleAdminLoginResponseDto>>(result);
var okResult = Assert.IsType<GoogleAdminLoginResponseDto>(actionResult.Value);
Assert.Equal(jwtToken, okResult.Token);
}

[Fact]
public async Task GoogleLogin_WithInvalidAccessToken_ReturnsErrorStatusCode()
{
// Arrange
const string accessToken = "valid-google-token";

_mockGoogleAuthService
.Setup(x => x.GetUserDataFromAccessToken(accessToken))
.ReturnsAsync(Result<GoogleUserData>.Failure(StatusCodes.Status401Unauthorized, "Invalid token"));

// Act
var result = await _controller.GoogleLogin(new GoogleAdminLoginDto(accessToken));

// Assert
var actionResult = Assert.IsType<ActionResult<GoogleAdminLoginResponseDto>>(result);
var objectResult = Assert.IsType<ObjectResult>(actionResult.Result);
Assert.Equal(StatusCodes.Status401Unauthorized, objectResult.StatusCode);
Assert.Equal("Invalid token", objectResult.Value);
}

[Fact]
public async Task GoogleLogin_WithNonAdminEmail_ReturnsErrorStatusCode()
{
// Arrange
const string email = "nonadmin@test.com";
const string accessToken = "valid-google-token";

_mockGoogleAuthService
.Setup(x => x.GetUserDataFromAccessToken(accessToken))
.ReturnsAsync(Result<GoogleUserData>.Success(new GoogleUserData(email, "First", "Last")));

_mockAdminService
.Setup(x => x.GetAdminByEmailAsync(email))
.ReturnsAsync(Result<Admin>.Failure(StatusCodes.Status404NotFound, "User is not an admin"));

// Act
var result = await _controller.GoogleLogin(new GoogleAdminLoginDto(accessToken));

// Assert
var actionResult = Assert.IsType<ActionResult<GoogleAdminLoginResponseDto>>(result);
var objectResult = Assert.IsType<ObjectResult>(actionResult.Result);
Assert.Equal(StatusCodes.Status404NotFound, objectResult.StatusCode);
Assert.Equal("User is not an admin", objectResult.Value);
}


[Fact]
public async Task AboutMe_WithValidClaims_ReturnsAdminData()
{
// Arrange
var email = "admin@example.com";
var admin = new Admin { Email = email, Login = "admin" };

// Mock the HttpContext and User
var claims = new List<Claim>
{
new Claim(ClaimTypes.Email, email)
};
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = principal }
};

_mockClaimsService
.Setup(x => x.GetEmailFromClaims(It.IsAny<IEnumerable<Claim>>()))
.Returns(Result<string>.Success(email));

_mockAdminService
.Setup(x => x.GetAdminByEmailAsync(email))
.ReturnsAsync(Result<Admin>.Success(admin));

// Act
var result = await _controller.AboutMe();

// Assert
var actionResult = Assert.IsType<ActionResult<AboutMeAdminResponseDto>>(result);
var okResult = Assert.IsType<AboutMeAdminResponseDto>(actionResult.Value);
Assert.Equal(admin.Email, okResult.Email);
Assert.Equal(admin.Login, okResult.Login);
}

[Fact]
public async Task AboutMe_WithInvalidClaims_ReturnsErrorStatusCode()
{
// Arrange
var claims = new List<Claim>();
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = principal }
};

_mockClaimsService
.Setup(x => x.GetEmailFromClaims(It.IsAny<IEnumerable<Claim>>()))
.Returns(Result<string>.Failure(StatusCodes.Status401Unauthorized, "Email claim not found"));

// Act
var result = await _controller.AboutMe();

// Assert
var actionResult = Assert.IsType<ActionResult<AboutMeAdminResponseDto>>(result);
var objectResult = Assert.IsType<ObjectResult>(actionResult.Result);
Assert.Equal(StatusCodes.Status401Unauthorized, objectResult.StatusCode);
Assert.Equal("Email claim not found", objectResult.Value);
}

}
48 changes: 48 additions & 0 deletions TickAPI/TickAPI.Tests/Admins/Services/AdminsServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Http;
using Moq;
using TickAPI.Admins.Abstractions;
using TickAPI.Admins.Models;
using TickAPI.Admins.Services;
using TickAPI.Common.Results.Generic;
using TickAPI.Common.Time.Abstractions;
using TickAPI.Customers.Abstractions;
using TickAPI.Customers.Models;
using TickAPI.Customers.Services;

namespace TickAPI.Tests.Admins.Services;

public class AdminsServiceTests
{
[Fact]
public async Task GetAdminByEmailAsync_WhenAdminWithEmailIsReturnedFromRepository_ShouldReturnUser()
{
var admin = new Admin
{
Email = "example@test.com"
};
var adminRepositoryMock = new Mock<IAdminRepository>();
adminRepositoryMock.Setup(m => m.GetAdminByEmailAsync(admin.Email)).ReturnsAsync(Result<Admin>.Success(admin));
var sut = new AdminService(adminRepositoryMock.Object);

var result = await sut.GetAdminByEmailAsync(admin.Email);

Assert.True(result.IsSuccess);
Assert.Equal(admin, result.Value);
}

[Fact]
public async Task GetAdminByEmailAsync_WhenAdminWithEmailIsNotReturnedFromRepository_ShouldReturnFailure()
{
const string email = "not@existing.com";
var adminRepositoryMock = new Mock<IAdminRepository>();
adminRepositoryMock.Setup(m => m.GetAdminByEmailAsync(email)).ReturnsAsync(Result<Admin>.Failure(StatusCodes.Status404NotFound, $"admin with email '{email}' not found"));
var sut = new AdminService(adminRepositoryMock.Object);

var result = await sut.GetAdminByEmailAsync(email);

Assert.True(result.IsError);
Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode);
Assert.Equal($"admin with email '{email}' not found", result.ErrorMsg);
}

}
7 changes: 5 additions & 2 deletions TickAPI/TickAPI/Admins/Abstractions/IAdminRepository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace TickAPI.Admins.Abstractions;
using TickAPI.Admins.Models;
using TickAPI.Common.Results.Generic;

namespace TickAPI.Admins.Abstractions;

public interface IAdminRepository
{

Task<Result<Admin>> GetAdminByEmailAsync(string adminEmail);
}
7 changes: 5 additions & 2 deletions TickAPI/TickAPI/Admins/Abstractions/IAdminService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace TickAPI.Admins.Abstractions;
using TickAPI.Admins.Models;
using TickAPI.Common.Results.Generic;

namespace TickAPI.Admins.Abstractions;

public interface IAdminService
{

public Task<Result<Admin>> GetAdminByEmailAsync(string adminEmail);
}
65 changes: 64 additions & 1 deletion TickAPI/TickAPI/Admins/Controllers/AdminsController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,73 @@
using Microsoft.AspNetCore.Mvc;
using TickAPI.Admins.Abstractions;
using TickAPI.Admins.DTOs.Request;
using TickAPI.Admins.DTOs.Response;
using TickAPI.Common.Auth.Abstractions;
using TickAPI.Common.Auth.Attributes;
using TickAPI.Common.Auth.Enums;
using TickAPI.Common.Claims.Abstractions;
using TickAPI.Customers.DTOs.Request;

namespace TickAPI.Admins.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AdminsController : ControllerBase
{
private readonly IGoogleAuthService _googleAuthService;
private readonly IJwtService _jwtService;
private readonly IAdminService _adminService;
private readonly IClaimsService _claimsService;

}
public AdminsController(IGoogleAuthService googleAuthService, IJwtService jwtService, IAdminService adminService, IClaimsService claimsService)
{
_googleAuthService = googleAuthService;
_jwtService = jwtService;
_adminService = adminService;
_claimsService = claimsService;
}

[HttpPost("google-login")]
public async Task<ActionResult<GoogleAdminLoginResponseDto>> GoogleLogin([FromBody] GoogleAdminLoginDto request)
{
var userDataResult = await _googleAuthService.GetUserDataFromAccessToken(request.AccessToken);
if(userDataResult.IsError)
return StatusCode(userDataResult.StatusCode, userDataResult.ErrorMsg);

var userData = userDataResult.Value!;

var adminResult = await _adminService.GetAdminByEmailAsync(userData.Email);
if (adminResult.IsError)
{
return StatusCode(adminResult.StatusCode, adminResult.ErrorMsg);
}

var jwtTokenResult = _jwtService.GenerateJwtToken(userData.Email, UserRole.Admin);
if (jwtTokenResult.IsError)
return StatusCode(jwtTokenResult.StatusCode, jwtTokenResult.ErrorMsg);

return new ActionResult<GoogleAdminLoginResponseDto>(new GoogleAdminLoginResponseDto(jwtTokenResult.Value!));
}

[AuthorizeWithPolicy(AuthPolicies.AdminPolicy)]
[HttpGet("about-me")]
public async Task<ActionResult<AboutMeAdminResponseDto>> AboutMe()
{
var emailResult = _claimsService.GetEmailFromClaims(User.Claims);
if (emailResult.IsError)
{
return StatusCode(emailResult.StatusCode, emailResult.ErrorMsg);
}
var email = emailResult.Value!;

var adminResult = await _adminService.GetAdminByEmailAsync(email);
if (adminResult.IsError)
return StatusCode(StatusCodes.Status500InternalServerError,
"cannot find user with admin privilages in database for authorized admin request");

var admin = adminResult.Value!;
var aboutMeResponse =
new AboutMeAdminResponseDto(admin.Email, admin.Login);
return new ActionResult<AboutMeAdminResponseDto>(aboutMeResponse);
}
}
5 changes: 5 additions & 0 deletions TickAPI/TickAPI/Admins/DTOs/Request/GoogleAdminLoginDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace TickAPI.Admins.DTOs.Request;

public record GoogleAdminLoginDto(
string AccessToken
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TickAPI.Admins.DTOs.Response;

public record AboutMeAdminResponseDto(
string Email,
string Login
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace TickAPI.Admins.DTOs.Response;

public record GoogleAdminLoginResponseDto(
string Token
);
25 changes: 23 additions & 2 deletions TickAPI/TickAPI/Admins/Repositories/AdminRepository.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
using TickAPI.Admins.Abstractions;
using Microsoft.EntityFrameworkCore;
using TickAPI.Admins.Abstractions;
using TickAPI.Admins.Models;
using TickAPI.Common.Results.Generic;
using TickAPI.Common.TickApiDbContext;
using TickAPI.Customers.Models;

namespace TickAPI.Admins.Repositories;

public class AdminRepository : IAdminRepository
{

private readonly TickApiDbContext _tickApiDbContext;

public AdminRepository(TickApiDbContext tickApiDbContext)
{
_tickApiDbContext = tickApiDbContext;
}
public async Task<Result<Admin>> GetAdminByEmailAsync(string adminEmail)
{
var admin = await _tickApiDbContext.Admins.FirstOrDefaultAsync(admin => admin.Email == adminEmail);

if (admin == null)
{
return Result<Admin>.Failure(StatusCodes.Status404NotFound, $"admin with email '{adminEmail}' not found");
}

return Result<Admin>.Success(admin);
}
}
Loading
Loading