From 0f5d817fc3386281ba3b4b1d6f0cc5e5317b3c56 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 17:56:11 +0100 Subject: [PATCH 1/9] Change `IAuthService` to `IGoogleAuthService` --- .../Common/Auth/Abstractions/IAuthService.cs | 8 -------- .../Common/Auth/Abstractions/IGoogleAuthService.cs | 9 +++++++++ .../Common/Auth/Controllers/AuthController.cs | 14 +++++++------- .../Common/Auth/Responses/GoogleUserData.cs | 7 +++++++ .../Common/Auth/Services/GoogleAuthService.cs | 13 +++++++------ 5 files changed, 30 insertions(+), 21 deletions(-) delete mode 100644 TickAPI/TickAPI/Common/Auth/Abstractions/IAuthService.cs create mode 100644 TickAPI/TickAPI/Common/Auth/Abstractions/IGoogleAuthService.cs create mode 100644 TickAPI/TickAPI/Common/Auth/Responses/GoogleUserData.cs diff --git a/TickAPI/TickAPI/Common/Auth/Abstractions/IAuthService.cs b/TickAPI/TickAPI/Common/Auth/Abstractions/IAuthService.cs deleted file mode 100644 index df0f33f..0000000 --- a/TickAPI/TickAPI/Common/Auth/Abstractions/IAuthService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using TickAPI.Common.Result; - -namespace TickAPI.Common.Auth.Abstractions; - -public interface IAuthService -{ - Task> LoginAsync(string idToken); -} \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Auth/Abstractions/IGoogleAuthService.cs b/TickAPI/TickAPI/Common/Auth/Abstractions/IGoogleAuthService.cs new file mode 100644 index 0000000..039e75c --- /dev/null +++ b/TickAPI/TickAPI/Common/Auth/Abstractions/IGoogleAuthService.cs @@ -0,0 +1,9 @@ +using TickAPI.Common.Auth.Responses; +using TickAPI.Common.Result; + +namespace TickAPI.Common.Auth.Abstractions; + +public interface IGoogleAuthService +{ + Task> GetUserDataFromToken(string token); +} \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Auth/Controllers/AuthController.cs b/TickAPI/TickAPI/Common/Auth/Controllers/AuthController.cs index bdeedc5..6483535 100644 --- a/TickAPI/TickAPI/Common/Auth/Controllers/AuthController.cs +++ b/TickAPI/TickAPI/Common/Auth/Controllers/AuthController.cs @@ -8,12 +8,12 @@ namespace TickAPI.Common.Auth.Controllers; [Route("api/[controller]")] public class AuthController : ControllerBase { - private readonly IAuthService _authService; + private readonly IGoogleAuthService _googleAuthService; private readonly IJwtService _jwtService; - public AuthController(IAuthService authService, IJwtService jwtService) + public AuthController(IGoogleAuthService googleAuthService, IJwtService jwtService) { - _authService = authService; + _googleAuthService = googleAuthService; _jwtService = jwtService; } @@ -22,12 +22,12 @@ public AuthController(IAuthService authService, IJwtService jwtService) [HttpPost("google-login")] public async Task GoogleLogin([FromBody] GoogleLoginRequest request) { - var loginResult = await _authService.LoginAsync(request.IdToken); + var userDataResult = await _googleAuthService.GetUserDataFromToken(request.IdToken); - if(loginResult.IsError) - return StatusCode(loginResult.StatusCode, loginResult.ErrorMsg); + if(userDataResult.IsError) + return StatusCode(userDataResult.StatusCode, userDataResult.ErrorMsg); - var jwtTokenResult = _jwtService.GenerateJwtToken(loginResult.Value, UserRole.Customer); + var jwtTokenResult = _jwtService.GenerateJwtToken(userDataResult.Value?.Email, UserRole.Customer); if(jwtTokenResult.IsError) return StatusCode(jwtTokenResult.StatusCode, jwtTokenResult.ErrorMsg); diff --git a/TickAPI/TickAPI/Common/Auth/Responses/GoogleUserData.cs b/TickAPI/TickAPI/Common/Auth/Responses/GoogleUserData.cs new file mode 100644 index 0000000..d317e07 --- /dev/null +++ b/TickAPI/TickAPI/Common/Auth/Responses/GoogleUserData.cs @@ -0,0 +1,7 @@ +namespace TickAPI.Common.Auth.Responses; + +public record GoogleUserData( + string Email, + string FirstName, + string LastName +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Auth/Services/GoogleAuthService.cs b/TickAPI/TickAPI/Common/Auth/Services/GoogleAuthService.cs index 4abf2ad..b8fc876 100644 --- a/TickAPI/TickAPI/Common/Auth/Services/GoogleAuthService.cs +++ b/TickAPI/TickAPI/Common/Auth/Services/GoogleAuthService.cs @@ -1,10 +1,10 @@ -using Google.Apis.Auth; -using TickAPI.Common.Auth.Abstractions; +using TickAPI.Common.Auth.Abstractions; +using TickAPI.Common.Auth.Responses; using TickAPI.Common.Result; namespace TickAPI.Common.Auth.Services; -public class GoogleAuthService : IAuthService +public class GoogleAuthService : IGoogleAuthService { private readonly IGoogleTokenValidator _googleTokenValidator; @@ -13,16 +13,17 @@ public GoogleAuthService(IGoogleTokenValidator googleTokenValidator) _googleTokenValidator = googleTokenValidator; } - public async Task> LoginAsync(string idToken) + public async Task> GetUserDataFromToken(string idToken) { try { var payload = await _googleTokenValidator.ValidateAsync(idToken); - return Result.Success(payload.Email); + var userData = new GoogleUserData(payload.Email, payload.GivenName, payload.FamilyName); + return Result.Success(userData); } catch (Exception) { - return Result.Failure(StatusCodes.Status401Unauthorized, "Invalid Google ID token"); + return Result.Failure(StatusCodes.Status401Unauthorized, "Invalid Google ID token"); } } } \ No newline at end of file From 43adeff0805323555f031e7469930d96da6f909d Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 17:57:42 +0100 Subject: [PATCH 2/9] Use only one endpoint for customer google auth --- .../Controllers/CustomerController.cs | 56 +++++-------------- .../DTOs/Request/GoogleCreateNewAccountDto.cs | 6 -- .../GoogleCreateNewAccountResponseDto.cs | 5 -- .../DTOs/Response/GoogleLoginResponseDto.cs | 3 +- 4 files changed, 16 insertions(+), 54 deletions(-) delete mode 100644 TickAPI/TickAPI/Customers/DTOs/Request/GoogleCreateNewAccountDto.cs delete mode 100644 TickAPI/TickAPI/Customers/DTOs/Response/GoogleCreateNewAccountResponseDto.cs diff --git a/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs b/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs index fabb150..d18d01d 100644 --- a/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs +++ b/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs @@ -1,7 +1,5 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using TickAPI.Common.Auth.Abstractions; -using TickAPI.Common.Auth.Attributes; using TickAPI.Common.Auth.Enums; using TickAPI.Customers.Abstractions; using TickAPI.Customers.DTOs.Request; @@ -13,13 +11,13 @@ namespace TickAPI.Customers.Controllers; [Route("api/[controller]")] public class CustomerController : ControllerBase { - private readonly IAuthService _authService; + private readonly IGoogleAuthService _googleAuthService; private readonly IJwtService _jwtService; private readonly ICustomerService _customerService; - public CustomerController(IAuthService authService, IJwtService jwtService, ICustomerService customerService) + public CustomerController(IGoogleAuthService googleAuthService, IJwtService jwtService, ICustomerService customerService) { - _authService = authService; + _googleAuthService = googleAuthService; _jwtService = jwtService; _customerService = customerService; } @@ -27,48 +25,24 @@ public CustomerController(IAuthService authService, IJwtService jwtService, ICus [HttpPost("google-login")] public async Task> GoogleLogin([FromBody] GoogleLoginDto request) { - var loginResult = await _authService.LoginAsync(request.IdToken); - if(loginResult.IsError) - return StatusCode(loginResult.StatusCode, loginResult.ErrorMsg); + var userDataResult = await _googleAuthService.GetUserDataFromToken(request.IdToken); + if(userDataResult.IsError) + return StatusCode(userDataResult.StatusCode, userDataResult.ErrorMsg); - UserRole role; - bool isNewCustomer; + var userData = userDataResult.Value!; - var existingCustomerResult = await _customerService.GetCustomerByEmailAsync(loginResult.Value!); - if (existingCustomerResult.IsSuccess) + var existingCustomerResult = await _customerService.GetCustomerByEmailAsync(userData.Email); + if (existingCustomerResult.IsError) { - role = UserRole.Customer; - isNewCustomer = false; + var newCustomerResult = await _customerService.CreateNewCustomerAsync(userData.Email, userData.FirstName, userData.LastName); + if (newCustomerResult.IsError) + return StatusCode(newCustomerResult.StatusCode, newCustomerResult.ErrorMsg); } - else - { - role = UserRole.NewCustomer; - isNewCustomer = true; - } - - var jwtTokenResult = _jwtService.GenerateJwtToken(loginResult.Value, role); - if (jwtTokenResult.IsError) - return StatusCode(jwtTokenResult.StatusCode, jwtTokenResult.ErrorMsg); - - return new ActionResult(new GoogleLoginResponseDto(jwtTokenResult.Value!, isNewCustomer)); - } - - [AuthorizeWithPolicy(AuthPolicies.NewCustomerPolicy)] - [HttpPost("google-create-new-account")] - public async Task> GoogleCreateNewAccount([FromBody] GoogleCreateNewAccountDto request) - { - var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; - if (email == null) - return StatusCode(StatusCodes.Status400BadRequest, "missing email claim"); - - var newCustomerResult = await _customerService.CreateNewCustomerAsync(email, request.FirstName, request.LastName); - if (newCustomerResult.IsError) - return StatusCode(newCustomerResult.StatusCode, newCustomerResult.ErrorMsg); - var jwtTokenResult = _jwtService.GenerateJwtToken(newCustomerResult.Value!.Email, UserRole.Customer); + var jwtTokenResult = _jwtService.GenerateJwtToken(userData.Email, UserRole.Customer); if (jwtTokenResult.IsError) return StatusCode(jwtTokenResult.StatusCode, jwtTokenResult.ErrorMsg); - return new ActionResult(new GoogleCreateNewAccountResponseDto(jwtTokenResult.Value!)); + return new ActionResult(new GoogleLoginResponseDto(jwtTokenResult.Value!)); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Customers/DTOs/Request/GoogleCreateNewAccountDto.cs b/TickAPI/TickAPI/Customers/DTOs/Request/GoogleCreateNewAccountDto.cs deleted file mode 100644 index f896265..0000000 --- a/TickAPI/TickAPI/Customers/DTOs/Request/GoogleCreateNewAccountDto.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TickAPI.Customers.DTOs.Request; - -public record GoogleCreateNewAccountDto( - string FirstName, - string LastName -); \ No newline at end of file diff --git a/TickAPI/TickAPI/Customers/DTOs/Response/GoogleCreateNewAccountResponseDto.cs b/TickAPI/TickAPI/Customers/DTOs/Response/GoogleCreateNewAccountResponseDto.cs deleted file mode 100644 index b49ac83..0000000 --- a/TickAPI/TickAPI/Customers/DTOs/Response/GoogleCreateNewAccountResponseDto.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TickAPI.Customers.DTOs.Response; - -public record GoogleCreateNewAccountResponseDto( - string Token -); \ No newline at end of file diff --git a/TickAPI/TickAPI/Customers/DTOs/Response/GoogleLoginResponseDto.cs b/TickAPI/TickAPI/Customers/DTOs/Response/GoogleLoginResponseDto.cs index b2d5434..0090b70 100644 --- a/TickAPI/TickAPI/Customers/DTOs/Response/GoogleLoginResponseDto.cs +++ b/TickAPI/TickAPI/Customers/DTOs/Response/GoogleLoginResponseDto.cs @@ -1,6 +1,5 @@ namespace TickAPI.Customers.DTOs.Response; public record GoogleLoginResponseDto( - string Token, - bool IsNewCustomer + string Token ); \ No newline at end of file From bbf0644f1f2be40e87ad3cee191fdab3e6e27929 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 17:58:24 +0100 Subject: [PATCH 3/9] Remove `NewCustomer` role and related policy + clean up `Program.cs` --- TickAPI/TickAPI/Common/Auth/Enums/AuthPolicies.cs | 1 - TickAPI/TickAPI/Common/Auth/Enums/UserRole.cs | 1 - TickAPI/TickAPI/Program.cs | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/TickAPI/TickAPI/Common/Auth/Enums/AuthPolicies.cs b/TickAPI/TickAPI/Common/Auth/Enums/AuthPolicies.cs index ec06e2e..c5d6408 100644 --- a/TickAPI/TickAPI/Common/Auth/Enums/AuthPolicies.cs +++ b/TickAPI/TickAPI/Common/Auth/Enums/AuthPolicies.cs @@ -6,5 +6,4 @@ public enum AuthPolicies OrganizerPolicy, CustomerPolicy, NewOrganizerPolicy, - NewCustomerPolicy, } \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Auth/Enums/UserRole.cs b/TickAPI/TickAPI/Common/Auth/Enums/UserRole.cs index 93e3677..d382a54 100644 --- a/TickAPI/TickAPI/Common/Auth/Enums/UserRole.cs +++ b/TickAPI/TickAPI/Common/Auth/Enums/UserRole.cs @@ -6,5 +6,4 @@ public enum UserRole Organizer, Customer, NewOrganizer, - NewCustomer, } \ No newline at end of file diff --git a/TickAPI/TickAPI/Program.cs b/TickAPI/TickAPI/Program.cs index 725812a..0faecf6 100644 --- a/TickAPI/TickAPI/Program.cs +++ b/TickAPI/TickAPI/Program.cs @@ -69,7 +69,6 @@ options.AddPolicy(AuthPolicies.CustomerPolicy.ToString(), policy => policy.RequireRole(UserRole.Customer.ToString())); options.AddPolicy(AuthPolicies.NewOrganizerPolicy.ToString(), policy => policy.RequireRole(UserRole.NewOrganizer.ToString())); - options.AddPolicy(AuthPolicies.NewCustomerPolicy.ToString(), policy => policy.RequireRole(UserRole.NewCustomer.ToString())); }); // Add admin services. @@ -93,7 +92,7 @@ builder.Services.AddScoped(); // Add common services. -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); From a6fb00216f3ef372441469767d7e4511a8afb10c Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 18:05:39 +0100 Subject: [PATCH 4/9] Add proper namespace to `GoogleTokenValidator` --- TickAPI/TickAPI/Common/Auth/Services/GoogleTokenValidator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TickAPI/TickAPI/Common/Auth/Services/GoogleTokenValidator.cs b/TickAPI/TickAPI/Common/Auth/Services/GoogleTokenValidator.cs index 060a2f0..a6bd4d4 100644 --- a/TickAPI/TickAPI/Common/Auth/Services/GoogleTokenValidator.cs +++ b/TickAPI/TickAPI/Common/Auth/Services/GoogleTokenValidator.cs @@ -1,6 +1,8 @@ using Google.Apis.Auth; using TickAPI.Common.Auth.Abstractions; +namespace TickAPI.Common.Auth.Services; + public class GoogleTokenValidator : IGoogleTokenValidator { private readonly IConfiguration _configuration; From e7e822e8ad973e7c05bb35a11488fdbf184ab0b4 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 18:06:57 +0100 Subject: [PATCH 5/9] Add `about-me` endpoint to `CustomerController` --- .../Controllers/CustomerController.cs | 24 ++++++++++++++++++- .../DTOs/Response/AboutMeResponseDto.cs | 8 +++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 TickAPI/TickAPI/Customers/DTOs/Response/AboutMeResponseDto.cs diff --git a/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs b/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs index d18d01d..ea02b83 100644 --- a/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs +++ b/TickAPI/TickAPI/Customers/Controllers/CustomerController.cs @@ -1,5 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; +using Microsoft.AspNetCore.Mvc; using TickAPI.Common.Auth.Abstractions; +using TickAPI.Common.Auth.Attributes; using TickAPI.Common.Auth.Enums; using TickAPI.Customers.Abstractions; using TickAPI.Customers.DTOs.Request; @@ -45,4 +47,24 @@ public async Task> GoogleLogin([FromBody] G return new ActionResult(new GoogleLoginResponseDto(jwtTokenResult.Value!)); } + + [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] + [HttpGet("about-me")] + public async Task> AboutMe() + { + var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + if (email == null) + return StatusCode(StatusCodes.Status400BadRequest, "missing email claim"); + + var customerResult = await _customerService.GetCustomerByEmailAsync(email); + if (customerResult.IsError) + return StatusCode(StatusCodes.Status500InternalServerError, + "cannot find customer in database for authorized customer request"); + + var customer = customerResult.Value!; + + var aboutMeResponse = + new AboutMeResponseDto(customer.Email, customer.FirstName, customer.LastName, customer.CreationDate); + return new ActionResult(aboutMeResponse); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Customers/DTOs/Response/AboutMeResponseDto.cs b/TickAPI/TickAPI/Customers/DTOs/Response/AboutMeResponseDto.cs new file mode 100644 index 0000000..c813f55 --- /dev/null +++ b/TickAPI/TickAPI/Customers/DTOs/Response/AboutMeResponseDto.cs @@ -0,0 +1,8 @@ +namespace TickAPI.Customers.DTOs.Response; + +public record AboutMeResponseDto( + string Email, + string FirstName, + string LastName, + DateTime CreationDate +); \ No newline at end of file From f6bfd7b229f9394be556a2f59c882c588a2e5a09 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 18:19:36 +0100 Subject: [PATCH 6/9] Fix tests for `GoogleAuthService` --- .../Common/Auth/Services/GoogleAuthServiceTests.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Common/Auth/Services/GoogleAuthServiceTests.cs b/TickAPI/TickAPI.Tests/Common/Auth/Services/GoogleAuthServiceTests.cs index ad5e695..c9c3824 100644 --- a/TickAPI/TickAPI.Tests/Common/Auth/Services/GoogleAuthServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Common/Auth/Services/GoogleAuthServiceTests.cs @@ -10,22 +10,24 @@ namespace TickAPI.Tests.Common.Auth.Services; public class GoogleAuthServiceTests { [Fact] - public async Task LoginAsync_WhenTokenValidatorReturnsPayload_ShouldReturnEmailFromPayload() + public async Task GetUserDataFromToken_WhenTokenValidatorReturnsPayload_ShouldReturnEmailFromPayload() { var googleTokenValidatorMock = new Mock(); googleTokenValidatorMock .Setup(m => m.ValidateAsync("validToken")) - .ReturnsAsync(new GoogleJsonWebSignature.Payload { Email = "example@test.com" }); + .ReturnsAsync(new GoogleJsonWebSignature.Payload { Email = "example@test.com", GivenName = "First", FamilyName = "Last"}); var sut = new GoogleAuthService(googleTokenValidatorMock.Object); - var result = await sut.LoginAsync("validToken"); + var result = await sut.GetUserDataFromToken("validToken"); Assert.True(result.IsSuccess); - Assert.Equal("example@test.com", result.Value); + Assert.Equal("example@test.com", result.Value?.Email); + Assert.Equal("First", result.Value?.FirstName); + Assert.Equal("Last", result.Value?.LastName); } [Fact] - public async Task LoginAsync_WhenTokenValidatorThrowsException_ShouldReturnFailure() + public async Task GetUserDataFromToken_WhenTokenValidatorThrowsException_ShouldReturnFailure() { var googleTokenValidatorMock = new Mock(); googleTokenValidatorMock @@ -33,7 +35,7 @@ public async Task LoginAsync_WhenTokenValidatorThrowsException_ShouldReturnFailu .Throws(new InvalidJwtException("Invalid Google ID token")); var sut = new GoogleAuthService(googleTokenValidatorMock.Object); - var result = await sut.LoginAsync("invalidToken"); + var result = await sut.GetUserDataFromToken("invalidToken"); Assert.True(result.IsError); Assert.Equal(StatusCodes.Status401Unauthorized, result.StatusCode); From 403608314632e82fed4b93310ca83d2774159fdf Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 18:33:29 +0100 Subject: [PATCH 7/9] Fix tests for `CustomerContoller` --- .../Controllers/CustomerControllerTests.cs | 103 ++++-------------- 1 file changed, 21 insertions(+), 82 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs b/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs index d36ac0f..dc6b52e 100644 --- a/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs +++ b/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs @@ -1,9 +1,8 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; using Moq; using TickAPI.Common.Auth.Abstractions; using TickAPI.Common.Auth.Enums; +using TickAPI.Common.Auth.Responses; using TickAPI.Common.Result; using TickAPI.Customers.Abstractions; using TickAPI.Customers.Controllers; @@ -15,15 +14,16 @@ namespace TickAPI.Tests.Customers.Controllers; public class CustomerControllerTests { [Fact] - public async Task GoogleLogin_WhenAuthSuccessAndCustomerExists_ShouldReturnTokenAndNotNewCustomer() + public async Task GoogleLogin_WhenAuthSuccessAndCustomerExists_ShouldReturnToken() { + // Arrange const string email = "existing@test.com"; const string idToken = "valid-google-token"; const string jwtToken = "valid-jwt-token"; - var authServiceMock = new Mock(); - authServiceMock.Setup(m => m.LoginAsync(idToken)) - .ReturnsAsync(Result.Success(email)); + var googleAuthServiceMock = new Mock(); + googleAuthServiceMock.Setup(m => m.GetUserDataFromToken(idToken)) + .ReturnsAsync(Result.Success(new GoogleUserData(email, "First", "Last"))); var customerServiceMock = new Mock(); customerServiceMock.Setup(m => m.GetCustomerByEmailAsync(email)) @@ -34,56 +34,34 @@ public async Task GoogleLogin_WhenAuthSuccessAndCustomerExists_ShouldReturnToken .Returns(Result.Success(jwtToken)); var sut = new CustomerController( - authServiceMock.Object, + googleAuthServiceMock.Object, jwtServiceMock.Object, customerServiceMock.Object); + // Act var actionResult = await sut.GoogleLogin(new GoogleLoginDto(idToken)); + // Assert Assert.Equal(jwtToken, actionResult.Value?.Token); - Assert.False(actionResult.Value?.IsNewCustomer); } [Fact] - public async Task GoogleLogin_WhenAuthSuccessAndCustomerDoesNotExist_ShouldReturnTokenAndNewCustomer() + public async Task GoogleLogin_WhenAuthSuccessAndCustomerDoesNotExist_ShouldCreateCustomerAndReturnToken() { + // Arrange const string email = "new@test.com"; const string idToken = "valid-google-token"; + const string firstName = "First"; + const string lastName = "Last"; const string jwtToken = "valid-jwt-token"; - var authServiceMock = new Mock(); - authServiceMock.Setup(m => m.LoginAsync(idToken)) - .ReturnsAsync(Result.Success(email)); + var googleAuthServiceMock = new Mock(); + googleAuthServiceMock.Setup(m => m.GetUserDataFromToken(idToken)) + .ReturnsAsync(Result.Success(new GoogleUserData(email, "First", "Last"))); var customerServiceMock = new Mock(); customerServiceMock.Setup(m => m.GetCustomerByEmailAsync(email)) .ReturnsAsync(Result.Failure(StatusCodes.Status404NotFound, $"customer with email '{email}' not found")); - - var jwtServiceMock = new Mock(); - jwtServiceMock.Setup(m => m.GenerateJwtToken(email, UserRole.NewCustomer)) - .Returns(Result.Success(jwtToken)); - - var sut = new CustomerController( - authServiceMock.Object, - jwtServiceMock.Object, - customerServiceMock.Object); - - var result = await sut.GoogleLogin(new GoogleLoginDto(idToken)); - - Assert.Equal(jwtToken, result.Value?.Token); - Assert.True(result.Value?.IsNewCustomer); - } - - [Fact] - public async Task GoogleCreateNewAccount_WhenCreatingAccountIsSuccessful_ShouldReturnToken() - { - const string email = "new@test.com"; - const string firstName = "First"; - const string lastName = "Last"; - const string jwtToken = "valid-jwt-token"; - - var authServiceMock = new Mock(); - var customerServiceMock = new Mock(); customerServiceMock.Setup(m => m.CreateNewCustomerAsync(email, firstName, lastName)) .ReturnsAsync(Result.Success(new Customer { @@ -98,53 +76,14 @@ public async Task GoogleCreateNewAccount_WhenCreatingAccountIsSuccessful_ShouldR .Returns(Result.Success(jwtToken)); var sut = new CustomerController( - authServiceMock.Object, + googleAuthServiceMock.Object, jwtServiceMock.Object, customerServiceMock.Object); - var claims = new List - { - new Claim(ClaimTypes.Email, email) - }; - sut.ControllerContext = new ControllerContext - { - HttpContext = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(claims)) - } - }; - - var result = await sut.GoogleCreateNewAccount( - new GoogleCreateNewAccountDto( firstName, lastName )); + // Act + var result = await sut.GoogleLogin(new GoogleLoginDto( idToken )); + // Assert Assert.Equal(jwtToken, result.Value?.Token); } - - [Fact] - public async Task GoogleCreateNewAccount_WhenEmailClaimIsMissing_ShouldReturnBadRequest() - { - var authServiceMock = new Mock(); - var jwtServiceMock = new Mock(); - var customerServiceMock = new Mock(); - - var sut = new CustomerController( - authServiceMock.Object, - jwtServiceMock.Object, - customerServiceMock.Object); - - sut.ControllerContext = new ControllerContext - { - HttpContext = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(new List())) - } - }; - - var result = await sut.GoogleCreateNewAccount( - new GoogleCreateNewAccountDto("First","Last")); - - var objectResult = Assert.IsType(result.Result); - Assert.Equal(StatusCodes.Status400BadRequest, objectResult.StatusCode); - Assert.Equal("missing email claim", objectResult.Value); - } } \ No newline at end of file From bd23e474c0997fd436c355b83af7b3d16d6a26c6 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 18:42:23 +0100 Subject: [PATCH 8/9] Add tests for "about-me" customer endpoint --- .../Controllers/CustomerControllerTests.cs | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs b/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs index dc6b52e..0c0270b 100644 --- a/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs +++ b/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Http; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Moq; using TickAPI.Common.Auth.Abstractions; using TickAPI.Common.Auth.Enums; @@ -86,4 +88,92 @@ public async Task GoogleLogin_WhenAuthSuccessAndCustomerDoesNotExist_ShouldCreat // Assert Assert.Equal(jwtToken, result.Value?.Token); } + + [Fact] + public async Task AboutMe_WithValidEmailClaim_ShouldReturnCustomerDetails() + { + // Arrange + const string email = "test@example.com"; + const string firstName = "John"; + const string lastName = "Doe"; + var creationDate = DateTime.UtcNow.AddDays(-30); + + var customer = new Customer + { + Email = email, + FirstName = firstName, + LastName = lastName, + CreationDate = creationDate + }; + + var customerServiceMock = new Mock(); + customerServiceMock.Setup(m => m.GetCustomerByEmailAsync(email)) + .ReturnsAsync(Result.Success(customer)); + + var googleAuthServiceMock = new Mock(); + var jwtServiceMock = new Mock(); + + var sut = new CustomerController( + googleAuthServiceMock.Object, + jwtServiceMock.Object, + customerServiceMock.Object); + + var claims = new List + { + new Claim(ClaimTypes.Email, email) + }; + var identity = new ClaimsIdentity(claims); + var claimsPrincipal = new ClaimsPrincipal(identity); + + sut.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext + { + User = claimsPrincipal + } + }; + + // Act + var result = await sut.AboutMe(); + + // Assert + Assert.Equal(email, result.Value?.Email); + Assert.Equal(firstName, result.Value?.FirstName); + Assert.Equal(lastName, result.Value?.LastName); + Assert.Equal(creationDate, result.Value?.CreationDate); + } + + [Fact] + public async Task AboutMe_WithMissingEmailClaim_ShouldReturnBadRequest() + { + // Arrange + var customerServiceMock = new Mock(); + var googleAuthServiceMock = new Mock(); + var jwtServiceMock = new Mock(); + + var sut = new CustomerController( + googleAuthServiceMock.Object, + jwtServiceMock.Object, + customerServiceMock.Object); + + var claims = new List(); + var identity = new ClaimsIdentity(claims); + var claimsPrincipal = new ClaimsPrincipal(identity); + + sut.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext + { + User = claimsPrincipal + } + }; + + // Act + var result = await sut.AboutMe(); + + // Assert + var objectResult = Assert.IsType(result.Result); + Assert.Equal(StatusCodes.Status400BadRequest, objectResult.StatusCode); + Assert.Equal("missing email claim", objectResult.Value); + } } \ No newline at end of file From 4d8d8ea8cb1a327ef8ec01884466c43d41b6f414 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Tue, 18 Mar 2025 19:52:56 +0100 Subject: [PATCH 9/9] Assign the same value to the `creationDate` in every test run --- .../Customers/Controllers/CustomerControllerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs b/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs index 0c0270b..9c8079b 100644 --- a/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs +++ b/TickAPI/TickAPI.Tests/Customers/Controllers/CustomerControllerTests.cs @@ -96,7 +96,7 @@ public async Task AboutMe_WithValidEmailClaim_ShouldReturnCustomerDetails() const string email = "test@example.com"; const string firstName = "John"; const string lastName = "Doe"; - var creationDate = DateTime.UtcNow.AddDays(-30); + var creationDate = new DateTime(1970, 1, 1, 8, 0, 0, DateTimeKind.Utc); var customer = new Customer {