Skip to content

Commit e17be3a

Browse files
authored
Merge pull request #88 from Resellio/feat/adminController
Admin controller
2 parents de2895c + 10cb41c commit e17be3a

File tree

10 files changed

+357
-8
lines changed

10 files changed

+357
-8
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
using System.Collections.Generic;
2+
using System.Security.Claims;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Moq;
7+
using TickAPI.Admins.Abstractions;
8+
using TickAPI.Admins.Controllers;
9+
using TickAPI.Admins.DTOs.Request;
10+
using TickAPI.Admins.DTOs.Response;
11+
using TickAPI.Admins.Models;
12+
using TickAPI.Common.Auth.Abstractions;
13+
using TickAPI.Common.Auth.Enums;
14+
using TickAPI.Common.Auth.Responses;
15+
using TickAPI.Common.Claims.Abstractions;
16+
using TickAPI.Common.Results.Generic;
17+
using TickAPI.Customers.DTOs.Request;
18+
using Xunit;
19+
20+
namespace TickAPI.Tests.Admins.Controllers;
21+
22+
public class AdminsControllerTests
23+
{
24+
private readonly Mock<IGoogleAuthService> _mockGoogleAuthService;
25+
private readonly Mock<IJwtService> _mockJwtService;
26+
private readonly Mock<IAdminService> _mockAdminService;
27+
private readonly Mock<IClaimsService> _mockClaimsService;
28+
private readonly AdminsController _controller;
29+
30+
public AdminsControllerTests()
31+
{
32+
_mockGoogleAuthService = new Mock<IGoogleAuthService>();
33+
_mockJwtService = new Mock<IJwtService>();
34+
_mockAdminService = new Mock<IAdminService>();
35+
_mockClaimsService = new Mock<IClaimsService>();
36+
37+
_controller = new AdminsController(
38+
_mockGoogleAuthService.Object,
39+
_mockJwtService.Object,
40+
_mockAdminService.Object,
41+
_mockClaimsService.Object
42+
);
43+
}
44+
45+
[Fact]
46+
public async Task GoogleLogin_WithValidAccessToken_ReturnsJwtToken()
47+
{
48+
// Arrange
49+
const string email = "existing@test.com";
50+
const string accessToken = "valid-google-token";
51+
const string jwtToken = "valid-jwt-token";
52+
53+
_mockGoogleAuthService
54+
.Setup(x => x.GetUserDataFromAccessToken(accessToken))
55+
.ReturnsAsync(Result<GoogleUserData>.Success(new GoogleUserData(email, "First", "Last")));
56+
57+
_mockAdminService
58+
.Setup(x => x.GetAdminByEmailAsync(email))
59+
.ReturnsAsync(Result<Admin>.Success(new Admin{Email = email}));
60+
61+
_mockJwtService
62+
.Setup(x => x.GenerateJwtToken(email, UserRole.Admin))
63+
.Returns(Result<string>.Success(jwtToken));
64+
65+
// Act
66+
var result = await _controller.GoogleLogin(new GoogleAdminLoginDto(accessToken));
67+
68+
// Assert
69+
var actionResult = Assert.IsType<ActionResult<GoogleAdminLoginResponseDto>>(result);
70+
var okResult = Assert.IsType<GoogleAdminLoginResponseDto>(actionResult.Value);
71+
Assert.Equal(jwtToken, okResult.Token);
72+
}
73+
74+
[Fact]
75+
public async Task GoogleLogin_WithInvalidAccessToken_ReturnsErrorStatusCode()
76+
{
77+
// Arrange
78+
const string accessToken = "valid-google-token";
79+
80+
_mockGoogleAuthService
81+
.Setup(x => x.GetUserDataFromAccessToken(accessToken))
82+
.ReturnsAsync(Result<GoogleUserData>.Failure(StatusCodes.Status401Unauthorized, "Invalid token"));
83+
84+
// Act
85+
var result = await _controller.GoogleLogin(new GoogleAdminLoginDto(accessToken));
86+
87+
// Assert
88+
var actionResult = Assert.IsType<ActionResult<GoogleAdminLoginResponseDto>>(result);
89+
var objectResult = Assert.IsType<ObjectResult>(actionResult.Result);
90+
Assert.Equal(StatusCodes.Status401Unauthorized, objectResult.StatusCode);
91+
Assert.Equal("Invalid token", objectResult.Value);
92+
}
93+
94+
[Fact]
95+
public async Task GoogleLogin_WithNonAdminEmail_ReturnsErrorStatusCode()
96+
{
97+
// Arrange
98+
const string email = "nonadmin@test.com";
99+
const string accessToken = "valid-google-token";
100+
101+
_mockGoogleAuthService
102+
.Setup(x => x.GetUserDataFromAccessToken(accessToken))
103+
.ReturnsAsync(Result<GoogleUserData>.Success(new GoogleUserData(email, "First", "Last")));
104+
105+
_mockAdminService
106+
.Setup(x => x.GetAdminByEmailAsync(email))
107+
.ReturnsAsync(Result<Admin>.Failure(StatusCodes.Status404NotFound, "User is not an admin"));
108+
109+
// Act
110+
var result = await _controller.GoogleLogin(new GoogleAdminLoginDto(accessToken));
111+
112+
// Assert
113+
var actionResult = Assert.IsType<ActionResult<GoogleAdminLoginResponseDto>>(result);
114+
var objectResult = Assert.IsType<ObjectResult>(actionResult.Result);
115+
Assert.Equal(StatusCodes.Status404NotFound, objectResult.StatusCode);
116+
Assert.Equal("User is not an admin", objectResult.Value);
117+
}
118+
119+
120+
[Fact]
121+
public async Task AboutMe_WithValidClaims_ReturnsAdminData()
122+
{
123+
// Arrange
124+
var email = "admin@example.com";
125+
var admin = new Admin { Email = email, Login = "admin" };
126+
127+
// Mock the HttpContext and User
128+
var claims = new List<Claim>
129+
{
130+
new Claim(ClaimTypes.Email, email)
131+
};
132+
var identity = new ClaimsIdentity(claims);
133+
var principal = new ClaimsPrincipal(identity);
134+
_controller.ControllerContext = new ControllerContext
135+
{
136+
HttpContext = new DefaultHttpContext { User = principal }
137+
};
138+
139+
_mockClaimsService
140+
.Setup(x => x.GetEmailFromClaims(It.IsAny<IEnumerable<Claim>>()))
141+
.Returns(Result<string>.Success(email));
142+
143+
_mockAdminService
144+
.Setup(x => x.GetAdminByEmailAsync(email))
145+
.ReturnsAsync(Result<Admin>.Success(admin));
146+
147+
// Act
148+
var result = await _controller.AboutMe();
149+
150+
// Assert
151+
var actionResult = Assert.IsType<ActionResult<AboutMeAdminResponseDto>>(result);
152+
var okResult = Assert.IsType<AboutMeAdminResponseDto>(actionResult.Value);
153+
Assert.Equal(admin.Email, okResult.Email);
154+
Assert.Equal(admin.Login, okResult.Login);
155+
}
156+
157+
[Fact]
158+
public async Task AboutMe_WithInvalidClaims_ReturnsErrorStatusCode()
159+
{
160+
// Arrange
161+
var claims = new List<Claim>();
162+
var identity = new ClaimsIdentity(claims);
163+
var principal = new ClaimsPrincipal(identity);
164+
_controller.ControllerContext = new ControllerContext
165+
{
166+
HttpContext = new DefaultHttpContext { User = principal }
167+
};
168+
169+
_mockClaimsService
170+
.Setup(x => x.GetEmailFromClaims(It.IsAny<IEnumerable<Claim>>()))
171+
.Returns(Result<string>.Failure(StatusCodes.Status401Unauthorized, "Email claim not found"));
172+
173+
// Act
174+
var result = await _controller.AboutMe();
175+
176+
// Assert
177+
var actionResult = Assert.IsType<ActionResult<AboutMeAdminResponseDto>>(result);
178+
var objectResult = Assert.IsType<ObjectResult>(actionResult.Result);
179+
Assert.Equal(StatusCodes.Status401Unauthorized, objectResult.StatusCode);
180+
Assert.Equal("Email claim not found", objectResult.Value);
181+
}
182+
183+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Moq;
3+
using TickAPI.Admins.Abstractions;
4+
using TickAPI.Admins.Models;
5+
using TickAPI.Admins.Services;
6+
using TickAPI.Common.Results.Generic;
7+
using TickAPI.Common.Time.Abstractions;
8+
using TickAPI.Customers.Abstractions;
9+
using TickAPI.Customers.Models;
10+
using TickAPI.Customers.Services;
11+
12+
namespace TickAPI.Tests.Admins.Services;
13+
14+
public class AdminsServiceTests
15+
{
16+
[Fact]
17+
public async Task GetAdminByEmailAsync_WhenAdminWithEmailIsReturnedFromRepository_ShouldReturnUser()
18+
{
19+
var admin = new Admin
20+
{
21+
Email = "example@test.com"
22+
};
23+
var adminRepositoryMock = new Mock<IAdminRepository>();
24+
adminRepositoryMock.Setup(m => m.GetAdminByEmailAsync(admin.Email)).ReturnsAsync(Result<Admin>.Success(admin));
25+
var sut = new AdminService(adminRepositoryMock.Object);
26+
27+
var result = await sut.GetAdminByEmailAsync(admin.Email);
28+
29+
Assert.True(result.IsSuccess);
30+
Assert.Equal(admin, result.Value);
31+
}
32+
33+
[Fact]
34+
public async Task GetAdminByEmailAsync_WhenAdminWithEmailIsNotReturnedFromRepository_ShouldReturnFailure()
35+
{
36+
const string email = "not@existing.com";
37+
var adminRepositoryMock = new Mock<IAdminRepository>();
38+
adminRepositoryMock.Setup(m => m.GetAdminByEmailAsync(email)).ReturnsAsync(Result<Admin>.Failure(StatusCodes.Status404NotFound, $"admin with email '{email}' not found"));
39+
var sut = new AdminService(adminRepositoryMock.Object);
40+
41+
var result = await sut.GetAdminByEmailAsync(email);
42+
43+
Assert.True(result.IsError);
44+
Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode);
45+
Assert.Equal($"admin with email '{email}' not found", result.ErrorMsg);
46+
}
47+
48+
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
namespace TickAPI.Admins.Abstractions;
1+
using TickAPI.Admins.Models;
2+
using TickAPI.Common.Results.Generic;
3+
4+
namespace TickAPI.Admins.Abstractions;
25

36
public interface IAdminRepository
47
{
5-
8+
Task<Result<Admin>> GetAdminByEmailAsync(string adminEmail);
69
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
namespace TickAPI.Admins.Abstractions;
1+
using TickAPI.Admins.Models;
2+
using TickAPI.Common.Results.Generic;
3+
4+
namespace TickAPI.Admins.Abstractions;
25

36
public interface IAdminService
47
{
5-
8+
public Task<Result<Admin>> GetAdminByEmailAsync(string adminEmail);
69
}
Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,73 @@
11
using Microsoft.AspNetCore.Mvc;
2+
using TickAPI.Admins.Abstractions;
3+
using TickAPI.Admins.DTOs.Request;
4+
using TickAPI.Admins.DTOs.Response;
5+
using TickAPI.Common.Auth.Abstractions;
6+
using TickAPI.Common.Auth.Attributes;
7+
using TickAPI.Common.Auth.Enums;
8+
using TickAPI.Common.Claims.Abstractions;
9+
using TickAPI.Customers.DTOs.Request;
210

311
namespace TickAPI.Admins.Controllers;
412

513
[ApiController]
614
[Route("api/[controller]")]
715
public class AdminsController : ControllerBase
816
{
17+
private readonly IGoogleAuthService _googleAuthService;
18+
private readonly IJwtService _jwtService;
19+
private readonly IAdminService _adminService;
20+
private readonly IClaimsService _claimsService;
921

10-
}
22+
public AdminsController(IGoogleAuthService googleAuthService, IJwtService jwtService, IAdminService adminService, IClaimsService claimsService)
23+
{
24+
_googleAuthService = googleAuthService;
25+
_jwtService = jwtService;
26+
_adminService = adminService;
27+
_claimsService = claimsService;
28+
}
29+
30+
[HttpPost("google-login")]
31+
public async Task<ActionResult<GoogleAdminLoginResponseDto>> GoogleLogin([FromBody] GoogleAdminLoginDto request)
32+
{
33+
var userDataResult = await _googleAuthService.GetUserDataFromAccessToken(request.AccessToken);
34+
if(userDataResult.IsError)
35+
return StatusCode(userDataResult.StatusCode, userDataResult.ErrorMsg);
36+
37+
var userData = userDataResult.Value!;
38+
39+
var adminResult = await _adminService.GetAdminByEmailAsync(userData.Email);
40+
if (adminResult.IsError)
41+
{
42+
return StatusCode(adminResult.StatusCode, adminResult.ErrorMsg);
43+
}
44+
45+
var jwtTokenResult = _jwtService.GenerateJwtToken(userData.Email, UserRole.Admin);
46+
if (jwtTokenResult.IsError)
47+
return StatusCode(jwtTokenResult.StatusCode, jwtTokenResult.ErrorMsg);
48+
49+
return new ActionResult<GoogleAdminLoginResponseDto>(new GoogleAdminLoginResponseDto(jwtTokenResult.Value!));
50+
}
51+
52+
[AuthorizeWithPolicy(AuthPolicies.AdminPolicy)]
53+
[HttpGet("about-me")]
54+
public async Task<ActionResult<AboutMeAdminResponseDto>> AboutMe()
55+
{
56+
var emailResult = _claimsService.GetEmailFromClaims(User.Claims);
57+
if (emailResult.IsError)
58+
{
59+
return StatusCode(emailResult.StatusCode, emailResult.ErrorMsg);
60+
}
61+
var email = emailResult.Value!;
62+
63+
var adminResult = await _adminService.GetAdminByEmailAsync(email);
64+
if (adminResult.IsError)
65+
return StatusCode(StatusCodes.Status500InternalServerError,
66+
"cannot find user with admin privilages in database for authorized admin request");
67+
68+
var admin = adminResult.Value!;
69+
var aboutMeResponse =
70+
new AboutMeAdminResponseDto(admin.Email, admin.Login);
71+
return new ActionResult<AboutMeAdminResponseDto>(aboutMeResponse);
72+
}
73+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace TickAPI.Admins.DTOs.Request;
2+
3+
public record GoogleAdminLoginDto(
4+
string AccessToken
5+
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace TickAPI.Admins.DTOs.Response;
2+
3+
public record AboutMeAdminResponseDto(
4+
string Email,
5+
string Login
6+
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace TickAPI.Admins.DTOs.Response;
2+
3+
public record GoogleAdminLoginResponseDto(
4+
string Token
5+
);
Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1-
using TickAPI.Admins.Abstractions;
1+
using Microsoft.EntityFrameworkCore;
2+
using TickAPI.Admins.Abstractions;
3+
using TickAPI.Admins.Models;
4+
using TickAPI.Common.Results.Generic;
5+
using TickAPI.Common.TickApiDbContext;
6+
using TickAPI.Customers.Models;
27

38
namespace TickAPI.Admins.Repositories;
49

510
public class AdminRepository : IAdminRepository
611
{
7-
12+
private readonly TickApiDbContext _tickApiDbContext;
13+
14+
public AdminRepository(TickApiDbContext tickApiDbContext)
15+
{
16+
_tickApiDbContext = tickApiDbContext;
17+
}
18+
public async Task<Result<Admin>> GetAdminByEmailAsync(string adminEmail)
19+
{
20+
var admin = await _tickApiDbContext.Admins.FirstOrDefaultAsync(admin => admin.Email == adminEmail);
21+
22+
if (admin == null)
23+
{
24+
return Result<Admin>.Failure(StatusCodes.Status404NotFound, $"admin with email '{adminEmail}' not found");
25+
}
26+
27+
return Result<Admin>.Success(admin);
28+
}
829
}

0 commit comments

Comments
 (0)