Skip to content

Commit

Permalink
Merge pull request #6 from barzin144/add-unit-tests
Browse files Browse the repository at this point in the history
add unit test for user service
  • Loading branch information
barzin144 authored Dec 29, 2023
2 parents 80d9acc + d3d0ca9 commit 7685fa9
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 32 deletions.
31 changes: 31 additions & 0 deletions DataAccess.test/DataAccess.test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DataAccess\DataAccess.csproj" />
<ProjectReference Include="..\Service\Service.csproj" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions DataAccess.test/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
global using Xunit;
global using Moq;
39 changes: 39 additions & 0 deletions DataAccess.test/UserRepositoryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Domain.Entities;
using Domain.Repositories;
using MongoDB.Driver;
using Service;

namespace DataAccess.test;

public class UserRepositoryTest
{
private UserRepository _userRepository;
private SecurityService _securityService;
private Mock<IMongoDbContext> _mongodbContext;
private Mock<IBaseRepository<User>> _baseRepository;

public UserRepositoryTest()
{
_securityService = new SecurityService();

_mongodbContext = new Mock<IMongoDbContext>();
_baseRepository = new Mock<IBaseRepository<User>>();
// _mongoCollection = new Mock<IMongoCollection<User>>();
// var a = new Mock<IFindFluent<User, User>>();
// a.Setup(x => x.SingleOrDefaultAsync(default)).Returns(() => Task<User>.FromResult(new User
// {
// UserName = "a",
// Password = "b"
// }));

// _mongodbContext.Setup(_ => _.GetCollection<User>("Users")).Returns(_mongoCollection.Object);

// _mongoCollection.Setup(_ => _.Find(It.IsAny<FilterDefinition<User>>(), null)).Returns(() => a);

_userRepository = new UserRepository(_mongodbContext.Object, _securityService);
}
[Fact]
public void FindUserByUsernameAndPasswordAsync_ShouldFindUserByUsernameAndPassword()
{
}
}
6 changes: 3 additions & 3 deletions DataAccess/BaseRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ namespace DataAccess
{
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private IMongoCollection<T> collection = null;
public BaseRepository(MongoDbContext mongoDbContext)
protected IMongoCollection<T> collection = null;
public BaseRepository(IMongoDbContext mongoDbContext)
{
collection = mongoDbContext.GetCollection<T>($"{typeof(T).Name}s");
}

public async Task<T> FindById(string Id)
public async Task<T> FindByIdAsync(string Id)
{
return await collection.Find(x => x.Id == Id).SingleOrDefaultAsync();
}
Expand Down
11 changes: 6 additions & 5 deletions DataAccess/MongoDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using Domain.Entities;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Text;

namespace DataAccess
{
public class MongoDbContext
public interface IMongoDbContext
{
IMongoCollection<T> GetCollection<T>(string name);
}

public class MongoDbContext : IMongoDbContext
{
private readonly IMongoDatabase mongoDatabase;

Expand All @@ -15,7 +17,6 @@ public MongoDbContext(IMongoClient client, string dbName)
mongoDatabase = client.GetDatabase(dbName);
}

public IMongoCollection<User> Users => mongoDatabase.GetCollection<User>("Users");
public IMongoCollection<T> GetCollection<T>(string name)
{
return mongoDatabase.GetCollection<T>(name);
Expand Down
28 changes: 13 additions & 15 deletions DataAccess/UserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,25 @@
using System;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace DataAccess
{
public class UserRepository : BaseRepository<User>, IUserRepository
{
private readonly MongoDbContext mongoDbContext;
private readonly ISecurityService securityService;

public UserRepository(MongoDbContext mongoDbContext, ISecurityService securityService) : base(mongoDbContext)
public UserRepository(IMongoDbContext mongoDbContext, ISecurityService securityService) : base(mongoDbContext)
{
this.mongoDbContext = mongoDbContext;
this.securityService = securityService;
}

public async Task<User> FindUserByUsernameAndPasswordAsync(string username, string password)
public async Task<User> FindUserByUsernameAndPasswordAsync(Expression<Func<User,bool>> filter)
{
try
{
string passwordHash = securityService.GetSha256Hash(password);
return await mongoDbContext.Users.Find(s => s.UserName == username && s.Password == passwordHash).SingleOrDefaultAsync();
return await collection.Find(filter).SingleOrDefaultAsync();
}
catch
{
Expand All @@ -40,7 +38,7 @@ public async Task<bool> DeleteUserTokensByUserIdAsync(string userId)
FilterDefinition<User> filter = new FilterDefinitionBuilder<User>().Eq(x => x.Id, userId);
UpdateDefinition<User> update = new UpdateDefinitionBuilder<User>().Unset(x => x.Tokens);

await mongoDbContext.Users.FindOneAndUpdateAsync(filter, update);
await collection.FindOneAndUpdateAsync(filter, update);

return true;
}
Expand All @@ -57,7 +55,7 @@ public async Task<bool> AddUserTokenByUserIdAsync(string userId, Token token)
FilterDefinition<User> filter = new FilterDefinitionBuilder<User>().Eq(x => x.Id, userId);
UpdateDefinition<User> update = new UpdateDefinitionBuilder<User>().AddToSet(x => x.Tokens, token);

await mongoDbContext.Users.UpdateOneAsync(filter, update);
await collection.UpdateOneAsync(filter, update);

return true;
}
Expand All @@ -73,7 +71,7 @@ public async Task<Token> FindTokenByUserIdAndAccessTokenAsync(string userId, str
{
FilterDefinition<User> filter = new FilterDefinitionBuilder<User>().Eq($"{nameof(User.Tokens)}.{nameof(Token.AccessTokenHash)}", accessTokenHash);

User user = await mongoDbContext.Users.Find(filter).FirstOrDefaultAsync();
User user = await collection.Find(filter).FirstOrDefaultAsync();

return user.Tokens.Where(x => x.AccessTokenHash == accessTokenHash).FirstOrDefault();
}
Expand Down Expand Up @@ -101,7 +99,7 @@ public async Task<bool> UpdateUserLastActivityDateAsync(User user)
FilterDefinition<User> filter = new FilterDefinitionBuilder<User>().Eq(x => x.Id, user.Id);
UpdateDefinition<User> update = new UpdateDefinitionBuilder<User>().Set(x => x.LastLoggedIn, currentUtc);

await mongoDbContext.Users.UpdateOneAsync(filter, update);
await collection.UpdateOneAsync(filter, update);
return true;
}
catch
Expand All @@ -119,7 +117,7 @@ public async Task<bool> DeleteExpiredTokensAsync(string userId)

UpdateDefinition<User> update = new UpdateDefinitionBuilder<User>().PullFilter(x => x.Tokens, i => i.RefreshTokenExpiresDateTime < DateTimeOffset.UtcNow);

await mongoDbContext.Users.UpdateManyAsync(filter, update);
await collection.UpdateManyAsync(filter, update);

return true;
}
Expand All @@ -142,7 +140,7 @@ public async Task<bool> DeleteTokensWithSameRefreshTokenSourceAsync(string refre

UpdateDefinition<User> update = new UpdateDefinitionBuilder<User>().PullFilter(x => x.Tokens, i => i.RefreshTokenIdHashSource == refreshTokenIdHashSource || (i.RefreshTokenIdHash == refreshTokenIdHashSource && i.RefreshTokenIdHashSource == null));

await mongoDbContext.Users.UpdateManyAsync(filter, update);
await collection.UpdateManyAsync(filter, update);

return true;
}
Expand All @@ -159,7 +157,7 @@ public async Task<bool> DeleteTokensWithSameRefreshTokenSourceAsync(string refre
string refreshTokenHash = securityService.GetSha256Hash(refreshToken);
FilterDefinition<User> filter = new FilterDefinitionBuilder<User>().Eq($"{nameof(User.Tokens)}.{nameof(Token.RefreshTokenIdHash)}", refreshTokenHash);

User user = await mongoDbContext.Users.Find(filter).FirstOrDefaultAsync();
User user = await collection.Find(filter).FirstOrDefaultAsync();
if (user == null)
{
throw new Exception("Invalid refresh token");
Expand All @@ -176,7 +174,7 @@ public async Task<User> FindUserByUsernameAsync(string username)
{
try
{
var s = await mongoDbContext.Users.Find(s => s.UserName == username).SingleOrDefaultAsync();
var s = await collection.Find(s => s.UserName == username).SingleOrDefaultAsync();
return s;
}
catch
Expand All @@ -191,7 +189,7 @@ public async Task<bool> ChangePassword(string userId, string newPasswordHash, st

try
{
await mongoDbContext.Users.UpdateOneAsync(i => i.Id == userId, update);
await collection.UpdateOneAsync(i => i.Id == userId, update);
return true;
}
catch
Expand Down
7 changes: 2 additions & 5 deletions Domain/Repositories/IBaseRepository.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;

namespace Domain.Repositories
{
public interface IBaseRepository<T> where T:class
{
Task<bool> InsertOneAsync(T entity);
Task<T> FindById(string Id);
Task<T> FindByIdAsync(string Id);
}
}
4 changes: 3 additions & 1 deletion Domain/Repositories/IUserRepository.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Domain.Entities;
using System;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Domain.Repositories
{
public interface IUserRepository: IBaseRepository<User>
{
Task<User> FindUserByUsernameAndPasswordAsync(string username, string password);
Task<User> FindUserByUsernameAndPasswordAsync(Expression<Func<User,bool>> filter);
Task<bool> DeleteUserTokensByUserIdAsync(string userId);
Task<bool> AddUserTokenByUserIdAsync(string userId, Token token);
Task<Token> FindTokenByUserIdAndAccessTokenAsync(string userId, string accessTokenHash);
Expand Down
2 changes: 1 addition & 1 deletion IoCConfig/ConfigureServicesExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public static void AddCustomSwagger(this IServiceCollection services)
public static void AddCustomMongoDbService(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IMongoClient>(s => new MongoClient(configuration.GetConnectionString("MongoDb")));
services.AddScoped<MongoDbContext>(s => new MongoDbContext(s.GetRequiredService<IMongoClient>(), configuration["DbName"]));
services.AddScoped<IMongoDbContext>(s => new MongoDbContext(s.GetRequiredService<IMongoClient>(), configuration["DbName"]));
}
}
}
6 changes: 6 additions & 0 deletions JWTAuthentication.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IoCConfig", "IoCConfig\IoCConfig.csproj", "{C8A3245A-4547-4738-AC3A-F288E54A1BB8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service.test", "Service.test\Service.test.csproj", "{A3D3B325-D116-4814-AA52-26D0FFC69A05}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,6 +41,10 @@ Global
{C8A3245A-4547-4738-AC3A-F288E54A1BB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8A3245A-4547-4738-AC3A-F288E54A1BB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8A3245A-4547-4738-AC3A-F288E54A1BB8}.Release|Any CPU.Build.0 = Release|Any CPU
{A3D3B325-D116-4814-AA52-26D0FFC69A05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3D3B325-D116-4814-AA52-26D0FFC69A05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3D3B325-D116-4814-AA52-26D0FFC69A05}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3D3B325-D116-4814-AA52-26D0FFC69A05}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 2 additions & 0 deletions Service.test/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
global using Xunit;
global using Moq;
31 changes: 31 additions & 0 deletions Service.test/Service.test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Service\Service.csproj" />
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>

</Project>
32 changes: 32 additions & 0 deletions Service.test/UserServiceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Domain.Repositories;
using Microsoft.AspNetCore.Http;

namespace Service.test;

public class UserServiceTest
{
private Mock<IUserRepository> _userRepository;
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
private readonly SecurityService _securityService;
private readonly UserService _userService;

public UserServiceTest()
{
_userRepository = new Mock<IUserRepository>();
_httpContextAccessor = new Mock<IHttpContextAccessor>();
_securityService = new SecurityService();
_userService = new UserService(_userRepository.Object, _httpContextAccessor.Object, _securityService);
}
[Fact]
public async void FindUserByUsernameAndPasswordAsync_ShouldSendCorrectFilter()
{
//Arrange
string username = "abc";
string password = "abc";
string passwordHash = _securityService.GetSha256Hash(password);
//Act
var result = await _userService.FindUserByUsernameAndPasswordAsync(username, password);
//Assert
_userRepository.Verify(x => x.FindUserByUsernameAndPasswordAsync(s => s.UserName == username && s.Password == passwordHash), Times.Once);
}
}
5 changes: 3 additions & 2 deletions Service/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ public async Task<bool> AddUserAsync(User user)

public async Task<User> FindUserByUsernameAndPasswordAsync(string username, string password)
{
return await userRepository.FindUserByUsernameAndPasswordAsync(username, password);
string passwordHash = securityService.GetSha256Hash(password);
return await userRepository.FindUserByUsernameAndPasswordAsync(s => s.UserName == username && s.Password == passwordHash);
}

public async ValueTask<User> FindUserByIdAsync(string userId)
{
return await userRepository.FindById(userId);
return await userRepository.FindByIdAsync(userId);
}

public async Task UpdateUserLastActivityDateAsync(User user)
Expand Down

0 comments on commit 7685fa9

Please sign in to comment.