mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
auth updates
This commit is contained in:
11
src/Core/API/API.Specs/Features/Auth.feature
Normal file
11
src/Core/API/API.Specs/Features/Auth.feature
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Feature: User Login
|
||||||
|
As a registered user
|
||||||
|
I want to log in to my account
|
||||||
|
So that I receive an authentication token to access authenticated routes
|
||||||
|
Scenario: Successful login with valid credentials
|
||||||
|
Given the API is running
|
||||||
|
And I have an existing account
|
||||||
|
And I submit a login request with a valid username and password
|
||||||
|
Then the system successfully authenticates the user
|
||||||
|
And returns a valid access token
|
||||||
|
And the response has HTTP status 200
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
Feature: NotFound API
|
Feature: NotFound Responses
|
||||||
As a client of the API
|
As a client of the API
|
||||||
I want consistent 404 responses
|
I want consistent 404 responses
|
||||||
So that consumers can handle missing routes
|
So that consumers can gracefully handle missing routes
|
||||||
|
|
||||||
Scenario: GET error 404 returns NotFound message
|
Scenario: GET request to an invalid route returns 404
|
||||||
Given the API is running
|
Given the API is running
|
||||||
When I GET "/error/404"
|
When I send an HTTP request "GET" to "/invalid-route"
|
||||||
Then the response status code should be 404
|
Then the response has HTTP status 404
|
||||||
And the response JSON should have "message" equal "Route not found."
|
And the response JSON should have "message" equal "Route not found."
|
||||||
@@ -8,14 +8,14 @@ namespace API.Specs.Steps;
|
|||||||
[Binding]
|
[Binding]
|
||||||
public class ApiSteps
|
public class ApiSteps
|
||||||
{
|
{
|
||||||
private readonly TestApiFactory _factory;
|
private readonly TestApiFactory _factory = new();
|
||||||
private HttpClient? _client;
|
private HttpClient? _client;
|
||||||
private HttpResponseMessage? _response;
|
private HttpResponseMessage? _response;
|
||||||
|
|
||||||
public ApiSteps()
|
private (string username, string password) testUser;
|
||||||
{
|
|
||||||
_factory = new TestApiFactory();
|
|
||||||
}
|
private
|
||||||
|
|
||||||
[Given("the API is running")]
|
[Given("the API is running")]
|
||||||
public void GivenTheApiIsRunning()
|
public void GivenTheApiIsRunning()
|
||||||
@@ -23,15 +23,6 @@ public class ApiSteps
|
|||||||
_client = _factory.CreateClient();
|
_client = _factory.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
// No user service assumptions needed for 404 tests
|
|
||||||
|
|
||||||
[When("I GET {string}")]
|
|
||||||
public async Task WhenIGet(string path)
|
|
||||||
{
|
|
||||||
_client.Should().NotBeNull("API client must be initialized");
|
|
||||||
_response = await _client!.GetAsync(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Then("the response status code should be {int}")]
|
[Then("the response status code should be {int}")]
|
||||||
public void ThenStatusCodeShouldBe(int expected)
|
public void ThenStatusCodeShouldBe(int expected)
|
||||||
{
|
{
|
||||||
@@ -48,4 +39,45 @@ public class ApiSteps
|
|||||||
dict!.TryGetValue(field, out var value).Should().BeTrue();
|
dict!.TryGetValue(field, out var value).Should().BeTrue();
|
||||||
(value?.ToString()).Should().Be(expected);
|
(value?.ToString()).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[When("I send an HTTP request {string} to {string} with body:")]
|
||||||
|
public async Task WhenISendAnHttpRequestToWithBody(string method, string url, string jsonBody)
|
||||||
|
{
|
||||||
|
_client.Should().NotBeNull();
|
||||||
|
|
||||||
|
var requestMessage = new HttpRequestMessage(new HttpMethod(method), url)
|
||||||
|
{
|
||||||
|
// Convert the string body into JSON content
|
||||||
|
Content = new StringContent(jsonBody, System.Text.Encoding.UTF8, "application/json")
|
||||||
|
};
|
||||||
|
|
||||||
|
_response = await _client!.SendAsync(requestMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[When("I send an HTTP request {string} to {string}")]
|
||||||
|
public async Task WhenISendAnHttpRequestTo(string method, string url)
|
||||||
|
{
|
||||||
|
var requestMessage = new HttpRequestMessage(new HttpMethod(method), url);
|
||||||
|
_response = await _client!.SendAsync(requestMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Then("the response has HTTP status {int}")]
|
||||||
|
public void ThenTheResponseHasHttpStatus(int expectedCode)
|
||||||
|
{
|
||||||
|
_response.Should().NotBeNull("No response was received from the API");
|
||||||
|
|
||||||
|
((int)_response!.StatusCode).Should().Be(expectedCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Given("I have an existing account")]
|
||||||
|
public void GivenIHaveAnExistingAccount()
|
||||||
|
{
|
||||||
|
testUser = ("test.user", "password");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Given("I submit a login request with a valid username and password")]
|
||||||
|
public void GivenISubmitALoginRequestWithAValidUsernameAndPassword()
|
||||||
|
{
|
||||||
|
WhenISendAnHttpRequestToWithBody("POST", "/api/v1/account/login");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,23 @@ namespace DBSeed
|
|||||||
int createdCredentials = 0;
|
int createdCredentials = 0;
|
||||||
int createdVerifications = 0;
|
int createdVerifications = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
const string firstName = "Test";
|
||||||
|
const string lastName = "User";
|
||||||
|
const string email = "test.user@thebiergarten.app";
|
||||||
|
var dob = new DateTime(1985, 03, 01);
|
||||||
|
var hash = GeneratePasswordHash("password");
|
||||||
|
|
||||||
|
var userAccountId = await RegisterUserAsync(
|
||||||
|
connection,
|
||||||
|
$"{firstName}.{lastName}",
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
dob,
|
||||||
|
email,
|
||||||
|
hash
|
||||||
|
);
|
||||||
|
}
|
||||||
foreach (var (firstName, lastName) in SeedNames)
|
foreach (var (firstName, lastName) in SeedNames)
|
||||||
{
|
{
|
||||||
// prepare user fields
|
// prepare user fields
|
||||||
@@ -142,6 +158,7 @@ namespace DBSeed
|
|||||||
);
|
);
|
||||||
string hash = GeneratePasswordHash(pwd);
|
string hash = GeneratePasswordHash(pwd);
|
||||||
|
|
||||||
|
|
||||||
// register the user (creates account + credential)
|
// register the user (creates account + credential)
|
||||||
var userAccountId = await RegisterUserAsync(
|
var userAccountId = await RegisterUserAsync(
|
||||||
connection,
|
connection,
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ namespace DataAccessLayer.Repositories
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task AddAsync(T entity);
|
|
||||||
public abstract Task<IEnumerable<T>> GetAllAsync(int? limit, int? offset);
|
|
||||||
public abstract Task<T?> GetByIdAsync(Guid id);
|
|
||||||
public abstract Task UpdateAsync(T entity);
|
|
||||||
public abstract Task DeleteAsync(Guid id);
|
|
||||||
|
|
||||||
protected abstract T MapToEntity(DbDataReader reader);
|
protected abstract T MapToEntity(DbDataReader reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace DataAccessLayer.Repositories.UserAccount
|
|||||||
{
|
{
|
||||||
public interface IUserAccountRepository
|
public interface IUserAccountRepository
|
||||||
{
|
{
|
||||||
Task AddAsync(Entities.UserAccount userAccount);
|
|
||||||
Task<Entities.UserAccount?> GetByIdAsync(Guid id);
|
Task<Entities.UserAccount?> GetByIdAsync(Guid id);
|
||||||
Task<IEnumerable<Entities.UserAccount>> GetAllAsync(int? limit, int? offset);
|
Task<IEnumerable<Entities.UserAccount>> GetAllAsync(int? limit, int? offset);
|
||||||
Task UpdateAsync(Entities.UserAccount userAccount);
|
Task UpdateAsync(Entities.UserAccount userAccount);
|
||||||
|
|||||||
@@ -7,27 +7,7 @@ namespace DataAccessLayer.Repositories.UserAccount
|
|||||||
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
|
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
|
||||||
: Repository<Entities.UserAccount>(connectionFactory), IUserAccountRepository
|
: Repository<Entities.UserAccount>(connectionFactory), IUserAccountRepository
|
||||||
{
|
{
|
||||||
/**
|
public async Task<Entities.UserAccount?> GetByIdAsync(Guid id)
|
||||||
* @todo update the create user account stored proc to add user credential creation in
|
|
||||||
* a single transaction, use that transaction instead.
|
|
||||||
*/
|
|
||||||
public override async Task AddAsync(Entities.UserAccount userAccount)
|
|
||||||
{
|
|
||||||
await using var connection = await CreateConnection();
|
|
||||||
await using var command = connection.CreateCommand();
|
|
||||||
command.CommandText = "usp_CreateUserAccount";
|
|
||||||
command.CommandType = CommandType.StoredProcedure;
|
|
||||||
AddParameter(command, "@UserAccountId", userAccount.UserAccountId);
|
|
||||||
AddParameter(command, "@Username", userAccount.Username);
|
|
||||||
AddParameter(command, "@FirstName", userAccount.FirstName);
|
|
||||||
AddParameter(command, "@LastName", userAccount.LastName);
|
|
||||||
AddParameter(command, "@Email", userAccount.Email);
|
|
||||||
AddParameter(command, "@DateOfBirth", userAccount.DateOfBirth);
|
|
||||||
|
|
||||||
await command.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<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();
|
||||||
@@ -40,7 +20,7 @@ namespace DataAccessLayer.Repositories.UserAccount
|
|||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<IEnumerable<Entities.UserAccount>> GetAllAsync(int? limit, int? offset)
|
public async Task<IEnumerable<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();
|
||||||
@@ -64,7 +44,7 @@ namespace DataAccessLayer.Repositories.UserAccount
|
|||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task UpdateAsync(Entities.UserAccount userAccount)
|
public async Task UpdateAsync(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();
|
||||||
@@ -81,7 +61,7 @@ namespace DataAccessLayer.Repositories.UserAccount
|
|||||||
await command.ExecuteNonQueryAsync();
|
await command.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task DeleteAsync(Guid id)
|
public async Task DeleteAsync(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();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace DataAccessLayer.Repositories.UserCredential
|
|||||||
command.CommandText = "USP_GetActiveUserCredentialByUserAccountId";
|
command.CommandText = "USP_GetActiveUserCredentialByUserAccountId";
|
||||||
command.CommandType = CommandType.StoredProcedure;
|
command.CommandType = CommandType.StoredProcedure;
|
||||||
|
|
||||||
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() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
@@ -44,21 +44,6 @@ namespace DataAccessLayer.Repositories.UserCredential
|
|||||||
await command.ExecuteNonQueryAsync();
|
await command.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task AddAsync(Entities.UserCredential entity)
|
|
||||||
=> throw new NotSupportedException("Use RotateCredentialAsync for adding/rotating credentials.");
|
|
||||||
|
|
||||||
public override Task<IEnumerable<Entities.UserCredential>> GetAllAsync(int? limit, int? offset)
|
|
||||||
=> throw new NotSupportedException("Listing credentials is not supported.");
|
|
||||||
|
|
||||||
public override Task<Entities.UserCredential?> GetByIdAsync(Guid id)
|
|
||||||
=> throw new NotSupportedException("Fetching credential by ID is not supported.");
|
|
||||||
|
|
||||||
public override Task UpdateAsync(Entities.UserCredential entity)
|
|
||||||
=> throw new NotSupportedException("Use RotateCredentialAsync to update credentials.");
|
|
||||||
|
|
||||||
public override Task DeleteAsync(Guid id)
|
|
||||||
=> throw new NotSupportedException("Deleting a credential by ID is not supported.");
|
|
||||||
|
|
||||||
protected override Entities.UserCredential MapToEntity(DbDataReader reader)
|
protected override Entities.UserCredential MapToEntity(DbDataReader reader)
|
||||||
{
|
{
|
||||||
var entity = new Entities.UserCredential
|
var entity = new Entities.UserCredential
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ public class UserAccountRepositoryTest
|
|||||||
("DateOfBirth", typeof(DateTime)),
|
("DateOfBirth", typeof(DateTime)),
|
||||||
("Timer", typeof(byte[]))
|
("Timer", typeof(byte[]))
|
||||||
).AddRow(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
).AddRow(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
||||||
"yerb","Aaron","Po","aaronpo@example.com",
|
"yerb", "Aaron", "Po", "aaronpo@example.com",
|
||||||
new DateTime(2020,1,1), null,
|
new DateTime(2020, 1, 1), null,
|
||||||
new DateTime(1990,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"));
|
||||||
@@ -55,8 +55,10 @@ public class UserAccountRepositoryTest
|
|||||||
("UpdatedAt", typeof(DateTime?)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("DateOfBirth", typeof(DateTime)),
|
||||||
("Timer", typeof(byte[]))
|
("Timer", typeof(byte[]))
|
||||||
).AddRow(Guid.NewGuid(), "a","A","A","a@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date, null)
|
).AddRow(Guid.NewGuid(), "a", "A", "A", "a@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date,
|
||||||
.AddRow(Guid.NewGuid(), "b","B","B","b@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date, null));
|
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();
|
||||||
@@ -64,27 +66,6 @@ public class UserAccountRepositoryTest
|
|||||||
results.Select(r => r.Username).Should().BeEquivalentTo(new[] { "a", "b" });
|
results.Select(r => r.Username).Should().BeEquivalentTo(new[] { "a", "b" });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AddAsync_ExecutesStoredProcedure()
|
|
||||||
{
|
|
||||||
var conn = new MockDbConnection();
|
|
||||||
conn.Mocks
|
|
||||||
.When(cmd => cmd.CommandText == "usp_CreateUserAccount")
|
|
||||||
.ReturnsScalar(1);
|
|
||||||
|
|
||||||
var repo = CreateRepo(conn);
|
|
||||||
var user = new DataAccessLayer.Entities.UserAccount
|
|
||||||
{
|
|
||||||
UserAccountId = Guid.NewGuid(),
|
|
||||||
Username = "newuser",
|
|
||||||
FirstName = "New",
|
|
||||||
LastName = "User",
|
|
||||||
Email = "newuser@example.com",
|
|
||||||
DateOfBirth = new DateTime(1991,1,1)
|
|
||||||
};
|
|
||||||
|
|
||||||
await repo.AddAsync(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetByUsername_ReturnsRow()
|
public async Task GetByUsername_ReturnsRow()
|
||||||
@@ -102,7 +83,8 @@ public class UserAccountRepositoryTest
|
|||||||
("UpdatedAt", typeof(DateTime?)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("DateOfBirth", typeof(DateTime)),
|
||||||
("Timer", typeof(byte[]))
|
("Timer", typeof(byte[]))
|
||||||
).AddRow(Guid.NewGuid(), "lookupuser","L","U","lookup@example.com", DateTime.UtcNow, null, DateTime.UtcNow.Date, null));
|
).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");
|
||||||
@@ -126,7 +108,8 @@ public class UserAccountRepositoryTest
|
|||||||
("UpdatedAt", typeof(DateTime?)),
|
("UpdatedAt", typeof(DateTime?)),
|
||||||
("DateOfBirth", typeof(DateTime)),
|
("DateOfBirth", typeof(DateTime)),
|
||||||
("Timer", typeof(byte[]))
|
("Timer", typeof(byte[]))
|
||||||
).AddRow(Guid.NewGuid(), "byemail","B","E","byemail@example.com", DateTime.UtcNow, null, 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");
|
||||||
|
|||||||
@@ -1,61 +1,11 @@
|
|||||||
using Apps72.Dev.Data.DbMocker;
|
using Apps72.Dev.Data.DbMocker;
|
||||||
using DataAccessLayer.Repositories.UserCredential;
|
using DataAccessLayer.Repositories.UserCredential;
|
||||||
using DataAccessLayer.Sql;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Moq;
|
|
||||||
using Repository.Tests.Database;
|
using Repository.Tests.Database;
|
||||||
|
|
||||||
namespace Repository.Tests.UserCredential;
|
namespace Repository.Tests.UserCredential;
|
||||||
|
|
||||||
public class UserCredentialRepositoryTests
|
public class UserCredentialRepositoryTests
|
||||||
{
|
{
|
||||||
private static UserCredentialRepository CreateRepo()
|
|
||||||
{
|
|
||||||
var factoryMock = new Mock<ISqlConnectionFactory>(MockBehavior.Strict);
|
|
||||||
// NotSupported methods do not use the factory; keep strict to ensure no unexpected calls.
|
|
||||||
return new UserCredentialRepository(factoryMock.Object);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AddAsync_ShouldThrow_NotSupported()
|
|
||||||
{
|
|
||||||
var repo = CreateRepo();
|
|
||||||
var act = async () => await repo.AddAsync(new DataAccessLayer.Entities.UserCredential());
|
|
||||||
await act.Should().ThrowAsync<NotSupportedException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetAllAsync_ShouldThrow_NotSupported()
|
|
||||||
{
|
|
||||||
var repo = CreateRepo();
|
|
||||||
var act = async () => await repo.GetAllAsync(null, null);
|
|
||||||
await act.Should().ThrowAsync<NotSupportedException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetByIdAsync_ShouldThrow_NotSupported()
|
|
||||||
{
|
|
||||||
var repo = CreateRepo();
|
|
||||||
var act = async () => await repo.GetByIdAsync(Guid.NewGuid());
|
|
||||||
await act.Should().ThrowAsync<NotSupportedException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UpdateAsync_ShouldThrow_NotSupported()
|
|
||||||
{
|
|
||||||
var repo = CreateRepo();
|
|
||||||
var act = async () => await repo.UpdateAsync(new DataAccessLayer.Entities.UserCredential());
|
|
||||||
await act.Should().ThrowAsync<NotSupportedException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task DeleteAsync_ShouldThrow_NotSupported()
|
|
||||||
{
|
|
||||||
var repo = CreateRepo();
|
|
||||||
var act = async () => await repo.DeleteAsync(Guid.NewGuid());
|
|
||||||
await act.Should().ThrowAsync<NotSupportedException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RotateCredentialAsync_ExecutesWithoutError()
|
public async Task RotateCredentialAsync_ExecutesWithoutError()
|
||||||
{
|
{
|
||||||
@@ -70,6 +20,5 @@ public class UserCredentialRepositoryTests
|
|||||||
Hash = "hashed_password"
|
Hash = "hashed_password"
|
||||||
};
|
};
|
||||||
await repo.RotateCredentialAsync(Guid.NewGuid(), credential);
|
await repo.RotateCredentialAsync(Guid.NewGuid(), credential);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,22 +7,7 @@ namespace ServiceCore.Services
|
|||||||
{
|
{
|
||||||
public async Task<UserAccount> RegisterAsync(UserAccount userAccount, string password)
|
public async Task<UserAccount> RegisterAsync(UserAccount userAccount, string password)
|
||||||
{
|
{
|
||||||
if (userAccount.UserAccountId == Guid.Empty)
|
throw new NotImplementedException();
|
||||||
{
|
|
||||||
userAccount.UserAccountId = Guid.NewGuid();
|
|
||||||
}
|
|
||||||
|
|
||||||
await userRepo.AddAsync(userAccount);
|
|
||||||
|
|
||||||
var credential = new UserCredential
|
|
||||||
{
|
|
||||||
UserAccountId = userAccount.UserAccountId,
|
|
||||||
Hash = PasswordHasher.Hash(password)
|
|
||||||
};
|
|
||||||
|
|
||||||
await credRepo.RotateCredentialAsync(userAccount.UserAccountId, credential);
|
|
||||||
|
|
||||||
return userAccount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserAccount?> LoginAsync(string usernameOrEmail, string password)
|
public async Task<UserAccount?> LoginAsync(string usernameOrEmail, string password)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
|
|
||||||
namespace ServiceCore.Services
|
namespace ServiceCore.Services
|
||||||
@@ -9,8 +7,6 @@ namespace ServiceCore.Services
|
|||||||
Task<IEnumerable<UserAccount>> GetAllAsync(int? limit = null, int? offset = null);
|
Task<IEnumerable<UserAccount>> GetAllAsync(int? limit = null, int? offset = null);
|
||||||
Task<UserAccount?> GetByIdAsync(Guid id);
|
Task<UserAccount?> GetByIdAsync(Guid id);
|
||||||
|
|
||||||
Task AddAsync(UserAccount userAccount);
|
|
||||||
|
|
||||||
Task UpdateAsync(UserAccount userAccount);
|
Task UpdateAsync(UserAccount userAccount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredCla
|
|||||||
namespace ServiceCore.Services;
|
namespace ServiceCore.Services;
|
||||||
public class JwtService(IConfiguration config) : IJwtService
|
public class JwtService(IConfiguration config) : IJwtService
|
||||||
{
|
{
|
||||||
private readonly string? _secret = config["Jwt:Secret"];
|
// private readonly string? _secret = config["Jwt:Secret"];
|
||||||
|
private readonly string? _secret = "128490218jfklsdajfdsa90f8sd0fid0safasr31jl2k1j4AFSDR!@#$fdsafjdslajfl";
|
||||||
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();
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ namespace ServiceCore.Services
|
|||||||
return await repository.GetByIdAsync(id);
|
return await repository.GetByIdAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAsync(UserAccount userAccount)
|
|
||||||
{
|
|
||||||
await repository.AddAsync(userAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateAsync(UserAccount userAccount)
|
public async Task UpdateAsync(UserAccount userAccount)
|
||||||
{
|
{
|
||||||
await repository.UpdateAsync(userAccount);
|
await repository.UpdateAsync(userAccount);
|
||||||
|
|||||||
Reference in New Issue
Block a user