mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 02:39:03 +00:00
Format infrastructure dir
This commit is contained in:
@@ -3,4 +3,4 @@ namespace Service.Core.Jwt;
|
|||||||
public interface IJwtService
|
public interface IJwtService
|
||||||
{
|
{
|
||||||
string GenerateJwt(Guid userId, string username, DateTime expiry);
|
string GenerateJwt(Guid userId, string username, DateTime expiry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>Service.Core.Jwt</RootNamespace>
|
<RootNamespace>Service.Core.Jwt</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
|
<PackageReference
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
|
Include="Microsoft.IdentityModel.JsonWebTokens"
|
||||||
</ItemGroup>
|
Version="8.2.1"
|
||||||
|
/>
|
||||||
|
<PackageReference
|
||||||
|
Include="System.IdentityModel.Tokens.Jwt"
|
||||||
|
Version="8.2.1"
|
||||||
|
/>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,21 +5,27 @@ using Microsoft.IdentityModel.Tokens;
|
|||||||
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
||||||
|
|
||||||
namespace Service.Core.Jwt;
|
namespace Service.Core.Jwt;
|
||||||
|
|
||||||
public class JwtService : IJwtService
|
public class JwtService : IJwtService
|
||||||
{
|
{
|
||||||
private readonly string? _secret = Environment.GetEnvironmentVariable("JWT_SECRET");
|
private readonly string? _secret = Environment.GetEnvironmentVariable(
|
||||||
|
"JWT_SECRET"
|
||||||
|
);
|
||||||
|
|
||||||
public string GenerateJwt(Guid userId, string username, DateTime expiry)
|
public string GenerateJwt(Guid userId, string username, DateTime expiry)
|
||||||
{
|
{
|
||||||
var handler = new JsonWebTokenHandler();
|
var handler = new JsonWebTokenHandler();
|
||||||
|
|
||||||
var key = Encoding.UTF8.GetBytes(_secret ?? throw new InvalidOperationException("secret not set"));
|
var key = Encoding.UTF8.GetBytes(
|
||||||
|
_secret ?? throw new InvalidOperationException("secret not set")
|
||||||
|
);
|
||||||
|
|
||||||
// Base claims (always present)
|
// Base claims (always present)
|
||||||
var claims = new List<Claim>
|
var claims = new List<Claim>
|
||||||
{
|
{
|
||||||
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||||
new(JwtRegisteredClaimNames.UniqueName, username),
|
new(JwtRegisteredClaimNames.UniqueName, username),
|
||||||
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||||
};
|
};
|
||||||
|
|
||||||
var tokenDescriptor = new SecurityTokenDescriptor
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
@@ -28,7 +34,8 @@ public class JwtService : IJwtService
|
|||||||
Expires = expiry,
|
Expires = expiry,
|
||||||
SigningCredentials = new SigningCredentials(
|
SigningCredentials = new SigningCredentials(
|
||||||
new SymmetricSecurityKey(key),
|
new SymmetricSecurityKey(key),
|
||||||
SecurityAlgorithms.HmacSha256)
|
SecurityAlgorithms.HmacSha256
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return handler.CreateToken(tokenDescriptor);
|
return handler.CreateToken(tokenDescriptor);
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ public interface IPasswordService
|
|||||||
{
|
{
|
||||||
public string Hash(string password);
|
public string Hash(string password);
|
||||||
public bool Verify(string password, string stored);
|
public bool Verify(string password, string stored);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>Service.Core.Password</RootNamespace>
|
<RootNamespace>Service.Core.Password</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
<PackageReference
|
||||||
</ItemGroup>
|
Include="Konscious.Security.Cryptography.Argon2"
|
||||||
|
Version="1.3.1"
|
||||||
|
/>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class PasswordService : IPasswordService
|
|||||||
Salt = salt,
|
Salt = salt,
|
||||||
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
||||||
MemorySize = ArgonMemoryKb,
|
MemorySize = ArgonMemoryKb,
|
||||||
Iterations = ArgonIterations
|
Iterations = ArgonIterations,
|
||||||
};
|
};
|
||||||
|
|
||||||
var hash = argon2.GetBytes(HashSize);
|
var hash = argon2.GetBytes(HashSize);
|
||||||
@@ -30,8 +30,12 @@ public class PasswordService : IPasswordService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parts = stored.Split(':', StringSplitOptions.RemoveEmptyEntries);
|
var parts = stored.Split(
|
||||||
if (parts.Length != 2) return false;
|
':',
|
||||||
|
StringSplitOptions.RemoveEmptyEntries
|
||||||
|
);
|
||||||
|
if (parts.Length != 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
var salt = Convert.FromBase64String(parts[0]);
|
var salt = Convert.FromBase64String(parts[0]);
|
||||||
var expected = Convert.FromBase64String(parts[1]);
|
var expected = Convert.FromBase64String(parts[1]);
|
||||||
@@ -41,7 +45,7 @@ public class PasswordService : IPasswordService
|
|||||||
Salt = salt,
|
Salt = salt,
|
||||||
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
||||||
MemorySize = ArgonMemoryKb,
|
MemorySize = ArgonMemoryKb,
|
||||||
Iterations = ArgonIterations
|
Iterations = ArgonIterations,
|
||||||
};
|
};
|
||||||
|
|
||||||
var actual = argon2.GetBytes(expected.Length);
|
var actual = argon2.GetBytes(expected.Length);
|
||||||
@@ -52,4 +56,4 @@ public class PasswordService : IPasswordService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ using Repository.Core.Sql;
|
|||||||
|
|
||||||
namespace Repository.Core.Repositories.Auth
|
namespace Repository.Core.Repositories.Auth
|
||||||
{
|
{
|
||||||
public class AuthRepository : Repository<Domain.Core.Entities.UserAccount>, IAuthRepository
|
public class AuthRepository
|
||||||
|
: Repository<Domain.Core.Entities.UserAccount>,
|
||||||
|
IAuthRepository
|
||||||
{
|
{
|
||||||
public AuthRepository(ISqlConnectionFactory connectionFactory)
|
public AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||||
: base(connectionFactory)
|
: base(connectionFactory) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<Domain.Core.Entities.UserAccount> RegisterUserAsync(
|
public async Task<Domain.Core.Entities.UserAccount> RegisterUserAsync(
|
||||||
string username,
|
string username,
|
||||||
@@ -19,7 +18,8 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
string lastName,
|
string lastName,
|
||||||
string email,
|
string email,
|
||||||
DateTime dateOfBirth,
|
DateTime dateOfBirth,
|
||||||
string passwordHash)
|
string passwordHash
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -45,12 +45,13 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
LastName = lastName,
|
LastName = lastName,
|
||||||
Email = email,
|
Email = email,
|
||||||
DateOfBirth = dateOfBirth,
|
DateOfBirth = dateOfBirth,
|
||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(
|
||||||
public async Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(string email)
|
string email
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -63,8 +64,9 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(
|
||||||
public async Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(string username)
|
string username
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -77,7 +79,9 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId)
|
public async Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(
|
||||||
|
Guid userAccountId
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -87,10 +91,15 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
AddParameter(command, "@UserAccountId", userAccountId);
|
AddParameter(command, "@UserAccountId", userAccountId);
|
||||||
|
|
||||||
await using var reader = await command.ExecuteReaderAsync();
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
return await reader.ReadAsync() ? MapToCredentialEntity(reader) : null;
|
return await reader.ReadAsync()
|
||||||
|
? MapToCredentialEntity(reader)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RotateCredentialAsync(Guid userAccountId, string newPasswordHash)
|
public async Task RotateCredentialAsync(
|
||||||
|
Guid userAccountId,
|
||||||
|
string newPasswordHash
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -106,11 +115,15 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a data reader row to a UserAccount entity.
|
/// Maps a data reader row to a UserAccount entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override Domain.Core.Entities.UserAccount MapToEntity(DbDataReader reader)
|
protected override Domain.Core.Entities.UserAccount MapToEntity(
|
||||||
|
DbDataReader reader
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return new Domain.Core.Entities.UserAccount
|
return new Domain.Core.Entities.UserAccount
|
||||||
{
|
{
|
||||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
UserAccountId = reader.GetGuid(
|
||||||
|
reader.GetOrdinal("UserAccountId")
|
||||||
|
),
|
||||||
Username = reader.GetString(reader.GetOrdinal("Username")),
|
Username = reader.GetString(reader.GetOrdinal("Username")),
|
||||||
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
||||||
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
||||||
@@ -119,10 +132,12 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
||||||
? null
|
? null
|
||||||
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
||||||
DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")),
|
DateOfBirth = reader.GetDateTime(
|
||||||
|
reader.GetOrdinal("DateOfBirth")
|
||||||
|
),
|
||||||
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||||
? null
|
? null
|
||||||
: (byte[])reader["Timer"]
|
: (byte[])reader["Timer"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,22 +148,34 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
{
|
{
|
||||||
var entity = new UserCredential
|
var entity = new UserCredential
|
||||||
{
|
{
|
||||||
UserCredentialId = reader.GetGuid(reader.GetOrdinal("UserCredentialId")),
|
UserCredentialId = reader.GetGuid(
|
||||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
reader.GetOrdinal("UserCredentialId")
|
||||||
|
),
|
||||||
|
UserAccountId = reader.GetGuid(
|
||||||
|
reader.GetOrdinal("UserAccountId")
|
||||||
|
),
|
||||||
Hash = reader.GetString(reader.GetOrdinal("Hash")),
|
Hash = reader.GetString(reader.GetOrdinal("Hash")),
|
||||||
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt"))
|
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optional columns
|
// Optional columns
|
||||||
var hasTimer = reader.GetSchemaTable()?.Rows
|
var hasTimer =
|
||||||
.Cast<System.Data.DataRow>()
|
reader
|
||||||
.Any(r => string.Equals(r["ColumnName"]?.ToString(), "Timer",
|
.GetSchemaTable()
|
||||||
StringComparison.OrdinalIgnoreCase)) ??
|
?.Rows.Cast<System.Data.DataRow>()
|
||||||
false;
|
.Any(r =>
|
||||||
|
string.Equals(
|
||||||
|
r["ColumnName"]?.ToString(),
|
||||||
|
"Timer",
|
||||||
|
StringComparison.OrdinalIgnoreCase
|
||||||
|
)
|
||||||
|
) ?? false;
|
||||||
|
|
||||||
if (hasTimer)
|
if (hasTimer)
|
||||||
{
|
{
|
||||||
entity.Timer = reader.IsDBNull(reader.GetOrdinal("Timer")) ? null : (byte[])reader["Timer"];
|
entity.Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||||
|
? null
|
||||||
|
: (byte[])reader["Timer"];
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
@@ -157,7 +184,11 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper method to add a parameter to a database command.
|
/// Helper method to add a parameter to a database command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void AddParameter(DbCommand command, string name, object? value)
|
private static void AddParameter(
|
||||||
|
DbCommand command,
|
||||||
|
string name,
|
||||||
|
object? value
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var p = command.CreateParameter();
|
var p = command.CreateParameter();
|
||||||
p.ParameterName = name;
|
p.ParameterName = name;
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
string lastName,
|
string lastName,
|
||||||
string email,
|
string email,
|
||||||
DateTime dateOfBirth,
|
DateTime dateOfBirth,
|
||||||
string passwordHash);
|
string passwordHash
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a user account by email address (typically used for login).
|
/// Retrieves a user account by email address (typically used for login).
|
||||||
@@ -32,7 +33,9 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="email">Email address to search for</param>
|
/// <param name="email">Email address to search for</param>
|
||||||
/// <returns>UserAccount if found, null otherwise</returns>
|
/// <returns>UserAccount if found, null otherwise</returns>
|
||||||
Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(string email);
|
Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(
|
||||||
|
string email
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a user account by username (typically used for login).
|
/// Retrieves a user account by username (typically used for login).
|
||||||
@@ -40,7 +43,9 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="username">Username to search for</param>
|
/// <param name="username">Username to search for</param>
|
||||||
/// <returns>UserAccount if found, null otherwise</returns>
|
/// <returns>UserAccount if found, null otherwise</returns>
|
||||||
Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(string username);
|
Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(
|
||||||
|
string username
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the active (non-revoked) credential for a user account.
|
/// Retrieves the active (non-revoked) credential for a user account.
|
||||||
@@ -48,7 +53,9 @@ namespace Repository.Core.Repositories.Auth
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userAccountId">ID of the user account</param>
|
/// <param name="userAccountId">ID of the user account</param>
|
||||||
/// <returns>Active UserCredential if found, null otherwise</returns>
|
/// <returns>Active UserCredential if found, null otherwise</returns>
|
||||||
Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId);
|
Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(
|
||||||
|
Guid userAccountId
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rotates a user's credential by invalidating all existing credentials and creating a new one.
|
/// Rotates a user's credential by invalidating all existing credentials and creating a new one.
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
using Domain.Core.Entities;
|
using Domain.Core.Entities;
|
||||||
|
|
||||||
|
|
||||||
namespace Repository.Core.Repositories.UserAccount
|
namespace Repository.Core.Repositories.UserAccount
|
||||||
{
|
{
|
||||||
public interface IUserAccountRepository
|
public interface IUserAccountRepository
|
||||||
{
|
{
|
||||||
Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id);
|
Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id);
|
||||||
Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(int? limit, int? offset);
|
Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(
|
||||||
|
int? limit,
|
||||||
|
int? offset
|
||||||
|
);
|
||||||
Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount);
|
Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount);
|
||||||
Task DeleteAsync(Guid id);
|
Task DeleteAsync(Guid id);
|
||||||
Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(string username);
|
Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(
|
||||||
|
string username
|
||||||
|
);
|
||||||
Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email);
|
Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ using Repository.Core.Sql;
|
|||||||
namespace Repository.Core.Repositories.UserAccount
|
namespace Repository.Core.Repositories.UserAccount
|
||||||
{
|
{
|
||||||
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
|
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
|
||||||
: Repository<Domain.Core.Entities.UserAccount>(connectionFactory), IUserAccountRepository
|
: Repository<Domain.Core.Entities.UserAccount>(connectionFactory),
|
||||||
|
IUserAccountRepository
|
||||||
{
|
{
|
||||||
public async Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id)
|
public async Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(
|
||||||
|
Guid id
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -21,7 +24,9 @@ namespace Repository.Core.Repositories.UserAccount
|
|||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(int? limit, int? offset)
|
public async Task<
|
||||||
|
IEnumerable<Domain.Core.Entities.UserAccount>
|
||||||
|
> GetAllAsync(int? limit, int? offset)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -45,7 +50,9 @@ namespace Repository.Core.Repositories.UserAccount
|
|||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount)
|
public async Task UpdateAsync(
|
||||||
|
Domain.Core.Entities.UserAccount userAccount
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -73,7 +80,9 @@ namespace Repository.Core.Repositories.UserAccount
|
|||||||
await command.ExecuteNonQueryAsync();
|
await command.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(string username)
|
public async Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(
|
||||||
|
string username
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -86,7 +95,9 @@ namespace Repository.Core.Repositories.UserAccount
|
|||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email)
|
public async Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(
|
||||||
|
string email
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await using var connection = await CreateConnection();
|
await using var connection = await CreateConnection();
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
@@ -99,11 +110,15 @@ namespace Repository.Core.Repositories.UserAccount
|
|||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Domain.Core.Entities.UserAccount MapToEntity(DbDataReader reader)
|
protected override Domain.Core.Entities.UserAccount MapToEntity(
|
||||||
|
DbDataReader reader
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return new Domain.Core.Entities.UserAccount
|
return new Domain.Core.Entities.UserAccount
|
||||||
{
|
{
|
||||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
UserAccountId = reader.GetGuid(
|
||||||
|
reader.GetOrdinal("UserAccountId")
|
||||||
|
),
|
||||||
Username = reader.GetString(reader.GetOrdinal("Username")),
|
Username = reader.GetString(reader.GetOrdinal("Username")),
|
||||||
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
||||||
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
||||||
@@ -112,14 +127,20 @@ namespace Repository.Core.Repositories.UserAccount
|
|||||||
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
||||||
? null
|
? null
|
||||||
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
||||||
DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")),
|
DateOfBirth = reader.GetDateTime(
|
||||||
|
reader.GetOrdinal("DateOfBirth")
|
||||||
|
),
|
||||||
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||||
? null
|
? null
|
||||||
: (byte[])reader["Timer"]
|
: (byte[])reader["Timer"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddParameter(DbCommand command, string name, object? value)
|
private static void AddParameter(
|
||||||
|
DbCommand command,
|
||||||
|
string name,
|
||||||
|
object? value
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var p = command.CreateParameter();
|
var p = command.CreateParameter();
|
||||||
p.ParameterName = name;
|
p.ParameterName = name;
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
Version="160.1000.6"
|
Version="160.1000.6"
|
||||||
/>
|
/>
|
||||||
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
|
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference
|
||||||
|
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||||
|
Version="8.0.0"
|
||||||
|
/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\Domain\Domain.csproj" />
|
<ProjectReference Include="..\..\..\Domain\Domain.csproj" />
|
||||||
|
|||||||
@@ -2,17 +2,21 @@ using System.Data.Common;
|
|||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
|
||||||
namespace Repository.Core.Sql
|
namespace Repository.Core.Sql
|
||||||
{
|
{
|
||||||
public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
|
public class DefaultSqlConnectionFactory(IConfiguration configuration)
|
||||||
|
: ISqlConnectionFactory
|
||||||
{
|
{
|
||||||
private readonly string _connectionString = GetConnectionString(configuration);
|
private readonly string _connectionString = GetConnectionString(
|
||||||
|
configuration
|
||||||
|
);
|
||||||
|
|
||||||
private static string GetConnectionString(IConfiguration configuration)
|
private static string GetConnectionString(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
// Check for full connection string first
|
// Check for full connection string first
|
||||||
var fullConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
|
var fullConnectionString = Environment.GetEnvironmentVariable(
|
||||||
|
"DB_CONNECTION_STRING"
|
||||||
|
);
|
||||||
if (!string.IsNullOrEmpty(fullConnectionString))
|
if (!string.IsNullOrEmpty(fullConnectionString))
|
||||||
{
|
{
|
||||||
return fullConnectionString;
|
return fullConnectionString;
|
||||||
|
|||||||
@@ -12,18 +12,30 @@ namespace Repository.Core.Sql
|
|||||||
/// <returns>A properly formatted SQL Server connection string.</returns>
|
/// <returns>A properly formatted SQL Server connection string.</returns>
|
||||||
public static string BuildConnectionString(string? databaseName = null)
|
public static string BuildConnectionString(string? databaseName = null)
|
||||||
{
|
{
|
||||||
var server = Environment.GetEnvironmentVariable("DB_SERVER")
|
var server =
|
||||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
Environment.GetEnvironmentVariable("DB_SERVER")
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
"DB_SERVER environment variable is not set"
|
||||||
|
);
|
||||||
|
|
||||||
var dbName = databaseName
|
var dbName =
|
||||||
|
databaseName
|
||||||
?? Environment.GetEnvironmentVariable("DB_NAME")
|
?? Environment.GetEnvironmentVariable("DB_NAME")
|
||||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
?? throw new InvalidOperationException(
|
||||||
|
"DB_NAME environment variable is not set"
|
||||||
|
);
|
||||||
|
|
||||||
var user = Environment.GetEnvironmentVariable("DB_USER")
|
var user =
|
||||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
Environment.GetEnvironmentVariable("DB_USER")
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
"DB_USER environment variable is not set"
|
||||||
|
);
|
||||||
|
|
||||||
var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
|
var password =
|
||||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
"DB_PASSWORD environment variable is not set"
|
||||||
|
);
|
||||||
|
|
||||||
var builder = new SqlConnectionStringBuilder
|
var builder = new SqlConnectionStringBuilder
|
||||||
{
|
{
|
||||||
@@ -32,7 +44,7 @@ namespace Repository.Core.Sql
|
|||||||
UserID = user,
|
UserID = user,
|
||||||
Password = password,
|
Password = password,
|
||||||
TrustServerCertificate = true,
|
TrustServerCertificate = true,
|
||||||
Encrypt = true
|
Encrypt = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return builder.ConnectionString;
|
return builder.ConnectionString;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
using Apps72.Dev.Data.DbMocker;
|
|
||||||
using Repository.Core.Repositories.Auth;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Repository.Tests.Database;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using Apps72.Dev.Data.DbMocker;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Repository.Core.Repositories.Auth;
|
||||||
|
using Repository.Tests.Database;
|
||||||
|
|
||||||
namespace Repository.Tests.Auth;
|
namespace Repository.Tests.Auth;
|
||||||
|
|
||||||
public class AuthRepositoryTest
|
public class AuthRepositoryTest
|
||||||
{
|
{
|
||||||
private static AuthRepository CreateRepo(MockDbConnection conn)
|
private static AuthRepository CreateRepo(MockDbConnection conn) =>
|
||||||
=> new(new TestConnectionFactory(conn));
|
new(new TestConnectionFactory(conn));
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RegisterUserAsync_CreatesUserWithCredential_ReturnsUserAccount()
|
public async Task RegisterUserAsync_CreatesUserWithCredential_ReturnsUserAccount()
|
||||||
@@ -17,10 +17,12 @@ public class AuthRepositoryTest
|
|||||||
var expectedUserId = Guid.NewGuid();
|
var expectedUserId = Guid.NewGuid();
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "USP_RegisterUser")
|
||||||
.When(cmd => cmd.CommandText == "USP_RegisterUser")
|
.ReturnsTable(
|
||||||
.ReturnsTable(MockTable.WithColumns(("UserAccountId", typeof(Guid)))
|
MockTable
|
||||||
.AddRow(expectedUserId));
|
.WithColumns(("UserAccountId", typeof(Guid)))
|
||||||
|
.AddRow(expectedUserId)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.RegisterUserAsync(
|
var result = await repo.RegisterUserAsync(
|
||||||
@@ -47,29 +49,32 @@ public class AuthRepositoryTest
|
|||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
.ReturnsTable(
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
MockTable
|
||||||
("UserAccountId", typeof(Guid)),
|
.WithColumns(
|
||||||
("Username", typeof(string)),
|
("UserAccountId", typeof(Guid)),
|
||||||
("FirstName", typeof(string)),
|
("Username", typeof(string)),
|
||||||
("LastName", typeof(string)),
|
("FirstName", typeof(string)),
|
||||||
("Email", typeof(string)),
|
("LastName", typeof(string)),
|
||||||
("CreatedAt", typeof(DateTime)),
|
("Email", typeof(string)),
|
||||||
("UpdatedAt", typeof(DateTime?)),
|
("CreatedAt", typeof(DateTime)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("Timer", typeof(byte[]))
|
("DateOfBirth", typeof(DateTime)),
|
||||||
).AddRow(
|
("Timer", typeof(byte[]))
|
||||||
userId,
|
)
|
||||||
"emailuser",
|
.AddRow(
|
||||||
"Email",
|
userId,
|
||||||
"User",
|
"emailuser",
|
||||||
"emailuser@example.com",
|
"Email",
|
||||||
DateTime.UtcNow,
|
"User",
|
||||||
null,
|
"emailuser@example.com",
|
||||||
new DateTime(1990, 5, 15),
|
DateTime.UtcNow,
|
||||||
null
|
null,
|
||||||
));
|
new DateTime(1990, 5, 15),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.GetUserByEmailAsync("emailuser@example.com");
|
var result = await repo.GetUserByEmailAsync("emailuser@example.com");
|
||||||
@@ -87,8 +92,7 @@ public class AuthRepositoryTest
|
|||||||
{
|
{
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
|
||||||
.ReturnsTable(MockTable.Empty());
|
.ReturnsTable(MockTable.Empty());
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
@@ -103,29 +107,34 @@ public class AuthRepositoryTest
|
|||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd =>
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByUsername")
|
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
)
|
||||||
("UserAccountId", typeof(Guid)),
|
.ReturnsTable(
|
||||||
("Username", typeof(string)),
|
MockTable
|
||||||
("FirstName", typeof(string)),
|
.WithColumns(
|
||||||
("LastName", typeof(string)),
|
("UserAccountId", typeof(Guid)),
|
||||||
("Email", typeof(string)),
|
("Username", typeof(string)),
|
||||||
("CreatedAt", typeof(DateTime)),
|
("FirstName", typeof(string)),
|
||||||
("UpdatedAt", typeof(DateTime?)),
|
("LastName", typeof(string)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("Email", typeof(string)),
|
||||||
("Timer", typeof(byte[]))
|
("CreatedAt", typeof(DateTime)),
|
||||||
).AddRow(
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
userId,
|
("DateOfBirth", typeof(DateTime)),
|
||||||
"usernameuser",
|
("Timer", typeof(byte[]))
|
||||||
"Username",
|
)
|
||||||
"User",
|
.AddRow(
|
||||||
"username@example.com",
|
userId,
|
||||||
DateTime.UtcNow,
|
"usernameuser",
|
||||||
null,
|
"Username",
|
||||||
new DateTime(1985, 8, 20),
|
"User",
|
||||||
null
|
"username@example.com",
|
||||||
));
|
DateTime.UtcNow,
|
||||||
|
null,
|
||||||
|
new DateTime(1985, 8, 20),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.GetUserByUsernameAsync("usernameuser");
|
var result = await repo.GetUserByUsernameAsync("usernameuser");
|
||||||
@@ -141,8 +150,9 @@ public class AuthRepositoryTest
|
|||||||
{
|
{
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd =>
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByUsername")
|
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||||
|
)
|
||||||
.ReturnsTable(MockTable.Empty());
|
.ReturnsTable(MockTable.Empty());
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
@@ -158,21 +168,26 @@ public class AuthRepositoryTest
|
|||||||
var credentialId = Guid.NewGuid();
|
var credentialId = Guid.NewGuid();
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd =>
|
||||||
.When(cmd => cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId")
|
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
)
|
||||||
("UserCredentialId", typeof(Guid)),
|
.ReturnsTable(
|
||||||
("UserAccountId", typeof(Guid)),
|
MockTable
|
||||||
("Hash", typeof(string)),
|
.WithColumns(
|
||||||
("CreatedAt", typeof(DateTime)),
|
("UserCredentialId", typeof(Guid)),
|
||||||
("Timer", typeof(byte[]))
|
("UserAccountId", typeof(Guid)),
|
||||||
).AddRow(
|
("Hash", typeof(string)),
|
||||||
credentialId,
|
("CreatedAt", typeof(DateTime)),
|
||||||
userId,
|
("Timer", typeof(byte[]))
|
||||||
"hashed_password_value",
|
)
|
||||||
DateTime.UtcNow,
|
.AddRow(
|
||||||
null
|
credentialId,
|
||||||
));
|
userId,
|
||||||
|
"hashed_password_value",
|
||||||
|
DateTime.UtcNow,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
|
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
|
||||||
@@ -189,8 +204,9 @@ public class AuthRepositoryTest
|
|||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd =>
|
||||||
.When(cmd => cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId")
|
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
|
||||||
|
)
|
||||||
.ReturnsTable(MockTable.Empty());
|
.ReturnsTable(MockTable.Empty());
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
@@ -206,14 +222,14 @@ public class AuthRepositoryTest
|
|||||||
var newPasswordHash = "new_hashed_password";
|
var newPasswordHash = "new_hashed_password";
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
|
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
|
||||||
.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
|
|
||||||
.ReturnsScalar(1);
|
.ReturnsScalar(1);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
|
|
||||||
// Should not throw
|
// Should not throw
|
||||||
var act = async () => await repo.RotateCredentialAsync(userId, newPasswordHash);
|
var act = async () =>
|
||||||
|
await repo.RotateCredentialAsync(userId, newPasswordHash);
|
||||||
await act.Should().NotThrowAsync();
|
await act.Should().NotThrowAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,18 @@
|
|||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
||||||
<PackageReference Include="DbMocker" Version="1.26.0" />
|
<PackageReference Include="DbMocker" Version="1.26.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
|
<PackageReference
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
Include="Microsoft.Extensions.Configuration"
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
|
Version="9.0.0"
|
||||||
|
/>
|
||||||
|
<PackageReference
|
||||||
|
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||||
|
Version="9.0.0"
|
||||||
|
/>
|
||||||
|
<PackageReference
|
||||||
|
Include="Microsoft.Extensions.Configuration.Binder"
|
||||||
|
Version="9.0.0"
|
||||||
|
/>
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -28,4 +37,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Repository.Core\Repository.Core.csproj" />
|
<ProjectReference Include="..\Repository.Core\Repository.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,38 +1,50 @@
|
|||||||
using Apps72.Dev.Data.DbMocker;
|
using Apps72.Dev.Data.DbMocker;
|
||||||
using Repository.Core.Repositories.UserAccount;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Repository.Core.Repositories.UserAccount;
|
||||||
using Repository.Tests.Database;
|
using Repository.Tests.Database;
|
||||||
|
|
||||||
namespace Repository.Tests.UserAccount;
|
namespace Repository.Tests.UserAccount;
|
||||||
|
|
||||||
public class UserAccountRepositoryTest
|
public class UserAccountRepositoryTest
|
||||||
{
|
{
|
||||||
private static UserAccountRepository CreateRepo(MockDbConnection conn)
|
private static UserAccountRepository CreateRepo(MockDbConnection conn) =>
|
||||||
=> new(new TestConnectionFactory(conn));
|
new(new TestConnectionFactory(conn));
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetByIdAsync_ReturnsRow_Mapped()
|
public async Task GetByIdAsync_ReturnsRow_Mapped()
|
||||||
{
|
{
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountById")
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountById")
|
.ReturnsTable(
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
MockTable
|
||||||
("UserAccountId", typeof(Guid)),
|
.WithColumns(
|
||||||
("Username", typeof(string)),
|
("UserAccountId", typeof(Guid)),
|
||||||
("FirstName", typeof(string)),
|
("Username", typeof(string)),
|
||||||
("LastName", typeof(string)),
|
("FirstName", typeof(string)),
|
||||||
("Email", typeof(string)),
|
("LastName", typeof(string)),
|
||||||
("CreatedAt", typeof(DateTime)),
|
("Email", typeof(string)),
|
||||||
("UpdatedAt", typeof(DateTime?)),
|
("CreatedAt", typeof(DateTime)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("Timer", typeof(byte[]))
|
("DateOfBirth", typeof(DateTime)),
|
||||||
).AddRow(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
("Timer", typeof(byte[]))
|
||||||
"yerb", "Aaron", "Po", "aaronpo@example.com",
|
)
|
||||||
new DateTime(2020, 1, 1), null,
|
.AddRow(
|
||||||
new DateTime(1990, 1, 1), null));
|
Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
||||||
|
"yerb",
|
||||||
|
"Aaron",
|
||||||
|
"Po",
|
||||||
|
"aaronpo@example.com",
|
||||||
|
new DateTime(2020, 1, 1),
|
||||||
|
null,
|
||||||
|
new DateTime(1990, 1, 1),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.GetByIdAsync(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"));
|
var result = await repo.GetByIdAsync(
|
||||||
|
Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||||
|
);
|
||||||
|
|
||||||
result.Should().NotBeNull();
|
result.Should().NotBeNull();
|
||||||
result!.Username.Should().Be("yerb");
|
result!.Username.Should().Be("yerb");
|
||||||
@@ -43,48 +55,85 @@ public class UserAccountRepositoryTest
|
|||||||
public async Task GetAllAsync_ReturnsMultipleRows()
|
public async Task GetAllAsync_ReturnsMultipleRows()
|
||||||
{
|
{
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetAllUserAccounts")
|
||||||
.When(cmd => cmd.CommandText == "usp_GetAllUserAccounts")
|
.ReturnsTable(
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
MockTable
|
||||||
("UserAccountId", typeof(Guid)),
|
.WithColumns(
|
||||||
("Username", typeof(string)),
|
("UserAccountId", typeof(Guid)),
|
||||||
("FirstName", typeof(string)),
|
("Username", typeof(string)),
|
||||||
("LastName", typeof(string)),
|
("FirstName", typeof(string)),
|
||||||
("Email", typeof(string)),
|
("LastName", typeof(string)),
|
||||||
("CreatedAt", typeof(DateTime)),
|
("Email", typeof(string)),
|
||||||
("UpdatedAt", typeof(DateTime?)),
|
("CreatedAt", typeof(DateTime)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("Timer", typeof(byte[]))
|
("DateOfBirth", typeof(DateTime)),
|
||||||
).AddRow(Guid.NewGuid(), "a", "A", "A", "a@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date,
|
("Timer", typeof(byte[]))
|
||||||
null)
|
)
|
||||||
.AddRow(Guid.NewGuid(), "b", "B", "B", "b@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date,
|
.AddRow(
|
||||||
null));
|
Guid.NewGuid(),
|
||||||
|
"a",
|
||||||
|
"A",
|
||||||
|
"A",
|
||||||
|
"a@example.com",
|
||||||
|
DateTime.UtcNow,
|
||||||
|
null,
|
||||||
|
DateTime.UtcNow.Date,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
.AddRow(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
"b",
|
||||||
|
"B",
|
||||||
|
"B",
|
||||||
|
"b@example.com",
|
||||||
|
DateTime.UtcNow,
|
||||||
|
null,
|
||||||
|
DateTime.UtcNow.Date,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var results = (await repo.GetAllAsync(null, null)).ToList();
|
var results = (await repo.GetAllAsync(null, null)).ToList();
|
||||||
results.Should().HaveCount(2);
|
results.Should().HaveCount(2);
|
||||||
results.Select(r => r.Username).Should().BeEquivalentTo(new[] { "a", "b" });
|
results
|
||||||
|
.Select(r => r.Username)
|
||||||
|
.Should()
|
||||||
|
.BeEquivalentTo(new[] { "a", "b" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetByUsername_ReturnsRow()
|
public async Task GetByUsername_ReturnsRow()
|
||||||
{
|
{
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd =>
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByUsername")
|
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
)
|
||||||
("UserAccountId", typeof(Guid)),
|
.ReturnsTable(
|
||||||
("Username", typeof(string)),
|
MockTable
|
||||||
("FirstName", typeof(string)),
|
.WithColumns(
|
||||||
("LastName", typeof(string)),
|
("UserAccountId", typeof(Guid)),
|
||||||
("Email", typeof(string)),
|
("Username", typeof(string)),
|
||||||
("CreatedAt", typeof(DateTime)),
|
("FirstName", typeof(string)),
|
||||||
("UpdatedAt", typeof(DateTime?)),
|
("LastName", typeof(string)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("Email", typeof(string)),
|
||||||
("Timer", typeof(byte[]))
|
("CreatedAt", typeof(DateTime)),
|
||||||
).AddRow(Guid.NewGuid(), "lookupuser", "L", "U", "lookup@example.com", DateTime.UtcNow, null,
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
DateTime.UtcNow.Date, null));
|
("DateOfBirth", typeof(DateTime)),
|
||||||
|
("Timer", typeof(byte[]))
|
||||||
|
)
|
||||||
|
.AddRow(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
"lookupuser",
|
||||||
|
"L",
|
||||||
|
"U",
|
||||||
|
"lookup@example.com",
|
||||||
|
DateTime.UtcNow,
|
||||||
|
null,
|
||||||
|
DateTime.UtcNow.Date,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.GetByUsernameAsync("lookupuser");
|
var result = await repo.GetByUsernameAsync("lookupuser");
|
||||||
@@ -96,20 +145,32 @@ public class UserAccountRepositoryTest
|
|||||||
public async Task GetByEmail_ReturnsRow()
|
public async Task GetByEmail_ReturnsRow()
|
||||||
{
|
{
|
||||||
var conn = new MockDbConnection();
|
var conn = new MockDbConnection();
|
||||||
conn.Mocks
|
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||||
.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
.ReturnsTable(
|
||||||
.ReturnsTable(MockTable.WithColumns(
|
MockTable
|
||||||
("UserAccountId", typeof(Guid)),
|
.WithColumns(
|
||||||
("Username", typeof(string)),
|
("UserAccountId", typeof(Guid)),
|
||||||
("FirstName", typeof(string)),
|
("Username", typeof(string)),
|
||||||
("LastName", typeof(string)),
|
("FirstName", typeof(string)),
|
||||||
("Email", typeof(string)),
|
("LastName", typeof(string)),
|
||||||
("CreatedAt", typeof(DateTime)),
|
("Email", typeof(string)),
|
||||||
("UpdatedAt", typeof(DateTime?)),
|
("CreatedAt", typeof(DateTime)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("Timer", typeof(byte[]))
|
("DateOfBirth", typeof(DateTime)),
|
||||||
).AddRow(Guid.NewGuid(), "byemail", "B", "E", "byemail@example.com", DateTime.UtcNow, null,
|
("Timer", typeof(byte[]))
|
||||||
DateTime.UtcNow.Date, null));
|
)
|
||||||
|
.AddRow(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
"byemail",
|
||||||
|
"B",
|
||||||
|
"E",
|
||||||
|
"byemail@example.com",
|
||||||
|
DateTime.UtcNow,
|
||||||
|
null,
|
||||||
|
DateTime.UtcNow.Date,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
var repo = CreateRepo(conn);
|
||||||
var result = await repo.GetByEmailAsync("byemail@example.com");
|
var result = await repo.GetByEmailAsync("byemail@example.com");
|
||||||
|
|||||||
Reference in New Issue
Block a user