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

[RSN-31] - Create validators according to constraints #32

Merged
merged 27 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
423326f
Created basic validators for all models in db
mkoper02 May 6, 2024
4f39e38
Created regex check for all models
mkoper02 May 11, 2024
b2825e8
Added regex in validators, changed models into dtos, created unit tests
mkoper02 May 13, 2024
33a51b0
Revert "Added regex in validators, changed models into dtos, created …
mkoper02 May 13, 2024
444e194
Added tests, changed models in validators into dtos
mkoper02 May 13, 2024
a337026
Deleted EventDto changes
mkoper02 May 13, 2024
e794972
Unit test fix
mkoper02 May 13, 2024
c5dcd82
Restore ReasnAPI.csproj
mkoper02 May 14, 2024
be3fc47
Created const variables for regex strings and hardcoded numbers
mkoper02 May 15, 2024
740fdce
Created basic validators for all models in db
mkoper02 May 6, 2024
5c611e9
Created regex check for all models
mkoper02 May 11, 2024
3c3a10b
Added regex in validators, changed models into dtos, created unit tests
mkoper02 May 13, 2024
0ac564d
Revert "Added regex in validators, changed models into dtos, created …
mkoper02 May 13, 2024
d1e447c
Added tests, changed models in validators into dtos
mkoper02 May 13, 2024
4ace779
Unit test fix
mkoper02 May 13, 2024
934e445
Restore ReasnAPI.csproj
mkoper02 May 14, 2024
dee72d6
Created const variables for regex strings and hardcoded numbers
mkoper02 May 15, 2024
208050c
Updated validator after scaffold
mkoper02 May 20, 2024
9f0295a
Merge branch 'RSN-31' of https://github.com/wzarek/Reasn into RSN-31
mkoper02 May 20, 2024
9a151d0
Updated unit tests after scaffold
mkoper02 May 20, 2024
30a8666
feat: add jwt and exceptions handlers
raczu Jun 1, 2024
14e3333
test: add unit tests for jwt, handlers and validators
raczu Jun 1, 2024
26348f0
style: format code with dotnet format
raczu Jun 4, 2024
c6ad6fe
Merge remote-tracking branch 'origin/RSN-40' into RSN-31
raczu Jun 4, 2024
fe7e7be
refactor: rewrite validators to use fluent validation
raczu Jun 5, 2024
8a25fe8
refactor: make regexes and length variables as constants
raczu Jun 5, 2024
ffeb579
Merge remote-tracking branch 'origin/main' into RSN-31
raczu Jun 6, 2024
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
5 changes: 5 additions & 0 deletions Server/ReasnAPI/ReasnAPI.Tests/ReasnAPI.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.Playwright.MSTest" Version="1.27.1" />
<PackageReference Include="Moq.EntityFrameworkCore" Version="8.0.1.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReasnAPI\ReasnAPI.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.Playwright.MSTest" />
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
Expand Down
12 changes: 0 additions & 12 deletions Server/ReasnAPI/ReasnAPI.Tests/UnitTest1.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Moq;
using ReasnAPI.Exceptions;
using ReasnAPI.Services.Exceptions;

namespace ReasnAPI.Tests.UnitTests.Exceptions;

[TestClass]
public class ServiceExceptionHandlerTests
{
private Mock<IProblemDetailsService> _mockProblemDetailsService = null!;
private ServiceExceptionHandler _handler = null!;

[TestInitialize]
public void Setup()
{
_mockProblemDetailsService = new Mock<IProblemDetailsService>();
_handler = new ServiceExceptionHandler(_mockProblemDetailsService.Object);
}

[TestMethod]
public async Task HandleException_WhenBadRequestException_ShouldReturnProblemDetails()
{
var httpContext = new DefaultHttpContext();
var exception = new BadRequestException("Bad request");

ProblemDetailsContext? problemDetailsContext = null;
_mockProblemDetailsService.Setup(x =>
x.TryWriteAsync(It.IsAny<ProblemDetailsContext>()))
.Callback<ProblemDetailsContext>(context => problemDetailsContext = context)
.ReturnsAsync(true);

await _handler.TryHandleAsync(httpContext, exception, CancellationToken.None);

Assert.AreEqual((int)HttpStatusCode.BadRequest, httpContext.Response.StatusCode);
Assert.IsNotNull(problemDetailsContext);
Assert.AreEqual("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
problemDetailsContext.ProblemDetails.Type);
Assert.AreEqual("A bad request was made",
problemDetailsContext.ProblemDetails.Title);
Assert.AreEqual(exception, problemDetailsContext.Exception);
Assert.AreEqual(exception.Message, problemDetailsContext.ProblemDetails.Detail);
}

[TestMethod]
public async Task HandleException_WhenNotFoundException_ShouldReturnProblemDetails()
{
var httpContext = new DefaultHttpContext();
var exception = new NotFoundException("Resource not found");

ProblemDetailsContext? problemDetailsContext = null;
_mockProblemDetailsService.Setup(x =>
x.TryWriteAsync(It.IsAny<ProblemDetailsContext>()))
.Callback<ProblemDetailsContext>(context => problemDetailsContext = context)
.ReturnsAsync(true);

await _handler.TryHandleAsync(httpContext, exception, CancellationToken.None);

Assert.AreEqual((int)HttpStatusCode.NotFound, httpContext.Response.StatusCode);
Assert.IsNotNull(problemDetailsContext);
Assert.AreEqual("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4",
problemDetailsContext.ProblemDetails.Type);
Assert.AreEqual("A resource was not found",
problemDetailsContext.ProblemDetails.Title);
Assert.AreEqual(exception, problemDetailsContext.Exception);
Assert.AreEqual(exception.Message, problemDetailsContext.ProblemDetails.Detail);
}

[TestMethod]
public async Task HandleException_WhenVerificationException_ShouldReturnProblemDetails()
{
var httpContext = new DefaultHttpContext();
var exception = new VerificationException("Verification error");

ProblemDetailsContext? problemDetailsContext = null;
_mockProblemDetailsService.Setup(x =>
x.TryWriteAsync(It.IsAny<ProblemDetailsContext>()))
.Callback<ProblemDetailsContext>(context => problemDetailsContext = context)
.ReturnsAsync(true);

await _handler.TryHandleAsync(httpContext, exception, CancellationToken.None);

Assert.AreEqual((int)HttpStatusCode.BadRequest, httpContext.Response.StatusCode);
Assert.IsNotNull(problemDetailsContext);
Assert.AreEqual("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
problemDetailsContext.ProblemDetails.Type);
Assert.AreEqual("A verification error occurred",
problemDetailsContext.ProblemDetails.Title);
Assert.AreEqual(exception, problemDetailsContext.Exception);
Assert.AreEqual(exception.Message, problemDetailsContext.ProblemDetails.Detail);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Net;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Moq;
using ReasnAPI.Exceptions;

namespace ReasnAPI.Tests.UnitTests.Exceptions;

[TestClass]
public class ValidationExceptionHandlerTests
{
private Mock<IProblemDetailsService> _mockProblemDetailsService = null!;
private ValidationExceptionHandler _handler = null!;

[TestInitialize]
public void Setup()
{
_mockProblemDetailsService = new Mock<IProblemDetailsService>();
_handler = new ValidationExceptionHandler(_mockProblemDetailsService.Object);
}

[TestMethod]
public async Task HandleException_WhenValidationException_ShouldReturnProblemDetails()
{
var httpContext = new DefaultHttpContext();
var exception = new ValidationException(new List<ValidationFailure>
{
new ("Email", "'Email' must not be empty."),
new ("Password", "'Password' must not be empty.")
});

ProblemDetailsContext? problemDetailsContext = null;
_mockProblemDetailsService.Setup(x =>
x.TryWriteAsync(It.IsAny<ProblemDetailsContext>()))
.Callback<ProblemDetailsContext>(context => problemDetailsContext = context)
.ReturnsAsync(true);

await _handler.TryHandleAsync(httpContext, exception, CancellationToken.None);

Assert.AreEqual((int)HttpStatusCode.BadRequest, httpContext.Response.StatusCode);
Assert.IsNotNull(problemDetailsContext);
Assert.AreEqual("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
problemDetailsContext.ProblemDetails.Type);
Assert.AreEqual("A validation error occurred",
problemDetailsContext.ProblemDetails.Title);
Assert.AreEqual(exception, problemDetailsContext.Exception);
Assert.AreEqual("One or more validation errors occurred",
problemDetailsContext.ProblemDetails.Detail);

Assert.IsNotNull(problemDetailsContext.ProblemDetails.Extensions);
Assert.IsTrue(problemDetailsContext.ProblemDetails.Extensions.ContainsKey("errors"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.WebUtilities;
using Moq;
using Moq.EntityFrameworkCore;
using ReasnAPI.Models.Authentication;
using ReasnAPI.Models.Database;
using ReasnAPI.Models.DTOs;
using ReasnAPI.Services.Authentication;
using ReasnAPI.Services.Exceptions;

namespace ReasnAPI.Tests.UnitTests.Services.Authentication;

[TestClass]
public class AuthServiceTests
{
private Mock<ReasnContext> _mockContext = null!;
private PasswordHasher<User> _hasher = null!;
private AuthService _service = null!;

[TestInitialize]
public void Setup()
{
_mockContext = new Mock<ReasnContext>();
_hasher = new PasswordHasher<User>();
_service = new AuthService(_mockContext.Object);

var user = new User
{
Email = "[email protected]",
Username = "jsnow",
Password = _hasher.HashPassword(null!, "password")
};

_mockContext.Setup(c => c.Users)
.ReturnsDbSet(new List<User> { user });
}

[TestMethod]
public void Login_WhenUserExistsAndPasswordIsCorrect_ShouldReturnUser()
{
var request = new LoginRequest
{
Email = "[email protected]",
Password = "password"
};

var result = _service.Login(request);

Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(User));
}

[TestMethod]
public void Login_WhenUserDoesNotExist_ShouldThrowNotFoundException()
{
var request = new LoginRequest
{
Email = "[email protected]"
};

Assert.ThrowsException<NotFoundException>(() => _service.Login(request));
}

[TestMethod]
public void Login_WhenPasswordIsIncorrect_ShouldThrowVerificationException()
{
var request = new LoginRequest
{
Email = "[email protected]",
Password = "wrong-password"
};

Assert.ThrowsException<VerificationException>(() => _service.Login(request));
}

[TestMethod]
public void Register_WhenUserWithEmailAlreadyExists_ShouldThrowBadRequestException()
{
var request = new RegisterRequest
{
Email = "[email protected]"
};

Assert.ThrowsException<BadRequestException>(() => _service.Register(request));
}

[TestMethod]
public void Register_WhenUserWithUsernameAlreadyExists_ShouldThrowBadRequestException()
{
var request = new RegisterRequest
{
Email = "[email protected]",
Username = "jsnow"
};

Assert.ThrowsException<BadRequestException>(() => _service.Register(request));
}

[TestMethod]
public void Register_WhenUserDoesNotExist_ShouldReturnRegisteredUser()
{
var request = new RegisterRequest
{
Name = "Jon",
Surname = "Stark",
Email = "[email protected]",
Username = "jstark",
Password = "S3cureP@ssword!",
Phone = "+123 456789",
Address = new AddressDto
{
Street = "The Wall",
City = "Castle Black",
Country = "Westeros",
State = "The North"
},
Role = "User"
};

var result = _service.Register(request);

Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(User));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using Moq;
using ReasnAPI.Models.Database;
using ReasnAPI.Models.Enums;
using ReasnAPI.Services.Authentication;

namespace ReasnAPI.Tests.UnitTests.Services.Authentication;

[TestClass]
public class TokenServiceTests
{
private const int DurationInHours = 8;
private const string IssAudValue = "http://localhost:5272";
private TokenService _service = null!;
private Mock<IConfiguration> _mockConfiguration = null!;
private User _validUser = null!;

[TestInitialize]
public void Setup()
{
_mockConfiguration = new Mock<IConfiguration>();

var bytes = new byte[32];
RandomNumberGenerator.Fill(bytes);
_mockConfiguration.Setup(x =>
x["JwtSettings:Key"]).Returns(Convert.ToBase64String(bytes));

_mockConfiguration.Setup(x =>
x["JwtSettings:Issuer"]).Returns(IssAudValue);

var mockSection = new Mock<IConfigurationSection>();
var mockAudienceValue = new Mock<IConfigurationSection>();
mockAudienceValue.Setup(x => x.Value).Returns(IssAudValue);
mockSection.Setup(x =>
x.GetChildren()).Returns(new List<IConfigurationSection> { mockAudienceValue.Object });
_mockConfiguration.Setup(x =>
x.GetSection("JwtSettings:Audiences")).Returns(mockSection.Object);

var mockDurationValue = new Mock<IConfigurationSection>();
mockDurationValue.SetupGet(x => x.Value).Returns(DurationInHours.ToString());
_mockConfiguration.Setup(x =>
x.GetSection("JwtSettings:DurationInHours")).Returns(mockDurationValue.Object);

_service = new TokenService(_mockConfiguration.Object);

_validUser = new User
{
Id = 1,
Name = "Jon",
Surname = "Snow",
Email = "[email protected]",
Role = UserRole.User
};
}

[TestMethod]
public void GenerateToken_WhenValidUser_ShouldReturnTokenPayload()
{
var result = _service.GenerateToken(_validUser);

Assert.IsNotNull(result);
Assert.AreEqual("Bearer", result.TokenType);
Assert.IsNotNull(result.AccessToken);
Assert.AreEqual(DurationInHours * 60 * 60, result.ExpiresIn);
}

[TestMethod]
public void GenerateToken_WhenValidUser_ShouldReturnValidToken()
{
var result = _service.GenerateToken(_validUser);

var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.ReadToken(result.AccessToken) as JwtSecurityToken;

Assert.IsNotNull(token);
Assert.AreEqual(IssAudValue, token.Issuer);
Assert.AreEqual(IssAudValue, token.Audiences.First());
Assert.AreEqual(_validUser.Email, token.Subject);
Assert.AreEqual(DurationInHours * 60 * 60, (token.ValidTo - token.ValidFrom).TotalSeconds);
}
}
Loading