Merge pull request 'add unit tests' (#18) from feature/tests into develop
Reviewed-on: Natlinux/DotNetAngular#18
This commit was merged in pull request #18.
This commit is contained in:
+42
-32
@@ -20,10 +20,9 @@
|
|||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="1ac72a4a-52ad-4e70-9b15-c330b1ed3e7a" name="Changes" comment="">
|
<list default="true" id="1ac72a4a-52ad-4e70-9b15-c330b1ed3e7a" name="Changes" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Application/Application.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/Application/Application.csproj" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/tests/Application.FunctionalTest/UnitTest1.cs" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Application/Services/AuthenticationService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/Application/Services/AuthenticationService.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/tests/Application.UnitTest/Application.UnitTest.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/tests/Application.UnitTest/Application.UnitTest.csproj" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/ClientApp/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/package-lock.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/tests/Application.UnitTest/UnitTest1.cs" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/ClientApp/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/package.json" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -40,7 +39,7 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="develop" />
|
<entry key="$PROJECT_DIR$" value="feature/angular" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@@ -66,32 +65,33 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
".NET Launch Settings Profile.API: Angular_dev.executor": "Run",
|
".NET Launch Settings Profile.API: Angular_dev.executor": "Run",
|
||||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"cidr.known.project.marker": "true",
|
"cidr.known.project.marker": "true",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||||
"git-widget-placeholder": "feature/angular",
|
"git-widget-placeholder": "develop",
|
||||||
"junie.onboarding.icon.badge.shown": "true",
|
"git.auto.fetch.suggestion.counter": "1",
|
||||||
"node.js.detected.package.eslint": "true",
|
"junie.onboarding.icon.badge.shown": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"settings.editor.selected.configurable": "vcs.Git",
|
"nodejs_package_manager_path": "npm",
|
||||||
"to.speed.mode.migration.done": "true",
|
"settings.editor.selected.configurable": "vcs.Git",
|
||||||
"ts.external.directory.path": "/home/natlinux/RiderProjects/DotNetAngular/src/ClientApp/node_modules/typescript/lib",
|
"to.speed.mode.migration.done": "true",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"ts.external.directory.path": "/home/natlinux/RiderProjects/DotNetAngular/src/ClientApp/node_modules/typescript/lib",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="RecapUselessUpdatesCounter">
|
<component name="RecapUselessUpdatesCounter">
|
||||||
<option name="suspendCountdown" value="0" />
|
<option name="suspendCountdown" value="0" />
|
||||||
</component>
|
</component>
|
||||||
@@ -235,6 +235,7 @@
|
|||||||
<workItem from="1773605341198" duration="3147000" />
|
<workItem from="1773605341198" duration="3147000" />
|
||||||
<workItem from="1773678089390" duration="1025000" />
|
<workItem from="1773678089390" duration="1025000" />
|
||||||
<workItem from="1777730558210" duration="1422000" />
|
<workItem from="1777730558210" duration="1422000" />
|
||||||
|
<workItem from="1778341026510" duration="1284000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="updating template">
|
<task id="LOCAL-00001" summary="updating template">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -300,7 +301,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1773601314558</updated>
|
<updated>1773601314558</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="9" />
|
<task id="LOCAL-00009" summary="update angular and fix warnings">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1777739314669</created>
|
||||||
|
<option name="number" value="00009" />
|
||||||
|
<option name="presentableId" value="LOCAL-00009" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1777739314669</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="10" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -345,7 +354,8 @@
|
|||||||
<MESSAGE value="update Angular core to new version" />
|
<MESSAGE value="update Angular core to new version" />
|
||||||
<MESSAGE value="fix dropdown" />
|
<MESSAGE value="fix dropdown" />
|
||||||
<MESSAGE value="update nuget packages to new version" />
|
<MESSAGE value="update nuget packages to new version" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="update nuget packages to new version" />
|
<MESSAGE value="update angular and fix warnings" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="update angular and fix warnings" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=4adf65b1_002D2697_002D427f_002Db68a_002D93ae8ffb5baf/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
|
<Solution />
|
||||||
|
</SessionState></s:String>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hostingstartupassemblies/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=hostingstartupassemblies/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Application.FunctionalTest;
|
|
||||||
|
|
||||||
public class UnitTest1
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Test1()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,10 +18,17 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||||
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Using Include="Xunit"/>
|
<Using Include="Xunit"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Application\Application.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Domain\Domain.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Infrastructure\Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
using Application.Common.Results;
|
||||||
|
using Application.DTOs;
|
||||||
|
using Application.Errors;
|
||||||
|
using Application.Interfaces;
|
||||||
|
using Application.Models;
|
||||||
|
using Application.Services;
|
||||||
|
using Application.Validators;
|
||||||
|
using Domain.Entities;
|
||||||
|
using Domain.Interface;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using NSubstitute;
|
||||||
|
|
||||||
|
namespace Application.UnitTest.Services;
|
||||||
|
|
||||||
|
public class AuthenticationServiceTests
|
||||||
|
{
|
||||||
|
private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
|
||||||
|
private readonly IUserRepository _userRepository = Substitute.For<IUserRepository>();
|
||||||
|
private readonly IJwtService _jwtService = Substitute.For<IJwtService>();
|
||||||
|
private readonly IEmailService _emailService = Substitute.For<IEmailService>();
|
||||||
|
private readonly AuthenticationService _sut;
|
||||||
|
|
||||||
|
public AuthenticationServiceTests()
|
||||||
|
{
|
||||||
|
_sut = new AuthenticationService(
|
||||||
|
_unitOfWork,
|
||||||
|
_userRepository,
|
||||||
|
new LoginRequestValidator(),
|
||||||
|
new RegisterRequestValidator(),
|
||||||
|
_jwtService,
|
||||||
|
_emailService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RegisterAsync_ShouldReturnSuccess_WhenRequestIsValid()
|
||||||
|
{
|
||||||
|
var request = new RegisterRequest("newuser", "new@example.com", "ValidP@ss1!");
|
||||||
|
_userRepository.GetUserByEmailAsync(request.Email).Returns((User?)null);
|
||||||
|
_userRepository.GetUserByUsernameAsync(request.Username).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.RegisterAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
await _userRepository.Received(1).AddAsync(Arg.Any<User>());
|
||||||
|
await _unitOfWork.Received(1).CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RegisterAsync_ShouldReturnFailure_WhenValidationFails()
|
||||||
|
{
|
||||||
|
var request = new RegisterRequest("", "", "");
|
||||||
|
|
||||||
|
var result = await _sut.RegisterAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(ErrorTypeConstant.ValidationError, result.Error.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RegisterAsync_ShouldReturnFailure_WhenEmailAlreadyExists()
|
||||||
|
{
|
||||||
|
var request = new RegisterRequest("newuser", "existing@example.com", "ValidP@ss1!");
|
||||||
|
_userRepository.GetUserByEmailAsync(request.Email).Returns(new User
|
||||||
|
{
|
||||||
|
Username = "existing", Email = "existing@example.com", Password = "hash"
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await _sut.RegisterAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.EmailAlreadyExists, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RegisterAsync_ShouldReturnFailure_WhenUsernameAlreadyExists()
|
||||||
|
{
|
||||||
|
var request = new RegisterRequest("existinguser", "new@example.com", "ValidP@ss1!");
|
||||||
|
_userRepository.GetUserByEmailAsync(request.Email).Returns((User?)null);
|
||||||
|
_userRepository.GetUserByUsernameAsync(request.Username).Returns(new User
|
||||||
|
{
|
||||||
|
Username = "existinguser", Email = "existing@example.com", Password = "hash"
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await _sut.RegisterAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.UsernameAlreadyExists, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LoginAsync_ShouldReturnSuccess_WhenCredentialsAreValid()
|
||||||
|
{
|
||||||
|
var password = "ValidP@ss1!";
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Username = "testuser",
|
||||||
|
Email = "test@example.com",
|
||||||
|
Password = new PasswordHasher<User>().HashPassword(
|
||||||
|
new User { Username = "testuser", Email = "test@example.com", Password = password },
|
||||||
|
password)
|
||||||
|
};
|
||||||
|
var request = new LoginRequest(user.Email, password);
|
||||||
|
_userRepository.GetUserByEmailAsync(request.Email).Returns(user);
|
||||||
|
_jwtService.GenerateTokenAsync(user).Returns("access-token");
|
||||||
|
_jwtService.GenerateAndSaveRefreshTokenAsync(user).Returns("refresh-token");
|
||||||
|
|
||||||
|
var result = await _sut.LoginAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
_userRepository.Received(1).Update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LoginAsync_ShouldReturnFailure_WhenValidationFails()
|
||||||
|
{
|
||||||
|
var request = new LoginRequest("", "");
|
||||||
|
|
||||||
|
var result = await _sut.LoginAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(ErrorTypeConstant.ValidationError, result.Error.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LoginAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
var request = new LoginRequest("unknown@example.com", "password123");
|
||||||
|
_userRepository.GetUserByEmailAsync(request.Email).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.LoginAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RefreshTokensAsync_ShouldReturnSuccess_WhenRefreshTokenIsValid()
|
||||||
|
{
|
||||||
|
var request = new RefreshTokenRequest { UserId = 1, RefreshToken = "valid-refresh-token" };
|
||||||
|
var user = new User { Id = 1, Username = "testuser", Email = "test@example.com", Password = "hash" };
|
||||||
|
_jwtService.ValidateRefreshTokenAsync(request.UserId, request.RefreshToken).Returns(user);
|
||||||
|
_jwtService.GenerateTokenAsync(user).Returns("new-access-token");
|
||||||
|
_jwtService.GenerateAndSaveRefreshTokenAsync(user).Returns("new-refresh-token");
|
||||||
|
|
||||||
|
var result = await _sut.RefreshTokensAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RefreshTokensAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
var request = new RefreshTokenRequest { UserId = 99, RefreshToken = "invalid-token" };
|
||||||
|
_jwtService.ValidateRefreshTokenAsync(request.UserId, request.RefreshToken).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.RefreshTokensAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendResetEmailAsync_ShouldReturnSuccess_WhenUserExists()
|
||||||
|
{
|
||||||
|
var email = "test@example.com";
|
||||||
|
var user = new User { Id = 1, Username = "testuser", Email = email, Password = "hash" };
|
||||||
|
_userRepository.GetUserByEmailAsync(email).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.SendResetEmailAsync(email);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
_emailService.Received(1).SendEmailAsync(Arg.Any<EmailRequest>());
|
||||||
|
_userRepository.Received(1).Update(user);
|
||||||
|
await _unitOfWork.Received(1).CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendResetEmailAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
var email = "unknown@example.com";
|
||||||
|
_userRepository.GetUserByEmailAsync(email).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.SendResetEmailAsync(email);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResetPasswordAsync_ShouldReturnSuccess_WhenTokenIsValid()
|
||||||
|
{
|
||||||
|
var dto = new ResetPasswordDto
|
||||||
|
{
|
||||||
|
Email = "test@example.com",
|
||||||
|
EmailToken = "valid-token",
|
||||||
|
NewPassword = "NewValidP@ss1"
|
||||||
|
};
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Username = "testuser",
|
||||||
|
Email = dto.Email,
|
||||||
|
Password = "old-hash",
|
||||||
|
ResetPasswordToken = "valid-token",
|
||||||
|
ResetPasswordTokenExpiryTime = DateTime.UtcNow.AddHours(1)
|
||||||
|
};
|
||||||
|
_userRepository.GetUserByEmailAsync(dto.Email).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.ResetPasswordAsync(dto);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
_userRepository.Received(1).Update(user);
|
||||||
|
await _unitOfWork.Received(1).CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResetPasswordAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
var dto = new ResetPasswordDto
|
||||||
|
{
|
||||||
|
Email = "unknown@example.com",
|
||||||
|
EmailToken = "token",
|
||||||
|
NewPassword = "NewValidP@ss1"
|
||||||
|
};
|
||||||
|
_userRepository.GetUserByEmailAsync(dto.Email).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.ResetPasswordAsync(dto);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResetPasswordAsync_ShouldReturnFailure_WhenTokenIsInvalid()
|
||||||
|
{
|
||||||
|
var dto = new ResetPasswordDto
|
||||||
|
{
|
||||||
|
Email = "test@example.com",
|
||||||
|
EmailToken = "wrong-token",
|
||||||
|
NewPassword = "NewValidP@ss1"
|
||||||
|
};
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Username = "testuser",
|
||||||
|
Email = dto.Email,
|
||||||
|
Password = "old-hash",
|
||||||
|
ResetPasswordToken = "valid-token",
|
||||||
|
ResetPasswordTokenExpiryTime = DateTime.UtcNow.AddHours(1)
|
||||||
|
};
|
||||||
|
_userRepository.GetUserByEmailAsync(dto.Email).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.ResetPasswordAsync(dto);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.InvalidResetLink, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResetPasswordAsync_ShouldReturnFailure_WhenTokenIsExpired()
|
||||||
|
{
|
||||||
|
var dto = new ResetPasswordDto
|
||||||
|
{
|
||||||
|
Email = "test@example.com",
|
||||||
|
EmailToken = "valid-token",
|
||||||
|
NewPassword = "NewValidP@ss1"
|
||||||
|
};
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Username = "testuser",
|
||||||
|
Email = dto.Email,
|
||||||
|
Password = "old-hash",
|
||||||
|
ResetPasswordToken = "valid-token",
|
||||||
|
ResetPasswordTokenExpiryTime = DateTime.UtcNow.AddHours(-1)
|
||||||
|
};
|
||||||
|
_userRepository.GetUserByEmailAsync(dto.Email).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.ResetPasswordAsync(dto);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(AuthError.InvalidResetLink, result.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
using Application.Common.Results;
|
||||||
|
using Application.DTOs;
|
||||||
|
using Application.Errors;
|
||||||
|
using Application.Models;
|
||||||
|
using Application.Services;
|
||||||
|
using Application.Validators;
|
||||||
|
using Domain.Entities;
|
||||||
|
using Domain.Interface;
|
||||||
|
using NSubstitute;
|
||||||
|
|
||||||
|
namespace Application.UnitTest.Services;
|
||||||
|
|
||||||
|
public class UserServiceTests
|
||||||
|
{
|
||||||
|
private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
|
||||||
|
private readonly IUserRepository _userRepository = Substitute.For<IUserRepository>();
|
||||||
|
private readonly IUserRoleRepository _userRoleRepository = Substitute.For<IUserRoleRepository>();
|
||||||
|
private readonly UserService _sut;
|
||||||
|
|
||||||
|
public UserServiceTests()
|
||||||
|
{
|
||||||
|
_sut = new UserService(
|
||||||
|
_unitOfWork,
|
||||||
|
new UserUpdateRequestValidator(),
|
||||||
|
_userRepository,
|
||||||
|
_userRoleRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAsync_ShouldReturnPagedResults()
|
||||||
|
{
|
||||||
|
var users = new List<User>
|
||||||
|
{
|
||||||
|
new() { Id = 1, Username = "user1", Email = "user1@test.com", Password = "hash",
|
||||||
|
UserRoles = [new UserRole { Role = new Role { Id = 1, Name = "Admin" } }] },
|
||||||
|
new() { Id = 2, Username = "user2", Email = "user2@test.com", Password = "hash",
|
||||||
|
UserRoles = [new UserRole { Role = new Role { Id = 2, Name = "User" } }] }
|
||||||
|
};
|
||||||
|
_userRepository.GetAllAsync().Returns(users);
|
||||||
|
|
||||||
|
var result = await _sut.GetAsync(1, 10);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.Equal(2, result.Value.Items.Count);
|
||||||
|
Assert.Equal(2, result.Value.TotalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_ShouldReturnSuccess_WhenRequestIsValid()
|
||||||
|
{
|
||||||
|
var request = new UserUpdateRequest(1, "updateduser", "updated@test.com");
|
||||||
|
var user = new User { Id = 1, Username = "olduser", Email = "old@test.com", Password = "hash" };
|
||||||
|
_userRepository.GetByIdAsync(request.Id).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.UpdateAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.Equal(request.Username, user.Username);
|
||||||
|
Assert.Equal(request.Email, user.Email);
|
||||||
|
_userRepository.Received(1).Update(user);
|
||||||
|
await _unitOfWork.Received(1).CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_ShouldReturnFailure_WhenValidationFails()
|
||||||
|
{
|
||||||
|
var request = new UserUpdateRequest(0, "", "");
|
||||||
|
|
||||||
|
var result = await _sut.UpdateAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(ErrorTypeConstant.ValidationError, result.Error.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
var request = new UserUpdateRequest(99, "user", "user@test.com");
|
||||||
|
_userRepository.GetByIdAsync(request.Id).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.UpdateAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ShouldReturnSuccess_WhenUserExists()
|
||||||
|
{
|
||||||
|
var user = new User { Id = 2, Username = "user2", Email = "user2@test.com", Password = "hash" };
|
||||||
|
_userRepository.GetByIdAsync(2).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.DeleteAsync(2, 1);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
_userRepository.Received(1).Delete(user);
|
||||||
|
await _unitOfWork.Received(1).CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ShouldReturnFailure_WhenDeletingYourself()
|
||||||
|
{
|
||||||
|
var result = await _sut.DeleteAsync(1, 1);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.CannotDeleteYourself, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
_userRepository.GetByIdAsync(99).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.DeleteAsync(99, 1);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetUserByIdAsync_ShouldReturnUser_WhenUserExists()
|
||||||
|
{
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
Id = 1, Username = "testuser", Email = "test@test.com", Password = "hash",
|
||||||
|
UserRoles = [new UserRole { Role = new Role { Id = 1, Name = "Admin" } }]
|
||||||
|
};
|
||||||
|
_userRepository.GetByIdAsync(1).Returns(user);
|
||||||
|
|
||||||
|
var result = await _sut.GetUserByIdAsync(1);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.Equal(user.Id, result.Value.Id);
|
||||||
|
Assert.Equal(user.Username, result.Value.Username);
|
||||||
|
Assert.Equal(user.Email, result.Value.Email);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetUserByIdAsync_ShouldReturnFailure_WhenUserNotFound()
|
||||||
|
{
|
||||||
|
_userRepository.GetByIdAsync(99).Returns((User?)null);
|
||||||
|
|
||||||
|
var result = await _sut.GetUserByIdAsync(99);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.UserNotFound, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AssignRoleAsync_ShouldReturnSuccess_WhenUserDoesNotHaveRole()
|
||||||
|
{
|
||||||
|
var request = new AssingRoleRequest(1, 2);
|
||||||
|
_userRoleRepository.HasRoleAsync(request.UserId, request.RoleId).Returns(false);
|
||||||
|
_userRoleRepository.AddRoleAsync(request.UserId, request.RoleId).Returns(true);
|
||||||
|
|
||||||
|
var result = await _sut.AssignRoleAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AssignRoleAsync_ShouldReturnFailure_WhenUserAlreadyHasRole()
|
||||||
|
{
|
||||||
|
var request = new AssingRoleRequest(1, 2);
|
||||||
|
_userRoleRepository.HasRoleAsync(request.UserId, request.RoleId).Returns(true);
|
||||||
|
|
||||||
|
var result = await _sut.AssignRoleAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.UserAlreadyHasRole, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AssignRoleAsync_ShouldReturnFailure_WhenAddRoleFails()
|
||||||
|
{
|
||||||
|
var request = new AssingRoleRequest(1, 2);
|
||||||
|
_userRoleRepository.HasRoleAsync(request.UserId, request.RoleId).Returns(false);
|
||||||
|
_userRoleRepository.AddRoleAsync(request.UserId, request.RoleId).Returns(false);
|
||||||
|
|
||||||
|
var result = await _sut.AssignRoleAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.FailedToAssignRole, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RevokeRoleAsync_ShouldReturnSuccess_WhenUserHasRole()
|
||||||
|
{
|
||||||
|
var request = new AssingRoleRequest(1, 2);
|
||||||
|
_userRoleRepository.HasRoleAsync(request.UserId, request.RoleId).Returns(true);
|
||||||
|
_userRoleRepository.RemoveRoleAsync(request.UserId, request.RoleId).Returns(true);
|
||||||
|
|
||||||
|
var result = await _sut.RevokeRoleAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RevokeRoleAsync_ShouldReturnFailure_WhenUserHasNoRole()
|
||||||
|
{
|
||||||
|
var request = new AssingRoleRequest(1, 2);
|
||||||
|
_userRoleRepository.HasRoleAsync(request.UserId, request.RoleId).Returns(false);
|
||||||
|
|
||||||
|
var result = await _sut.RevokeRoleAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.UserHasNoRole, result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RevokeRoleAsync_ShouldReturnFailure_WhenRemoveRoleFails()
|
||||||
|
{
|
||||||
|
var request = new AssingRoleRequest(1, 2);
|
||||||
|
_userRoleRepository.HasRoleAsync(request.UserId, request.RoleId).Returns(true);
|
||||||
|
_userRoleRepository.RemoveRoleAsync(request.UserId, request.RoleId).Returns(false);
|
||||||
|
|
||||||
|
var result = await _sut.RevokeRoleAsync(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsFailure);
|
||||||
|
Assert.Equal(UserError.FailedToRevokeRole, result.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Application.UnitTest;
|
|
||||||
|
|
||||||
public class UnitTest1
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Test1()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Application.Models;
|
||||||
|
using Application.Validators;
|
||||||
|
|
||||||
|
namespace Application.UnitTest.Validators;
|
||||||
|
|
||||||
|
public class LoginRequestValidatorTests
|
||||||
|
{
|
||||||
|
private readonly LoginRequestValidator _sut = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_ShouldPass_WhenAllFieldsAreValid()
|
||||||
|
{
|
||||||
|
var request = new LoginRequest("test@example.com", "password123");
|
||||||
|
|
||||||
|
var result = _sut.Validate(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", "password123", "Email")]
|
||||||
|
[InlineData("not-an-email", "password123", "Email")]
|
||||||
|
[InlineData("test@example.com", "", "Password")]
|
||||||
|
public void Validate_ShouldFail_WhenFieldIsInvalid(string email, string password, string expectedProperty)
|
||||||
|
{
|
||||||
|
var request = new LoginRequest(email, password);
|
||||||
|
|
||||||
|
var result = _sut.Validate(request);
|
||||||
|
|
||||||
|
Assert.False(result.IsValid);
|
||||||
|
Assert.Contains(result.Errors, e => e.PropertyName == expectedProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Application.Models;
|
||||||
|
using Application.Validators;
|
||||||
|
|
||||||
|
namespace Application.UnitTest.Validators;
|
||||||
|
|
||||||
|
public class RegisterRequestValidatorTests
|
||||||
|
{
|
||||||
|
private readonly RegisterRequestValidator _sut = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_ShouldPass_WhenAllFieldsAreValid()
|
||||||
|
{
|
||||||
|
var request = new RegisterRequest("ValidUser", "test@example.com", "ValidP@ss1");
|
||||||
|
|
||||||
|
var result = _sut.Validate(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ValidUser", "", "ValidP@ss1", "Email")]
|
||||||
|
[InlineData("ValidUser", "invalid-email", "ValidP@ss1", "Email")]
|
||||||
|
[InlineData("ValidUser", "test@example.com", "", "Password")]
|
||||||
|
[InlineData("ValidUser", "test@example.com", "short1A@", "Password")]
|
||||||
|
[InlineData("ValidUser", "test@example.com", "nouppercase1@", "Password")]
|
||||||
|
[InlineData("ValidUser", "test@example.com", "NOLOWERCASE1@", "Password")]
|
||||||
|
[InlineData("ValidUser", "test@example.com", "NoDigit@aa", "Password")]
|
||||||
|
[InlineData("ValidUser", "test@example.com", "NoSpecialChar1", "Password")]
|
||||||
|
[InlineData("", "test@example.com", "ValidP@ss1", "Username")]
|
||||||
|
[InlineData("ab", "test@example.com", "ValidP@ss1", "Username")]
|
||||||
|
[InlineData("user@name", "test@example.com", "ValidP@ss1", "Username")]
|
||||||
|
public void Validate_ShouldFail_WhenFieldIsInvalid(string username, string email, string password, string expectedProperty)
|
||||||
|
{
|
||||||
|
var request = new RegisterRequest(username, email, password);
|
||||||
|
|
||||||
|
var result = _sut.Validate(request);
|
||||||
|
|
||||||
|
Assert.False(result.IsValid);
|
||||||
|
Assert.Contains(result.Errors, e => e.PropertyName == expectedProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Application.Models;
|
||||||
|
using Application.Validators;
|
||||||
|
|
||||||
|
namespace Application.UnitTest.Validators;
|
||||||
|
|
||||||
|
public class UserUpdateRequestValidatorTests
|
||||||
|
{
|
||||||
|
private readonly UserUpdateRequestValidator _sut = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_ShouldPass_WhenAllFieldsAreValid()
|
||||||
|
{
|
||||||
|
var request = new UserUpdateRequest(1, "ValidUser", "test@example.com");
|
||||||
|
|
||||||
|
var result = _sut.Validate(request);
|
||||||
|
|
||||||
|
Assert.True(result.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0, "ValidUser", "test@example.com", "Id")]
|
||||||
|
[InlineData(1, "", "test@example.com", "Username")]
|
||||||
|
[InlineData(1, "ValidUser", "", "Email")]
|
||||||
|
[InlineData(1, "ValidUser", "invalid-email", "Email")]
|
||||||
|
public void Validate_ShouldFail_WhenFieldIsInvalid(int id, string username, string email, string expectedProperty)
|
||||||
|
{
|
||||||
|
var request = new UserUpdateRequest(id, username, email);
|
||||||
|
|
||||||
|
var result = _sut.Validate(request);
|
||||||
|
|
||||||
|
Assert.False(result.IsValid);
|
||||||
|
Assert.Contains(result.Errors, e => e.PropertyName == expectedProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user