Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Streetcode.Auth.BLL.DTO.Auth
{
public class ChangePasswordRequestDTO

Check warning on line 9 in Streetcode/Streetcode.Auth.BLL/DTO/Auth/ChangePasswordRequestDTO.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename class 'ChangePasswordRequestDTO' to match pascal case naming rules, consider using 'ChangePasswordRequestDto'.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy1MZOJ4jZ5ofG2plwT&open=AZy1MZOJ4jZ5ofG2plwT&pullRequest=123
{
public string Email { get; set; }

Check warning on line 11 in Streetcode/Streetcode.Auth.BLL/DTO/Auth/ChangePasswordRequestDTO.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Non-nullable property 'Email' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy1MZOJ4jZ5ofG2plwU&open=AZy1MZOJ4jZ5ofG2plwU&pullRequest=123
public string CurrentPassword { get; set; }

Check warning on line 12 in Streetcode/Streetcode.Auth.BLL/DTO/Auth/ChangePasswordRequestDTO.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Non-nullable property 'CurrentPassword' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy1MZOJ4jZ5ofG2plwV&open=AZy1MZOJ4jZ5ofG2plwV&pullRequest=123
public string NewPassword { get; set; }

Check warning on line 13 in Streetcode/Streetcode.Auth.BLL/DTO/Auth/ChangePasswordRequestDTO.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Non-nullable property 'NewPassword' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy1MZOJ4jZ5ofG2plwW&open=AZy1MZOJ4jZ5ofG2plwW&pullRequest=123
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using FluentResults;
using MediatR;
using Streetcode.Auth.BLL.DTO.Auth;

namespace Streetcode.Auth.BLL.MediatR.ChangePassword
{
public record ChangePasswordCommand(ChangePasswordRequestDTO Request, string Email) : IRequest<Result<Unit>>;

Check warning on line 7 in Streetcode/Streetcode.Auth.BLL/MediatR/ChangePassword/ChangePasswordCommand.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Closing parenthesis should not be followed by a space.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy1MZIn4jZ5ofG2plwR&open=AZy1MZIn4jZ5ofG2plwR&pullRequest=123
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using FluentValidation;

namespace Streetcode.Auth.BLL.MediatR.ChangePassword
{
public class ChangePasswordCommandValidator : AbstractValidator<ChangePasswordCommand>
{
public ChangePasswordCommandValidator()
{
RuleFor(x => x.Request)
.SetValidator(new ChangePasswordRequestDTOValidator());

RuleFor(x => x.Email)
.NotEmpty().WithMessage("User email is missing from the identity claim.")
.EmailAddress();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Linq;
using FluentResults;
using MediatR;
using Microsoft.AspNetCore.Identity;
using Streetcode.Auth.BLL.Interfaces;
using Streetcode.Auth.DAL.Entities;

namespace Streetcode.Auth.BLL.MediatR.ChangePassword
{
public class ChangePasswordHandler : IRequestHandler<ChangePasswordCommand, Result<Unit>>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ITokenService _tokenService;

public ChangePasswordHandler(UserManager<ApplicationUser> userManager, ITokenService tokenService)
{
_userManager = userManager;
_tokenService = tokenService;
}

public async Task<Result<Unit>> Handle(ChangePasswordCommand request, CancellationToken cancellationToken)
{
var user = await _userManager.FindByEmailAsync(request.Email);

if (user == null)
{
return Result.Fail("User not found");
}

var result = await _userManager.ChangePasswordAsync(
user,
request.Request.CurrentPassword,
request.Request.NewPassword);

if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description);
return Result.Fail(string.Join(", ", errors));
}

await _tokenService.RevokeAllAsync(user.Id);

return Result.Ok(Unit.Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using FluentValidation;
using Streetcode.Auth.BLL.DTO.Auth;

namespace Streetcode.Auth.BLL.MediatR.ChangePassword
{
public class ChangePasswordRequestDTOValidator : AbstractValidator<ChangePasswordRequestDTO>

Check warning on line 6 in Streetcode/Streetcode.Auth.BLL/MediatR/ChangePassword/ChangePasswordRequestDTOValidator.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename class 'ChangePasswordRequestDTOValidator' to match pascal case naming rules, consider using 'ChangePasswordRequestDtoValidator'.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy1MZOA4jZ5ofG2plwS&open=AZy1MZOA4jZ5ofG2plwS&pullRequest=123
{
public ChangePasswordRequestDTOValidator()
{
RuleFor(x => x.CurrentPassword)
.NotEmpty().WithMessage("Password is required.");

RuleFor(x => x.NewPassword)
.NotEmpty().WithMessage("Password is required.")
.MinimumLength(6).WithMessage("Password must be at least 6 characters long.")
.Matches("[A-Z]").WithMessage("Password must contain at least one uppercase letter.")
.Matches("[a-z]").WithMessage("Password must contain at least one lowercase letter.")
.Matches("[0-9]").WithMessage("Password must contain at least one number.")
.Matches("[^a-zA-Z0-9]").WithMessage("Password must contain at least one special character.")
.NotEqual(x => x.CurrentPassword).WithMessage("New password cannot be the same as the current password.");
}
}
}
29 changes: 27 additions & 2 deletions Streetcode/Streetcode.Auth.WebApi/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using MediatR;
using System.Security.Claims;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Streetcode.Auth.BLL.DTO.Auth;
using Streetcode.Auth.BLL.MediatR.ChangePassword;
using Streetcode.Auth.BLL.MediatR.Login;
using Streetcode.Auth.BLL.MediatR.Logout;
using Streetcode.Auth.BLL.MediatR.RefreshToken;
Expand Down Expand Up @@ -89,5 +92,27 @@ public async Task<IActionResult> Logout()

return Ok(new { message = "Logged out" });
}

[Authorize]
[HttpPost("change-password")]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequestDTO request)
{
var userEmail = User.FindFirstValue(ClaimTypes.Email);

if (string.IsNullOrEmpty(userEmail))
{
return Unauthorized("User email not found in token.");
}

var result = await _mediator.Send(new ChangePasswordCommand(request, userEmail));

if (result.IsSuccess)
{
_cookieService.DeleteRefreshTokenCookie(Response);
return Ok(new { message = "Password changed successfully." });
}

return BadRequest(result.Errors.Select(e => e.Message));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using FluentAssertions;

Check warning on line 1 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iG&open=AZy_qCKlRJSzbAuXg7iG&pullRequest=123
using MediatR;

Check warning on line 2 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iH&open=AZy_qCKlRJSzbAuXg7iH&pullRequest=123
using Microsoft.AspNetCore.Identity;

Check warning on line 3 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iI&open=AZy_qCKlRJSzbAuXg7iI&pullRequest=123
using Moq;

Check warning on line 4 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iJ&open=AZy_qCKlRJSzbAuXg7iJ&pullRequest=123
using Streetcode.Auth.BLL.DTO.Auth;

Check warning on line 5 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iK&open=AZy_qCKlRJSzbAuXg7iK&pullRequest=123
using Streetcode.Auth.BLL.Interfaces;

Check warning on line 6 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iL&open=AZy_qCKlRJSzbAuXg7iL&pullRequest=123
using Streetcode.Auth.BLL.MediatR.ChangePassword;

Check warning on line 7 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iM&open=AZy_qCKlRJSzbAuXg7iM&pullRequest=123
using Streetcode.Auth.DAL.Entities;

Check warning on line 8 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using directive should appear within a namespace declaration

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iN&open=AZy_qCKlRJSzbAuXg7iN&pullRequest=123

namespace Streetcode.Auth.XUnitTest.ChangePassword
{
public class ChangePasswordHandlerTests
{
private const string Email = "user@example.com";
private const string UserId = "user-id-123";
private const string CurrentPassword = "OldPassword123!";
private const string NewPassword = "NewPassword456!";

private readonly Mock<UserManager<ApplicationUser>> userManagerMock;
private readonly Mock<ITokenService> tokenServiceMock;
private readonly ChangePasswordHandler handler;

public ChangePasswordHandlerTests()
{
this.userManagerMock = this.CreateUserManagerMock();
this.tokenServiceMock = new Mock<ITokenService>();

this.handler = new ChangePasswordHandler(
this.userManagerMock.Object,
this.tokenServiceMock.Object);
}

[Fact]
public async Task Handle_ShouldReturnSuccessAndRevokeTokens_WhenCredentialsAreValid()
{
// Arrange
var user = new ApplicationUser { Id = UserId, Email = Email };
this.userManagerMock.Setup(m => m.FindByEmailAsync(Email)).ReturnsAsync(user);

this.userManagerMock.Setup(m => m.ChangePasswordAsync(user, CurrentPassword, NewPassword))
.ReturnsAsync(IdentityResult.Success);

var command = new ChangePasswordCommand(new ChangePasswordRequestDTO
{
CurrentPassword = CurrentPassword,
NewPassword = NewPassword

Check warning on line 46 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use trailing comma in multi-line initializers

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iO&open=AZy_qCKlRJSzbAuXg7iO&pullRequest=123
}, Email);

Check warning on line 47 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iP&open=AZy_qCKlRJSzbAuXg7iP&pullRequest=123

// Act
var result = await this.handler.Handle(command, CancellationToken.None);

// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().Be(Unit.Value);

this.tokenServiceMock.Verify(s => s.RevokeAllAsync(UserId), Times.Once);
}

[Fact]
public async Task Handle_ShouldReturnFail_WhenUserNotFound()
{
// Arrange
this.userManagerMock.Setup(m => m.FindByEmailAsync(Email)).ReturnsAsync((ApplicationUser?)null);

var command = new ChangePasswordCommand(new ChangePasswordRequestDTO
{
CurrentPassword = CurrentPassword,
NewPassword = NewPassword

Check warning on line 68 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use trailing comma in multi-line initializers

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iQ&open=AZy_qCKlRJSzbAuXg7iQ&pullRequest=123
}, Email);

Check warning on line 69 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iR&open=AZy_qCKlRJSzbAuXg7iR&pullRequest=123

// Act
var result = await this.handler.Handle(command, CancellationToken.None);

// Assert
result.IsFailed.Should().BeTrue();
result.Errors.First().Message.Should().Be("User not found");

this.tokenServiceMock.Verify(s => s.RevokeAllAsync(It.IsAny<string>()), Times.Never);
}

[Fact]
public async Task Handle_ShouldReturnFail_WhenIdentityChangePasswordFails()
{
// Arrange
var user = new ApplicationUser { Id = UserId, Email = Email };
var identityErrors = new[] { new IdentityError { Description = "Incorrect current password" } };

this.userManagerMock.Setup(m => m.FindByEmailAsync(Email)).ReturnsAsync(user);
this.userManagerMock.Setup(m => m.ChangePasswordAsync(user, CurrentPassword, NewPassword))
.ReturnsAsync(IdentityResult.Failed(identityErrors));

var command = new ChangePasswordCommand(new ChangePasswordRequestDTO
{
CurrentPassword = CurrentPassword,
NewPassword = NewPassword

Check warning on line 95 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use trailing comma in multi-line initializers

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iS&open=AZy_qCKlRJSzbAuXg7iS&pullRequest=123
}, Email);

Check warning on line 96 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iT&open=AZy_qCKlRJSzbAuXg7iT&pullRequest=123

// Act
var result = await this.handler.Handle(command, CancellationToken.None);

// Assert
result.IsFailed.Should().BeTrue();
result.Errors.First().Message.Should().Be("Incorrect current password");

this.tokenServiceMock.Verify(s => s.RevokeAllAsync(It.IsAny<string>()), Times.Never);
}

private Mock<UserManager<ApplicationUser>> CreateUserManagerMock()

Check warning on line 108 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'CreateUserManagerMock' does not access instance data and can be marked as static

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iU&open=AZy_qCKlRJSzbAuXg7iU&pullRequest=123
{
var store = new Mock<IUserStore<ApplicationUser>>();
return new Mock<UserManager<ApplicationUser>>(
store.Object, null, null, null, null, null, null, null, null);

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iD&open=AZy_qCKlRJSzbAuXg7iD&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iC&open=AZy_qCKlRJSzbAuXg7iC&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7h_&open=AZy_qCKlRJSzbAuXg7h_&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iB&open=AZy_qCKlRJSzbAuXg7iB&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7h-&open=AZy_qCKlRJSzbAuXg7h-&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iE&open=AZy_qCKlRJSzbAuXg7iE&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7h9&open=AZy_qCKlRJSzbAuXg7h9&pullRequest=123

Check warning on line 112 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cannot convert null literal to non-nullable reference type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCKlRJSzbAuXg7iA&open=AZy_qCKlRJSzbAuXg7iA&pullRequest=123
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
namespace Streetcode.Auth.XUnitTest.ChangePassword
{
using FluentValidation.TestHelper;
using Streetcode.Auth.BLL.DTO.Auth;
using Streetcode.Auth.BLL.MediatR.ChangePassword;
using Xunit;

public class ChangePasswordRequestDTOValidatorTests
{
private readonly ChangePasswordRequestDTOValidator validator;

public ChangePasswordRequestDTOValidatorTests()
{
this.validator = new ChangePasswordRequestDTOValidator();
}

[Theory]
[InlineData("")]
[InlineData(null)]

Check warning on line 19 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordRequestDTOValidatorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Null should not be used for type parameter 'password' of type 'string'. Use a non-null value, or convert the parameter to a nullable type.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCEDRJSzbAuXg7h6&open=AZy_qCEDRJSzbAuXg7h6&pullRequest=123
public void ShouldHaveError_WhenCurrentPasswordIsEmpty(string password)
{
var model = new ChangePasswordRequestDTO { CurrentPassword = password };
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.CurrentPassword)
.WithErrorMessage("Password is required.");
}

[Fact]
public void ShouldHaveError_WhenNewPasswordIsTooShort()
{
var model = new ChangePasswordRequestDTO { NewPassword = "Short" };
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.NewPassword)
.WithErrorMessage("Password must be at least 6 characters long.");
}

[Fact]
public void ShouldHaveError_WhenNewPasswordHasNoUppercase()
{
var model = new ChangePasswordRequestDTO { NewPassword = "password123!" };
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.NewPassword)
.WithErrorMessage("Password must contain at least one uppercase letter.");
}

[Fact]
public void ShouldHaveError_WhenNewPasswordHasNoLowercase()
{
var model = new ChangePasswordRequestDTO { NewPassword = "PASSWORD123!" };
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.NewPassword)
.WithErrorMessage("Password must contain at least one lowercase letter.");
}

[Fact]
public void ShouldHaveError_WhenNewPasswordHasNoDigit()
{
var model = new ChangePasswordRequestDTO { NewPassword = "Password!" };
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.NewPassword)
.WithErrorMessage("Password must contain at least one number.");
}

[Fact]
public void ShouldHaveError_WhenNewPasswordHasNoSpecialChar()
{
var model = new ChangePasswordRequestDTO { NewPassword = "Password123" };
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.NewPassword)
.WithErrorMessage("Password must contain at least one special character.");
}

[Fact]
public void ShouldHaveError_WhenNewPasswordIsSameAsCurrent()
{
var model = new ChangePasswordRequestDTO
{
CurrentPassword = "StrongPassword1!",
NewPassword = "StrongPassword1!"

Check warning on line 79 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordRequestDTOValidatorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use trailing comma in multi-line initializers

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCEDRJSzbAuXg7h8&open=AZy_qCEDRJSzbAuXg7h8&pullRequest=123
};
var result = this.validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(x => x.NewPassword)
.WithErrorMessage("New password cannot be the same as the current password.");
}

[Fact]
public void ShouldNotHaveError_WhenDTOIsValid()
{
var model = new ChangePasswordRequestDTO
{
CurrentPassword = "OldPassword1!",
NewPassword = "NewPassword2?"

Check warning on line 92 in Streetcode/Streetcode.Auth.XUnitTest/ChangePassword/ChangePasswordRequestDTOValidatorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use trailing comma in multi-line initializers

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-January-2026&issues=AZy_qCEDRJSzbAuXg7h7&open=AZy_qCEDRJSzbAuXg7h7&pullRequest=123
};
var result = this.validator.TestValidate(model);
result.ShouldNotHaveAnyValidationErrors();
}
}
}
Loading