diff --git a/web/backend/API/API.Core/API.Core.csproj b/web/backend/API/API.Core/API.Core.csproj
index 6c2b5ae..6e9825d 100644
--- a/web/backend/API/API.Core/API.Core.csproj
+++ b/web/backend/API/API.Core/API.Core.csproj
@@ -9,32 +9,32 @@
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs b/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs
index 90c5756..3833c64 100644
--- a/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs
+++ b/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs
@@ -1,20 +1,20 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
-using System.Text.Json;
-using Shared.Contracts;
using Infrastructure.Jwt;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+using Shared.Contracts;
namespace API.Core.Authentication;
///
-/// Custom ASP.NET Core authentication handler that validates bearer JWT access tokens
-/// supplied in the Authorization header against the JWT authentication scheme.
+/// Custom ASP.NET Core authentication handler that validates bearer JWT access tokens
+/// supplied in the Authorization header against the JWT authentication scheme.
///
/// The monitored options for the JWT authentication scheme.
-/// The logger factory used by the base .
-/// The URL encoder used by the base .
+/// The logger factory used by the base .
+/// The URL encoder used by the base .
/// The infrastructure service used to validate JWT access tokens.
/// The application configuration, used as a fallback source for the JWT secret key.
public class JwtAuthenticationHandler(
@@ -26,66 +26,57 @@ public class JwtAuthenticationHandler(
) : AuthenticationHandler(options, logger, encoder)
{
///
- /// Validates the incoming request's bearer JWT access token and produces an authentication result.
+ /// Validates the incoming request's bearer JWT access token and produces an authentication result.
///
///
- /// The signing secret is resolved first from the ACCESS_TOKEN_SECRET environment variable, falling
- /// back to the Jwt:SecretKey configuration value, to stay consistent with the secret source used
- /// when tokens are issued. Authentication fails if the secret is not configured, the
- /// Authorization header is missing, the header does not use the Bearer scheme, or the
- /// token fails validation.
+ /// The signing secret is resolved first from the ACCESS_TOKEN_SECRET environment variable, falling
+ /// back to the Jwt:SecretKey configuration value, to stay consistent with the secret source used
+ /// when tokens are issued. Authentication fails if the secret is not configured, the
+ /// Authorization header is missing, the header does not use the Bearer scheme, or the
+ /// token fails validation.
///
///
- /// A successful containing the validated
- /// on success, or a failure result describing why authentication could not be completed.
+ /// A successful containing the validated
+ ///
+ /// on success, or a failure result describing why authentication could not be completed.
///
protected override async Task HandleAuthenticateAsync()
{
// Use the same access-token secret source as TokenService to avoid mismatched validation.
- var secret = Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET");
- if (string.IsNullOrWhiteSpace(secret))
- {
- secret = configuration["Jwt:SecretKey"];
- }
+ string? secret = Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET");
+ if (string.IsNullOrWhiteSpace(secret)) secret = configuration["Jwt:SecretKey"];
- if (string.IsNullOrWhiteSpace(secret))
- {
- return AuthenticateResult.Fail("JWT secret is not configured");
- }
+ if (string.IsNullOrWhiteSpace(secret)) return AuthenticateResult.Fail("JWT secret is not configured");
// Check if Authorization header exists
if (
!Request.Headers.TryGetValue(
"Authorization",
- out var authHeaderValue
+ out StringValues authHeaderValue
)
)
- {
return AuthenticateResult.Fail("Authorization header is missing");
- }
- var authHeader = authHeaderValue.ToString();
+ string authHeader = authHeaderValue.ToString();
if (
!authHeader.StartsWith(
"Bearer ",
StringComparison.OrdinalIgnoreCase
)
)
- {
return AuthenticateResult.Fail(
"Invalid authorization header format"
);
- }
- var token = authHeader.Substring("Bearer ".Length).Trim();
+ string token = authHeader.Substring("Bearer ".Length).Trim();
try
{
- var claimsPrincipal = await tokenInfrastructure.ValidateJwtAsync(
+ ClaimsPrincipal claimsPrincipal = await tokenInfrastructure.ValidateJwtAsync(
token,
secret
);
- var ticket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
+ AuthenticationTicket ticket = new(claimsPrincipal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
@@ -97,7 +88,7 @@ public class JwtAuthenticationHandler(
}
///
- /// Writes a JSON 401 Unauthorized response when authentication fails or is required but not supplied.
+ /// Writes a JSON 401 Unauthorized response when authentication fails or is required but not supplied.
///
/// The authentication properties associated with the challenge.
/// A task that completes once the response body has been written.
@@ -106,13 +97,15 @@ public class JwtAuthenticationHandler(
Response.ContentType = "application/json";
Response.StatusCode = 401;
- var response = new ResponseBody { Message = "Unauthorized: Invalid or missing authentication token" };
+ ResponseBody response = new() { Message = "Unauthorized: Invalid or missing authentication token" };
await Response.WriteAsJsonAsync(response);
}
}
///
-/// Options for the JWT authentication scheme handled by .
+/// Options for the JWT authentication scheme handled by .
///
-/// No additional options are defined beyond those provided by .
-public class JwtAuthenticationOptions : AuthenticationSchemeOptions { }
+/// No additional options are defined beyond those provided by .
+public class JwtAuthenticationOptions : AuthenticationSchemeOptions
+{
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Core/Controllers/NotFoundController.cs b/web/backend/API/API.Core/Controllers/NotFoundController.cs
index ca6b1fa..1f3a31e 100644
--- a/web/backend/API/API.Core/Controllers/NotFoundController.cs
+++ b/web/backend/API/API.Core/Controllers/NotFoundController.cs
@@ -1,27 +1,27 @@
using Microsoft.AspNetCore.Mvc;
-namespace API.Core.Controllers
+namespace API.Core.Controllers;
+
+///
+/// Handles requests that do not match any other route, used as the application's fallback controller.
+///
+///
+/// Excluded from API explorer/Swagger output via [ApiExplorerSettings(IgnoreApi = true)] .
+/// Wired up as the fallback target via app.MapFallbackToController("Handle404", "NotFound") in
+/// Program.cs .
+///
+[ApiController]
+[ApiExplorerSettings(IgnoreApi = true)]
+[Route("error")] // required
+public class NotFoundController : ControllerBase
{
///
- /// Handles requests that do not match any other route, used as the application's fallback controller.
+ /// Returns a generic 404 response for any request that did not match a defined route.
///
- ///
- /// Excluded from API explorer/Swagger output via [ApiExplorerSettings(IgnoreApi = true)] .
- /// Wired up as the fallback target via app.MapFallbackToController("Handle404", "NotFound") in Program.cs .
- ///
- [ApiController]
- [ApiExplorerSettings(IgnoreApi = true)]
- [Route("error")] // required
- public class NotFoundController : ControllerBase
+ /// A 404 Not Found result with a JSON body containing a "Route not found." message.
+ [HttpGet("404")] //required
+ public IActionResult Handle404()
{
- ///
- /// Returns a generic 404 response for any request that did not match a defined route.
- ///
- /// A 404 Not Found result with a JSON body containing a "Route not found." message.
- [HttpGet("404")] //required
- public IActionResult Handle404()
- {
- return NotFound(new { message = "Route not found." });
- }
+ return NotFound(new { message = "Route not found." });
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Core/Controllers/ProtectedController.cs b/web/backend/API/API.Core/Controllers/ProtectedController.cs
index 4f1be4f..4efa192 100644
--- a/web/backend/API/API.Core/Controllers/ProtectedController.cs
+++ b/web/backend/API/API.Core/Controllers/ProtectedController.cs
@@ -1,12 +1,12 @@
using System.Security.Claims;
-using Shared.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Shared.Contracts;
namespace API.Core.Controllers;
///
-/// Sample controller demonstrating an endpoint that requires JWT authentication.
+/// Sample controller demonstrating an endpoint that requires JWT authentication.
///
[ApiController]
[Route("api/[controller]")]
@@ -14,25 +14,25 @@ namespace API.Core.Controllers;
public class ProtectedController : ControllerBase
{
///
- /// Returns the authenticated caller's user ID and username, extracted from the JWT claims.
+ /// Returns the authenticated caller's user ID and username, extracted from the JWT claims.
///
///
- /// A 200 OK result wrapping a whose payload contains the
- /// caller's userId (from ) and username
- /// (from ), either of which may be null if not present in the token.
+ /// A 200 OK result wrapping a whose payload contains the
+ /// caller's userId (from ) and username
+ /// (from ), either of which may be null if not present in the token.
///
[HttpGet]
public ActionResult> Get()
{
- var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
- var username = User.FindFirst(ClaimTypes.Name)?.Value;
+ string? userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+ string? username = User.FindFirst(ClaimTypes.Name)?.Value;
return Ok(
new ResponseBody
{
Message = "Protected endpoint accessed successfully",
- Payload = new { userId, username },
+ Payload = new { userId, username }
}
);
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Core/GlobalException.cs b/web/backend/API/API.Core/GlobalException.cs
index 4af77ad..78e8325 100644
--- a/web/backend/API/API.Core/GlobalException.cs
+++ b/web/backend/API/API.Core/GlobalException.cs
@@ -1,48 +1,88 @@
// API.Core/Filters/GlobalExceptionFilter.cs
-using Shared.Contracts;
using Domain.Exceptions;
-using FluentValidation;
-using Microsoft.Data.SqlClient;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Data.SqlClient;
+using Shared.Contracts;
+using ValidationException = FluentValidation.ValidationException;
namespace API.Core;
///
-/// MVC exception filter that converts unhandled exceptions raised by controller actions into
-/// consistent JSON error responses with appropriate HTTP status codes.
+/// MVC exception filter that converts unhandled exceptions raised by controller actions into
+/// consistent JSON error responses with appropriate HTTP status codes.
///
/// Logger used to record unhandled exceptions before they are translated into a response.
public class GlobalExceptionFilter(ILogger logger)
: IExceptionFilter
{
///
- /// Logs the exception and sets to an appropriate error response,
- /// marking the exception as handled.
+ /// Logs the exception and sets to an appropriate error response,
+ /// marking the exception as handled.
///
///
- /// Maps exception types to responses as follows:
- ///
- /// → 400 Bad Request with per-property validation error messages.
- /// → 409 Conflict with a message.
- /// → 404 Not Found with a message.
- /// → 401 Unauthorized with a message.
- /// → 403 Forbidden with a message.
- /// → 503 Service Unavailable with a generic database error message.
- /// → 400 Bad Request with a message.
- /// Any other exception → 500 Internal Server Error with a generic error message.
- ///
+ /// Maps exception types to responses as follows:
+ ///
+ /// -
+ ///
+ /// → 400 Bad Request with per-property
+ /// validation error messages.
+ ///
+ ///
+ /// -
+ ///
+ /// → 409 Conflict with a
+ /// message.
+ ///
+ ///
+ /// -
+ ///
+ /// → 404 Not Found with a
+ /// message.
+ ///
+ ///
+ /// -
+ ///
+ /// → 401 Unauthorized with a
+ /// message.
+ ///
+ ///
+ /// -
+ ///
+ /// → 403 Forbidden with a
+ /// message.
+ ///
+ ///
+ /// -
+ ///
+ /// → 503 Service Unavailable with a generic database error
+ /// message.
+ ///
+ ///
+ /// -
+ ///
+ /// → 400 Bad Request with a
+ /// message.
+ ///
+ ///
+ /// -
+ ///
Any other exception → 500 Internal Server Error with a generic error message.
+ ///
+ ///
///
- /// The context for the unhandled exception, including the exception itself and the result to populate.
+ ///
+ /// The context for the unhandled exception, including the exception itself and the result to
+ /// populate.
+ ///
public void OnException(ExceptionContext context)
{
logger.LogError(context.Exception, "Unhandled exception occurred");
switch (context.Exception)
{
- case FluentValidation.ValidationException fluentValidationException:
- var errors = fluentValidationException
+ case ValidationException fluentValidationException:
+ Dictionary errors = fluentValidationException
.Errors.GroupBy(e => e.PropertyName)
.ToDictionary(
g => g.Key,
@@ -60,7 +100,7 @@ public class GlobalExceptionFilter(ILogger logger)
new ResponseBody { Message = ex.Message }
)
{
- StatusCode = 409,
+ StatusCode = 409
};
context.ExceptionHandled = true;
break;
@@ -70,7 +110,7 @@ public class GlobalExceptionFilter(ILogger logger)
new ResponseBody { Message = ex.Message }
)
{
- StatusCode = 404,
+ StatusCode = 404
};
context.ExceptionHandled = true;
break;
@@ -80,7 +120,7 @@ public class GlobalExceptionFilter(ILogger logger)
new ResponseBody { Message = ex.Message }
)
{
- StatusCode = 401,
+ StatusCode = 401
};
context.ExceptionHandled = true;
break;
@@ -90,7 +130,7 @@ public class GlobalExceptionFilter(ILogger logger)
new ResponseBody { Message = ex.Message }
)
{
- StatusCode = 403,
+ StatusCode = 403
};
context.ExceptionHandled = true;
break;
@@ -100,7 +140,7 @@ public class GlobalExceptionFilter(ILogger logger)
new ResponseBody { Message = "A database error occurred." }
)
{
- StatusCode = 503,
+ StatusCode = 503
};
context.ExceptionHandled = true;
break;
@@ -110,7 +150,7 @@ public class GlobalExceptionFilter(ILogger logger)
new ResponseBody { Message = ex.Message }
)
{
- StatusCode = 400,
+ StatusCode = 400
};
context.ExceptionHandled = true;
break;
@@ -119,14 +159,14 @@ public class GlobalExceptionFilter(ILogger logger)
context.Result = new ObjectResult(
new ResponseBody
{
- Message = "An unexpected error occurred",
+ Message = "An unexpected error occurred"
}
)
{
- StatusCode = 500,
+ StatusCode = 500
};
context.ExceptionHandled = true;
break;
}
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Core/Program.cs b/web/backend/API/API.Core/Program.cs
index fabaa40..1bc9b48 100644
--- a/web/backend/API/API.Core/Program.cs
+++ b/web/backend/API/API.Core/Program.cs
@@ -14,13 +14,10 @@ using Infrastructure.Jwt;
using Infrastructure.Sql;
using Shared.Application.Behaviors;
-var builder = WebApplication.CreateBuilder(args);
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Global Exception Filter
-builder.Services.AddControllers(options =>
-{
- options.Filters.Add();
-})
+builder.Services.AddControllers(options => { options.Filters.Add(); })
.AddApplicationPart(typeof(BreweryController).Assembly)
.AddApplicationPart(typeof(UserController).Assembly)
.AddApplicationPart(typeof(AuthController).Assembly);
@@ -54,10 +51,7 @@ builder.Services.AddHealthChecks();
// Configure logging for container output
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
-if (!builder.Environment.IsProduction())
-{
- builder.Logging.AddDebug();
-}
+if (!builder.Environment.IsProduction()) builder.Logging.AddDebug();
// Configure Dependency Injection -------------------------------------------------------------------------------------
@@ -88,7 +82,7 @@ builder
builder.Services.AddAuthorization();
-var app = builder.Build();
+WebApplication app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
@@ -106,10 +100,10 @@ app.MapControllers();
app.MapFallbackToController("Handle404", "NotFound");
// Graceful shutdown handling
-var lifetime = app.Services.GetRequiredService();
+IHostApplicationLifetime lifetime = app.Services.GetRequiredService();
lifetime.ApplicationStopping.Register(() =>
{
app.Logger.LogInformation("Application is shutting down gracefully...");
});
-app.Run();
+app.Run();
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/API.Specs.csproj b/web/backend/API/API.Specs/API.Specs.csproj
index e986b35..8aeff0a 100644
--- a/web/backend/API/API.Specs/API.Specs.csproj
+++ b/web/backend/API/API.Specs/API.Specs.csproj
@@ -8,40 +8,40 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
+
-
-
-
+
+
+
diff --git a/web/backend/API/API.Specs/Features/AccessTokenValidation.feature b/web/backend/API/API.Specs/Features/AccessTokenValidation.feature
index e52e1bb..1a20e66 100644
--- a/web/backend/API/API.Specs/Features/AccessTokenValidation.feature
+++ b/web/backend/API/API.Specs/Features/AccessTokenValidation.feature
@@ -1,51 +1,51 @@
Feature: Protected Endpoint Access Token Validation
- As a backend developer
- I want protected endpoints to validate access tokens
- So that unauthorized requests are rejected
+As a backend developer
+I want protected endpoints to validate access tokens
+So that unauthorized requests are rejected
- Scenario: Protected endpoint accepts valid access token
- Given the API is running
- And I have an existing account
- And I am logged in
- When I submit a request to a protected endpoint with a valid access token
- Then the response has HTTP status 200
+Scenario: Protected endpoint accepts valid access token
+ Given the API is running
+ And I have an existing account
+ And I am logged in
+ When I submit a request to a protected endpoint with a valid access token
+ Then the response has HTTP status 200
- Scenario: Protected endpoint rejects missing access token
- Given the API is running
- When I submit a request to a protected endpoint without an access token
- Then the response has HTTP status 401
+Scenario: Protected endpoint rejects missing access token
+ Given the API is running
+ When I submit a request to a protected endpoint without an access token
+ Then the response has HTTP status 401
- Scenario: Protected endpoint rejects invalid access token
- Given the API is running
- When I submit a request to a protected endpoint with an invalid access token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Unauthorized"
+Scenario: Protected endpoint rejects invalid access token
+ Given the API is running
+ When I submit a request to a protected endpoint with an invalid access token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Unauthorized"
- Scenario: Protected endpoint rejects expired access token
- Given the API is running
- And I have an existing account
- And I am logged in with an immediately-expiring access token
- When I submit a request to a protected endpoint with the expired token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Unauthorized"
+Scenario: Protected endpoint rejects expired access token
+ Given the API is running
+ And I have an existing account
+ And I am logged in with an immediately-expiring access token
+ When I submit a request to a protected endpoint with the expired token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Unauthorized"
- Scenario: Protected endpoint rejects token signed with wrong secret
- Given the API is running
- And I have an access token signed with the wrong secret
- When I submit a request to a protected endpoint with the tampered token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Unauthorized"
+Scenario: Protected endpoint rejects token signed with wrong secret
+ Given the API is running
+ And I have an access token signed with the wrong secret
+ When I submit a request to a protected endpoint with the tampered token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Unauthorized"
- Scenario: Protected endpoint rejects refresh token as access token
- Given the API is running
- And I have an existing account
- And I am logged in
- When I submit a request to a protected endpoint with my refresh token instead of access token
- Then the response has HTTP status 401
+Scenario: Protected endpoint rejects refresh token as access token
+ Given the API is running
+ And I have an existing account
+ And I am logged in
+ When I submit a request to a protected endpoint with my refresh token instead of access token
+ Then the response has HTTP status 401
- Scenario: Protected endpoint rejects confirmation token as access token
- Given the API is running
- And I have registered a new account
- And I have a valid confirmation token
- When I submit a request to a protected endpoint with my confirmation token instead of access token
- Then the response has HTTP status 401
+Scenario: Protected endpoint rejects confirmation token as access token
+ Given the API is running
+ And I have registered a new account
+ And I have a valid confirmation token
+ When I submit a request to a protected endpoint with my confirmation token instead of access token
+ Then the response has HTTP status 401
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Features/Confirmation.feature b/web/backend/API/API.Specs/Features/Confirmation.feature
index 0657aab..9cd8ff0 100644
--- a/web/backend/API/API.Specs/Features/Confirmation.feature
+++ b/web/backend/API/API.Specs/Features/Confirmation.feature
@@ -1,76 +1,77 @@
Feature: User Account Confirmation
- As a newly registered user
- I want to confirm my email address via a validation token
- So that my account is fully activated
- Scenario: Successful confirmation with valid token
- Given the API is running
- And I have registered a new account
- And I have a valid confirmation token for my account
- And I have a valid access token for my account
- When I submit a confirmation request with the valid token
- Then the response has HTTP status 200
- And the response JSON should have "message" containing "is confirmed"
+As a newly registered user
+I want to confirm my email address via a validation token
+So that my account is fully activated
- Scenario: Re-confirming an already verified account remains successful
- Given the API is running
- And I have registered a new account
- And I have a valid confirmation token for my account
- And I have a valid access token for my account
- When I submit a confirmation request with the valid token
- And I submit the same confirmation request again
- Then the response has HTTP status 200
- And the response JSON should have "message" containing "is confirmed"
+Scenario: Successful confirmation with valid token
+ Given the API is running
+ And I have registered a new account
+ And I have a valid confirmation token for my account
+ And I have a valid access token for my account
+ When I submit a confirmation request with the valid token
+ Then the response has HTTP status 200
+ And the response JSON should have "message" containing "is confirmed"
- Scenario: Confirmation fails with invalid token
- Given the API is running
- And I have registered a new account
- And I have a valid access token for my account
- When I submit a confirmation request with an invalid token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Invalid token"
+Scenario: Re-confirming an already verified account remains successful
+ Given the API is running
+ And I have registered a new account
+ And I have a valid confirmation token for my account
+ And I have a valid access token for my account
+ When I submit a confirmation request with the valid token
+ And I submit the same confirmation request again
+ Then the response has HTTP status 200
+ And the response JSON should have "message" containing "is confirmed"
- Scenario: Confirmation fails with expired token
- Given the API is running
- And I have registered a new account
- And I have an expired confirmation token for my account
- And I have a valid access token for my account
- When I submit a confirmation request with the expired token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Invalid token"
+Scenario: Confirmation fails with invalid token
+ Given the API is running
+ And I have registered a new account
+ And I have a valid access token for my account
+ When I submit a confirmation request with an invalid token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Invalid token"
- Scenario: Confirmation fails with tampered token (wrong secret)
- Given the API is running
- And I have registered a new account
- And I have a confirmation token signed with the wrong secret
- And I have a valid access token for my account
- When I submit a confirmation request with the tampered token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Invalid token"
+Scenario: Confirmation fails with expired token
+ Given the API is running
+ And I have registered a new account
+ And I have an expired confirmation token for my account
+ And I have a valid access token for my account
+ When I submit a confirmation request with the expired token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Invalid token"
- Scenario: Confirmation fails when token is missing
- Given the API is running
- And I have registered a new account
- And I have a valid access token for my account
- When I submit a confirmation request with a missing token
- Then the response has HTTP status 400
+Scenario: Confirmation fails with tampered token (wrong secret)
+ Given the API is running
+ And I have registered a new account
+ And I have a confirmation token signed with the wrong secret
+ And I have a valid access token for my account
+ When I submit a confirmation request with the tampered token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Invalid token"
- Scenario: Confirmation endpoint only accepts POST requests
- Given the API is running
- And I have a valid confirmation token
- When I submit a confirmation request using an invalid HTTP method
- Then the response has HTTP status 404
+Scenario: Confirmation fails when token is missing
+ Given the API is running
+ And I have registered a new account
+ And I have a valid access token for my account
+ When I submit a confirmation request with a missing token
+ Then the response has HTTP status 400
- Scenario: Confirmation fails with malformed token
- Given the API is running
- And I have registered a new account
- And I have a valid access token for my account
- When I submit a confirmation request with a malformed token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Invalid token"
+Scenario: Confirmation endpoint only accepts POST requests
+ Given the API is running
+ And I have a valid confirmation token
+ When I submit a confirmation request using an invalid HTTP method
+ Then the response has HTTP status 404
- Scenario: Confirmation fails without an access token
- Given the API is running
- And I have registered a new account
- And I have a valid confirmation token for my account
- When I submit a confirmation request with the valid token without an access token
- Then the response has HTTP status 401
+Scenario: Confirmation fails with malformed token
+ Given the API is running
+ And I have registered a new account
+ And I have a valid access token for my account
+ When I submit a confirmation request with a malformed token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Invalid token"
+
+Scenario: Confirmation fails without an access token
+ Given the API is running
+ And I have registered a new account
+ And I have a valid confirmation token for my account
+ When I submit a confirmation request with the valid token without an access token
+ Then the response has HTTP status 401
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Features/Login.feature b/web/backend/API/API.Specs/Features/Login.feature
index 0adad13..4fc5826 100644
--- a/web/backend/API/API.Specs/Features/Login.feature
+++ b/web/backend/API/API.Specs/Features/Login.feature
@@ -3,37 +3,37 @@ 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
- When I submit a login request with a username and password
- Then the response has HTTP status 200
- And the response JSON should have "message" equal "Logged in successfully."
- And the response JSON should have an access token
+Scenario: Successful login with valid credentials
+ Given the API is running
+ And I have an existing account
+ When I submit a login request with a username and password
+ Then the response has HTTP status 200
+ And the response JSON should have "message" equal "Logged in successfully."
+ And the response JSON should have an access token
- Scenario: Login fails with invalid credentials
- Given the API is running
- And I do not have an existing account
- When I submit a login request with a username and password
- Then the response has HTTP status 401
- And the response JSON should have "message" equal "Invalid username or password."
+Scenario: Login fails with invalid credentials
+ Given the API is running
+ And I do not have an existing account
+ When I submit a login request with a username and password
+ Then the response has HTTP status 401
+ And the response JSON should have "message" equal "Invalid username or password."
- Scenario: Login fails when required missing username
- Given the API is running
- When I submit a login request with a missing username
- Then the response has HTTP status 400
+Scenario: Login fails when required missing username
+ Given the API is running
+ When I submit a login request with a missing username
+ Then the response has HTTP status 400
- Scenario: Login fails when required missing password
- Given the API is running
- When I submit a login request with a missing password
- Then the response has HTTP status 400
+Scenario: Login fails when required missing password
+ Given the API is running
+ When I submit a login request with a missing password
+ Then the response has HTTP status 400
- Scenario: Login fails when both username and password are missing
- Given the API is running
- When I submit a login request with both username and password missing
- Then the response has HTTP status 400
+Scenario: Login fails when both username and password are missing
+ Given the API is running
+ When I submit a login request with both username and password missing
+ Then the response has HTTP status 400
- Scenario: Login endpoint only accepts POST requests
- Given the API is running
- When I submit a login request using a GET request
- Then the response has HTTP status 404
\ No newline at end of file
+Scenario: Login endpoint only accepts POST requests
+ Given the API is running
+ When I submit a login request using a GET request
+ Then the response has HTTP status 404
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Features/NotFound.feature b/web/backend/API/API.Specs/Features/NotFound.feature
index fcfc041..1e5e1cb 100644
--- a/web/backend/API/API.Specs/Features/NotFound.feature
+++ b/web/backend/API/API.Specs/Features/NotFound.feature
@@ -3,8 +3,8 @@ As a client of the API
I want consistent 404 responses
So that consumers can gracefully handle missing routes
- Scenario: GET request to an invalid route returns 404
- Given the API is running
- When I send an HTTP request "GET" to "/invalid-route"
- Then the response has HTTP status 404
- And the response JSON should have "message" equal "Route not found."
\ No newline at end of file
+Scenario: GET request to an invalid route returns 404
+ Given the API is running
+ When I send an HTTP request "GET" to "/invalid-route"
+ Then the response has HTTP status 404
+ And the response JSON should have "message" equal "Route not found."
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Features/Registration.feature b/web/backend/API/API.Specs/Features/Registration.feature
index 0ff53c3..edc3367 100644
--- a/web/backend/API/API.Specs/Features/Registration.feature
+++ b/web/backend/API/API.Specs/Features/Registration.feature
@@ -1,60 +1,60 @@
Feature: User Registration
- As a new user
- I want to register an account
- So that I can log in and access authenticated routes
+As a new user
+I want to register an account
+So that I can log in and access authenticated routes
- Scenario: Successful registration with valid details
+Scenario: Successful registration with valid details
Given the API is running
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | newuser | New | User | newuser@example.com | 1990-01-01 | Password1! |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | newuser | New | User | newuser@example.com | 1990-01-01 | Password1! |
Then the response has HTTP status 201
And the response JSON should have "message" equal "User registered successfully."
And the response JSON should have an access token
- Scenario: Registration fails with existing username
+Scenario: Registration fails with existing username
Given the API is running
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | test.user | Test | User | example@example.com | 2001-11-11 | Password1! |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | test.user | Test | User | example@example.com | 2001-11-11 | Password1! |
Then the response has HTTP status 409
- Scenario: Registration fails with existing email
+Scenario: Registration fails with existing email
Given the API is running
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | newuser | New | User | test.user@thebiergarten.app | 1990-01-01 | Password1! |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | newuser | New | User | test.user@thebiergarten.app | 1990-01-01 | Password1! |
Then the response has HTTP status 409
- Scenario: Registration fails with missing required fields
+Scenario: Registration fails with missing required fields
Given the API is running
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | | New | User | | | Password1! |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | | New | User | | | Password1! |
Then the response has HTTP status 400
- Scenario: Registration fails with invalid email format
+Scenario: Registration fails with invalid email format
Given the API is running
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | newuser | New | User | invalidemail | 1990-01-01 | Password1! |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | newuser | New | User | invalidemail | 1990-01-01 | Password1! |
Then the response has HTTP status 400
- Scenario: Registration fails with weak password
+Scenario: Registration fails with weak password
Given the API is running
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | newuser | New | User | newuser@example.com | 1990-01-01 | weakpass |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | newuser | New | User | newuser@example.com | 1990-01-01 | weakpass |
Then the response has HTTP status 400
- 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
When I submit a registration request with values:
- | Username | FirstName | LastName | Email | DateOfBirth | Password |
- | younguser | Young | User | younguser@example.com | {underage_date} | Password1! |
+ | Username | FirstName | LastName | Email | DateOfBirth | Password |
+ | younguser | Young | User | younguser@example.com | {underage_date} | Password1! |
Then the response has HTTP status 400
- Scenario: Registration endpoint only accepts POST requests
+Scenario: Registration endpoint only accepts POST requests
Given the API is running
When I submit a registration request using a GET request
- Then the response has HTTP status 404
+ Then the response has HTTP status 404
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Features/ResendConfirmation.feature b/web/backend/API/API.Specs/Features/ResendConfirmation.feature
index 9cf414c..9eb407f 100644
--- a/web/backend/API/API.Specs/Features/ResendConfirmation.feature
+++ b/web/backend/API/API.Specs/Features/ResendConfirmation.feature
@@ -1,36 +1,36 @@
Feature: Resend Confirmation Email
- As a user who did not receive the confirmation email
- I want to request a resend of the confirmation email
- So that I can obtain a working confirmation link while preventing abuse
+As a user who did not receive the confirmation email
+I want to request a resend of the confirmation email
+So that I can obtain a working confirmation link while preventing abuse
- Scenario: Legitimate resend for an unconfirmed user
- Given the API is running
- And I have registered a new account
- And I have a valid access token for my account
- When I submit a resend confirmation request for my account
- Then the response has HTTP status 200
- And the response JSON should have "message" containing "confirmation email has been resent"
+Scenario: Legitimate resend for an unconfirmed user
+ Given the API is running
+ And I have registered a new account
+ And I have a valid access token for my account
+ When I submit a resend confirmation request for my account
+ Then the response has HTTP status 200
+ And the response JSON should have "message" containing "confirmation email has been resent"
- Scenario: Resend is a no-op for an already confirmed user
- Given the API is running
- And I have registered a new account
- And I have a valid confirmation token for my account
- And I have a valid access token for my account
- And I have confirmed my account
- When I submit a resend confirmation request for my account
- Then the response has HTTP status 200
- And the response JSON should have "message" containing "confirmation email has been resent"
+Scenario: Resend is a no-op for an already confirmed user
+ Given the API is running
+ And I have registered a new account
+ And I have a valid confirmation token for my account
+ And I have a valid access token for my account
+ And I have confirmed my account
+ When I submit a resend confirmation request for my account
+ Then the response has HTTP status 200
+ And the response JSON should have "message" containing "confirmation email has been resent"
- Scenario: Resend is a no-op for a non-existent user
- Given the API is running
- And I have registered a new account
- And I have a valid access token for my account
- When I submit a resend confirmation request for a non-existent user
- Then the response has HTTP status 200
- And the response JSON should have "message" containing "confirmation email has been resent"
+Scenario: Resend is a no-op for a non-existent user
+ Given the API is running
+ And I have registered a new account
+ And I have a valid access token for my account
+ When I submit a resend confirmation request for a non-existent user
+ Then the response has HTTP status 200
+ And the response JSON should have "message" containing "confirmation email has been resent"
- Scenario: Resend requires authentication
- Given the API is running
- And I have registered a new account
- When I submit a resend confirmation request without an access token
- Then the response has HTTP status 401
+Scenario: Resend requires authentication
+ Given the API is running
+ And I have registered a new account
+ When I submit a resend confirmation request without an access token
+ Then the response has HTTP status 401
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Features/TokenRefresh.feature b/web/backend/API/API.Specs/Features/TokenRefresh.feature
index c63bc55..fa0fa84 100644
--- a/web/backend/API/API.Specs/Features/TokenRefresh.feature
+++ b/web/backend/API/API.Specs/Features/TokenRefresh.feature
@@ -1,39 +1,39 @@
Feature: Token Refresh
- As an authenticated user
- I want to refresh my access token using my refresh token
- So that I can maintain my session without logging in again
+As an authenticated user
+I want to refresh my access token using my refresh token
+So that I can maintain my session without logging in again
- Scenario: Successful token refresh with valid refresh token
- Given the API is running
- And I have an existing account
- And I am logged in
- When I submit a refresh token request with a valid refresh token
- Then the response has HTTP status 200
- And the response JSON should have "message" equal "Token refreshed successfully."
- And the response JSON should have a new access token
- And the response JSON should have a new refresh token
+Scenario: Successful token refresh with valid refresh token
+ Given the API is running
+ And I have an existing account
+ And I am logged in
+ When I submit a refresh token request with a valid refresh token
+ Then the response has HTTP status 200
+ And the response JSON should have "message" equal "Token refreshed successfully."
+ And the response JSON should have a new access token
+ And the response JSON should have a new refresh token
- Scenario: Token refresh fails with invalid refresh token
- Given the API is running
- When I submit a refresh token request with an invalid refresh token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Invalid"
+Scenario: Token refresh fails with invalid refresh token
+ Given the API is running
+ When I submit a refresh token request with an invalid refresh token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Invalid"
- Scenario: Token refresh fails with expired refresh token
- Given the API is running
- And I have an existing account
- And I am logged in with an immediately-expiring refresh token
- When I submit a refresh token request with the expired refresh token
- Then the response has HTTP status 401
- And the response JSON should have "message" containing "Invalid token"
+Scenario: Token refresh fails with expired refresh token
+ Given the API is running
+ And I have an existing account
+ And I am logged in with an immediately-expiring refresh token
+ When I submit a refresh token request with the expired refresh token
+ Then the response has HTTP status 401
+ And the response JSON should have "message" containing "Invalid token"
- Scenario: Token refresh fails when refresh token is missing
- Given the API is running
- When I submit a refresh token request with a missing refresh token
- Then the response has HTTP status 400
+Scenario: Token refresh fails when refresh token is missing
+ Given the API is running
+ When I submit a refresh token request with a missing refresh token
+ Then the response has HTTP status 400
- Scenario: Token refresh endpoint only accepts POST requests
- Given the API is running
- And I have a valid refresh token
- When I submit a refresh token request using a GET request
- Then the response has HTTP status 404
+Scenario: Token refresh endpoint only accepts POST requests
+ Given the API is running
+ And I have a valid refresh token
+ When I submit a refresh token request using a GET request
+ Then the response has HTTP status 404
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Mocks/MockEmailDispatcher.cs b/web/backend/API/API.Specs/Mocks/MockEmailDispatcher.cs
index 643aaa6..e3277d2 100644
--- a/web/backend/API/API.Specs/Mocks/MockEmailDispatcher.cs
+++ b/web/backend/API/API.Specs/Mocks/MockEmailDispatcher.cs
@@ -20,7 +20,7 @@ public class MockEmailDispatcher : IEmailDispatcher
FirstName = firstName,
Email = email,
ConfirmationToken = confirmationToken,
- SentAt = DateTime.UtcNow,
+ SentAt = DateTime.UtcNow
}
);
@@ -39,7 +39,7 @@ public class MockEmailDispatcher : IEmailDispatcher
FirstName = firstName,
Email = email,
ConfirmationToken = confirmationToken,
- SentAt = DateTime.UtcNow,
+ SentAt = DateTime.UtcNow
}
);
@@ -67,4 +67,4 @@ public class MockEmailDispatcher : IEmailDispatcher
public string ConfirmationToken { get; init; } = string.Empty;
public DateTime SentAt { get; init; }
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Mocks/MockEmailProvider.cs b/web/backend/API/API.Specs/Mocks/MockEmailProvider.cs
index 0c8b113..ba5cf65 100644
--- a/web/backend/API/API.Specs/Mocks/MockEmailProvider.cs
+++ b/web/backend/API/API.Specs/Mocks/MockEmailProvider.cs
@@ -3,8 +3,8 @@ using Infrastructure.Email;
namespace API.Specs.Mocks;
///
-/// Mock email provider for testing that doesn't actually send emails.
-/// Tracks sent emails for verification in tests if needed.
+/// Mock email provider for testing that doesn't actually send emails.
+/// Tracks sent emails for verification in tests if needed.
///
public class MockEmailProvider : IEmailProvider
{
@@ -24,7 +24,7 @@ public class MockEmailProvider : IEmailProvider
Subject = subject,
Body = body,
IsHtml = isHtml,
- SentAt = DateTime.UtcNow,
+ SentAt = DateTime.UtcNow
}
);
@@ -45,7 +45,7 @@ public class MockEmailProvider : IEmailProvider
Subject = subject,
Body = body,
IsHtml = isHtml,
- SentAt = DateTime.UtcNow,
+ SentAt = DateTime.UtcNow
}
);
@@ -65,4 +65,4 @@ public class MockEmailProvider : IEmailProvider
public bool IsHtml { get; init; }
public DateTime SentAt { get; init; }
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Steps/ApiGeneralSteps.cs b/web/backend/API/API.Specs/Steps/ApiGeneralSteps.cs
index 44273f4..65d5940 100644
--- a/web/backend/API/API.Specs/Steps/ApiGeneralSteps.cs
+++ b/web/backend/API/API.Specs/Steps/ApiGeneralSteps.cs
@@ -1,5 +1,5 @@
+using System.Text;
using System.Text.Json;
-using API.Specs;
using FluentAssertions;
using Reqnroll;
@@ -15,14 +15,11 @@ public class ApiGeneralSteps(ScenarioContext scenario)
private HttpClient GetClient()
{
- if (scenario.TryGetValue(ClientKey, out var client))
- {
- return client;
- }
+ if (scenario.TryGetValue(ClientKey, out HttpClient? client)) return client;
- var factory = scenario.TryGetValue(
+ TestApiFactory? factory = scenario.TryGetValue(
FactoryKey,
- out var f
+ out TestApiFactory? f
)
? f
: new TestApiFactory();
@@ -46,19 +43,19 @@ public class ApiGeneralSteps(ScenarioContext scenario)
string jsonBody
)
{
- var client = GetClient();
+ HttpClient client = GetClient();
- var requestMessage = new HttpRequestMessage(new HttpMethod(method), url)
+ HttpRequestMessage requestMessage = new(new HttpMethod(method), url)
{
Content = new StringContent(
jsonBody,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -70,13 +67,13 @@ public class ApiGeneralSteps(ScenarioContext scenario)
string url
)
{
- var client = GetClient();
- var requestMessage = new HttpRequestMessage(
+ HttpClient client = GetClient();
+ HttpRequestMessage requestMessage = new(
new HttpMethod(method),
url
);
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -86,7 +83,7 @@ public class ApiGeneralSteps(ScenarioContext scenario)
public void ThenTheResponseStatusCodeShouldBeInt(int expected)
{
scenario
- .TryGetValue(ResponseKey, out var response)
+ .TryGetValue(ResponseKey, out HttpResponseMessage? response)
.Should()
.BeTrue();
((int)response!.StatusCode).Should().Be(expected);
@@ -96,7 +93,7 @@ public class ApiGeneralSteps(ScenarioContext scenario)
public void ThenTheResponseHasHttpStatusInt(int expectedCode)
{
scenario
- .TryGetValue(ResponseKey, out var response)
+ .TryGetValue(ResponseKey, out HttpResponseMessage? response)
.Should()
.BeTrue("No response was received from the API");
((int)response!.StatusCode).Should().Be(expectedCode);
@@ -109,20 +106,20 @@ public class ApiGeneralSteps(ScenarioContext scenario)
)
{
scenario
- .TryGetValue(ResponseKey, out var response)
+ .TryGetValue(ResponseKey, out HttpResponseMessage? response)
.Should()
.BeTrue();
scenario
- .TryGetValue(ResponseBodyKey, out var responseBody)
+ .TryGetValue(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
- using var doc = JsonDocument.Parse(responseBody!);
- var root = doc.RootElement;
+ using JsonDocument doc = JsonDocument.Parse(responseBody!);
+ JsonElement root = doc.RootElement;
- if (!root.TryGetProperty(field, out var value))
+ if (!root.TryGetProperty(field, out JsonElement value))
{
- root.TryGetProperty("payload", out var payloadElem)
+ root.TryGetProperty("payload", out JsonElement payloadElem)
.Should()
.BeTrue(
"Expected field '{0}' to be present either at the root or inside 'payload'",
@@ -157,20 +154,20 @@ public class ApiGeneralSteps(ScenarioContext scenario)
)
{
scenario
- .TryGetValue(ResponseKey, out var response)
+ .TryGetValue(ResponseKey, out HttpResponseMessage? response)
.Should()
.BeTrue();
scenario
- .TryGetValue(ResponseBodyKey, out var responseBody)
+ .TryGetValue(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
- using var doc = JsonDocument.Parse(responseBody!);
- var root = doc.RootElement;
+ using JsonDocument doc = JsonDocument.Parse(responseBody!);
+ JsonElement root = doc.RootElement;
- if (!root.TryGetProperty(field, out var value))
+ if (!root.TryGetProperty(field, out JsonElement value))
{
- root.TryGetProperty("payload", out var payloadElem)
+ root.TryGetProperty("payload", out JsonElement payloadElem)
.Should()
.BeTrue(
"Expected field '{0}' to be present either at the root or inside 'payload'",
@@ -195,7 +192,7 @@ public class ApiGeneralSteps(ScenarioContext scenario)
"Expected field '{0}' to be a string",
field
);
- var actualValue = value.GetString();
+ string? actualValue = value.GetString();
actualValue
.Should()
.Contain(
@@ -206,4 +203,4 @@ public class ApiGeneralSteps(ScenarioContext scenario)
actualValue
);
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/Steps/AuthSteps.cs b/web/backend/API/API.Specs/Steps/AuthSteps.cs
index e80dcb1..489d42f 100644
--- a/web/backend/API/API.Specs/Steps/AuthSteps.cs
+++ b/web/backend/API/API.Specs/Steps/AuthSteps.cs
@@ -1,5 +1,5 @@
+using System.Text;
using System.Text.Json;
-using API.Specs;
using FluentAssertions;
using Infrastructure.Jwt;
using Reqnroll;
@@ -21,14 +21,11 @@ public class AuthSteps(ScenarioContext scenario)
private HttpClient GetClient()
{
- if (scenario.TryGetValue(ClientKey, out var client))
- {
- return client;
- }
+ if (scenario.TryGetValue(ClientKey, out HttpClient? client)) return client;
- var factory = scenario.TryGetValue(
+ TestApiFactory? factory = scenario.TryGetValue(
FactoryKey,
- out var f
+ out TestApiFactory? f
)
? f
: new TestApiFactory();
@@ -42,9 +39,9 @@ public class AuthSteps(ScenarioContext scenario)
private static string GetRequiredEnvVar(string name)
{
return Environment.GetEnvironmentVariable(name)
- ?? throw new InvalidOperationException(
- $"{name} environment variable is not set"
- );
+ ?? throw new InvalidOperationException(
+ $"{name} environment variable is not set"
+ );
}
private static string GenerateJwtToken(
@@ -54,7 +51,7 @@ public class AuthSteps(ScenarioContext scenario)
DateTime expiry
)
{
- var infra = new JwtInfrastructure();
+ JwtInfrastructure infra = new();
return infra.GenerateJwt(userId, username, expiry, secret);
}
@@ -69,12 +66,12 @@ public class AuthSteps(ScenarioContext scenario)
private static string ParseRegisteredUsername(JsonElement root)
{
return root
- .GetProperty("payload")
- .GetProperty("username")
- .GetString()
- ?? throw new InvalidOperationException(
- "username missing from registration payload"
- );
+ .GetProperty("payload")
+ .GetProperty("username")
+ .GetString()
+ ?? throw new InvalidOperationException(
+ "username missing from registration payload"
+ );
}
private static string ParseTokenFromPayload(
@@ -84,15 +81,13 @@ public class AuthSteps(ScenarioContext scenario)
)
{
if (
- payload.TryGetProperty(camelCaseName, out var tokenElem)
+ payload.TryGetProperty(camelCaseName, out JsonElement tokenElem)
|| payload.TryGetProperty(pascalCaseName, out tokenElem)
)
- {
return tokenElem.GetString()
- ?? throw new InvalidOperationException(
- $"{camelCaseName} is null"
- );
- }
+ ?? throw new InvalidOperationException(
+ $"{camelCaseName} is null"
+ );
throw new InvalidOperationException(
$"Could not find token field '{camelCaseName}' in payload"
@@ -114,30 +109,30 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a login request with a username and password")]
public async Task WhenISubmitALoginRequestWithAUsernameAndPassword()
{
- var client = GetClient();
- var (username, password) = scenario.TryGetValue<(
+ HttpClient client = GetClient();
+ (string username, string password) = scenario.TryGetValue<(
string username,
string password
- )>(TestUserKey, out var user)
+ )>(TestUserKey, out (string username, string password) user)
? user
: ("test.user", "password");
- var body = JsonSerializer.Serialize(new { username, password });
+ string body = JsonSerializer.Serialize(new { username, password });
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -146,23 +141,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a login request with a missing username")]
public async Task WhenISubmitALoginRequestWithAMissingUsername()
{
- var client = GetClient();
- var body = JsonSerializer.Serialize(new { password = "test" });
+ HttpClient client = GetClient();
+ string body = JsonSerializer.Serialize(new { password = "test" });
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -171,23 +166,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a login request with a missing password")]
public async Task WhenISubmitALoginRequestWithAMissingPassword()
{
- var client = GetClient();
- var body = JsonSerializer.Serialize(new { username = "test" });
+ HttpClient client = GetClient();
+ string body = JsonSerializer.Serialize(new { username = "test" });
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -196,21 +191,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a login request with both username and password missing")]
public async Task WhenISubmitALoginRequestWithBothUsernameAndPasswordMissing()
{
- var client = GetClient();
- var requestMessage = new HttpRequestMessage(
+ HttpClient client = GetClient();
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
"{}",
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -220,28 +215,26 @@ public class AuthSteps(ScenarioContext scenario)
public void ThenTheResponseJsonShouldHaveAnAccessToken()
{
scenario
- .TryGetValue(ResponseKey, out var response)
+ .TryGetValue(ResponseKey, out HttpResponseMessage? response)
.Should()
.BeTrue();
scenario
- .TryGetValue(ResponseBodyKey, out var responseBody)
+ .TryGetValue(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
- var doc = JsonDocument.Parse(responseBody!);
- var root = doc.RootElement;
+ JsonDocument doc = JsonDocument.Parse(responseBody!);
+ JsonElement root = doc.RootElement;
JsonElement tokenElem = default;
- var hasToken = false;
+ bool hasToken = false;
if (
- root.TryGetProperty("payload", out var payloadElem)
+ root.TryGetProperty("payload", out JsonElement payloadElem)
&& payloadElem.ValueKind == JsonValueKind.Object
)
- {
hasToken =
payloadElem.TryGetProperty("accessToken", out tokenElem)
|| payloadElem.TryGetProperty("AccessToken", out tokenElem);
- }
hasToken
.Should()
@@ -249,29 +242,29 @@ public class AuthSteps(ScenarioContext scenario)
"Expected an access token either at the root or inside 'payload'"
);
- var token = tokenElem.GetString();
+ string? token = tokenElem.GetString();
token.Should().NotBeNullOrEmpty();
}
[When("I submit a login request using a GET request")]
public async Task WhenISubmitALoginRequestUsingAgetRequest()
{
- var client = GetClient();
+ HttpClient client = GetClient();
// testing GET
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/auth/login"
)
{
Content = new StringContent(
"{}",
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -280,33 +273,27 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a registration request with values:")]
public async Task WhenISubmitARegistrationRequestWithValues(Table table)
{
- var client = GetClient();
- var row = table.Rows[0];
+ HttpClient client = GetClient();
+ DataTableRow? row = table.Rows[0];
- var username = row["Username"] ?? "";
- var firstName = row["FirstName"] ?? "";
- var lastName = row["LastName"] ?? "";
- var email = row["Email"] ?? "";
- var dateOfBirth = row["DateOfBirth"] ?? "";
+ string username = row["Username"] ?? "";
+ string firstName = row["FirstName"] ?? "";
+ string lastName = row["LastName"] ?? "";
+ string email = row["Email"] ?? "";
+ string dateOfBirth = row["DateOfBirth"] ?? "";
- if (dateOfBirth == "{underage_date}")
- {
- dateOfBirth = DateTime.UtcNow.AddYears(-18).ToString("yyyy-MM-dd");
- }
+ if (dateOfBirth == "{underage_date}") dateOfBirth = DateTime.UtcNow.AddYears(-18).ToString("yyyy-MM-dd");
// Keep default registration fixture values unique across repeated runs.
if (email == "newuser@example.com")
{
- var suffix = Guid.NewGuid().ToString("N")[..8];
+ string suffix = Guid.NewGuid().ToString("N")[..8];
email = $"newuser-{suffix}@example.com";
- if (username == "newuser")
- {
- username = $"newuser-{suffix}";
- }
+ if (username == "newuser") username = $"newuser-{suffix}";
}
- var password = row["Password"];
+ string? password = row["Password"];
var registrationData = new
{
@@ -315,25 +302,25 @@ public class AuthSteps(ScenarioContext scenario)
lastName,
email,
dateOfBirth,
- password,
+ password
};
- var body = JsonSerializer.Serialize(registrationData);
+ string body = JsonSerializer.Serialize(registrationData);
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/register"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -342,21 +329,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a registration request using a GET request")]
public async Task WhenISubmitARegistrationRequestUsingAGetRequest()
{
- var client = GetClient();
- var requestMessage = new HttpRequestMessage(
+ HttpClient client = GetClient();
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/auth/register"
)
{
Content = new StringContent(
"{}",
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
@@ -365,8 +352,8 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have registered a new account")]
public async Task GivenIHaveRegisteredANewAccount()
{
- var client = GetClient();
- var suffix = Guid.NewGuid().ToString("N")[..8];
+ HttpClient client = GetClient();
+ string suffix = Guid.NewGuid().ToString("N")[..8];
var registrationData = new
{
username = $"newuser-{suffix}",
@@ -374,29 +361,29 @@ public class AuthSteps(ScenarioContext scenario)
lastName = "User",
email = $"newuser-{suffix}@example.com",
dateOfBirth = "1990-01-01",
- password = "Password1!",
+ password = "Password1!"
};
- var body = JsonSerializer.Serialize(registrationData);
- var requestMessage = new HttpRequestMessage(
+ string body = JsonSerializer.Serialize(registrationData);
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/register"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
- using var doc = JsonDocument.Parse(responseBody);
- var root = doc.RootElement;
+ using JsonDocument doc = JsonDocument.Parse(responseBody);
+ JsonElement root = doc.RootElement;
scenario[RegisteredUserIdKey] = ParseRegisteredUserId(root);
scenario[RegisteredUsernameKey] = ParseRegisteredUsername(root);
}
@@ -404,43 +391,39 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I am logged in")]
public async Task GivenIAmLoggedIn()
{
- var client = GetClient();
+ HttpClient client = GetClient();
var loginData = new { username = "test.user", password = "password" };
- var body = JsonSerializer.Serialize(loginData);
+ string body = JsonSerializer.Serialize(loginData);
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
- var doc = JsonDocument.Parse(responseBody);
- var root = doc.RootElement;
- if (root.TryGetProperty("payload", out var payloadElem))
+ JsonDocument doc = JsonDocument.Parse(responseBody);
+ JsonElement root = doc.RootElement;
+ if (root.TryGetProperty("payload", out JsonElement payloadElem))
{
if (
- payloadElem.TryGetProperty("accessToken", out var tokenElem)
+ payloadElem.TryGetProperty("accessToken", out JsonElement tokenElem)
|| payloadElem.TryGetProperty("AccessToken", out tokenElem)
)
- {
scenario["accessToken"] = tokenElem.GetString();
- }
if (
- payloadElem.TryGetProperty("refreshToken", out var refreshElem)
+ payloadElem.TryGetProperty("refreshToken", out JsonElement refreshElem)
|| payloadElem.TryGetProperty("RefreshToken", out refreshElem)
)
- {
scenario["refreshToken"] = refreshElem.GetString();
- }
}
}
@@ -460,21 +443,21 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have a valid access token for my account")]
public void GivenIHaveAValidAccessTokenForMyAccount()
{
- var userId = scenario.TryGetValue(RegisteredUserIdKey, out var id)
+ Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
- var username = scenario.TryGetValue(
+ string? username = scenario.TryGetValue(
RegisteredUsernameKey,
- out var user
+ out string? user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
- var secret = GetRequiredEnvVar("ACCESS_TOKEN_SECRET");
+ string secret = GetRequiredEnvVar("ACCESS_TOKEN_SECRET");
scenario["accessToken"] = GenerateJwtToken(
userId,
username,
@@ -486,21 +469,21 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have a valid confirmation token for my account")]
public void GivenIHaveAValidConfirmationTokenForMyAccount()
{
- var userId = scenario.TryGetValue(RegisteredUserIdKey, out var id)
+ Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
- var username = scenario.TryGetValue(
+ string? username = scenario.TryGetValue(
RegisteredUsernameKey,
- out var user
+ out string? user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
- var secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
+ string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,
@@ -512,21 +495,21 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have an expired confirmation token for my account")]
public void GivenIHaveAnExpiredConfirmationTokenForMyAccount()
{
- var userId = scenario.TryGetValue(RegisteredUserIdKey, out var id)
+ Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
- var username = scenario.TryGetValue(
+ string? username = scenario.TryGetValue(
RegisteredUsernameKey,
- out var user
+ out string? user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
- var secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
+ string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,
@@ -538,14 +521,14 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have a confirmation token signed with the wrong secret")]
public void GivenIHaveAConfirmationTokenSignedWithTheWrongSecret()
{
- var userId = scenario.TryGetValue(RegisteredUserIdKey, out var id)
+ Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
- var username = scenario.TryGetValue(
+ string? username = scenario.TryGetValue(
RegisteredUsernameKey,
- out var user
+ out string? user
)
? user
: throw new InvalidOperationException(
@@ -567,21 +550,21 @@ public class AuthSteps(ScenarioContext scenario)
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithAValidAccessToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("accessToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("accessToken", out string? t)
? t
: "invalid-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
)
{
- Headers = { { "Authorization", $"Bearer {token}" } },
+ Headers = { { "Authorization", $"Bearer {token}" } }
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -591,17 +574,17 @@ public class AuthSteps(ScenarioContext scenario)
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithAnInvalidAccessToken()
{
- var client = GetClient();
- var requestMessage = new HttpRequestMessage(
+ HttpClient client = GetClient();
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
)
{
- Headers = { { "Authorization", "Bearer invalid-token-format" } },
+ Headers = { { "Authorization", "Bearer invalid-token-format" } }
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -609,23 +592,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with the valid token")]
public async Task WhenISubmitAConfirmationRequestWithTheValidToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "valid-token";
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -633,23 +616,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit the same confirmation request again")]
public async Task WhenISubmitTheSameConfirmationRequestAgain()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "valid-token";
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -657,21 +640,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with a malformed token")]
public async Task WhenISubmitAConfirmationRequestWithAMalformedToken()
{
- var client = GetClient();
+ HttpClient client = GetClient();
const string token = "malformed-token-not-jwt";
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -679,35 +662,31 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a refresh token request with a valid refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithTheValidRefreshToken()
{
- var client = GetClient();
- if (scenario.TryGetValue("accessToken", out var oldAccessToken))
- {
+ HttpClient client = GetClient();
+ if (scenario.TryGetValue("accessToken", out string? oldAccessToken))
scenario[PreviousAccessTokenKey] = oldAccessToken;
- }
- if (scenario.TryGetValue("refreshToken", out var oldRefreshToken))
- {
+ if (scenario.TryGetValue("refreshToken", out string? oldRefreshToken))
scenario[PreviousRefreshTokenKey] = oldRefreshToken;
- }
- var token = scenario.TryGetValue("refreshToken", out var t)
+ string? token = scenario.TryGetValue("refreshToken", out string? t)
? t
: "valid-refresh-token";
- var body = JsonSerializer.Serialize(new { refreshToken = token });
+ string body = JsonSerializer.Serialize(new { refreshToken = token });
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -715,25 +694,25 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a refresh token request with an invalid refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithAnInvalidRefreshToken()
{
- var client = GetClient();
- var body = JsonSerializer.Serialize(
+ HttpClient client = GetClient();
+ string body = JsonSerializer.Serialize(
new { refreshToken = "invalid-refresh-token" }
);
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -741,26 +720,26 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a refresh token request with the expired refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithTheExpiredRefreshToken()
{
- var client = GetClient();
+ HttpClient client = GetClient();
// Use an expired token
- var body = JsonSerializer.Serialize(
+ string body = JsonSerializer.Serialize(
new { refreshToken = "expired-refresh-token" }
);
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -768,23 +747,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a refresh token request with a missing refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithAMissingRefreshToken()
{
- var client = GetClient();
- var body = JsonSerializer.Serialize(new { });
+ HttpClient client = GetClient();
+ string body = JsonSerializer.Serialize(new { });
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -792,21 +771,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a refresh token request using a GET request")]
public async Task WhenISubmitARefreshTokenRequestUsingAGETRequest()
{
- var client = GetClient();
- var requestMessage = new HttpRequestMessage(
+ HttpClient client = GetClient();
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/auth/refresh"
)
{
Content = new StringContent(
"{}",
- System.Text.Encoding.UTF8,
+ Encoding.UTF8,
"application/json"
- ),
+ )
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -815,14 +794,14 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a request to a protected endpoint without an access token")]
public async Task WhenISubmitARequestToAProtectedEndpointWithoutAnAccessToken()
{
- var client = GetClient();
- var requestMessage = new HttpRequestMessage(
+ HttpClient client = GetClient();
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
);
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -846,21 +825,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a request to a protected endpoint with the expired token")]
public async Task WhenISubmitARequestToAProtectedEndpointWithTheExpiredToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("accessToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("accessToken", out string? t)
? t
: "expired-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
)
{
- Headers = { { "Authorization", $"Bearer {token}" } },
+ Headers = { { "Authorization", $"Bearer {token}" } }
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -868,21 +847,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a request to a protected endpoint with the tampered token")]
public async Task WhenISubmitARequestToAProtectedEndpointWithTheTamperedToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("accessToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("accessToken", out string? t)
? t
: "tampered-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
)
{
- Headers = { { "Authorization", $"Bearer {token}" } },
+ Headers = { { "Authorization", $"Bearer {token}" } }
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -892,21 +871,21 @@ public class AuthSteps(ScenarioContext scenario)
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithMyRefreshTokenInsteadOfAccessToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("refreshToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("refreshToken", out string? t)
? t
: "refresh-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
)
{
- Headers = { { "Authorization", $"Bearer {token}" } },
+ Headers = { { "Authorization", $"Bearer {token}" } }
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -920,23 +899,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with the expired token")]
public async Task WhenISubmitAConfirmationRequestWithTheExpiredToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "expired-confirmation-token";
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -944,23 +923,23 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with the tampered token")]
public async Task WhenISubmitAConfirmationRequestWithTheTamperedToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "tampered-confirmation-token";
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -968,17 +947,17 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with a missing token")]
public async Task WhenISubmitAConfirmationRequestWithAMissingToken()
{
- var client = GetClient();
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ HttpClient client = GetClient();
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/auth/confirm");
+ HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/confirm");
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -986,18 +965,18 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request using an invalid HTTP method")]
public async Task WhenISubmitAConfirmationRequestUsingAnInvalidHttpMethod()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "valid-confirmation-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -1007,21 +986,21 @@ public class AuthSteps(ScenarioContext scenario)
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithMyConfirmationTokenInsteadOfAccessToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "confirmation-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Get,
"/api/protected"
)
{
- Headers = { { "Authorization", $"Bearer {token}" } },
+ Headers = { { "Authorization", $"Bearer {token}" } }
};
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -1029,21 +1008,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with an invalid token")]
public async Task WhenISubmitAConfirmationRequestWithAnInvalidToken()
{
- var client = GetClient();
+ HttpClient client = GetClient();
const string token = "invalid-confirmation-token";
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -1051,18 +1030,18 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a confirmation request with the valid token without an access token")]
public async Task WhenISubmitAConfirmationRequestWithTheValidTokenWithoutAnAccessToken()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: "valid-token";
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -1071,13 +1050,13 @@ public class AuthSteps(ScenarioContext scenario)
public void ThenTheResponseJsonShouldHaveANewAccessToken()
{
scenario
- .TryGetValue(ResponseBodyKey, out var responseBody)
+ .TryGetValue(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
- using var doc = JsonDocument.Parse(responseBody!);
- var payload = doc.RootElement.GetProperty("payload");
- var accessToken = ParseTokenFromPayload(
+ using JsonDocument doc = JsonDocument.Parse(responseBody!);
+ JsonElement payload = doc.RootElement.GetProperty("payload");
+ string accessToken = ParseTokenFromPayload(
payload,
"accessToken",
"AccessToken"
@@ -1088,25 +1067,23 @@ public class AuthSteps(ScenarioContext scenario)
if (
scenario.TryGetValue(
PreviousAccessTokenKey,
- out var previousAccessToken
+ out string? previousAccessToken
)
)
- {
accessToken.Should().NotBe(previousAccessToken);
- }
}
[Then("the response JSON should have a new refresh token")]
public void ThenTheResponseJsonShouldHaveANewRefreshToken()
{
scenario
- .TryGetValue(ResponseBodyKey, out var responseBody)
+ .TryGetValue(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
- using var doc = JsonDocument.Parse(responseBody!);
- var payload = doc.RootElement.GetProperty("payload");
- var refreshToken = ParseTokenFromPayload(
+ using JsonDocument doc = JsonDocument.Parse(responseBody!);
+ JsonElement payload = doc.RootElement.GetProperty("payload");
+ string refreshToken = ParseTokenFromPayload(
payload,
"refreshToken",
"RefreshToken"
@@ -1117,56 +1094,54 @@ public class AuthSteps(ScenarioContext scenario)
if (
scenario.TryGetValue(
PreviousRefreshTokenKey,
- out var previousRefreshToken
+ out string? previousRefreshToken
)
)
- {
refreshToken.Should().NotBe(previousRefreshToken);
- }
}
[Given("I have confirmed my account")]
public async Task GivenIHaveConfirmedMyAccount()
{
- var client = GetClient();
- var token = scenario.TryGetValue("confirmationToken", out var t)
+ HttpClient client = GetClient();
+ string? token = scenario.TryGetValue("confirmationToken", out string? t)
? t
: throw new InvalidOperationException("confirmation token not found");
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
}
[When("I submit a resend confirmation request for my account")]
public async Task WhenISubmitAResendConfirmationRequestForMyAccount()
{
- var client = GetClient();
- var userId = scenario.TryGetValue(RegisteredUserIdKey, out var id)
+ HttpClient client = GetClient();
+ Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException("registered user ID not found");
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm/resend?userId={userId}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -1174,21 +1149,21 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a resend confirmation request for a non-existent user")]
public async Task WhenISubmitAResendConfirmationRequestForANonExistentUser()
{
- var client = GetClient();
- var fakeUserId = Guid.NewGuid();
- var accessToken = scenario.TryGetValue("accessToken", out var at)
+ HttpClient client = GetClient();
+ Guid fakeUserId = Guid.NewGuid();
+ string? accessToken = scenario.TryGetValue("accessToken", out string? at)
? at
: string.Empty;
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm/resend?userId={fakeUserId}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
@@ -1196,19 +1171,19 @@ public class AuthSteps(ScenarioContext scenario)
[When("I submit a resend confirmation request without an access token")]
public async Task WhenISubmitAResendConfirmationRequestWithoutAnAccessToken()
{
- var client = GetClient();
- var userId = scenario.TryGetValue(RegisteredUserIdKey, out var id)
+ HttpClient client = GetClient();
+ Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: Guid.NewGuid();
- var requestMessage = new HttpRequestMessage(
+ HttpRequestMessage requestMessage = new(
HttpMethod.Post,
$"/api/auth/confirm/resend?userId={userId}"
);
- var response = await client.SendAsync(requestMessage);
- var responseBody = await response.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await client.SendAsync(requestMessage);
+ string responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/API/API.Specs/TestApiFactory.cs b/web/backend/API/API.Specs/TestApiFactory.cs
index 2b2046d..b01dc65 100644
--- a/web/backend/API/API.Specs/TestApiFactory.cs
+++ b/web/backend/API/API.Specs/TestApiFactory.cs
@@ -1,46 +1,37 @@
-using System.Collections.Generic;
using API.Specs.Mocks;
using Features.Emails.Services;
using Infrastructure.Email;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-namespace API.Specs
+namespace API.Specs;
+
+public class TestApiFactory : WebApplicationFactory
{
- public class TestApiFactory : WebApplicationFactory
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
- protected override void ConfigureWebHost(IWebHostBuilder builder)
+ builder.UseEnvironment("Testing");
+
+ builder.ConfigureServices(services =>
{
- builder.UseEnvironment("Testing");
+ // Replace the real email provider with mock for testing
+ ServiceDescriptor? emailProviderDescriptor = services.SingleOrDefault(d =>
+ d.ServiceType == typeof(IEmailProvider)
+ );
- builder.ConfigureServices(services =>
- {
- // Replace the real email provider with mock for testing
- var emailProviderDescriptor = services.SingleOrDefault(d =>
- d.ServiceType == typeof(IEmailProvider)
- );
+ if (emailProviderDescriptor != null) services.Remove(emailProviderDescriptor);
- if (emailProviderDescriptor != null)
- {
- services.Remove(emailProviderDescriptor);
- }
+ services.AddScoped();
- services.AddScoped();
+ // Replace the real email dispatcher with mock for testing
+ ServiceDescriptor? emailDispatcherDescriptor = services.SingleOrDefault(d =>
+ d.ServiceType == typeof(IEmailDispatcher)
+ );
- // Replace the real email dispatcher with mock for testing
- var emailDispatcherDescriptor = services.SingleOrDefault(d =>
- d.ServiceType == typeof(IEmailDispatcher)
- );
+ if (emailDispatcherDescriptor != null) services.Remove(emailDispatcherDescriptor);
- if (emailDispatcherDescriptor != null)
- {
- services.Remove(emailDispatcherDescriptor);
- }
-
- services.AddScoped();
- });
- }
+ services.AddScoped();
+ });
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Core.slnx b/web/backend/Core.slnx
index c3a2f2d..7c93c7a 100644
--- a/web/backend/Core.slnx
+++ b/web/backend/Core.slnx
@@ -1,37 +1,37 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+ Path="Infrastructure/Infrastructure.Email.Templates/Infrastructure.Email.Templates.csproj"/>
+
-
+ Path="Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj"/>
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
diff --git a/web/backend/Database/Database.Migrations/Database.Migrations.csproj b/web/backend/Database/Database.Migrations/Database.Migrations.csproj
index ab0b5d7..fced2a1 100644
--- a/web/backend/Database/Database.Migrations/Database.Migrations.csproj
+++ b/web/backend/Database/Database.Migrations/Database.Migrations.csproj
@@ -1,23 +1,23 @@
-
- Exe
- net10.0
- enable
- enable
- Database.Migrations
- Linux
-
+
+ Exe
+ net10.0
+ enable
+ enable
+ Database.Migrations
+ Linux
+
-
-
-
-
-
-
-
-
-
- .dockerignore
-
-
+
+
+
+
+
+
+
+
+
+ .dockerignore
+
+
diff --git a/web/backend/Database/Database.Migrations/Program.cs b/web/backend/Database/Database.Migrations/Program.cs
index ed48d08..b4afe9c 100644
--- a/web/backend/Database/Database.Migrations/Program.cs
+++ b/web/backend/Database/Database.Migrations/Program.cs
@@ -1,50 +1,57 @@
using System.Data;
using System.Reflection;
using DbUp;
+using DbUp.Engine;
using Microsoft.Data.SqlClient;
namespace Database.Migrations;
///
-/// Entry point for the database migration runner. Reads connection details from
-/// environment variables, optionally clears and recreates the target database, and
-/// applies all SQL migration scripts embedded in this assembly using DbUp.
+/// Entry point for the database migration runner. Reads connection details from
+/// environment variables, optionally clears and recreates the target database, and
+/// applies all SQL migration scripts embedded in this assembly using DbUp.
///
public static class Program
{
+ /// The connection string for the target application database (DB_NAME ).
+ private static readonly string connectionString = BuildConnectionString();
+
+ /// The connection string for the master database, used for create/drop operations.
+ private static readonly string masterConnectionString = BuildConnectionString("master");
+
///
- /// Builds a SQL Server connection string from the DB_SERVER , DB_NAME ,
- /// DB_USER , DB_PASSWORD , and DB_TRUST_SERVER_CERTIFICATE
- /// environment variables.
+ /// Builds a SQL Server connection string from the DB_SERVER , DB_NAME ,
+ /// DB_USER , DB_PASSWORD , and DB_TRUST_SERVER_CERTIFICATE
+ /// environment variables.
///
///
- /// The database (initial catalog) to connect to. When null , falls back to the
- /// DB_NAME environment variable.
+ /// The database (initial catalog) to connect to. When null , falls back to the
+ /// DB_NAME environment variable.
///
/// A fully built SQL Server connection string.
///
- /// Thrown when DB_SERVER , DB_USER , DB_PASSWORD , or (when
- /// is null ) DB_NAME is not set.
+ /// Thrown when DB_SERVER , DB_USER , DB_PASSWORD , or (when
+ /// is null ) DB_NAME is not set.
///
private static string BuildConnectionString(string? databaseName = null)
{
- var server = Environment.GetEnvironmentVariable("DB_SERVER")
- ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
+ string server = Environment.GetEnvironmentVariable("DB_SERVER")
+ ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
- var dbName = databaseName
- ?? Environment.GetEnvironmentVariable("DB_NAME")
- ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
+ string dbName = databaseName
+ ?? Environment.GetEnvironmentVariable("DB_NAME")
+ ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
- var user = Environment.GetEnvironmentVariable("DB_USER")
- ?? throw new InvalidOperationException("DB_USER environment variable is not set");
+ string user = Environment.GetEnvironmentVariable("DB_USER")
+ ?? throw new InvalidOperationException("DB_USER environment variable is not set");
- var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
- ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
+ string password = Environment.GetEnvironmentVariable("DB_PASSWORD")
+ ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
- var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
- ?? "True";
+ string trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
+ ?? "True";
- var builder = new SqlConnectionStringBuilder
+ SqlConnectionStringBuilder builder = new()
{
DataSource = server,
InitialCatalog = dbName,
@@ -57,47 +64,41 @@ public static class Program
return builder.ConnectionString;
}
- /// The connection string for the target application database (DB_NAME ).
- private static readonly string connectionString = BuildConnectionString();
-
- /// The connection string for the master database, used for create/drop operations.
- private static readonly string masterConnectionString = BuildConnectionString("master");
-
///
- /// Applies all pending SQL migration scripts embedded in this assembly to the target
- /// database using DbUp, logging progress to the console.
+ /// Applies all pending SQL migration scripts embedded in this assembly to the target
+ /// database using DbUp, logging progress to the console.
///
/// true if the upgrade completed successfully; otherwise false .
private static bool DeployMigrations()
{
- var upgrader = DeployChanges
+ UpgradeEngine? upgrader = DeployChanges
.To.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build();
- var result = upgrader.PerformUpgrade();
+ DatabaseUpgradeResult? result = upgrader.PerformUpgrade();
return result.Successful;
}
///
- /// Drops the Biergarten database if it exists, first forcing it into single-user
- /// mode with rollback to terminate any existing connections.
+ /// Drops the Biergarten database if it exists, first forcing it into single-user
+ /// mode with rollback to terminate any existing connections.
///
///
- /// true if the database was dropped (or did not exist) without error; false
- /// if an error occurred while connecting or dropping the database.
+ /// true if the database was dropped (or did not exist) without error; false
+ /// if an error occurred while connecting or dropping the database.
///
private static bool ClearDatabase()
{
- var myConn = new SqlConnection(masterConnectionString);
+ SqlConnection myConn = new(masterConnectionString);
try
{
myConn.Open();
// First, set the database to single user mode to close all connections
- var setModeCommand = new SqlCommand(
+ SqlCommand setModeCommand = new(
"IF EXISTS (SELECT 1 FROM sys.databases WHERE name = 'Biergarten') " +
"ALTER DATABASE [Biergarten] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;",
myConn);
@@ -106,79 +107,75 @@ public static class Program
setModeCommand.ExecuteNonQuery();
Console.WriteLine("Database set to single user mode.");
}
- catch (System.Exception ex)
+ catch (Exception ex)
{
Console.WriteLine($"Warning: Could not set single user mode: {ex.Message}");
}
// Then drop the database
- var dropCommand = new SqlCommand("DROP DATABASE IF EXISTS [Biergarten];", myConn);
+ SqlCommand dropCommand = new("DROP DATABASE IF EXISTS [Biergarten];", myConn);
try
{
dropCommand.ExecuteNonQuery();
Console.WriteLine("Database cleared successfully.");
}
- catch (System.Exception ex)
+ catch (Exception ex)
{
Console.WriteLine($"Error dropping database: {ex}");
return false;
}
}
- catch (System.Exception ex)
+ catch (Exception ex)
{
Console.WriteLine($"Error clearing database: {ex}");
return false;
}
finally
{
- if (myConn.State == ConnectionState.Open)
- {
- myConn.Close();
- }
+ if (myConn.State == ConnectionState.Open) myConn.Close();
}
+
return true;
}
///
- /// Creates the Biergarten database on the master connection if it does not
- /// already exist. Errors encountered while creating the database are logged but do
- /// not stop execution.
+ /// Creates the Biergarten database on the master connection if it does not
+ /// already exist. Errors encountered while creating the database are logged but do
+ /// not stop execution.
///
/// true always; this method does not propagate database errors as a failure result.
private static bool CreateDatabaseIfNotExists()
{
- var myConn = new SqlConnection(masterConnectionString);
+ SqlConnection myConn = new(masterConnectionString);
const string str = """
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE name = 'Biergarten')
CREATE DATABASE [Biergarten]
""";
- var myCommand = new SqlCommand(str, myConn);
+ SqlCommand myCommand = new(str, myConn);
try
{
myConn.Open();
myCommand.ExecuteNonQuery();
Console.WriteLine("Database creation command executed successfully.");
}
- catch (System.Exception ex)
+ catch (Exception ex)
{
Console.WriteLine($"Error creating database: {ex}");
}
finally
{
- if (myConn.State == ConnectionState.Open)
- {
- myConn.Close();
- }
+ if (myConn.State == ConnectionState.Open) myConn.Close();
}
+
return true;
}
///
- /// Migration runner entry point. Optionally clears the existing database when the
- /// CLEAR_DATABASE environment variable is set to "true" , ensures the
- /// database exists, then deploys all pending migrations.
+ /// Migration runner entry point. Optionally clears the existing database when the
+ /// CLEAR_DATABASE environment variable is set to "true" , ensures the
+ /// database exists, then deploys all pending migrations.
///
/// Command-line arguments (unused).
/// 0 if migrations completed successfully; 1 if they failed or an error occurred.
@@ -188,7 +185,7 @@ public static class Program
try
{
- var clearDatabase = Environment.GetEnvironmentVariable("CLEAR_DATABASE");
+ string? clearDatabase = Environment.GetEnvironmentVariable("CLEAR_DATABASE");
if (clearDatabase == "true")
{
Console.WriteLine("CLEAR_DATABASE is enabled. Clearing existing database...");
@@ -196,18 +193,16 @@ public static class Program
}
CreateDatabaseIfNotExists();
- var success = DeployMigrations();
+ bool success = DeployMigrations();
if (success)
{
Console.WriteLine("Database migrations completed successfully.");
return 0;
}
- else
- {
- Console.WriteLine("Database migrations failed.");
- return 1;
- }
+
+ Console.WriteLine("Database migrations failed.");
+ return 1;
}
catch (Exception ex)
{
@@ -216,4 +211,4 @@ public static class Program
return 1;
}
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Database/Database.Migrations/scripts/01-schema/schema.sql b/web/backend/Database/Database.Migrations/scripts/01-schema/schema.sql
index af6d79c..f3ffb1a 100644
--- a/web/backend/Database/Database.Migrations/scripts/01-schema/schema.sql
+++ b/web/backend/Database/Database.Migrations/scripts/01-schema/schema.sql
@@ -24,22 +24,22 @@ CREATE TABLE dbo.UserAccount
UserAccountID UNIQUEIDENTIFIER
CONSTRAINT DF_UserAccountID DEFAULT NEWID(),
- Username VARCHAR(64) NOT NULL,
+ Username VARCHAR(64) NOT NULL,
- FirstName NVARCHAR(128) NOT NULL,
+ FirstName NVARCHAR(128) NOT NULL,
- LastName NVARCHAR(128) NOT NULL,
+ LastName NVARCHAR(128) NOT NULL,
- Email VARCHAR(128) NOT NULL,
+ Email VARCHAR(128) NOT NULL,
- CreatedAt DATETIME NOT NULL
+ CreatedAt DATETIME NOT NULL
CONSTRAINT DF_UserAccount_CreatedAt DEFAULT GETDATE(),
- UpdatedAt DATETIME,
+ UpdatedAt DATETIME,
- DateOfBirth DATE NOT NULL,
+ DateOfBirth DATE NOT NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_UserAccount
PRIMARY KEY (UserAccountID),
@@ -56,29 +56,30 @@ CREATE TABLE dbo.UserAccount
CREATE TABLE Photo -- All photos must be linked to a user account, you cannot delete a user account if they have uploaded photos
(
- PhotoID UNIQUEIDENTIFIER
+ PhotoID UNIQUEIDENTIFIER
CONSTRAINT DF_PhotoID DEFAULT NEWID(),
- Hyperlink NVARCHAR(256),
+ Hyperlink NVARCHAR(256),
-- storage is handled via filesystem or cloud service
UploadedByID UNIQUEIDENTIFIER NOT NULL,
- UploadedAt DATETIME NOT NULL
+ UploadedAt DATETIME NOT NULL
CONSTRAINT DF_Photo_UploadedAt DEFAULT GETDATE(),
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_Photo
PRIMARY KEY (PhotoID),
CONSTRAINT FK_Photo_UploadedBy
FOREIGN KEY (UploadedByID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE NO ACTION
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE NO ACTION
);
-CREATE NONCLUSTERED INDEX IX_Photo_UploadedByID
+CREATE
+NONCLUSTERED INDEX IX_Photo_UploadedByID
ON Photo(UploadedByID);
----------------------------------------------------------------------------
@@ -86,31 +87,32 @@ CREATE NONCLUSTERED INDEX IX_Photo_UploadedByID
CREATE TABLE UserAvatar -- delete avatar photo when user account is deleted
(
- UserAvatarID UNIQUEIDENTIFIER
+ UserAvatarID UNIQUEIDENTIFIER
CONSTRAINT DF_UserAvatarID DEFAULT NEWID(),
UserAccountID UNIQUEIDENTIFIER NOT NULL,
- PhotoID UNIQUEIDENTIFIER NOT NULL,
+ PhotoID UNIQUEIDENTIFIER NOT NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_UserAvatar PRIMARY KEY (UserAvatarID),
CONSTRAINT FK_UserAvatar_UserAccount
FOREIGN KEY (UserAccountID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE CASCADE,
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE CASCADE,
CONSTRAINT FK_UserAvatar_PhotoID
FOREIGN KEY (PhotoID)
- REFERENCES Photo(PhotoID),
+ REFERENCES Photo (PhotoID),
CONSTRAINT AK_UserAvatar_UserAccountID
UNIQUE (UserAccountID)
);
-CREATE NONCLUSTERED INDEX IX_UserAvatar_UserAccount
+CREATE
+NONCLUSTERED INDEX IX_UserAvatar_UserAccount
ON UserAvatar(UserAccountID);
----------------------------------------------------------------------------
@@ -118,29 +120,30 @@ CREATE NONCLUSTERED INDEX IX_UserAvatar_UserAccount
CREATE TABLE UserVerification -- delete verification data when user account is deleted
(
- UserVerificationID UNIQUEIDENTIFIER
+ UserVerificationID UNIQUEIDENTIFIER
CONSTRAINT DF_UserVerificationID DEFAULT NEWID(),
- UserAccountID UNIQUEIDENTIFIER NOT NULL,
+ UserAccountID UNIQUEIDENTIFIER NOT NULL,
- VerificationDateTime DATETIME NOT NULL
+ VerificationDateTime DATETIME NOT NULL
CONSTRAINT DF_VerificationDateTime DEFAULT GETDATE(),
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_UserVerification
PRIMARY KEY (UserVerificationID),
CONSTRAINT FK_UserVerification_UserAccount
FOREIGN KEY (UserAccountID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE CASCADE,
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE CASCADE,
CONSTRAINT AK_UserVerification_UserAccountID
UNIQUE (UserAccountID)
);
-CREATE NONCLUSTERED INDEX IX_UserVerification_UserAccount
+CREATE
+NONCLUSTERED INDEX IX_UserVerification_UserAccount
ON UserVerification(UserAccountID);
----------------------------------------------------------------------------
@@ -151,37 +154,39 @@ CREATE TABLE UserCredential -- delete credentials when user account is deleted
UserCredentialID UNIQUEIDENTIFIER
CONSTRAINT DF_UserCredentialID DEFAULT NEWID(),
- UserAccountID UNIQUEIDENTIFIER NOT NULL,
+ UserAccountID UNIQUEIDENTIFIER NOT NULL,
- CreatedAt DATETIME NOT NULL
+ CreatedAt DATETIME NOT NULL
CONSTRAINT DF_UserCredential_CreatedAt DEFAULT GETDATE(),
- Expiry DATETIME NOT NULL
+ Expiry DATETIME NOT NULL
CONSTRAINT DF_UserCredential_Expiry DEFAULT DATEADD(DAY, 90, GETDATE()),
- Hash NVARCHAR(256) NOT NULL,
+ Hash NVARCHAR(256) NOT NULL,
-- uses argon2
- IsRevoked BIT NOT NULL
+ IsRevoked BIT NOT NULL
CONSTRAINT DF_UserCredential_IsRevoked DEFAULT 0,
- RevokedAt DATETIME NULL,
+ RevokedAt DATETIME NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_UserCredential
PRIMARY KEY (UserCredentialID),
CONSTRAINT FK_UserCredential_UserAccount
FOREIGN KEY (UserAccountID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE CASCADE
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE CASCADE
);
-CREATE NONCLUSTERED INDEX IX_UserCredential_UserAccount
+CREATE
+NONCLUSTERED INDEX IX_UserCredential_UserAccount
ON UserCredential(UserAccountID);
-CREATE NONCLUSTERED INDEX IX_UserCredential_Account_Active
+CREATE
+NONCLUSTERED INDEX IX_UserCredential_Account_Active
ON UserCredential(UserAccountID, IsRevoked, Expiry)
INCLUDE (Hash);
@@ -190,39 +195,42 @@ CREATE NONCLUSTERED INDEX IX_UserCredential_Account_Active
CREATE TABLE UserFollow
(
- UserFollowID UNIQUEIDENTIFIER
+ UserFollowID UNIQUEIDENTIFIER
CONSTRAINT DF_UserFollowID DEFAULT NEWID(),
UserAccountID UNIQUEIDENTIFIER NOT NULL,
- FollowingID UNIQUEIDENTIFIER NOT NULL,
+ FollowingID UNIQUEIDENTIFIER NOT NULL,
- CreatedAt DATETIME NOT NULL
+ CreatedAt DATETIME NOT NULL
CONSTRAINT DF_UserFollow_CreatedAt DEFAULT GETDATE(),
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_UserFollow
PRIMARY KEY (UserFollowID),
CONSTRAINT FK_UserFollow_UserAccount
FOREIGN KEY (UserAccountID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE NO ACTION,
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE NO ACTION,
CONSTRAINT FK_UserFollow_UserAccountFollowing
FOREIGN KEY (FollowingID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE NO ACTION,
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE NO ACTION,
CONSTRAINT CK_CannotFollowOwnAccount
- CHECK (UserAccountID != FollowingID)
-);
+ CHECK (UserAccountID != FollowingID
+)
+ );
-CREATE NONCLUSTERED INDEX IX_UserFollow_UserAccount_FollowingID
+CREATE
+NONCLUSTERED INDEX IX_UserFollow_UserAccount_FollowingID
ON UserFollow(UserAccountID, FollowingID);
-CREATE NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
+CREATE
+NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
ON UserFollow(FollowingID, UserAccountID);
----------------------------------------------------------------------------
@@ -230,14 +238,14 @@ CREATE NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
CREATE TABLE Country
(
- CountryID UNIQUEIDENTIFIER
+ CountryID UNIQUEIDENTIFIER
CONSTRAINT DF_CountryID DEFAULT NEWID(),
CountryName NVARCHAR(100) NOT NULL,
- ISO3166_1 CHAR(2) NOT NULL,
+ ISO3166_1 CHAR(2) NOT NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_Country
PRIMARY KEY (CountryID),
@@ -251,17 +259,17 @@ CREATE TABLE Country
CREATE TABLE StateProvince
(
- StateProvinceID UNIQUEIDENTIFIER
+ StateProvinceID UNIQUEIDENTIFIER
CONSTRAINT DF_StateProvinceID DEFAULT NEWID(),
StateProvinceName NVARCHAR(100) NOT NULL,
- ISO3166_2 CHAR(6) NOT NULL,
+ ISO3166_2 CHAR(6) NOT NULL,
-- eg 'US-CA' for California, 'CA-ON' for Ontario
- CountryID UNIQUEIDENTIFIER NOT NULL,
+ CountryID UNIQUEIDENTIFIER NOT NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_StateProvince
PRIMARY KEY (StateProvinceID),
@@ -271,10 +279,11 @@ CREATE TABLE StateProvince
CONSTRAINT FK_StateProvince_Country
FOREIGN KEY (CountryID)
- REFERENCES Country(CountryID)
+ REFERENCES Country (CountryID)
);
-CREATE NONCLUSTERED INDEX IX_StateProvince_Country
+CREATE
+NONCLUSTERED INDEX IX_StateProvince_Country
ON StateProvince(CountryID);
----------------------------------------------------------------------------
@@ -282,24 +291,25 @@ CREATE NONCLUSTERED INDEX IX_StateProvince_Country
CREATE TABLE City
(
- CityID UNIQUEIDENTIFIER
+ CityID UNIQUEIDENTIFIER
CONSTRAINT DF_CityID DEFAULT NEWID(),
- CityName NVARCHAR(100) NOT NULL,
+ CityName NVARCHAR(100) NOT NULL,
StateProvinceID UNIQUEIDENTIFIER NOT NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_City
PRIMARY KEY (CityID),
CONSTRAINT FK_City_StateProvince
FOREIGN KEY (StateProvinceID)
- REFERENCES StateProvince(StateProvinceID)
+ REFERENCES StateProvince (StateProvinceID)
);
-CREATE NONCLUSTERED INDEX IX_City_StateProvince
+CREATE
+NONCLUSTERED INDEX IX_City_StateProvince
ON City(StateProvinceID);
----------------------------------------------------------------------------
@@ -310,29 +320,30 @@ CREATE TABLE BreweryPost -- A user cannot be deleted if they have a post
BreweryPostID UNIQUEIDENTIFIER
CONSTRAINT DF_BreweryPostID DEFAULT NEWID(),
- BreweryName NVARCHAR(256) NOT NULL,
+ BreweryName NVARCHAR(256) NOT NULL,
- PostedByID UNIQUEIDENTIFIER NOT NULL,
+ PostedByID UNIQUEIDENTIFIER NOT NULL,
- Description NVARCHAR(512) NOT NULL,
+ Description NVARCHAR(512) NOT NULL,
- CreatedAt DATETIME NOT NULL
+ CreatedAt DATETIME NOT NULL
CONSTRAINT DF_BreweryPost_CreatedAt DEFAULT GETDATE(),
- UpdatedAt DATETIME NULL,
+ UpdatedAt DATETIME NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BreweryPost
PRIMARY KEY (BreweryPostID),
CONSTRAINT FK_BreweryPost_UserAccount
FOREIGN KEY (PostedByID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE NO ACTION
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE NO ACTION
);
-CREATE NONCLUSTERED INDEX IX_BreweryPost_PostedByID
+CREATE
+NONCLUSTERED INDEX IX_BreweryPost_PostedByID
ON BreweryPost(PostedByID);
----------------------------------------------------------------------------
@@ -343,19 +354,19 @@ CREATE TABLE BreweryPostLocation
BreweryPostLocationID UNIQUEIDENTIFIER
CONSTRAINT DF_BreweryPostLocationID DEFAULT NEWID(),
- BreweryPostID UNIQUEIDENTIFIER NOT NULL,
+ BreweryPostID UNIQUEIDENTIFIER NOT NULL,
- AddressLine1 NVARCHAR(256) NOT NULL,
+ AddressLine1 NVARCHAR(256) NOT NULL,
- AddressLine2 NVARCHAR(256),
+ AddressLine2 NVARCHAR(256),
- PostalCode NVARCHAR(20) NOT NULL,
+ PostalCode NVARCHAR(20) NOT NULL,
- CityID UNIQUEIDENTIFIER NOT NULL,
+ CityID UNIQUEIDENTIFIER NOT NULL,
- Coordinates GEOGRAPHY NULL,
+ Coordinates GEOGRAPHY NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BreweryPostLocation
PRIMARY KEY (BreweryPostLocationID),
@@ -365,18 +376,20 @@ CREATE TABLE BreweryPostLocation
CONSTRAINT FK_BreweryPostLocation_BreweryPost
FOREIGN KEY (BreweryPostID)
- REFERENCES BreweryPost(BreweryPostID)
- ON DELETE CASCADE,
+ REFERENCES BreweryPost (BreweryPostID)
+ ON DELETE CASCADE,
CONSTRAINT FK_BreweryPostLocation_City
FOREIGN KEY (CityID)
- REFERENCES City(CityID)
+ REFERENCES City (CityID)
);
-CREATE NONCLUSTERED INDEX IX_BreweryPostLocation_BreweryPost
+CREATE
+NONCLUSTERED INDEX IX_BreweryPostLocation_BreweryPost
ON BreweryPostLocation(BreweryPostID);
-CREATE NONCLUSTERED INDEX IX_BreweryPostLocation_City
+CREATE
+NONCLUSTERED INDEX IX_BreweryPostLocation_City
ON BreweryPostLocation(CityID);
-- To assess when the time comes:
@@ -399,33 +412,35 @@ CREATE TABLE BreweryPostPhoto -- All photos linked to a post are deleted if the
BreweryPostPhotoID UNIQUEIDENTIFIER
CONSTRAINT DF_BreweryPostPhotoID DEFAULT NEWID(),
- BreweryPostID UNIQUEIDENTIFIER NOT NULL,
+ BreweryPostID UNIQUEIDENTIFIER NOT NULL,
- PhotoID UNIQUEIDENTIFIER NOT NULL,
+ PhotoID UNIQUEIDENTIFIER NOT NULL,
- LinkedAt DATETIME NOT NULL
+ LinkedAt DATETIME NOT NULL
CONSTRAINT DF_BreweryPostPhoto_LinkedAt DEFAULT GETDATE(),
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BreweryPostPhoto
PRIMARY KEY (BreweryPostPhotoID),
CONSTRAINT FK_BreweryPostPhoto_BreweryPost
FOREIGN KEY (BreweryPostID)
- REFERENCES BreweryPost(BreweryPostID)
- ON DELETE CASCADE,
+ REFERENCES BreweryPost (BreweryPostID)
+ ON DELETE CASCADE,
CONSTRAINT FK_BreweryPostPhoto_Photo
FOREIGN KEY (PhotoID)
- REFERENCES Photo(PhotoID)
- ON DELETE CASCADE
+ REFERENCES Photo (PhotoID)
+ ON DELETE CASCADE
);
-CREATE NONCLUSTERED INDEX IX_BreweryPostPhoto_Photo_BreweryPost
+CREATE
+NONCLUSTERED INDEX IX_BreweryPostPhoto_Photo_BreweryPost
ON BreweryPostPhoto(PhotoID, BreweryPostID);
-CREATE NONCLUSTERED INDEX IX_BreweryPostPhoto_BreweryPost_Photo
+CREATE
+NONCLUSTERED INDEX IX_BreweryPostPhoto_BreweryPost_Photo
ON BreweryPostPhoto(BreweryPostID, PhotoID);
----------------------------------------------------------------------------
@@ -436,11 +451,11 @@ CREATE TABLE BeerStyle
BeerStyleID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerStyleID DEFAULT NEWID(),
- StyleName NVARCHAR(100) NOT NULL,
+ StyleName NVARCHAR(100) NOT NULL,
Description NVARCHAR(MAX),
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BeerStyle
PRIMARY KEY (BeerStyleID),
@@ -454,47 +469,47 @@ CREATE TABLE BeerStyle
CREATE TABLE BeerPost
(
- BeerPostID UNIQUEIDENTIFIER
+ BeerPostID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerPostID DEFAULT NEWID(),
- Name NVARCHAR(100) NOT NULL,
+ Name NVARCHAR(100) NOT NULL,
Description NVARCHAR(MAX) NOT NULL,
- ABV DECIMAL(4,2) NOT NULL,
+ ABV DECIMAL(4, 2) NOT NULL,
-- Alcohol By Volume (typically 0-67%)
- IBU INT NOT NULL,
+ IBU INT NOT NULL,
-- International Bitterness Units (typically 0-120)
- PostedByID UNIQUEIDENTIFIER NOT NULL,
+ PostedByID UNIQUEIDENTIFIER NOT NULL,
BeerStyleID UNIQUEIDENTIFIER NOT NULL,
- BrewedByID UNIQUEIDENTIFIER NOT NULL,
+ BrewedByID UNIQUEIDENTIFIER NOT NULL,
- CreatedAt DATETIME NOT NULL
+ CreatedAt DATETIME NOT NULL
CONSTRAINT DF_BeerPost_CreatedAt DEFAULT GETDATE(),
- UpdatedAt DATETIME,
+ UpdatedAt DATETIME,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BeerPost
PRIMARY KEY (BeerPostID),
CONSTRAINT FK_BeerPost_PostedBy
FOREIGN KEY (PostedByID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE NO ACTION,
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE NO ACTION,
CONSTRAINT FK_BeerPost_BeerStyle
FOREIGN KEY (BeerStyleID)
- REFERENCES BeerStyle(BeerStyleID),
+ REFERENCES BeerStyle (BeerStyleID),
CONSTRAINT FK_BeerPost_Brewery
FOREIGN KEY (BrewedByID)
- REFERENCES BreweryPost(BreweryPostID),
+ REFERENCES BreweryPost (BreweryPostID),
CONSTRAINT CHK_BeerPost_ABV
CHECK (ABV >= 0 AND ABV <= 67),
@@ -503,13 +518,16 @@ CREATE TABLE BeerPost
CHECK (IBU >= 0 AND IBU <= 120)
);
-CREATE NONCLUSTERED INDEX IX_BeerPost_PostedBy
+CREATE
+NONCLUSTERED INDEX IX_BeerPost_PostedBy
ON BeerPost(PostedByID);
-CREATE NONCLUSTERED INDEX IX_BeerPost_BeerStyle
+CREATE
+NONCLUSTERED INDEX IX_BeerPost_BeerStyle
ON BeerPost(BeerStyleID);
-CREATE NONCLUSTERED INDEX IX_BeerPost_BrewedBy
+CREATE
+NONCLUSTERED INDEX IX_BeerPost_BrewedBy
ON BeerPost(BrewedByID);
----------------------------------------------------------------------------
@@ -520,33 +538,35 @@ CREATE TABLE BeerPostPhoto -- All photos linked to a beer post are deleted if th
BeerPostPhotoID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerPostPhotoID DEFAULT NEWID(),
- BeerPostID UNIQUEIDENTIFIER NOT NULL,
+ BeerPostID UNIQUEIDENTIFIER NOT NULL,
- PhotoID UNIQUEIDENTIFIER NOT NULL,
+ PhotoID UNIQUEIDENTIFIER NOT NULL,
- LinkedAt DATETIME NOT NULL
+ LinkedAt DATETIME NOT NULL
CONSTRAINT DF_BeerPostPhoto_LinkedAt DEFAULT GETDATE(),
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BeerPostPhoto
PRIMARY KEY (BeerPostPhotoID),
CONSTRAINT FK_BeerPostPhoto_BeerPost
FOREIGN KEY (BeerPostID)
- REFERENCES BeerPost(BeerPostID)
- ON DELETE CASCADE,
+ REFERENCES BeerPost (BeerPostID)
+ ON DELETE CASCADE,
CONSTRAINT FK_BeerPostPhoto_Photo
FOREIGN KEY (PhotoID)
- REFERENCES Photo(PhotoID)
- ON DELETE CASCADE
+ REFERENCES Photo (PhotoID)
+ ON DELETE CASCADE
);
-CREATE NONCLUSTERED INDEX IX_BeerPostPhoto_Photo_BeerPost
+CREATE
+NONCLUSTERED INDEX IX_BeerPostPhoto_Photo_BeerPost
ON BeerPostPhoto(PhotoID, BeerPostID);
-CREATE NONCLUSTERED INDEX IX_BeerPostPhoto_BeerPost_Photo
+CREATE
+NONCLUSTERED INDEX IX_BeerPostPhoto_BeerPost_Photo
ON BeerPostPhoto(BeerPostID, PhotoID);
----------------------------------------------------------------------------
@@ -557,39 +577,41 @@ CREATE TABLE BeerPostComment
BeerPostCommentID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerPostComment DEFAULT NEWID(),
- Comment NVARCHAR(250) NOT NULL,
+ Comment NVARCHAR(250) NOT NULL,
- BeerPostID UNIQUEIDENTIFIER NOT NULL,
+ BeerPostID UNIQUEIDENTIFIER NOT NULL,
- CommentedByID UNIQUEIDENTIFIER NOT NULL,
+ CommentedByID UNIQUEIDENTIFIER NOT NULL,
- Rating INT NOT NULL,
+ Rating INT NOT NULL,
- CreatedAt DATETIME NOT NULL
+ CreatedAt DATETIME NOT NULL
CONSTRAINT DF_BeerPostComment_CreatedAt DEFAULT GETDATE(),
- UpdatedAt DATETIME NULL,
+ UpdatedAt DATETIME NULL,
- Timer ROWVERSION,
+ Timer ROWVERSION,
CONSTRAINT PK_BeerPostComment
PRIMARY KEY (BeerPostCommentID),
CONSTRAINT FK_BeerPostComment_BeerPost
FOREIGN KEY (BeerPostID)
- REFERENCES BeerPost(BeerPostID),
+ REFERENCES BeerPost (BeerPostID),
CONSTRAINT FK_BeerPostComment_UserAccount
FOREIGN KEY (CommentedByID)
- REFERENCES UserAccount(UserAccountID)
- ON DELETE NO ACTION,
+ REFERENCES UserAccount (UserAccountID)
+ ON DELETE NO ACTION,
CONSTRAINT CHK_BeerPostComment_Rating
CHECK (Rating BETWEEN 1 AND 5)
);
-CREATE NONCLUSTERED INDEX IX_BeerPostComment_BeerPost
+CREATE
+NONCLUSTERED INDEX IX_BeerPostComment_BeerPost
ON BeerPostComment(BeerPostID);
-CREATE NONCLUSTERED INDEX IX_BeerPostComment_CommentedBy
+CREATE
+NONCLUSTERED INDEX IX_BeerPostComment_CommentedBy
ON BeerPostComment(CommentedByID);
diff --git a/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetCountryIdByCode.sql b/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetCountryIdByCode.sql
index e341a35..d8a4414 100644
--- a/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetCountryIdByCode.sql
+++ b/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetCountryIdByCode.sql
@@ -1,15 +1,18 @@
-CREATE OR ALTER FUNCTION dbo.UDF_GetCountryIdByCode
-(
+CREATE
+OR
+ALTER FUNCTION dbo.UDF_GetCountryIdByCode
+ (
@CountryCode NVARCHAR(2)
-)
-RETURNS UNIQUEIDENTIFIER
-AS
+ )
+ RETURNS UNIQUEIDENTIFIER
+ AS
BEGIN
- DECLARE @CountryId UNIQUEIDENTIFIER;
+ DECLARE
+@CountryId UNIQUEIDENTIFIER;
- SELECT @CountryId = CountryID
- FROM dbo.Country
- WHERE ISO3166_1 = @CountryCode;
+SELECT @CountryId = CountryID
+FROM dbo.Country
+WHERE ISO3166_1 = @CountryCode;
- RETURN @CountryId;
+RETURN @CountryId;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetStateProvinceIdByCode.sql b/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetStateProvinceIdByCode.sql
index 1f4bc20..f07c30d 100644
--- a/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetStateProvinceIdByCode.sql
+++ b/web/backend/Database/Database.Migrations/scripts/02-functions/UDF_GetStateProvinceIdByCode.sql
@@ -1,13 +1,16 @@
-CREATE OR ALTER FUNCTION dbo.UDF_GetStateProvinceIdByCode
-(
+CREATE
+OR
+ALTER FUNCTION dbo.UDF_GetStateProvinceIdByCode
+ (
@StateProvinceCode NVARCHAR(6)
-)
-RETURNS UNIQUEIDENTIFIER
-AS
+ )
+ RETURNS UNIQUEIDENTIFIER
+ AS
BEGIN
- DECLARE @StateProvinceId UNIQUEIDENTIFIER;
- SELECT @StateProvinceId = StateProvinceID
- FROM dbo.StateProvince
- WHERE ISO3166_2 = @StateProvinceCode;
- RETURN @StateProvinceId;
+ DECLARE
+@StateProvinceId UNIQUEIDENTIFIER;
+SELECT @StateProvinceId = StateProvinceID
+FROM dbo.StateProvince
+WHERE ISO3166_2 = @StateProvinceCode;
+RETURN @StateProvinceId;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql
index 80f71d5..9de882b 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql
@@ -1,36 +1,34 @@
-
-CREATE OR ALTER PROCEDURE usp_CreateUserAccount
-(
+CREATE
+OR
+ALTER PROCEDURE usp_CreateUserAccount
+ (
@UserAccountId UNIQUEIDENTIFIER OUTPUT,
- @Username VARCHAR(64),
+ @Username VARCHAR (64),
@FirstName NVARCHAR(128),
@LastName NVARCHAR(128),
@DateOfBirth DATETIME,
- @Email VARCHAR(128)
-)
-AS
-BEGIN
- SET NOCOUNT ON;
-
- DECLARE @Inserted TABLE (UserAccountID UNIQUEIDENTIFIER);
-
- INSERT INTO UserAccount
- (
- Username,
- FirstName,
- LastName,
- DateOfBirth,
- Email
+ @Email VARCHAR (128)
)
+ AS
+BEGIN
+ SET
+NOCOUNT ON;
+
+ DECLARE
+@Inserted TABLE (UserAccountID UNIQUEIDENTIFIER);
+
+INSERT INTO UserAccount
+(Username,
+ FirstName,
+ LastName,
+ DateOfBirth,
+ Email)
OUTPUT INSERTED.UserAccountID INTO @Inserted
- VALUES
+VALUES
(
- @Username,
- @FirstName,
- @LastName,
- @DateOfBirth,
- @Email
+ @Username, @FirstName, @LastName, @DateOfBirth, @Email
);
- SELECT @UserAccountId = UserAccountID FROM @Inserted;
+SELECT @UserAccountId = UserAccountID
+FROM @Inserted;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_DeleteUserAccount.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_DeleteUserAccount.sql
index 7ea5d3e..86be614 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_DeleteUserAccount.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_DeleteUserAccount.sql
@@ -1,20 +1,24 @@
-
-CREATE OR ALTER PROCEDURE usp_DeleteUserAccount
-(
+CREATE
+OR
+ALTER PROCEDURE usp_DeleteUserAccount
+ (
@UserAccountId UNIQUEIDENTIFIER
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON
+ SET
+NOCOUNT ON
IF NOT EXISTS (SELECT 1 FROM UserAccount WHERE UserAccountId = @UserAccountId)
- BEGIN
- RAISERROR('UserAccount with the specified ID does not exist.', 16,
+BEGIN
+ RAISERROR
+('UserAccount with the specified ID does not exist.', 16,
1);
- ROLLBACK TRANSACTION
+ROLLBACK TRANSACTION
RETURN
- END
+END
- DELETE FROM UserAccount
- WHERE UserAccountId = @UserAccountId;
+DELETE
+FROM UserAccount
+WHERE UserAccountId = @UserAccountId;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetAllUserAccounts.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetAllUserAccounts.sql
index 233acf5..2e6a6f0 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetAllUserAccounts.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetAllUserAccounts.sql
@@ -1,16 +1,19 @@
-CREATE OR ALTER PROCEDURE usp_GetAllUserAccounts
-AS
+CREATE
+OR
+ALTER PROCEDURE usp_GetAllUserAccounts
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- SELECT UserAccountID,
- Username,
- FirstName,
- LastName,
- Email,
- CreatedAt,
- UpdatedAt,
- DateOfBirth,
- Timer
- FROM dbo.UserAccount;
+SELECT UserAccountID,
+ Username,
+ FirstName,
+ LastName,
+ Email,
+ CreatedAt,
+ UpdatedAt,
+ DateOfBirth,
+ Timer
+FROM dbo.UserAccount;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByEmail.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByEmail.sql
index 5e6fc2e..786de35 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByEmail.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByEmail.sql
@@ -1,19 +1,22 @@
-CREATE OR ALTER PROCEDURE usp_GetUserAccountByEmail(
- @Email VARCHAR(128)
-)
-AS
+CREATE
+OR
+ALTER PROCEDURE usp_GetUserAccountByEmail(
+ @Email VARCHAR (128)
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- SELECT UserAccountID,
- Username,
- FirstName,
- LastName,
- Email,
- CreatedAt,
- UpdatedAt,
- DateOfBirth,
- Timer
- FROM dbo.UserAccount
- WHERE Email = @Email;
+SELECT UserAccountID,
+ Username,
+ FirstName,
+ LastName,
+ Email,
+ CreatedAt,
+ UpdatedAt,
+ DateOfBirth,
+ Timer
+FROM dbo.UserAccount
+WHERE Email = @Email;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountById.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountById.sql
index 7113807..510a79f 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountById.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountById.sql
@@ -1,19 +1,22 @@
-CREATE OR ALTER PROCEDURE USP_GetUserAccountById(
+CREATE
+OR
+ALTER PROCEDURE USP_GetUserAccountById(
@UserAccountId UNIQUEIDENTIFIER
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- SELECT UserAccountID,
- Username,
- FirstName,
- LastName,
- Email,
- CreatedAt,
- UpdatedAt,
- DateOfBirth,
- Timer
- FROM dbo.UserAccount
- WHERE UserAccountID = @UserAccountId;
+SELECT UserAccountID,
+ Username,
+ FirstName,
+ LastName,
+ Email,
+ CreatedAt,
+ UpdatedAt,
+ DateOfBirth,
+ Timer
+FROM dbo.UserAccount
+WHERE UserAccountID = @UserAccountId;
END
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByUsername.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByUsername.sql
index 96fcca9..572ac35 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByUsername.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_GetUserAccountByUsername.sql
@@ -1,19 +1,22 @@
-CREATE OR ALTER PROCEDURE usp_GetUserAccountByUsername(
- @Username VARCHAR(64)
-)
-AS
+CREATE
+OR
+ALTER PROCEDURE usp_GetUserAccountByUsername(
+ @Username VARCHAR (64)
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- SELECT UserAccountID,
- Username,
- FirstName,
- LastName,
- Email,
- CreatedAt,
- UpdatedAt,
- DateOfBirth,
- Timer
- FROM dbo.UserAccount
- WHERE Username = @Username;
+SELECT UserAccountID,
+ Username,
+ FirstName,
+ LastName,
+ Email,
+ CreatedAt,
+ UpdatedAt,
+ DateOfBirth,
+ Timer
+FROM dbo.UserAccount
+WHERE Username = @Username;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_UpdateUserAccount.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_UpdateUserAccount.sql
index e0355b8..413d923 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_UpdateUserAccount.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/01-UserAccount/USP_UpdateUserAccount.sql
@@ -1,27 +1,30 @@
-CREATE OR ALTER PROCEDURE usp_UpdateUserAccount(
- @Username VARCHAR(64),
+CREATE
+OR
+ALTER PROCEDURE usp_UpdateUserAccount(
+ @Username VARCHAR (64),
@FirstName NVARCHAR(128),
@LastName NVARCHAR(128),
@DateOfBirth DATETIME,
- @Email VARCHAR(128),
+ @Email VARCHAR (128),
@UserAccountId UNIQUEIDENTIFIER
-)
-AS
+ )
+ AS
BEGIN
SET
- NOCOUNT ON;
+NOCOUNT ON;
- UPDATE UserAccount
- SET Username = @Username,
- FirstName = @FirstName,
- LastName = @LastName,
- DateOfBirth = @DateOfBirth,
- Email = @Email
- WHERE UserAccountId = @UserAccountId;
+UPDATE UserAccount
+SET Username = @Username,
+ FirstName = @FirstName,
+ LastName = @LastName,
+ DateOfBirth = @DateOfBirth,
+ Email = @Email
+WHERE UserAccountId = @UserAccountId;
- IF @@ROWCOUNT = 0
- BEGIN
+IF
+@@ROWCOUNT = 0
+BEGIN
THROW
- 50001, 'UserAccount with the specified ID does not exist.', 1;
- END
+50001, 'UserAccount with the specified ID does not exist.', 1;
+END
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_GetUserCredentialByUserAccountId.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_GetUserCredentialByUserAccountId.sql
index bc385e9..f387e92 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_GetUserCredentialByUserAccountId.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_GetUserCredentialByUserAccountId.sql
@@ -1,17 +1,20 @@
-CREATE OR ALTER PROCEDURE dbo.USP_GetActiveUserCredentialByUserAccountId(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_GetActiveUserCredentialByUserAccountId(
@UserAccountId UNIQUEIDENTIFIER
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- SELECT
- UserCredentialId,
- UserAccountId,
- Hash,
- IsRevoked,
- CreatedAt,
- RevokedAt
- FROM dbo.UserCredential
- WHERE UserAccountId = @UserAccountId AND IsRevoked = 0;
+SELECT UserCredentialId,
+ UserAccountId,
+ Hash,
+ IsRevoked,
+ CreatedAt,
+ RevokedAt
+FROM dbo.UserCredential
+WHERE UserAccountId = @UserAccountId
+ AND IsRevoked = 0;
END;
\ No newline at end of file
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_InvalidateUserCredential.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_InvalidateUserCredential.sql
index 08c7d53..c74118e 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_InvalidateUserCredential.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_InvalidateUserCredential.sql
@@ -1,24 +1,30 @@
-CREATE OR ALTER PROCEDURE dbo.USP_InvalidateUserCredential(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_InvalidateUserCredential(
@UserAccountId_ UNIQUEIDENTIFIER
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- BEGIN TRANSACTION;
+BEGIN
+TRANSACTION;
- EXEC dbo.USP_GetUserAccountByID @UserAccountId = @UserAccountId_;
- IF @@ROWCOUNT = 0
+EXEC dbo.USP_GetUserAccountByID @UserAccountId = @UserAccountId_;
+ IF
+@@ROWCOUNT = 0
THROW 50001, 'User account not found', 1;
-- invalidate all other credentials by setting them to revoked
- UPDATE dbo.UserCredential
- SET IsRevoked = 1,
- RevokedAt = GETDATE()
- WHERE UserAccountId = @UserAccountId_
- AND IsRevoked != 1;
+UPDATE dbo.UserCredential
+SET IsRevoked = 1,
+ RevokedAt = GETDATE()
+WHERE UserAccountId = @UserAccountId_
+ AND IsRevoked != 1;
- COMMIT TRANSACTION;
+COMMIT TRANSACTION;
END;
\ No newline at end of file
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RegisterUser.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RegisterUser.sql
index d09a7e0..a85f8e4 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RegisterUser.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RegisterUser.sql
@@ -1,21 +1,27 @@
-CREATE OR ALTER PROCEDURE dbo.USP_RegisterUser(
- @Username VARCHAR(64),
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_RegisterUser(
+ @Username VARCHAR (64),
@FirstName NVARCHAR(128),
@LastName NVARCHAR(128),
@DateOfBirth DATETIME,
- @Email VARCHAR(128),
+ @Email VARCHAR (128),
@Hash NVARCHAR(MAX)
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- DECLARE @UserAccountId_ UNIQUEIDENTIFIER;
+ DECLARE
+@UserAccountId_ UNIQUEIDENTIFIER;
- BEGIN TRANSACTION;
+BEGIN
+TRANSACTION;
- EXEC usp_CreateUserAccount
+EXEC usp_CreateUserAccount
@UserAccountId = @UserAccountId_ OUTPUT,
@Username = @Username,
@FirstName = @FirstName,
@@ -23,20 +29,24 @@ BEGIN
@DateOfBirth = @DateOfBirth,
@Email = @Email;
- IF @UserAccountId_ IS NULL
- BEGIN
- THROW 50000, 'Failed to create user account.', 1;
- END
-
- INSERT INTO dbo.UserCredential
- (UserAccountId, Hash)
- VALUES (@UserAccountId_, @Hash);
-
- IF @@ROWCOUNT = 0
- BEGIN
- THROW 50002, 'Failed to create user credential.', 1;
- END
- COMMIT TRANSACTION;
-
- SELECT @UserAccountId_ AS UserAccountId;
+ IF
+@UserAccountId_ IS NULL
+BEGIN
+ THROW
+50000, 'Failed to create user account.', 1;
+END
+
+INSERT INTO dbo.UserCredential
+ (UserAccountId, Hash)
+VALUES (@UserAccountId_, @Hash);
+
+IF
+@@ROWCOUNT = 0
+BEGIN
+ THROW
+50002, 'Failed to create user credential.', 1;
+END
+COMMIT TRANSACTION;
+
+SELECT @UserAccountId_ AS UserAccountId;
END
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RotateUserCredential.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RotateUserCredential.sql
index d4d4e60..e00317a 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RotateUserCredential.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/02-Auth/USP_RotateUserCredential.sql
@@ -1,28 +1,33 @@
-CREATE OR ALTER PROCEDURE dbo.USP_RotateUserCredential(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_RotateUserCredential(
@UserAccountId_ UNIQUEIDENTIFIER,
@Hash NVARCHAR(MAX)
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
- BEGIN TRANSACTION;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
+BEGIN
+TRANSACTION;
- EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountId_
+EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountId_
IF @@ROWCOUNT = 0
THROW 50001, 'User account not found', 1;
-- invalidate all other credentials -- set them to revoked
- UPDATE dbo.UserCredential
- SET IsRevoked = 1,
- RevokedAt = GETDATE()
- WHERE UserAccountId = @UserAccountId_;
+UPDATE dbo.UserCredential
+SET IsRevoked = 1,
+ RevokedAt = GETDATE()
+WHERE UserAccountId = @UserAccountId_;
- INSERT INTO dbo.UserCredential
- (UserAccountId, Hash)
- VALUES (@UserAccountId_, @Hash);
+INSERT INTO dbo.UserCredential
+ (UserAccountId, Hash)
+VALUES (@UserAccountId_, @Hash);
END;
\ No newline at end of file
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/03-UserVerification/USP_AddUserVerification.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/03-UserVerification/USP_AddUserVerification.sql
index e2fbe68..23a9e78 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/03-UserVerification/USP_AddUserVerification.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/03-UserVerification/USP_AddUserVerification.sql
@@ -1,22 +1,29 @@
-CREATE OR ALTER PROCEDURE dbo.USP_CreateUserVerification @UserAccountID_ UNIQUEIDENTIFIER,
- @VerificationDateTime DATETIME = NULL
-AS
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_CreateUserVerification @UserAccountID_ UNIQUEIDENTIFIER,
+ @VerificationDateTime DATETIME = NULL
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- IF @VerificationDateTime IS NULL
+ IF
+@VerificationDateTime IS NULL
SET @VerificationDateTime = GETDATE();
- BEGIN TRANSACTION;
+BEGIN
+TRANSACTION;
- EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountID_;
- IF @@ROWCOUNT = 0
+EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountID_;
+ IF
+@@ROWCOUNT = 0
THROW 50001, 'Could not find a user with that id', 1;
- INSERT INTO dbo.UserVerification
- (UserAccountId, VerificationDateTime)
- VALUES (@UserAccountID_, @VerificationDateTime);
+INSERT INTO dbo.UserVerification
+ (UserAccountId, VerificationDateTime)
+VALUES (@UserAccountID_, @VerificationDateTime);
- COMMIT TRANSACTION;
+COMMIT TRANSACTION;
END
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCity.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCity.sql
index 0de7c52..ef82d90 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCity.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCity.sql
@@ -1,30 +1,40 @@
-CREATE OR ALTER PROCEDURE dbo.USP_CreateCity(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_CreateCity(
@CityName NVARCHAR(100),
@StateProvinceCode NVARCHAR(6)
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- BEGIN TRANSACTION
- DECLARE @StateProvinceId UNIQUEIDENTIFIER = dbo.UDF_GetStateProvinceIdByCode(@StateProvinceCode);
- IF @StateProvinceId IS NULL
- BEGIN
- THROW 50001, 'State/province does not exist', 1;
- END
+BEGIN
+TRANSACTION
+ DECLARE
+@StateProvinceId UNIQUEIDENTIFIER = dbo.UDF_GetStateProvinceIdByCode(@StateProvinceCode);
+ IF
+@StateProvinceId IS NULL
+BEGIN
+ THROW
+50001, 'State/province does not exist', 1;
+END
- IF EXISTS (SELECT 1
+ IF
+EXISTS (SELECT 1
FROM dbo.City
WHERE CityName = @CityName
AND StateProvinceID = @StateProvinceId)
- BEGIN
+BEGIN
- THROW 50002, 'City already exists.', 1;
- END
+ THROW
+50002, 'City already exists.', 1;
+END
- INSERT INTO dbo.City
- (StateProvinceID, CityName)
- VALUES (@StateProvinceId, @CityName);
- COMMIT TRANSACTION
+INSERT INTO dbo.City
+ (StateProvinceID, CityName)
+VALUES (@StateProvinceId, @CityName);
+COMMIT TRANSACTION
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCountry.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCountry.sql
index c548872..acad245 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCountry.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateCountry.sql
@@ -1,21 +1,26 @@
-CREATE OR ALTER PROCEDURE dbo.USP_CreateCountry(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_CreateCountry(
@CountryName NVARCHAR(100),
@ISO3166_1 NVARCHAR(2)
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
- BEGIN TRANSACTION;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
+BEGIN
+TRANSACTION;
- IF EXISTS (SELECT 1
+ IF
+EXISTS (SELECT 1
FROM dbo.Country
WHERE ISO3166_1 = @ISO3166_1)
THROW 50001, 'Country already exists', 1;
- INSERT INTO dbo.Country
- (CountryName, ISO3166_1)
- VALUES
- (@CountryName, @ISO3166_1);
- COMMIT TRANSACTION;
+INSERT INTO dbo.Country
+ (CountryName, ISO3166_1)
+VALUES (@CountryName, @ISO3166_1);
+COMMIT TRANSACTION;
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateStateProvince.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateStateProvince.sql
index de1e369..a850ac8 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateStateProvince.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/04-Location/USP_CreateStateProvince.sql
@@ -1,27 +1,34 @@
-CREATE OR ALTER PROCEDURE dbo.USP_CreateStateProvince(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_CreateStateProvince(
@StateProvinceName NVARCHAR(100),
@ISO3166_2 NVARCHAR(6),
@CountryCode NVARCHAR(2)
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- IF EXISTS (SELECT 1
+ IF
+EXISTS (SELECT 1
FROM dbo.StateProvince
WHERE ISO3166_2 = @ISO3166_2)
RETURN;
- DECLARE @CountryId UNIQUEIDENTIFIER = dbo.UDF_GetCountryIdByCode(@CountryCode);
- IF @CountryId IS NULL
- BEGIN
- THROW 50001, 'Country does not exist', 1;
+ DECLARE
+@CountryId UNIQUEIDENTIFIER = dbo.UDF_GetCountryIdByCode(@CountryCode);
+ IF
+@CountryId IS NULL
+BEGIN
+ THROW
+50001, 'Country does not exist', 1;
END
INSERT INTO dbo.StateProvince
(StateProvinceName, ISO3166_2, CountryID)
-VALUES
- (@StateProvinceName, @ISO3166_2, @CountryId);
+VALUES (@StateProvinceName, @ISO3166_2, @CountryId);
END;
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_CreateBrewery.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_CreateBrewery.sql
index 3683dc7..bfb29f8 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_CreateBrewery.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_CreateBrewery.sql
@@ -1,4 +1,6 @@
-CREATE OR ALTER PROCEDURE dbo.USP_CreateBrewery(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_CreateBrewery(
@BreweryName NVARCHAR(256),
@Description NVARCHAR(512),
@PostedByID UNIQUEIDENTIFIER,
@@ -7,44 +9,53 @@ CREATE OR ALTER PROCEDURE dbo.USP_CreateBrewery(
@AddressLine2 NVARCHAR(256) = NULL,
@PostalCode NVARCHAR(20),
@Coordinates GEOGRAPHY = NULL
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- IF @BreweryName IS NULL
+ IF
+@BreweryName IS NULL
THROW 50001, 'Brewery name cannot be null.', 1;
- IF @Description IS NULL
+ IF
+@Description IS NULL
THROW 50002, 'Brewery description cannot be null.', 1;
- IF NOT EXISTS (SELECT 1
+ IF
+NOT EXISTS (SELECT 1
FROM dbo.UserAccount
WHERE UserAccountID = @PostedByID)
THROW 50404, 'User not found.', 1;
- IF NOT EXISTS (SELECT 1
+ IF
+NOT EXISTS (SELECT 1
FROM dbo.City
WHERE CityID = @CityID)
THROW 50404, 'City not found.', 1;
- DECLARE @NewBreweryID UNIQUEIDENTIFIER = NEWID();
- DECLARE @NewBrewerLocationID UNIQUEIDENTIFIER = NEWID();
+ DECLARE
+@NewBreweryID UNIQUEIDENTIFIER = NEWID();
+ DECLARE
+@NewBrewerLocationID UNIQUEIDENTIFIER = NEWID();
- BEGIN TRANSACTION;
+BEGIN
+TRANSACTION;
- INSERT INTO dbo.BreweryPost
- (BreweryPostID, BreweryName, Description, PostedByID)
- VALUES (@NewBreweryID, @BreweryName, @Description, @PostedByID);
+INSERT INTO dbo.BreweryPost
+ (BreweryPostID, BreweryName, Description, PostedByID)
+VALUES (@NewBreweryID, @BreweryName, @Description, @PostedByID);
- INSERT INTO dbo.BreweryPostLocation
- (BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
- VALUES (@NewBrewerLocationID, @NewBreweryID, @CityID, @AddressLine1, @AddressLine2, @PostalCode, @Coordinates);
+INSERT INTO dbo.BreweryPostLocation
+(BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
+VALUES (@NewBrewerLocationID, @NewBreweryID, @CityID, @AddressLine1, @AddressLine2, @PostalCode, @Coordinates);
- COMMIT TRANSACTION;
+COMMIT TRANSACTION;
- SELECT @NewBreweryID AS BreweryPostID,
- @NewBrewerLocationID AS BreweryPostLocationID;
+SELECT @NewBreweryID AS BreweryPostID,
+ @NewBrewerLocationID AS BreweryPostLocationID;
END
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_DeleteBrewery.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_DeleteBrewery.sql
index 30d9baf..4b83179 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_DeleteBrewery.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_DeleteBrewery.sql
@@ -1,15 +1,20 @@
-CREATE OR ALTER PROCEDURE dbo.USP_DeleteBrewery
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_DeleteBrewery
@BreweryPostID UNIQUEIDENTIFIER
-AS
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- IF NOT EXISTS (SELECT 1
+ IF
+NOT EXISTS (SELECT 1
FROM dbo.BreweryPost
WHERE BreweryPostID = @BreweryPostID)
THROW 50404, 'Brewery not found.', 1;
-- BreweryPostLocation and BreweryPostPhoto cascade-delete with their parent BreweryPost.
- DELETE FROM dbo.BreweryPost
- WHERE BreweryPostID = @BreweryPostID;
+DELETE
+FROM dbo.BreweryPost
+WHERE BreweryPostID = @BreweryPostID;
END
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetAllBreweries.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetAllBreweries.sql
index 5933a25..4f92e20 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetAllBreweries.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetAllBreweries.sql
@@ -1,28 +1,30 @@
-CREATE OR ALTER PROCEDURE dbo.USP_GetAllBreweries(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_GetAllBreweries(
@Limit INT = NULL,
@Offset INT = NULL
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
+ SET
+NOCOUNT ON;
- SELECT bp.BreweryPostID,
- bp.PostedByID,
- bp.BreweryName,
- bp.Description,
- bp.CreatedAt,
- bp.UpdatedAt,
- bp.Timer,
- bpl.BreweryPostLocationID,
- bpl.CityID,
- bpl.AddressLine1,
- bpl.AddressLine2,
- bpl.PostalCode,
- bpl.Coordinates
- FROM dbo.BreweryPost bp
- LEFT JOIN dbo.BreweryPostLocation bpl
- ON bp.BreweryPostID = bpl.BreweryPostID
- ORDER BY bp.CreatedAt DESC
- OFFSET ISNULL(@Offset, 0) ROWS
- FETCH NEXT ISNULL(@Limit, 2147483647) ROWS ONLY;
+SELECT bp.BreweryPostID,
+ bp.PostedByID,
+ bp.BreweryName,
+ bp.Description,
+ bp.CreatedAt,
+ bp.UpdatedAt,
+ bp.Timer,
+ bpl.BreweryPostLocationID,
+ bpl.CityID,
+ bpl.AddressLine1,
+ bpl.AddressLine2,
+ bpl.PostalCode,
+ bpl.Coordinates
+FROM dbo.BreweryPost bp
+ LEFT JOIN dbo.BreweryPostLocation bpl
+ ON bp.BreweryPostID = bpl.BreweryPostID
+ORDER BY bp.CreatedAt DESC
+OFFSET ISNULL(@Offset, 0) ROWS FETCH NEXT ISNULL(@Limit, 2147483647) ROWS ONLY;
END
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetBreweryById.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetBreweryById.sql
index 25425ef..730adf3 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetBreweryById.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_GetBreweryById.sql
@@ -1,9 +1,11 @@
-CREATE OR ALTER PROCEDURE dbo.USP_GetBreweryById @BreweryPostID UNIQUEIDENTIFIER
-AS
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_GetBreweryById @BreweryPostID UNIQUEIDENTIFIER
+ AS
BEGIN
- SELECT *
- FROM BreweryPost bp
- INNER JOIN BreweryPostLocation bpl
- ON bp.BreweryPostID = bpl.BreweryPostID
- WHERE bp.BreweryPostID = @BreweryPostID;
+SELECT *
+FROM BreweryPost bp
+ INNER JOIN BreweryPostLocation bpl
+ ON bp.BreweryPostID = bpl.BreweryPostID
+WHERE bp.BreweryPostID = @BreweryPostID;
END
\ No newline at end of file
diff --git a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_UpdateBrewery.sql b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_UpdateBrewery.sql
index dcadaa6..cd7d43d 100644
--- a/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_UpdateBrewery.sql
+++ b/web/backend/Database/Database.Migrations/scripts/03-crud/05-Breweries/USP_UpdateBrewery.sql
@@ -1,4 +1,6 @@
-CREATE OR ALTER PROCEDURE dbo.USP_UpdateBrewery(
+CREATE
+OR
+ALTER PROCEDURE dbo.USP_UpdateBrewery(
@BreweryPostID UNIQUEIDENTIFIER,
@BreweryName NVARCHAR(256),
@Description NVARCHAR(512),
@@ -8,61 +10,70 @@ CREATE OR ALTER PROCEDURE dbo.USP_UpdateBrewery(
@AddressLine2 NVARCHAR(256) = NULL,
@PostalCode NVARCHAR(20) = NULL,
@Coordinates GEOGRAPHY = NULL
-)
-AS
+ )
+ AS
BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
+ SET
+NOCOUNT ON;
+ SET
+XACT_ABORT ON;
- IF @BreweryName IS NULL
+ IF
+@BreweryName IS NULL
THROW 50001, 'Brewery name cannot be null.', 1;
- IF @Description IS NULL
+ IF
+@Description IS NULL
THROW 50002, 'Brewery description cannot be null.', 1;
- IF NOT EXISTS (SELECT 1
+ IF
+NOT EXISTS (SELECT 1
FROM dbo.BreweryPost
WHERE BreweryPostID = @BreweryPostID)
THROW 50404, 'Brewery not found.', 1;
- IF @CityID IS NOT NULL AND NOT EXISTS (SELECT 1
+ IF
+@CityID IS NOT NULL AND NOT EXISTS (SELECT 1
FROM dbo.City
WHERE CityID = @CityID)
THROW 50404, 'City not found.', 1;
- BEGIN TRANSACTION;
+BEGIN
+TRANSACTION;
- UPDATE dbo.BreweryPost
- SET BreweryName = @BreweryName,
- Description = @Description,
- UpdatedAt = GETDATE()
- WHERE BreweryPostID = @BreweryPostID;
+UPDATE dbo.BreweryPost
+SET BreweryName = @BreweryName,
+ Description = @Description,
+ UpdatedAt = GETDATE()
+WHERE BreweryPostID = @BreweryPostID;
- IF @CityID IS NULL
- BEGIN
+IF
+@CityID IS NULL
+BEGIN
-- No location supplied: clear any existing location for this brewery.
- DELETE FROM dbo.BreweryPostLocation
- WHERE BreweryPostID = @BreweryPostID;
- END
- ELSE IF EXISTS (SELECT 1
+DELETE
+FROM dbo.BreweryPostLocation
+WHERE BreweryPostID = @BreweryPostID;
+END
+ELSE IF EXISTS (SELECT 1
FROM dbo.BreweryPostLocation
WHERE BreweryPostID = @BreweryPostID)
- BEGIN
- UPDATE dbo.BreweryPostLocation
- SET CityID = @CityID,
- AddressLine1 = @AddressLine1,
- AddressLine2 = @AddressLine2,
- PostalCode = @PostalCode,
- Coordinates = @Coordinates
- WHERE BreweryPostID = @BreweryPostID;
- END
- ELSE
- BEGIN
- INSERT INTO dbo.BreweryPostLocation
- (BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
- VALUES (ISNULL(@BreweryPostLocationID, NEWID()), @BreweryPostID, @CityID, @AddressLine1, @AddressLine2,
- @PostalCode, @Coordinates);
- END
-
- COMMIT TRANSACTION;
+BEGIN
+UPDATE dbo.BreweryPostLocation
+SET CityID = @CityID,
+ AddressLine1 = @AddressLine1,
+ AddressLine2 = @AddressLine2,
+ PostalCode = @PostalCode,
+ Coordinates = @Coordinates
+WHERE BreweryPostID = @BreweryPostID;
+END
+ELSE
+BEGIN
+INSERT INTO dbo.BreweryPostLocation
+(BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
+VALUES (ISNULL(@BreweryPostLocationID, NEWID()), @BreweryPostID, @CityID, @AddressLine1, @AddressLine2,
+ @PostalCode, @Coordinates);
+END
+
+COMMIT TRANSACTION;
END
diff --git a/web/backend/Database/Database.Seed/Database.Seed.csproj b/web/backend/Database/Database.Seed/Database.Seed.csproj
index 27f356a..45c69e9 100644
--- a/web/backend/Database/Database.Seed/Database.Seed.csproj
+++ b/web/backend/Database/Database.Seed/Database.Seed.csproj
@@ -1,23 +1,23 @@
-
- Exe
- net10.0
- enable
- enable
- Database.Seed
-
+
+ Exe
+ net10.0
+ enable
+ enable
+ Database.Seed
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
diff --git a/web/backend/Database/Database.Seed/ISeeder.cs b/web/backend/Database/Database.Seed/ISeeder.cs
index 07918bd..94e1a83 100644
--- a/web/backend/Database/Database.Seed/ISeeder.cs
+++ b/web/backend/Database/Database.Seed/ISeeder.cs
@@ -3,15 +3,15 @@ using Microsoft.Data.SqlClient;
namespace Database.Seed;
///
-/// Defines a unit of seed data that can be applied to the database. Implementations
-/// should be safe to run against an already-seeded database (e.g. by checking for
-/// existing data before inserting) and may depend on data created by seeders that run
-/// before them.
+/// Defines a unit of seed data that can be applied to the database. Implementations
+/// should be safe to run against an already-seeded database (e.g. by checking for
+/// existing data before inserting) and may depend on data created by seeders that run
+/// before them.
///
internal interface ISeeder
{
///
- /// Inserts this seeder's data into the database using the supplied open connection.
+ /// Inserts this seeder's data into the database using the supplied open connection.
///
/// An open connection to the target database.
/// A task that completes when seeding is finished.
diff --git a/web/backend/Database/Database.Seed/LocationSeeder.cs b/web/backend/Database/Database.Seed/LocationSeeder.cs
index 61df53a..b3b971c 100644
--- a/web/backend/Database/Database.Seed/LocationSeeder.cs
+++ b/web/backend/Database/Database.Seed/LocationSeeder.cs
@@ -4,13 +4,13 @@ using Microsoft.Data.SqlClient;
namespace Database.Seed;
///
-/// Seeds the location hierarchy (countries, states/provinces, and cities) used to
-/// associate other entities (e.g. breweries, users) with a geographic location.
-/// Countries must be seeded before states/provinces, which must be seeded before
-/// cities, since each level references its parent by code. The underlying stored
-/// procedures (USP_CreateCountry , USP_CreateStateProvince ,
-/// USP_CreateCity ) are expected to be idempotent, so re-running this seeder
-/// against an already-seeded database is safe.
+/// Seeds the location hierarchy (countries, states/provinces, and cities) used to
+/// associate other entities (e.g. breweries, users) with a geographic location.
+/// Countries must be seeded before states/provinces, which must be seeded before
+/// cities, since each level references its parent by code. The underlying stored
+/// procedures (USP_CreateCountry , USP_CreateStateProvince ,
+/// USP_CreateCity ) are expected to be idempotent, so re-running this seeder
+/// against an already-seeded database is safe.
///
internal class LocationSeeder : ISeeder
{
@@ -22,12 +22,12 @@ internal class LocationSeeder : ISeeder
[
("Canada", "CA"),
("Mexico", "MX"),
- ("United States", "US"),
+ ("United States", "US")
];
///
- /// The set of states/provinces to seed, each identified by name, ISO 3166-2 code,
- /// and the ISO 3166-1 code of its parent country.
+ /// The set of states/provinces to seed, each identified by name, ISO 3166-2 code,
+ /// and the ISO 3166-1 code of its parent country.
///
private static IReadOnlyList<(string StateProvinceName, string StateProvinceCode, string CountryCode)> States
{
@@ -134,12 +134,12 @@ internal class LocationSeeder : ISeeder
("Veracruz de Ignacio de la Llave", "MX-VER", "MX"),
("Yucatán", "MX-YUC", "MX"),
("Zacatecas", "MX-ZAC", "MX"),
- ("Ciudad de México", "MX-CMX", "MX"),
+ ("Ciudad de México", "MX-CMX", "MX")
];
///
- /// The set of cities to seed, each identified by name and the ISO 3166-2 code of its
- /// parent state/province.
+ /// The set of cities to seed, each identified by name and the ISO 3166-2 code of its
+ /// parent state/province.
///
private static IReadOnlyList<(string StateProvinceCode, string CityName)> Cities { get; } =
[
@@ -255,38 +255,32 @@ internal class LocationSeeder : ISeeder
("MX-COA", "Saltillo"),
("MX-BCS", "La Paz"),
("MX-NAY", "Tepic"),
- ("MX-ZAC", "Zacatecas"),
+ ("MX-ZAC", "Zacatecas")
];
///
- /// Seeds all countries, then states/provinces, then cities, in that order, so that
- /// each level's parent reference already exists by the time it is created.
+ /// Seeds all countries, then states/provinces, then cities, in that order, so that
+ /// each level's parent reference already exists by the time it is created.
///
/// An open connection to the target database.
/// A task that completes when all locations have been seeded.
public async Task SeedAsync(SqlConnection connection)
{
- foreach (var (countryName, countryCode) in Countries)
- {
+ foreach ((string countryName, string countryCode) in Countries)
await CreateCountryAsync(connection, countryName, countryCode);
- }
foreach (
- var (stateProvinceName, stateProvinceCode, countryCode) in States
+ (string stateProvinceName, string stateProvinceCode, string countryCode) in States
)
- {
await CreateStateProvinceAsync(
connection,
stateProvinceName,
stateProvinceCode,
countryCode
);
- }
- foreach (var (stateProvinceCode, cityName) in Cities)
- {
+ foreach ((string stateProvinceCode, string cityName) in Cities)
await CreateCityAsync(connection, cityName, stateProvinceCode);
- }
}
/// Creates a single country by invoking the dbo.USP_CreateCountry stored procedure.
@@ -300,7 +294,7 @@ internal class LocationSeeder : ISeeder
string countryCode
)
{
- await using var command = new SqlCommand(
+ await using SqlCommand command = new(
"dbo.USP_CreateCountry",
connection
);
@@ -324,7 +318,7 @@ internal class LocationSeeder : ISeeder
string countryCode
)
{
- await using var command = new SqlCommand(
+ await using SqlCommand command = new(
"dbo.USP_CreateStateProvince",
connection
);
@@ -350,7 +344,7 @@ internal class LocationSeeder : ISeeder
string stateProvinceCode
)
{
- await using var command = new SqlCommand(
+ await using SqlCommand command = new(
"dbo.USP_CreateCity",
connection
);
@@ -363,4 +357,4 @@ internal class LocationSeeder : ISeeder
await command.ExecuteNonQueryAsync();
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Database/Database.Seed/Program.cs b/web/backend/Database/Database.Seed/Program.cs
index cdb4652..47d2cd5 100644
--- a/web/backend/Database/Database.Seed/Program.cs
+++ b/web/backend/Database/Database.Seed/Program.cs
@@ -1,7 +1,5 @@
-using Microsoft.Data.SqlClient;
-using DbUp;
-using System.Reflection;
-using Database.Seed;
+using Database.Seed;
+using Microsoft.Data.SqlClient;
// Entry point for the database seeding utility. Connects to the target database
// (retrying on transient failures), then runs each registered ISeeder in order to
@@ -19,22 +17,22 @@ using Database.Seed;
///
string BuildConnectionString()
{
- var server = Environment.GetEnvironmentVariable("DB_SERVER")
- ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
+ string server = Environment.GetEnvironmentVariable("DB_SERVER")
+ ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
- var dbName = Environment.GetEnvironmentVariable("DB_NAME")
- ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
+ string dbName = Environment.GetEnvironmentVariable("DB_NAME")
+ ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
- var user = Environment.GetEnvironmentVariable("DB_USER")
- ?? throw new InvalidOperationException("DB_USER environment variable is not set");
+ string user = Environment.GetEnvironmentVariable("DB_USER")
+ ?? throw new InvalidOperationException("DB_USER environment variable is not set");
- var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
- ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
+ string password = Environment.GetEnvironmentVariable("DB_PASSWORD")
+ ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
- var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
- ?? "True";
+ string trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
+ ?? "True";
- var builder = new SqlConnectionStringBuilder
+ SqlConnectionStringBuilder builder = new()
{
DataSource = server,
InitialCatalog = dbName,
@@ -50,7 +48,7 @@ string BuildConnectionString()
try
{
- var connectionString = BuildConnectionString();
+ string connectionString = BuildConnectionString();
Console.WriteLine("Attempting to connect to database...");
@@ -60,7 +58,6 @@ try
int retryDelayMs = 2000;
for (int attempt = 1; attempt <= maxRetries; attempt++)
- {
try
{
connection = new SqlConnection(connectionString);
@@ -76,12 +73,8 @@ try
connection?.Dispose();
connection = null;
}
- }
- if (connection == null)
- {
- throw new Exception($"Failed to connect to database after {maxRetries} attempts.");
- }
+ if (connection == null) throw new Exception($"Failed to connect to database after {maxRetries} attempts.");
Console.WriteLine("Starting seeding...");
@@ -90,10 +83,10 @@ try
ISeeder[] seeders =
[
new LocationSeeder(),
- new UserSeeder(),
+ new UserSeeder()
];
- foreach (var seeder in seeders)
+ foreach (ISeeder seeder in seeders)
{
Console.WriteLine($"Seeding {seeder.GetType().Name}...");
await seeder.SeedAsync(connection);
diff --git a/web/backend/Database/Database.Seed/UserSeeder.cs b/web/backend/Database/Database.Seed/UserSeeder.cs
index 28d4efe..32b2c98 100644
--- a/web/backend/Database/Database.Seed/UserSeeder.cs
+++ b/web/backend/Database/Database.Seed/UserSeeder.cs
@@ -8,13 +8,13 @@ using Microsoft.Data.SqlClient;
namespace Database.Seed;
///
-/// Seeds user accounts, credentials, and verification records. Creates one fixed
-/// "Test User" account (test.user@thebiergarten.app / password "password" )
-/// for testing, followed by a randomly-generated account for each name in
-/// , each with a random password and date of birth. Skips
-/// adding a verification record for a user that already has one, so this seeder is
-/// safe to re-run, though re-running will still attempt to register (and may fail on)
-/// users that already exist.
+/// Seeds user accounts, credentials, and verification records. Creates one fixed
+/// "Test User" account (test.user@thebiergarten.app / password "password" )
+/// for testing, followed by a randomly-generated account for each name in
+/// , each with a random password and date of birth. Skips
+/// adding a verification record for a user that already has one, so this seeder is
+/// safe to re-run, though re-running will still attempt to register (and may fail on)
+/// users that already exist.
///
internal class UserSeeder : ISeeder
{
@@ -123,21 +123,21 @@ internal class UserSeeder : ISeeder
("Zara", "Wilkinson"),
("Zaria", "Gibson"),
("Zion", "Watkins"),
- ("Zoie", "Armstrong"),
+ ("Zoie", "Armstrong")
];
///
- /// Registers a fixed test user account followed by one randomly-generated account
- /// per entry in , adding a user verification record for each
- /// newly created account that does not already have one. Progress counts are
- /// written to the console on completion.
+ /// Registers a fixed test user account followed by one randomly-generated account
+ /// per entry in , adding a user verification record for each
+ /// newly created account that does not already have one. Progress counts are
+ /// written to the console on completion.
///
/// An open connection to the target database.
/// A task that completes when all users have been seeded.
public async Task SeedAsync(SqlConnection connection)
{
- var generator = new PasswordGenerator();
- var rng = new Random();
+ PasswordGenerator generator = new();
+ Random rng = new();
int createdUsers = 0;
int createdCredentials = 0;
int createdVerifications = 0;
@@ -147,8 +147,8 @@ internal class UserSeeder : ISeeder
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");
+ DateTime dob = new(1985, 03, 01);
+ string hash = GeneratePasswordHash("password");
await RegisterUserAsync(
connection,
@@ -160,24 +160,24 @@ internal class UserSeeder : ISeeder
hash
);
}
- foreach (var (firstName, lastName) in SeedNames)
+ foreach ((string firstName, string lastName) in SeedNames)
{
// prepare user fields
- var username = $"{firstName[0]}.{lastName}";
- var email = $"{firstName}.{lastName}@thebiergarten.app";
- var dob = GenerateDateOfBirth(rng);
+ string username = $"{firstName[0]}.{lastName}";
+ string email = $"{firstName}.{lastName}@thebiergarten.app";
+ DateTime dob = GenerateDateOfBirth(rng);
// generate a password and hash it
string pwd = generator.Generate(
- length: 64,
- numberOfDigits: 10,
- numberOfSymbols: 10
+ 64,
+ 10,
+ 10
);
string hash = GeneratePasswordHash(pwd);
// register the user (creates account + credential)
- var id = await RegisterUserAsync(
+ Guid id = await RegisterUserAsync(
connection,
username,
firstName,
@@ -203,8 +203,8 @@ internal class UserSeeder : ISeeder
}
///
- /// Registers a new user account and its credential by invoking the
- /// dbo.USP_RegisterUser stored procedure.
+ /// Registers a new user account and its credential by invoking the
+ /// dbo.USP_RegisterUser stored procedure.
///
/// An open connection to the target database.
/// The unique username for the account.
@@ -212,8 +212,8 @@ internal class UserSeeder : ISeeder
/// The user's last name.
/// The user's date of birth.
/// The user's email address.
- /// The salted password hash, as produced by .
- /// The newly created user account's identifier.
+ /// The salted password hash, as produced by .
+ /// The newly created user account's identifier.
private static async Task RegisterUserAsync(
SqlConnection connection,
string username,
@@ -224,7 +224,7 @@ internal class UserSeeder : ISeeder
string hash
)
{
- await using var command = new SqlCommand("dbo.USP_RegisterUser", connection);
+ await using SqlCommand command = new("dbo.USP_RegisterUser", connection);
command.CommandType = CommandType.StoredProcedure;
@@ -235,16 +235,16 @@ internal class UserSeeder : ISeeder
command.Parameters.Add("@Email", SqlDbType.VarChar, 128).Value = email;
command.Parameters.Add("@Hash", SqlDbType.NVarChar, -1).Value = hash;
- var result = await command.ExecuteScalarAsync();
+ object? result = await command.ExecuteScalarAsync();
return (Guid)result!;
}
///
- /// Hashes a plaintext password using Argon2id with a randomly generated 16-byte
- /// salt, a degree of parallelism matching the available processor count, 64 MB of
- /// memory, and 4 iterations.
+ /// Hashes a plaintext password using Argon2id with a randomly generated 16-byte
+ /// salt, a degree of parallelism matching the available processor count, 64 MB of
+ /// memory, and 4 iterations.
///
/// The plaintext password to hash.
/// A string containing the base64-encoded salt and hash, separated by a colon.
@@ -252,12 +252,12 @@ internal class UserSeeder : ISeeder
{
byte[] salt = RandomNumberGenerator.GetBytes(16);
- var argon2 = new Argon2id(Encoding.UTF8.GetBytes(pwd))
+ Argon2id argon2 = new(Encoding.UTF8.GetBytes(pwd))
{
Salt = salt,
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
MemorySize = 65536,
- Iterations = 4,
+ Iterations = 4
};
byte[] hash = argon2.GetBytes(32);
@@ -278,9 +278,9 @@ internal class UserSeeder : ISeeder
FROM dbo.UserVerification
WHERE UserAccountId = @UserAccountId;
""";
- await using var command = new SqlCommand(sql, connection);
+ await using SqlCommand command = new(sql, connection);
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
- var result = await command.ExecuteScalarAsync();
+ object? result = await command.ExecuteScalarAsync();
return result is not null;
}
@@ -293,7 +293,7 @@ internal class UserSeeder : ISeeder
Guid userAccountId
)
{
- await using var command = new SqlCommand(
+ await using SqlCommand command = new(
"dbo.USP_CreateUserVerification",
connection
);
@@ -304,8 +304,8 @@ internal class UserSeeder : ISeeder
}
///
- /// Generates a random date of birth corresponding to an age between 19 and 48
- /// years (inclusive), with a random day offset within that birth year.
+ /// Generates a random date of birth corresponding to an age between 19 and 48
+ /// years (inclusive), with a random day offset within that birth year.
///
/// The random number source to use.
/// A randomly generated date of birth.
@@ -316,4 +316,4 @@ internal class UserSeeder : ISeeder
int offsetDays = random.Next(0, 365);
return baseDate.AddDays(-offsetDays);
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Domain/Domain.Entities/Domain.Entities.csproj b/web/backend/Domain/Domain.Entities/Domain.Entities.csproj
index 424ac3a..976a018 100644
--- a/web/backend/Domain/Domain.Entities/Domain.Entities.csproj
+++ b/web/backend/Domain/Domain.Entities/Domain.Entities.csproj
@@ -1,8 +1,8 @@
-
- net10.0
- enable
- enable
- Domain
-
+
+ net10.0
+ enable
+ enable
+ Domain
+
diff --git a/web/backend/Domain/Domain.Entities/Entities/BreweryPost.cs b/web/backend/Domain/Domain.Entities/Entities/BreweryPost.cs
index 36d5299..0594a9b 100644
--- a/web/backend/Domain/Domain.Entities/Entities/BreweryPost.cs
+++ b/web/backend/Domain/Domain.Entities/Entities/BreweryPost.cs
@@ -1,48 +1,50 @@
namespace Domain.Entities;
///
-/// Represents a user-submitted post about a brewery. Maps to the BreweryPost table.
-/// A user account cannot be deleted while it has an associated brewery post.
+/// Represents a user-submitted post about a brewery. Maps to the BreweryPost table.
+/// A user account cannot be deleted while it has an associated brewery post.
///
public class BreweryPost
{
///
- /// Primary key identifying this brewery post. Maps to BreweryPostID .
+ /// Primary key identifying this brewery post. Maps to BreweryPostID .
///
public Guid BreweryPostId { get; set; }
///
- /// Foreign key referencing the that authored this post. Maps to PostedByID .
+ /// Foreign key referencing the that authored this post. Maps to PostedByID .
///
public Guid PostedById { get; set; }
///
- /// The name of the brewery being posted about.
+ /// The name of the brewery being posted about.
///
public string BreweryName { get; set; } = string.Empty;
///
- /// Free-text description of the brewery.
+ /// Free-text description of the brewery.
///
public string Description { get; set; } = string.Empty;
///
- /// The date and time the post was created.
+ /// The date and time the post was created.
///
public DateTime CreatedAt { get; set; }
///
- /// The date and time the post was last updated, or null if it has never been updated.
+ /// The date and time the post was last updated, or null if it has never been updated.
///
public DateTime? UpdatedAt { get; set; }
///
- /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has been read from the database.
+ /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has
+ /// been read from the database.
///
public byte[]? Timer { get; set; }
///
- /// The associated for this post, if one has been set. This is a one-to-one navigation property.
+ /// The associated for this post, if one has been set. This is a one-to-one
+ /// navigation property.
///
public BreweryPostLocation? Location { get; set; }
-}
+}
\ No newline at end of file
diff --git a/web/backend/Domain/Domain.Entities/Entities/BreweryPostLocation.cs b/web/backend/Domain/Domain.Entities/Entities/BreweryPostLocation.cs
index 8da3311..c59917d 100644
--- a/web/backend/Domain/Domain.Entities/Entities/BreweryPostLocation.cs
+++ b/web/backend/Domain/Domain.Entities/Entities/BreweryPostLocation.cs
@@ -1,48 +1,52 @@
namespace Domain.Entities;
///
-/// Represents the physical location of a . Maps to the BreweryPostLocation table.
-/// Each brewery post has at most one location, and the location is deleted automatically when its parent post is deleted.
+/// Represents the physical location of a . Maps to the BreweryPostLocation table.
+/// Each brewery post has at most one location, and the location is deleted automatically when its parent post is
+/// deleted.
///
public class BreweryPostLocation
{
///
- /// Primary key identifying this location. Maps to BreweryPostLocationID .
+ /// Primary key identifying this location. Maps to BreweryPostLocationID .
///
public Guid BreweryPostLocationId { get; set; }
///
- /// Foreign key referencing the owning . Maps to BreweryPostID ; unique, enforcing a one-to-one relationship.
+ /// Foreign key referencing the owning . Maps to BreweryPostID ; unique, enforcing a
+ /// one-to-one relationship.
///
public Guid BreweryPostId { get; set; }
///
- /// The primary street address line.
+ /// The primary street address line.
///
public string AddressLine1 { get; set; } = string.Empty;
///
- /// An optional secondary address line (e.g., suite or unit number).
+ /// An optional secondary address line (e.g., suite or unit number).
///
public string? AddressLine2 { get; set; }
///
- /// The postal/ZIP code of the location.
+ /// The postal/ZIP code of the location.
///
public string PostalCode { get; set; } = string.Empty;
///
- /// Foreign key referencing the city in which the brewery is located. Maps to CityID .
+ /// Foreign key referencing the city in which the brewery is located. Maps to CityID .
///
public Guid CityId { get; set; }
///
- /// Serialized SQL Server GEOGRAPHY value representing the geographic coordinates of the location, or null if not set.
+ /// Serialized SQL Server GEOGRAPHY value representing the geographic coordinates of the location, or
+ /// null if not set.
///
public byte[]? Coordinates { get; set; }
///
- /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has been read from the database.
+ /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has
+ /// been read from the database.
///
public byte[]? Timer { get; set; }
-}
+}
\ No newline at end of file
diff --git a/web/backend/Domain/Domain.Entities/Entities/UserAccount.cs b/web/backend/Domain/Domain.Entities/Entities/UserAccount.cs
index 4c63f58..acc5090 100644
--- a/web/backend/Domain/Domain.Entities/Entities/UserAccount.cs
+++ b/web/backend/Domain/Domain.Entities/Entities/UserAccount.cs
@@ -1,53 +1,55 @@
namespace Domain.Entities;
///
-/// Represents a registered user of the application. Maps to the UserAccount table, the root entity
-/// referenced by , , brewery posts, and other user-owned data.
+/// Represents a registered user of the application. Maps to the UserAccount table, the root entity
+/// referenced by , , brewery posts, and other user-owned
+/// data.
///
public class UserAccount
{
///
- /// Primary key identifying this user account. Maps to UserAccountID .
+ /// Primary key identifying this user account. Maps to UserAccountID .
///
public Guid UserAccountId { get; set; }
///
- /// The user's unique username, used for login and display. Enforced unique at the database level.
+ /// The user's unique username, used for login and display. Enforced unique at the database level.
///
public string Username { get; set; } = string.Empty;
///
- /// The user's first name.
+ /// The user's first name.
///
public string FirstName { get; set; } = string.Empty;
///
- /// The user's last name.
+ /// The user's last name.
///
public string LastName { get; set; } = string.Empty;
///
- /// The user's email address. Enforced unique at the database level.
+ /// The user's email address. Enforced unique at the database level.
///
public string Email { get; set; } = string.Empty;
///
- /// The date and time the account was created.
+ /// The date and time the account was created.
///
public DateTime CreatedAt { get; set; }
///
- /// The date and time the account was last updated, or null if it has never been updated.
+ /// The date and time the account was last updated, or null if it has never been updated.
///
public DateTime? UpdatedAt { get; set; }
///
- /// The user's date of birth.
+ /// The user's date of birth.
///
public DateTime DateOfBirth { get; set; }
///
- /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has been read from the database.
+ /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has
+ /// been read from the database.
///
public byte[]? Timer { get; set; }
-}
+}
\ No newline at end of file
diff --git a/web/backend/Domain/Domain.Entities/Entities/UserCredential.cs b/web/backend/Domain/Domain.Entities/Entities/UserCredential.cs
index 9f1b337..4be575e 100644
--- a/web/backend/Domain/Domain.Entities/Entities/UserCredential.cs
+++ b/web/backend/Domain/Domain.Entities/Entities/UserCredential.cs
@@ -1,38 +1,40 @@
namespace Domain.Entities;
///
-/// Represents an issued authentication credential (refresh token) for a .
-/// Maps to the UserCredential table. Credentials are deleted automatically when the owning user account is deleted.
+/// Represents an issued authentication credential (refresh token) for a .
+/// Maps to the UserCredential table. Credentials are deleted automatically when the owning user account is
+/// deleted.
///
public class UserCredential
{
///
- /// Primary key identifying this credential. Maps to UserCredentialID .
+ /// Primary key identifying this credential. Maps to UserCredentialID .
///
public Guid UserCredentialId { get; set; }
///
- /// Foreign key referencing the owning . Maps to UserAccountID .
+ /// Foreign key referencing the owning . Maps to UserAccountID .
///
public Guid UserAccountId { get; set; }
///
- /// The date and time the credential was issued.
+ /// The date and time the credential was issued.
///
public DateTime CreatedAt { get; set; }
///
- /// The date and time after which the credential is no longer valid.
+ /// The date and time after which the credential is no longer valid.
///
public DateTime Expiry { get; set; }
///
- /// The Argon2 hash of the credential's secret value. The plaintext secret is never persisted.
+ /// The Argon2 hash of the credential's secret value. The plaintext secret is never persisted.
///
public string Hash { get; set; } = string.Empty;
///
- /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has been read from the database.
+ /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has
+ /// been read from the database.
///
public byte[]? Timer { get; set; }
-}
+}
\ No newline at end of file
diff --git a/web/backend/Domain/Domain.Entities/Entities/UserVerification.cs b/web/backend/Domain/Domain.Entities/Entities/UserVerification.cs
index 132b773..abd5e0e 100644
--- a/web/backend/Domain/Domain.Entities/Entities/UserVerification.cs
+++ b/web/backend/Domain/Domain.Entities/Entities/UserVerification.cs
@@ -1,29 +1,31 @@
namespace Domain.Entities;
///
-/// Records that a has completed verification (e.g., email confirmation).
-/// Maps to the UserVerification table. A user account has at most one verification record,
-/// and it is deleted automatically when the owning user account is deleted.
+/// Records that a has completed verification (e.g., email confirmation).
+/// Maps to the UserVerification table. A user account has at most one verification record,
+/// and it is deleted automatically when the owning user account is deleted.
///
public class UserVerification
{
///
- /// Primary key identifying this verification record. Maps to UserVerificationID .
+ /// Primary key identifying this verification record. Maps to UserVerificationID .
///
public Guid UserVerificationId { get; set; }
///
- /// Foreign key referencing the verified . Maps to UserAccountID ; unique, enforcing a one-to-one relationship.
+ /// Foreign key referencing the verified . Maps to UserAccountID ; unique, enforcing a
+ /// one-to-one relationship.
///
public Guid UserAccountId { get; set; }
///
- /// The date and time at which the user account was verified.
+ /// The date and time at which the user account was verified.
///
public DateTime VerificationDateTime { get; set; }
///
- /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has been read from the database.
+ /// SQL Server ROWVERSION concurrency token used to detect concurrent updates. null until the row has
+ /// been read from the database.
///
public byte[]? Timer { get; set; }
-}
+}
\ No newline at end of file
diff --git a/web/backend/Domain/Domain.Exceptions/Exceptions.cs b/web/backend/Domain/Domain.Exceptions/Exceptions.cs
index 209880c..cf6964b 100644
--- a/web/backend/Domain/Domain.Exceptions/Exceptions.cs
+++ b/web/backend/Domain/Domain.Exceptions/Exceptions.cs
@@ -1,33 +1,33 @@
namespace Domain.Exceptions;
///
-/// Exception thrown when a resource conflict occurs (e.g., duplicate username, email already in use).
-/// Maps to HTTP 409 Conflict.
+/// Exception thrown when a resource conflict occurs (e.g., duplicate username, email already in use).
+/// Maps to HTTP 409 Conflict.
///
public class ConflictException(string message) : Exception(message);
///
-/// Exception thrown when a requested resource is not found.
-/// Maps to HTTP 404 Not Found.
+/// Exception thrown when a requested resource is not found.
+/// Maps to HTTP 404 Not Found.
///
public class NotFoundException(string message) : Exception(message);
// Domain.Exceptions/UnauthorizedException.cs
///
-/// Exception thrown when authentication fails or is required.
-/// Maps to HTTP 401 Unauthorized.
+/// Exception thrown when authentication fails or is required.
+/// Maps to HTTP 401 Unauthorized.
///
public class UnauthorizedException(string message) : Exception(message);
///
-/// Exception thrown when a user is authenticated but lacks permission to access a resource.
-/// Maps to HTTP 403 Forbidden.
+/// Exception thrown when a user is authenticated but lacks permission to access a resource.
+/// Maps to HTTP 403 Forbidden.
///
public class ForbiddenException(string message) : Exception(message);
///
-/// Exception thrown when business rule validation fails (distinct from FluentValidation).
-/// Maps to HTTP 400 Bad Request.
+/// Exception thrown when business rule validation fails (distinct from FluentValidation).
+/// Maps to HTTP 400 Bad Request.
///
public class ValidationException(string message) : Exception(message);
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Commands/ConfirmUserHandlerTests.cs b/web/backend/Features/Features.Auth.Tests/Commands/ConfirmUserHandlerTests.cs
index 9c54b8a..b6988a4 100644
--- a/web/backend/Features/Features.Auth.Tests/Commands/ConfirmUserHandlerTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Commands/ConfirmUserHandlerTests.cs
@@ -2,10 +2,11 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions;
-using FluentAssertions;
using Features.Auth.Commands.ConfirmUser;
+using Features.Auth.Dtos;
using Features.Auth.Repository;
using Features.Auth.Services;
+using FluentAssertions;
using Moq;
namespace Features.Auth.Tests.Commands;
@@ -13,8 +14,8 @@ namespace Features.Auth.Tests.Commands;
public class ConfirmUserHandlerTests
{
private readonly Mock _authRepositoryMock;
- private readonly Mock _tokenServiceMock;
private readonly ConfirmUserHandler _handler;
+ private readonly Mock _tokenServiceMock;
public ConfirmUserHandlerTests()
{
@@ -25,30 +26,30 @@ public class ConfirmUserHandlerTests
private static ValidatedToken MakeValidatedToken(Guid userId, string username)
{
- var claims = new List
+ List claims = new()
{
- new(JwtRegisteredClaimNames.Sub, userId.ToString()),
- new(JwtRegisteredClaimNames.UniqueName, username),
- new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
+ new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
+ new Claim(JwtRegisteredClaimNames.UniqueName, username),
+ new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
- var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));
+ ClaimsPrincipal principal = new(new ClaimsIdentity(claims));
return new ValidatedToken(userId, username, principal);
}
[Fact]
public async Task Handle_WithValidConfirmationToken_ConfirmsUser()
{
- var userId = Guid.NewGuid();
+ Guid userId = Guid.NewGuid();
const string username = "testuser";
const string confirmationToken = "valid-confirmation-token";
- var validatedToken = MakeValidatedToken(userId, username);
- var userAccount = new UserAccount { UserAccountId = userId, Username = username };
+ ValidatedToken validatedToken = MakeValidatedToken(userId, username);
+ UserAccount userAccount = new() { UserAccountId = userId, Username = username };
_tokenServiceMock.Setup(x => x.ValidateConfirmationTokenAsync(confirmationToken)).ReturnsAsync(validatedToken);
_authRepositoryMock.Setup(x => x.ConfirmUserAccountAsync(userId)).ReturnsAsync(userAccount);
- var result = await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
+ ConfirmationPayload result = await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
result.Should().NotBeNull();
result.UserAccountId.Should().Be(userId);
@@ -63,7 +64,7 @@ public class ConfirmUserHandlerTests
.Setup(x => x.ValidateConfirmationTokenAsync(invalidToken))
.ThrowsAsync(new UnauthorizedException("Invalid confirmation token"));
- var act = async () => await _handler.Handle(new ConfirmUserCommand(invalidToken), CancellationToken.None);
+ Func> act = async () => await _handler.Handle(new ConfirmUserCommand(invalidToken), CancellationToken.None);
await act.Should().ThrowAsync();
}
@@ -71,7 +72,7 @@ public class ConfirmUserHandlerTests
[Fact]
public async Task Handle_WithNonExistentUser_ThrowsUnauthorizedException()
{
- var userId = Guid.NewGuid();
+ Guid userId = Guid.NewGuid();
const string username = "nonexistent";
const string confirmationToken = "valid-token-for-nonexistent-user";
@@ -80,8 +81,8 @@ public class ConfirmUserHandlerTests
.ReturnsAsync(MakeValidatedToken(userId, username));
_authRepositoryMock.Setup(x => x.ConfirmUserAccountAsync(userId)).ReturnsAsync((UserAccount?)null);
- var act = async () => await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
+ Func> act = async () => await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
await act.Should().ThrowAsync().WithMessage("*User account not found*");
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs b/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs
index 35b9e07..610a23f 100644
--- a/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Commands/RefreshTokenHandlerTests.cs
@@ -1,7 +1,8 @@
using Domain.Entities;
-using FluentAssertions;
using Features.Auth.Commands.RefreshToken;
+using Features.Auth.Dtos;
using Features.Auth.Services;
+using FluentAssertions;
using Moq;
namespace Features.Auth.Tests.Commands;
@@ -11,20 +12,20 @@ public class RefreshTokenHandlerTests
[Fact]
public async Task Handle_MapsTokenServiceResult_ToLoginPayload()
{
- var tokenServiceMock = new Mock();
- var handler = new RefreshTokenHandler(tokenServiceMock.Object);
- var userId = Guid.NewGuid();
- var user = new UserAccount { UserAccountId = userId, Username = "testuser" };
+ Mock tokenServiceMock = new();
+ RefreshTokenHandler handler = new(tokenServiceMock.Object);
+ Guid userId = Guid.NewGuid();
+ UserAccount user = new() { UserAccountId = userId, Username = "testuser" };
tokenServiceMock
.Setup(x => x.RefreshTokenAsync("old-refresh-token"))
.ReturnsAsync(new RefreshTokenResult(user, "new-refresh-token", "new-access-token"));
- var result = await handler.Handle(new RefreshTokenCommand("old-refresh-token"), CancellationToken.None);
+ LoginPayload result = await handler.Handle(new RefreshTokenCommand("old-refresh-token"), CancellationToken.None);
result.UserAccountId.Should().Be(userId);
result.Username.Should().Be("testuser");
result.RefreshToken.Should().Be("new-refresh-token");
result.AccessToken.Should().Be("new-access-token");
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Commands/RegisterUserHandlerTests.cs b/web/backend/Features/Features.Auth.Tests/Commands/RegisterUserHandlerTests.cs
index d1e6cd4..c24bfbc 100644
--- a/web/backend/Features/Features.Auth.Tests/Commands/RegisterUserHandlerTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Commands/RegisterUserHandlerTests.cs
@@ -1,9 +1,10 @@
using Domain.Entities;
using Domain.Exceptions;
-using FluentAssertions;
using Features.Auth.Commands.RegisterUser;
+using Features.Auth.Dtos;
using Features.Auth.Repository;
using Features.Auth.Services;
+using FluentAssertions;
using Infrastructure.PasswordHashing;
using MediatR;
using Moq;
@@ -14,10 +15,10 @@ namespace Features.Auth.Tests.Commands;
public class RegisterUserHandlerTests
{
private readonly Mock _authRepoMock;
+ private readonly RegisterUserHandler _handler;
+ private readonly Mock _mediatorMock;
private readonly Mock _passwordInfraMock;
private readonly Mock _tokenServiceMock;
- private readonly Mock _mediatorMock;
- private readonly RegisterUserHandler _handler;
public RegisterUserHandlerTests()
{
@@ -34,22 +35,25 @@ public class RegisterUserHandlerTests
);
}
- private static RegisterUserCommand ValidCommand(string username = "newuser", string email = "john.doe@example.com") =>
- new(username, "John", "Doe", email, new DateTime(1990, 1, 1), "SecurePassword123!");
+ private static RegisterUserCommand ValidCommand(string username = "newuser", string email = "john.doe@example.com")
+ {
+ return new RegisterUserCommand(username, "John", "Doe", email, new DateTime(1990, 1, 1), "SecurePassword123!");
+ }
[Fact]
public async Task Handle_WithValidData_CreatesUserAndReturnsPayload()
{
- var command = ValidCommand();
+ RegisterUserCommand command = ValidCommand();
const string hashedPassword = "hashed_password_value";
- var expectedUserId = Guid.NewGuid();
+ Guid expectedUserId = Guid.NewGuid();
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(command.Username)).ReturnsAsync((UserAccount?)null);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(command.Email)).ReturnsAsync((UserAccount?)null);
_passwordInfraMock.Setup(x => x.Hash(command.Password)).Returns(hashedPassword);
_authRepoMock
- .Setup(x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email, command.DateOfBirth, hashedPassword))
+ .Setup(x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email,
+ command.DateOfBirth, hashedPassword))
.ReturnsAsync(new UserAccount
{
UserAccountId = expectedUserId,
@@ -58,14 +62,15 @@ public class RegisterUserHandlerTests
LastName = command.LastName,
Email = command.Email,
DateOfBirth = command.DateOfBirth,
- CreatedAt = DateTime.UtcNow,
+ CreatedAt = DateTime.UtcNow
});
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny())).Returns("access-token");
_tokenServiceMock.Setup(x => x.GenerateRefreshToken(It.IsAny())).Returns("refresh-token");
- _tokenServiceMock.Setup(x => x.GenerateConfirmationToken(It.IsAny())).Returns("confirmation-token");
+ _tokenServiceMock.Setup(x => x.GenerateConfirmationToken(It.IsAny()))
+ .Returns("confirmation-token");
- var result = await _handler.Handle(command, CancellationToken.None);
+ RegistrationPayload result = await _handler.Handle(command, CancellationToken.None);
result.Should().NotBeNull();
result.UserAccountId.Should().Be(expectedUserId);
@@ -83,18 +88,19 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_WithExistingUsername_ThrowsConflictException()
{
- var command = ValidCommand(username: "existinguser");
- var existingUser = new UserAccount { UserAccountId = Guid.NewGuid(), Username = "existinguser" };
+ RegisterUserCommand command = ValidCommand("existinguser");
+ UserAccount existingUser = new() { UserAccountId = Guid.NewGuid(), Username = "existinguser" };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(command.Username)).ReturnsAsync(existingUser);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(command.Email)).ReturnsAsync((UserAccount?)null);
- var act = async () => await _handler.Handle(command, CancellationToken.None);
+ Func> act = async () => await _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync().WithMessage("Username or email already exists");
_authRepoMock.Verify(
- x => x.RegisterUserAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()),
+ x => x.RegisterUserAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),
+ It.IsAny(), It.IsAny()),
Times.Never
);
}
@@ -102,13 +108,13 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_WithExistingEmail_ThrowsConflictException()
{
- var command = ValidCommand(email: "existing@example.com");
- var existingUser = new UserAccount { UserAccountId = Guid.NewGuid(), Email = "existing@example.com" };
+ RegisterUserCommand command = ValidCommand(email: "existing@example.com");
+ UserAccount existingUser = new() { UserAccountId = Guid.NewGuid(), Email = "existing@example.com" };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(command.Username)).ReturnsAsync((UserAccount?)null);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(command.Email)).ReturnsAsync(existingUser);
- var act = async () => await _handler.Handle(command, CancellationToken.None);
+ Func> act = async () => await _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync().WithMessage("Username or email already exists");
}
@@ -116,7 +122,7 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_PasswordIsHashed_BeforeStoringInDatabase()
{
- var command = ValidCommand();
+ RegisterUserCommand command = ValidCommand();
const string hashedPassword = "hashed_secure_password";
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(It.IsAny())).ReturnsAsync((UserAccount?)null);
@@ -124,7 +130,8 @@ public class RegisterUserHandlerTests
_passwordInfraMock.Setup(x => x.Hash(command.Password)).Returns(hashedPassword);
_authRepoMock
- .Setup(x => x.RegisterUserAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), hashedPassword))
+ .Setup(x => x.RegisterUserAsync(It.IsAny(), It.IsAny(), It.IsAny(),
+ It.IsAny(), It.IsAny(), hashedPassword))
.ReturnsAsync(new UserAccount { UserAccountId = Guid.NewGuid() });
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny())).Returns("access-token");
@@ -134,7 +141,8 @@ public class RegisterUserHandlerTests
_passwordInfraMock.Verify(x => x.Hash(command.Password), Times.Once);
_authRepoMock.Verify(
- x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email, command.DateOfBirth, hashedPassword),
+ x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email,
+ command.DateOfBirth, hashedPassword),
Times.Once
);
}
@@ -142,14 +150,16 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_SwallowsEmailFailure_AndReportsEmailNotSent()
{
- var command = ValidCommand();
+ RegisterUserCommand command = ValidCommand();
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(It.IsAny())).ReturnsAsync((UserAccount?)null);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(It.IsAny())).ReturnsAsync((UserAccount?)null);
_passwordInfraMock.Setup(x => x.Hash(It.IsAny())).Returns("hashed");
_authRepoMock
- .Setup(x => x.RegisterUserAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .ReturnsAsync(new UserAccount { UserAccountId = Guid.NewGuid(), Username = command.Username, Email = command.Email });
+ .Setup(x => x.RegisterUserAsync(It.IsAny(), It.IsAny(), It.IsAny(),
+ It.IsAny(), It.IsAny(), It.IsAny()))
+ .ReturnsAsync(new UserAccount
+ { UserAccountId = Guid.NewGuid(), Username = command.Username, Email = command.Email });
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny())).Returns("access-token");
_tokenServiceMock.Setup(x => x.GenerateRefreshToken(It.IsAny())).Returns("refresh-token");
@@ -157,9 +167,9 @@ public class RegisterUserHandlerTests
.Setup(x => x.Send(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception("smtp down"));
- var result = await _handler.Handle(command, CancellationToken.None);
+ RegistrationPayload result = await _handler.Handle(command, CancellationToken.None);
result.ConfirmationEmailSent.Should().BeFalse();
result.AccessToken.Should().Be("access-token");
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Commands/ResendConfirmationEmailHandlerTests.cs b/web/backend/Features/Features.Auth.Tests/Commands/ResendConfirmationEmailHandlerTests.cs
index 31e63f7..50289cd 100644
--- a/web/backend/Features/Features.Auth.Tests/Commands/ResendConfirmationEmailHandlerTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Commands/ResendConfirmationEmailHandlerTests.cs
@@ -11,20 +11,21 @@ namespace Features.Auth.Tests.Commands;
public class ResendConfirmationEmailHandlerTests
{
private readonly Mock _authRepositoryMock = new();
- private readonly Mock _tokenServiceMock = new();
- private readonly Mock _mediatorMock = new();
private readonly ResendConfirmationEmailHandler _handler;
+ private readonly Mock _mediatorMock = new();
+ private readonly Mock _tokenServiceMock = new();
public ResendConfirmationEmailHandlerTests()
{
- _handler = new ResendConfirmationEmailHandler(_authRepositoryMock.Object, _tokenServiceMock.Object, _mediatorMock.Object);
+ _handler = new ResendConfirmationEmailHandler(_authRepositoryMock.Object, _tokenServiceMock.Object,
+ _mediatorMock.Object);
}
[Fact]
public async Task Handle_SendsFreshConfirmationEmail_WhenUserExistsAndUnverified()
{
- var userId = Guid.NewGuid();
- var user = new UserAccount { UserAccountId = userId, FirstName = "Aaron", Email = "aaron@example.com" };
+ Guid userId = Guid.NewGuid();
+ UserAccount user = new() { UserAccountId = userId, FirstName = "Aaron", Email = "aaron@example.com" };
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(user);
_authRepositoryMock.Setup(x => x.IsUserVerifiedAsync(userId)).ReturnsAsync(false);
@@ -43,7 +44,7 @@ public class ResendConfirmationEmailHandlerTests
[Fact]
public async Task Handle_DoesNothing_WhenUserDoesNotExist()
{
- var userId = Guid.NewGuid();
+ Guid userId = Guid.NewGuid();
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync((UserAccount?)null);
await _handler.Handle(new ResendConfirmationEmailCommand(userId), CancellationToken.None);
@@ -57,8 +58,8 @@ public class ResendConfirmationEmailHandlerTests
[Fact]
public async Task Handle_DoesNothing_WhenUserAlreadyVerified()
{
- var userId = Guid.NewGuid();
- var user = new UserAccount { UserAccountId = userId };
+ Guid userId = Guid.NewGuid();
+ UserAccount user = new() { UserAccountId = userId };
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(user);
_authRepositoryMock.Setup(x => x.IsUserVerifiedAsync(userId)).ReturnsAsync(true);
@@ -70,4 +71,4 @@ public class ResendConfirmationEmailHandlerTests
Times.Never
);
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Features.Auth.Tests.csproj b/web/backend/Features/Features.Auth.Tests/Features.Auth.Tests.csproj
index b102ed2..c788b58 100644
--- a/web/backend/Features/Features.Auth.Tests/Features.Auth.Tests.csproj
+++ b/web/backend/Features/Features.Auth.Tests/Features.Auth.Tests.csproj
@@ -1,26 +1,26 @@
-
- net10.0
- enable
- enable
- false
- Features.Auth.Tests
-
+
+ net10.0
+ enable
+ enable
+ false
+ Features.Auth.Tests
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/web/backend/Features/Features.Auth.Tests/Queries/LoginHandlerTests.cs b/web/backend/Features/Features.Auth.Tests/Queries/LoginHandlerTests.cs
index 80b91d4..64d6e01 100644
--- a/web/backend/Features/Features.Auth.Tests/Queries/LoginHandlerTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Queries/LoginHandlerTests.cs
@@ -1,9 +1,10 @@
using Domain.Entities;
using Domain.Exceptions;
-using FluentAssertions;
+using Features.Auth.Dtos;
using Features.Auth.Queries.Login;
using Features.Auth.Repository;
using Features.Auth.Services;
+using FluentAssertions;
using Infrastructure.PasswordHashing;
using Moq;
@@ -12,9 +13,9 @@ namespace Features.Auth.Tests.Queries;
public class LoginHandlerTests
{
private readonly Mock _authRepoMock;
+ private readonly LoginHandler _handler;
private readonly Mock _passwordInfraMock;
private readonly Mock _tokenServiceMock;
- private readonly LoginHandler _handler;
public LoginHandlerTests()
{
@@ -28,24 +29,24 @@ public class LoginHandlerTests
public async Task Handle_WithValidData_ReturnsPayloadWithMatchingUsername()
{
const string username = "CogitoErgoSum";
- var userAccountId = Guid.NewGuid();
+ Guid userAccountId = Guid.NewGuid();
- var userAccount = new UserAccount
+ UserAccount userAccount = new()
{
UserAccountId = userAccountId,
Username = username,
FirstName = "René",
LastName = "Descartes",
Email = "r.descartes@example.com",
- DateOfBirth = new DateTime(1596, 03, 31),
+ DateOfBirth = new DateTime(1596, 03, 31)
};
- var userCredential = new UserCredential
+ UserCredential userCredential = new()
{
UserCredentialId = Guid.NewGuid(),
UserAccountId = userAccountId,
Hash = "some-hash",
- Expiry = DateTime.MaxValue,
+ Expiry = DateTime.MaxValue
};
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync(userAccount);
@@ -54,7 +55,7 @@ public class LoginHandlerTests
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny())).Returns("access-token");
_tokenServiceMock.Setup(x => x.GenerateRefreshToken(It.IsAny())).Returns("refresh-token");
- var result = await _handler.Handle(new LoginQuery(username, "any-password"), CancellationToken.None);
+ LoginPayload result = await _handler.Handle(new LoginQuery(username, "any-password"), CancellationToken.None);
result.Should().NotBeNull();
result.UserAccountId.Should().Be(userAccountId);
@@ -69,7 +70,7 @@ public class LoginHandlerTests
const string username = "de_beauvoir";
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync((UserAccount?)null);
- var act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
+ Func> act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
await act.Should().ThrowAsync();
_authRepoMock.Verify(x => x.GetActiveCredentialByUserAccountIdAsync(It.IsAny()), Times.Never);
@@ -79,13 +80,14 @@ public class LoginHandlerTests
public async Task Handle_WithNoActiveCredential_ThrowsUnauthorizedException()
{
const string username = "BRussell";
- var userAccountId = Guid.NewGuid();
- var userAccount = new UserAccount { UserAccountId = userAccountId, Username = username };
+ Guid userAccountId = Guid.NewGuid();
+ UserAccount userAccount = new() { UserAccountId = userAccountId, Username = username };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync(userAccount);
- _authRepoMock.Setup(x => x.GetActiveCredentialByUserAccountIdAsync(userAccountId)).ReturnsAsync((UserCredential?)null);
+ _authRepoMock.Setup(x => x.GetActiveCredentialByUserAccountIdAsync(userAccountId))
+ .ReturnsAsync((UserCredential?)null);
- var act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
+ Func> act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
await act.Should().ThrowAsync().WithMessage("Invalid username or password.");
_passwordInfraMock.Verify(x => x.Verify(It.IsAny(), It.IsAny()), Times.Never);
@@ -95,16 +97,16 @@ public class LoginHandlerTests
public async Task Handle_WithIncorrectPassword_ThrowsUnauthorizedException()
{
const string username = "RCarnap";
- var userAccountId = Guid.NewGuid();
- var userAccount = new UserAccount { UserAccountId = userAccountId, Username = username };
- var userCredential = new UserCredential { UserCredentialId = Guid.NewGuid(), UserAccountId = userAccountId, Hash = "hashed-password" };
+ Guid userAccountId = Guid.NewGuid();
+ UserAccount userAccount = new() { UserAccountId = userAccountId, Username = username };
+ UserCredential userCredential = new() { UserCredentialId = Guid.NewGuid(), UserAccountId = userAccountId, Hash = "hashed-password" };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync(userAccount);
_authRepoMock.Setup(x => x.GetActiveCredentialByUserAccountIdAsync(userAccountId)).ReturnsAsync(userCredential);
_passwordInfraMock.Setup(x => x.Verify(It.IsAny(), It.IsAny())).Returns(false);
- var act = async () => await _handler.Handle(new LoginQuery(username, "wrong-password"), CancellationToken.None);
+ Func> act = async () => await _handler.Handle(new LoginQuery(username, "wrong-password"), CancellationToken.None);
await act.Should().ThrowAsync().WithMessage("Invalid username or password.");
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Repository/AuthRepositoryTests.cs b/web/backend/Features/Features.Auth.Tests/Repository/AuthRepositoryTests.cs
index 9b8446f..b1e133c 100644
--- a/web/backend/Features/Features.Auth.Tests/Repository/AuthRepositoryTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Repository/AuthRepositoryTests.cs
@@ -1,19 +1,22 @@
using Apps72.Dev.Data.DbMocker;
-using FluentAssertions;
+using Domain.Entities;
using Features.Auth.Repository;
+using FluentAssertions;
namespace Features.Auth.Tests.Repository;
public class AuthRepositoryTests
{
- private static AuthRepository CreateRepo(MockDbConnection conn) =>
- new(new TestConnectionFactory(conn));
+ private static AuthRepository CreateRepo(MockDbConnection conn)
+ {
+ return new AuthRepository(new TestConnectionFactory(conn));
+ }
[Fact]
public async Task RegisterUserAsync_CreatesUserWithCredential_ReturnsUserAccount()
{
- var expectedUserId = Guid.NewGuid();
- var conn = new MockDbConnection();
+ Guid expectedUserId = Guid.NewGuid();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "USP_RegisterUser")
.ReturnsScalar(expectedUserId);
@@ -46,14 +49,14 @@ public class AuthRepositoryTests
)
);
- var repo = CreateRepo(conn);
- var result = await repo.RegisterUserAsync(
- username: "testuser",
- firstName: "Test",
- lastName: "User",
- email: "test@example.com",
- dateOfBirth: new DateTime(1990, 1, 1),
- passwordHash: "hashedpassword123"
+ AuthRepository repo = CreateRepo(conn);
+ UserAccount result = await repo.RegisterUserAsync(
+ "testuser",
+ "Test",
+ "User",
+ "test@example.com",
+ new DateTime(1990, 1, 1),
+ "hashedpassword123"
);
result.Should().NotBeNull();
@@ -68,8 +71,8 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByEmailAsync_ReturnsUser_WhenExists()
{
- var userId = Guid.NewGuid();
- var conn = new MockDbConnection();
+ Guid userId = Guid.NewGuid();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
.ReturnsTable(
@@ -98,8 +101,8 @@ public class AuthRepositoryTests
)
);
- var repo = CreateRepo(conn);
- var result = await repo.GetUserByEmailAsync("emailuser@example.com");
+ AuthRepository repo = CreateRepo(conn);
+ UserAccount? result = await repo.GetUserByEmailAsync("emailuser@example.com");
result.Should().NotBeNull();
result!.UserAccountId.Should().Be(userId);
@@ -112,13 +115,13 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByEmailAsync_ReturnsNull_WhenNotExists()
{
- var conn = new MockDbConnection();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
.ReturnsTable(MockTable.Empty());
- var repo = CreateRepo(conn);
- var result = await repo.GetUserByEmailAsync("nonexistent@example.com");
+ AuthRepository repo = CreateRepo(conn);
+ UserAccount? result = await repo.GetUserByEmailAsync("nonexistent@example.com");
result.Should().BeNull();
}
@@ -126,8 +129,8 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByUsernameAsync_ReturnsUser_WhenExists()
{
- var userId = Guid.NewGuid();
- var conn = new MockDbConnection();
+ Guid userId = Guid.NewGuid();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "usp_GetUserAccountByUsername"
@@ -158,8 +161,8 @@ public class AuthRepositoryTests
)
);
- var repo = CreateRepo(conn);
- var result = await repo.GetUserByUsernameAsync("usernameuser");
+ AuthRepository repo = CreateRepo(conn);
+ UserAccount? result = await repo.GetUserByUsernameAsync("usernameuser");
result.Should().NotBeNull();
result!.UserAccountId.Should().Be(userId);
@@ -170,15 +173,15 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByUsernameAsync_ReturnsNull_WhenNotExists()
{
- var conn = new MockDbConnection();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "usp_GetUserAccountByUsername"
)
.ReturnsTable(MockTable.Empty());
- var repo = CreateRepo(conn);
- var result = await repo.GetUserByUsernameAsync("nonexistent");
+ AuthRepository repo = CreateRepo(conn);
+ UserAccount? result = await repo.GetUserByUsernameAsync("nonexistent");
result.Should().BeNull();
}
@@ -186,9 +189,9 @@ public class AuthRepositoryTests
[Fact]
public async Task GetActiveCredentialByUserAccountIdAsync_ReturnsCredential_WhenExists()
{
- var userId = Guid.NewGuid();
- var credentialId = Guid.NewGuid();
- var conn = new MockDbConnection();
+ Guid userId = Guid.NewGuid();
+ Guid credentialId = Guid.NewGuid();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
@@ -211,8 +214,8 @@ public class AuthRepositoryTests
)
);
- var repo = CreateRepo(conn);
- var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
+ AuthRepository repo = CreateRepo(conn);
+ UserCredential? result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
result.Should().NotBeNull();
result!.UserCredentialId.Should().Be(credentialId);
@@ -223,16 +226,16 @@ public class AuthRepositoryTests
[Fact]
public async Task GetActiveCredentialByUserAccountIdAsync_ReturnsNull_WhenNotExists()
{
- var userId = Guid.NewGuid();
- var conn = new MockDbConnection();
+ Guid userId = Guid.NewGuid();
+ MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
)
.ReturnsTable(MockTable.Empty());
- var repo = CreateRepo(conn);
- var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
+ AuthRepository repo = CreateRepo(conn);
+ UserCredential? result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
result.Should().BeNull();
}
@@ -240,18 +243,18 @@ public class AuthRepositoryTests
[Fact]
public async Task RotateCredentialAsync_ExecutesSuccessfully()
{
- var userId = Guid.NewGuid();
- var newPasswordHash = "new_hashed_password";
- var conn = new MockDbConnection();
+ Guid userId = Guid.NewGuid();
+ string newPasswordHash = "new_hashed_password";
+ MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
.ReturnsScalar(1);
- var repo = CreateRepo(conn);
+ AuthRepository repo = CreateRepo(conn);
// Should not throw
- var act = async () =>
+ Func act = async () =>
await repo.RotateCredentialAsync(userId, newPasswordHash);
await act.Should().NotThrowAsync();
}
-}
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Repository/TestConnectionFactory.cs b/web/backend/Features/Features.Auth.Tests/Repository/TestConnectionFactory.cs
index e48d7cf..a20e082 100644
--- a/web/backend/Features/Features.Auth.Tests/Repository/TestConnectionFactory.cs
+++ b/web/backend/Features/Features.Auth.Tests/Repository/TestConnectionFactory.cs
@@ -7,5 +7,8 @@ internal class TestConnectionFactory(DbConnection conn) : ISqlConnectionFactory
{
private readonly DbConnection _conn = conn;
- public DbConnection CreateConnection() => _conn;
-}
+ public DbConnection CreateConnection()
+ {
+ return _conn;
+ }
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs
index 66b2c64..ac29471 100644
--- a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceRefreshTests.cs
@@ -2,9 +2,9 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions;
-using FluentAssertions;
using Features.Auth.Repository;
using Features.Auth.Services;
+using FluentAssertions;
using Infrastructure.Jwt;
using Moq;
@@ -12,152 +12,153 @@ namespace Features.Auth.Tests.Services;
public class TokenServiceRefreshTests
{
- private readonly Mock _tokenInfraMock;
- private readonly Mock _authRepositoryMock;
- private readonly TokenService _tokenService;
+ private readonly Mock _authRepositoryMock;
+ private readonly Mock _tokenInfraMock;
+ private readonly TokenService _tokenService;
- public TokenServiceRefreshTests()
- {
- _tokenInfraMock = new Mock();
- _authRepositoryMock = new Mock();
+ public TokenServiceRefreshTests()
+ {
+ _tokenInfraMock = new Mock();
+ _authRepositoryMock = new Mock();
- // Set environment variables for tokens
- Environment.SetEnvironmentVariable("ACCESS_TOKEN_SECRET", "test-access-secret-that-is-very-long-1234567890");
- Environment.SetEnvironmentVariable("REFRESH_TOKEN_SECRET", "test-refresh-secret-that-is-very-long-1234567890");
- Environment.SetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET", "test-confirmation-secret-that-is-very-long-1234567890");
+ // Set environment variables for tokens
+ Environment.SetEnvironmentVariable("ACCESS_TOKEN_SECRET", "test-access-secret-that-is-very-long-1234567890");
+ Environment.SetEnvironmentVariable("REFRESH_TOKEN_SECRET", "test-refresh-secret-that-is-very-long-1234567890");
+ Environment.SetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET",
+ "test-confirmation-secret-that-is-very-long-1234567890");
- _tokenService = new TokenService(
- _tokenInfraMock.Object,
- _authRepositoryMock.Object
- );
- }
+ _tokenService = new TokenService(
+ _tokenInfraMock.Object,
+ _authRepositoryMock.Object
+ );
+ }
- [Fact]
- public async Task RefreshTokenAsync_WithValidRefreshToken_ReturnsNewTokens()
- {
- // Arrange
- var userId = Guid.NewGuid();
- const string username = "testuser";
- const string refreshToken = "valid-refresh-token";
+ [Fact]
+ public async Task RefreshTokenAsync_WithValidRefreshToken_ReturnsNewTokens()
+ {
+ // Arrange
+ Guid userId = Guid.NewGuid();
+ const string username = "testuser";
+ const string refreshToken = "valid-refresh-token";
- var claims = new List
+ List claims = new()
{
- new(JwtRegisteredClaimNames.Sub, userId.ToString()),
- new(JwtRegisteredClaimNames.UniqueName, username),
- new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
+ new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
+ new Claim(JwtRegisteredClaimNames.UniqueName, username),
+ new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
- var claimsIdentity = new ClaimsIdentity(claims);
- var principal = new ClaimsPrincipal(claimsIdentity);
+ ClaimsIdentity claimsIdentity = new(claims);
+ ClaimsPrincipal principal = new(claimsIdentity);
- var userAccount = new UserAccount
- {
- UserAccountId = userId,
- Username = username,
- FirstName = "Test",
- LastName = "User",
- Email = "test@example.com",
- DateOfBirth = new DateTime(1990, 1, 1),
- };
-
- // Mock the validation of refresh token
- _tokenInfraMock
- .Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny()))
- .ReturnsAsync(principal);
-
- // Mock the generation of new tokens
- _tokenInfraMock
- .Setup(x => x.GenerateJwt(userId, username, It.IsAny(), It.IsAny()))
- .Returns((Guid _, string _, DateTime _, string _) => $"generated-token-{Guid.NewGuid()}");
-
- _authRepositoryMock
- .Setup(x => x.GetUserByIdAsync(userId))
- .ReturnsAsync(userAccount);
-
- // Act
- var result = await _tokenService.RefreshTokenAsync(refreshToken);
-
- // Assert
- result.Should().NotBeNull();
- result.UserAccount.UserAccountId.Should().Be(userId);
- result.UserAccount.Username.Should().Be(username);
- result.AccessToken.Should().NotBeEmpty();
- result.RefreshToken.Should().NotBeEmpty();
-
- _authRepositoryMock.Verify(
- x => x.GetUserByIdAsync(userId),
- Times.Once
- );
-
- // Verify tokens were generated (called twice - once for access, once for refresh)
- _tokenInfraMock.Verify(
- x => x.GenerateJwt(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()),
- Times.Exactly(2)
- );
- }
-
- [Fact]
- public async Task RefreshTokenAsync_WithInvalidRefreshToken_ThrowsUnauthorizedException()
- {
- // Arrange
- const string invalidToken = "invalid-refresh-token";
-
- _tokenInfraMock
- .Setup(x => x.ValidateJwtAsync(invalidToken, It.IsAny()))
- .ThrowsAsync(new UnauthorizedException("Invalid refresh token"));
-
- // Act & Assert
- await FluentActions.Invoking(async () =>
- await _tokenService.RefreshTokenAsync(invalidToken)
- ).Should().ThrowAsync();
- }
-
- [Fact]
- public async Task RefreshTokenAsync_WithExpiredRefreshToken_ThrowsUnauthorizedException()
- {
- // Arrange
- const string expiredToken = "expired-refresh-token";
-
- _tokenInfraMock
- .Setup(x => x.ValidateJwtAsync(expiredToken, It.IsAny()))
- .ThrowsAsync(new UnauthorizedException("Refresh token has expired"));
-
- // Act & Assert
- await FluentActions.Invoking(async () =>
- await _tokenService.RefreshTokenAsync(expiredToken)
- ).Should().ThrowAsync();
- }
-
- [Fact]
- public async Task RefreshTokenAsync_WithNonExistentUser_ThrowsUnauthorizedException()
- {
- // Arrange
- var userId = Guid.NewGuid();
- const string username = "testuser";
- const string refreshToken = "valid-refresh-token";
-
- var claims = new List
+ UserAccount userAccount = new()
{
- new(JwtRegisteredClaimNames.Sub, userId.ToString()),
- new(JwtRegisteredClaimNames.UniqueName, username),
- new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
+ UserAccountId = userId,
+ Username = username,
+ FirstName = "Test",
+ LastName = "User",
+ Email = "test@example.com",
+ DateOfBirth = new DateTime(1990, 1, 1)
};
- var claimsIdentity = new ClaimsIdentity(claims);
- var principal = new ClaimsPrincipal(claimsIdentity);
+ // Mock the validation of refresh token
+ _tokenInfraMock
+ .Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny()))
+ .ReturnsAsync(principal);
- _tokenInfraMock
- .Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny()))
- .ReturnsAsync(principal);
+ // Mock the generation of new tokens
+ _tokenInfraMock
+ .Setup(x => x.GenerateJwt(userId, username, It.IsAny(), It.IsAny()))
+ .Returns((Guid _, string _, DateTime _, string _) => $"generated-token-{Guid.NewGuid()}");
- _authRepositoryMock
- .Setup(x => x.GetUserByIdAsync(userId))
- .ReturnsAsync((UserAccount?)null);
+ _authRepositoryMock
+ .Setup(x => x.GetUserByIdAsync(userId))
+ .ReturnsAsync(userAccount);
- // Act & Assert
- await FluentActions.Invoking(async () =>
- await _tokenService.RefreshTokenAsync(refreshToken)
- ).Should().ThrowAsync()
- .WithMessage("*User account not found*");
- }
-}
+ // Act
+ RefreshTokenResult result = await _tokenService.RefreshTokenAsync(refreshToken);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.UserAccount.UserAccountId.Should().Be(userId);
+ result.UserAccount.Username.Should().Be(username);
+ result.AccessToken.Should().NotBeEmpty();
+ result.RefreshToken.Should().NotBeEmpty();
+
+ _authRepositoryMock.Verify(
+ x => x.GetUserByIdAsync(userId),
+ Times.Once
+ );
+
+ // Verify tokens were generated (called twice - once for access, once for refresh)
+ _tokenInfraMock.Verify(
+ x => x.GenerateJwt(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()),
+ Times.Exactly(2)
+ );
+ }
+
+ [Fact]
+ public async Task RefreshTokenAsync_WithInvalidRefreshToken_ThrowsUnauthorizedException()
+ {
+ // Arrange
+ const string invalidToken = "invalid-refresh-token";
+
+ _tokenInfraMock
+ .Setup(x => x.ValidateJwtAsync(invalidToken, It.IsAny()))
+ .ThrowsAsync(new UnauthorizedException("Invalid refresh token"));
+
+ // Act & Assert
+ await FluentActions.Invoking(async () =>
+ await _tokenService.RefreshTokenAsync(invalidToken)
+ ).Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task RefreshTokenAsync_WithExpiredRefreshToken_ThrowsUnauthorizedException()
+ {
+ // Arrange
+ const string expiredToken = "expired-refresh-token";
+
+ _tokenInfraMock
+ .Setup(x => x.ValidateJwtAsync(expiredToken, It.IsAny()))
+ .ThrowsAsync(new UnauthorizedException("Refresh token has expired"));
+
+ // Act & Assert
+ await FluentActions.Invoking(async () =>
+ await _tokenService.RefreshTokenAsync(expiredToken)
+ ).Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task RefreshTokenAsync_WithNonExistentUser_ThrowsUnauthorizedException()
+ {
+ // Arrange
+ Guid userId = Guid.NewGuid();
+ const string username = "testuser";
+ const string refreshToken = "valid-refresh-token";
+
+ List claims = new()
+ {
+ new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
+ new Claim(JwtRegisteredClaimNames.UniqueName, username),
+ new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
+ };
+
+ ClaimsIdentity claimsIdentity = new(claims);
+ ClaimsPrincipal principal = new(claimsIdentity);
+
+ _tokenInfraMock
+ .Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny()))
+ .ReturnsAsync(principal);
+
+ _authRepositoryMock
+ .Setup(x => x.GetUserByIdAsync(userId))
+ .ReturnsAsync((UserAccount?)null);
+
+ // Act & Assert
+ await FluentActions.Invoking(async () =>
+ await _tokenService.RefreshTokenAsync(refreshToken)
+ ).Should().ThrowAsync()
+ .WithMessage("*User account not found*");
+ }
+}
\ No newline at end of file
diff --git a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs
index 44b33a6..f3550cf 100644
--- a/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs
+++ b/web/backend/Features/Features.Auth.Tests/Services/TokenServiceValidationTests.cs
@@ -1,10 +1,9 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
-using Domain.Entities;
using Domain.Exceptions;
-using FluentAssertions;
using Features.Auth.Repository;
using Features.Auth.Services;
+using FluentAssertions;
using Infrastructure.Jwt;
using Moq;
@@ -12,272 +11,273 @@ namespace Features.Auth.Tests.Services;
public class TokenServiceValidationTests
{
- private readonly Mock _tokenInfraMock;
- private readonly Mock _authRepositoryMock;
- private readonly TokenService _tokenService;
+ private readonly Mock