Add user registration bdd tests

This commit is contained in:
Aaron Po
2026-02-10 23:09:00 -05:00
parent 656981003b
commit 8a4b833943
6 changed files with 315 additions and 282 deletions

View File

@@ -2,7 +2,7 @@ Feature: User Registration
As a new user As a new user
I want to register an account I want to register an account
So that I can log in and access authenticated routes So that I can log in and access authenticated routes
@Ignore
Scenario: Successful registration with valid details Scenario: Successful registration with valid details
Given the API is running Given the API is running
When I submit a registration request with values: When I submit a registration request with values:
@@ -11,6 +11,7 @@ Feature: User Registration
Then the response has HTTP status 201 Then the response has HTTP status 201
And the response JSON should have "message" equal "User registered successfully." And the response JSON should have "message" equal "User registered successfully."
And the response JSON should have an access token And the response JSON should have an access token
@Ignore @Ignore
Scenario: Registration fails with existing username Scenario: Registration fails with existing username
Given the API is running Given the API is running
@@ -20,6 +21,7 @@ Feature: User Registration
| existinguser | Existing | User | existing@example.com | 1990-01-01 | Password1! | | existinguser | Existing | User | existing@example.com | 1990-01-01 | Password1! |
Then the response has HTTP status 409 Then the response has HTTP status 409
And the response JSON should have "message" equal "Username already exists." And the response JSON should have "message" equal "Username already exists."
@Ignore @Ignore
Scenario: Registration fails with existing email Scenario: Registration fails with existing email
Given the API is running Given the API is running
@@ -29,6 +31,7 @@ Feature: User Registration
| newuser | New | User | existing@example.com | 1990-01-01 | Password1! | | newuser | New | User | existing@example.com | 1990-01-01 | Password1! |
Then the response has HTTP status 409 Then the response has HTTP status 409
And the response JSON should have "message" equal "Email already in use." And the response JSON should have "message" equal "Email already in use."
@Ignore @Ignore
Scenario: Registration fails with missing required fields Scenario: Registration fails with missing required fields
Given the API is running Given the API is running
@@ -37,6 +40,7 @@ Feature: User Registration
| | New | User | | | Password1! | | | New | User | | | Password1! |
Then the response has HTTP status 400 Then the response has HTTP status 400
And the response JSON should have "message" equal "Username is required." And the response JSON should have "message" equal "Username is required."
@Ignore @Ignore
Scenario: Registration fails with invalid email format Scenario: Registration fails with invalid email format
Given the API is running Given the API is running
@@ -45,6 +49,7 @@ Feature: User Registration
| newuser | New | User | invalidemail | 1990-01-01 | Password1! | | newuser | New | User | invalidemail | 1990-01-01 | Password1! |
Then the response has HTTP status 400 Then the response has HTTP status 400
And the response JSON should have "message" equal "Invalid email format." And the response JSON should have "message" equal "Invalid email format."
@Ignore @Ignore
Scenario: Registration fails with weak password Scenario: Registration fails with weak password
Given the API is running Given the API is running
@@ -53,6 +58,7 @@ Feature: User Registration
| newuser | New | User | newuser@example.com | 1990-01-01 | weakpass | | newuser | New | User | newuser@example.com | 1990-01-01 | weakpass |
Then the response has HTTP status 400 Then the response has HTTP status 400
And the response JSON should have "message" equal "Password does not meet complexity requirements." And the response JSON should have "message" equal "Password does not meet complexity requirements."
@Ignore @Ignore
Scenario: Cannot register a user younger than 19 years of age (regulatory requirement) Scenario: Cannot register a user younger than 19 years of age (regulatory requirement)
Given the API is running Given the API is running
@@ -61,7 +67,7 @@ Feature: User Registration
| younguser | Young | User | younguser@example.com | | Password1! | | younguser | Young | User | younguser@example.com | | Password1! |
Then the response has HTTP status 400 Then the response has HTTP status 400
And the response JSON should have "message" equal "You must be at least 19 years old to register." And the response JSON should have "message" equal "You must be at least 19 years old to register."
@Ignore
Scenario: Registration endpoint only accepts POST requests Scenario: Registration endpoint only accepts POST requests
Given the API is running Given the API is running
When I submit a registration request using a GET request When I submit a registration request using a GET request

View File

@@ -157,4 +157,64 @@ public class AuthSteps(ScenarioContext scenario)
scenario[ResponseKey] = response; scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody; scenario[ResponseBodyKey] = responseBody;
} }
[When("I submit a registration request with values:")]
public async Task WhenISubmitARegistrationRequestWithValues(Table table)
{
var client = GetClient();
var row = table.Rows[0];
var registrationData = new
{
username = row.TryGetValue("Username", out var value) ? value : null,
firstName = row.TryGetValue("FirstName", out var value1) ? value1 : null,
lastName = row.TryGetValue("LastName", out var value2) ? value2 : null,
email = row.TryGetValue("Email", out var value3) ? value3 : null,
dateOfBirth = row.ContainsKey("DateOfBirth") && !string.IsNullOrEmpty(row["DateOfBirth"])
? row["DateOfBirth"]
: null,
password = row.ContainsKey("Password") ? row["Password"] : null
};
var body = JsonSerializer.Serialize(registrationData);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/auth/register")
{
Content = new StringContent(body, System.Text.Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[Given("I have an existing account with username {string}")]
public void GivenIHaveAnExistingAccountWithUsername(string username)
{
}
[Given("I have an existing account with email {string}")]
public void GivenIHaveAnExistingAccountWithEmail(string email)
{
}
[When("I submit a registration request using a GET request")]
public async Task WhenISubmitARegistrationRequestUsingAGetRequest()
{
var client = GetClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/auth/register")
{
Content = new StringContent("{}", System.Text.Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
} }

View File

@@ -22,10 +22,10 @@ namespace Repository.Core.Repositories.Auth
{ {
await using var connection = await CreateConnection(); await using var connection = await CreateConnection();
await using var command = connection.CreateCommand(); await using var command = connection.CreateCommand();
command.CommandText = "USP_RegisterUser"; command.CommandText = "USP_RegisterUser";
command.CommandType = CommandType.StoredProcedure; command.CommandType = CommandType.StoredProcedure;
// Input parameters
AddParameter(command, "@Username", username); AddParameter(command, "@Username", username);
AddParameter(command, "@FirstName", firstName); AddParameter(command, "@FirstName", firstName);
AddParameter(command, "@LastName", lastName); AddParameter(command, "@LastName", lastName);
@@ -33,11 +33,9 @@ namespace Repository.Core.Repositories.Auth
AddParameter(command, "@DateOfBirth", dateOfBirth); AddParameter(command, "@DateOfBirth", dateOfBirth);
AddParameter(command, "@Hash", passwordHash); AddParameter(command, "@Hash", passwordHash);
// Execute and retrieve the generated UserAccountId from result set
var result = await command.ExecuteScalarAsync(); var result = await command.ExecuteScalarAsync();
var userAccountId = result != null ? (Guid)result : Guid.Empty; var userAccountId = result != null ? (Guid)result : Guid.Empty;
// Return the newly created user account
return new Entities.UserAccount return new Entities.UserAccount
{ {
UserAccountId = userAccountId, UserAccountId = userAccountId,
@@ -104,12 +102,6 @@ namespace Repository.Core.Repositories.Auth
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
} }
public async Task InvalidateCredentialsByUserAccountIdAsync(Guid userAccountId)
{
throw new NotImplementedException("InvalidateCredentialsByUserAccountIdAsync");
}
/// <summary> /// <summary>
/// Maps a data reader row to a UserAccount entity. /// Maps a data reader row to a UserAccount entity.
/// </summary> /// </summary>

View File

@@ -55,12 +55,5 @@ namespace Repository.Core.Repositories.Auth
/// <param name="userAccountId">ID of the user account</param> /// <param name="userAccountId">ID of the user account</param>
/// <param name="newPasswordHash">New hashed password</param> /// <param name="newPasswordHash">New hashed password</param>
Task RotateCredentialAsync(Guid userAccountId, string newPasswordHash); Task RotateCredentialAsync(Guid userAccountId, string newPasswordHash);
/// <summary>
/// Invalidates all credentials for a user account (e.g., for logout or security purposes).
/// Uses stored procedure: USP_InvalidateUserCredential
/// </summary>
/// <param name="userAccountId">ID of the user account</param>
Task InvalidateCredentialsByUserAccountIdAsync(Guid userAccountId);
} }
} }

View File

@@ -216,17 +216,4 @@ public class AuthRepositoryTest
var act = async () => await repo.RotateCredentialAsync(userId, newPasswordHash); var act = async () => await repo.RotateCredentialAsync(userId, newPasswordHash);
await act.Should().NotThrowAsync(); await act.Should().NotThrowAsync();
} }
[Fact]
public async Task InvalidateCredentialsByUserAccountIdAsync_ExecutesSuccessfully()
{
var userId = Guid.NewGuid();
var conn = new MockDbConnection();
var repo = CreateRepo(conn);
// Should complete without error
var act = async () => await repo.InvalidateCredentialsByUserAccountIdAsync(userId);
await act.Should().NotThrowAsync();
}
} }

View File

@@ -9,13 +9,13 @@ public class AuthService(
IPasswordService passwordService IPasswordService passwordService
) : IAuthService ) : IAuthService
{ {
public async Task<UserAccount?> RegisterAsync(UserAccount userAccount, string password) public async Task<UserAccount> RegisterAsync(UserAccount userAccount, string password)
{ {
// Check if user already exists // Check if user already exists
var user = await authRepo.GetUserByUsernameAsync(userAccount.Username); var user = await authRepo.GetUserByUsernameAsync(userAccount.Username);
if (user is not null) if (user is not null)
{ {
return null; return null!;
} }
// password hashing // password hashing
@@ -45,9 +45,4 @@ public class AuthService(
if (activeCred is null) return null; if (activeCred is null) return null;
return !passwordService.Verify(password, activeCred.Hash) ? null : user; return !passwordService.Verify(password, activeCred.Hash) ? null : user;
} }
public async Task InvalidateAsync(Guid userAccountId)
{
await authRepo.InvalidateCredentialsByUserAccountIdAsync(userAccountId);
}
} }