Fix code analysis warnings

This commit is contained in:
Aaron Po
2026-08-21 23:53:18 -04:00
parent 1031a76099
commit f433b21bc1
19 changed files with 9209 additions and 101 deletions

View File

@@ -8,6 +8,13 @@
"csharpier"
],
"rollForward": false
},
"jetbrains.resharper.globaltools": {
"version": "2026.2.1",
"commands": [
"jb"
],
"rollForward": false
}
}
}

View File

@@ -83,4 +83,4 @@ public class JwtAuthenticationHandler(
/// <summary>
/// Options for the <c>JWT</c> authentication scheme handled by <see cref="JwtAuthenticationHandler" />.
/// </summary>
public class JwtAuthenticationOptions : AuthenticationSchemeOptions { }
public class JwtAuthenticationOptions : AuthenticationSchemeOptions;

View File

@@ -413,7 +413,7 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have a valid access token for my account")]
public void GivenIHaveAValidAccessTokenForMyAccount()
{
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException("registered user ID not found in scenario");
string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
@@ -432,7 +432,7 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have a valid confirmation token for my account")]
public void GivenIHaveAValidConfirmationTokenForMyAccount()
{
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException("registered user ID not found in scenario");
string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
@@ -451,7 +451,7 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have an expired confirmation token for my account")]
public void GivenIHaveAnExpiredConfirmationTokenForMyAccount()
{
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException("registered user ID not found in scenario");
string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
@@ -470,7 +470,7 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have a confirmation token signed with the wrong secret")]
public void GivenIHaveAConfirmationTokenSignedWithTheWrongSecret()
{
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException("registered user ID not found in scenario");
string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
@@ -668,7 +668,7 @@ public class AuthSteps(ScenarioContext scenario)
}
[When("I submit a refresh token request using a GET request")]
public async Task WhenISubmitARefreshTokenRequestUsingAGETRequest()
public async Task WhenISubmitARefreshTokenRequestUsingAGetRequest()
{
HttpClient client = GetClient();
HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/auth/refresh")
@@ -979,7 +979,7 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitAResendConfirmationRequestForMyAccount()
{
HttpClient client = GetClient();
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: throw new InvalidOperationException("registered user ID not found");
string? accessToken = scenario.TryGetValue<string>("accessToken", out string? at)
@@ -1025,7 +1025,7 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitAResendConfirmationRequestWithoutAnAccessToken()
{
HttpClient client = GetClient();
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
Guid userId = scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
? id
: Guid.NewGuid();

View File

@@ -3,7 +3,6 @@ using Database.Seed.PipelineData;
using Database.Seed.Sqlite;
using Domain.Entities;
using Features.Auth.DependencyInjection;
using Features.Auth.Dtos;
using Features.Auth.Repository;
using Features.Breweries.DependencyInjection;
using Features.Breweries.Repository;
@@ -255,7 +254,7 @@ static Table BuildUserTable(IReadOnlyList<UserRecord> users)
user.User.FirstName,
user.User.LastName,
user.Email,
user.DateOfBirth.ToString()
user.DateOfBirth
);
}

View File

@@ -13,8 +13,8 @@ public sealed class BeerPost
// Attributes
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal ABV { get; set; }
public int IBU { get; set; }
public decimal Abv { get; set; }
public int Ibu { get; set; }
// Audit Fields
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

View File

@@ -8,7 +8,7 @@ public sealed class Country
// Attributes
public string CountryName { get; set; } = string.Empty;
public string ISO3166_1 { get; set; } = string.Empty; // this is the iso 3166-1 alpha-2 code for the country, e.g. "US" for United States, "CA" for Canada, etc.
public string Iso31661 { get; set; } = string.Empty; // this is the iso 3166-1 alpha-2 code for the country, e.g. "US" for United States, "CA" for Canada, etc.
// Audit Fields
public byte[]? RowVersion { get; set; }

View File

@@ -13,7 +13,7 @@ public sealed class StateProvince
// this is the iso 3166-2 code for the state/province, e.g. "CA" for California, "ON" for Ontario, etc.
// and is rendered like US-CA for California, CA-ON for Ontario, etc.
public string ISO3166_2 { get; set; } = string.Empty;
public string Iso31662 { get; set; } = string.Empty;
// Audit Fields
public byte[]? RowVersion { get; set; }

View File

@@ -11,14 +11,12 @@ namespace Features.Auth.Tests.Services;
public class TokenServiceValidationTests
{
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
private readonly TokenService _tokenService;
public TokenServiceValidationTests()
{
_tokenInfraMock = new Mock<ITokenInfrastructure>();
_authRepositoryMock = new Mock<IAuthRepository>();
// Set environment variables for tokens
Environment.SetEnvironmentVariable(
@@ -34,7 +32,7 @@ public class TokenServiceValidationTests
"test-confirmation-secret-that-is-very-long-1234567890"
);
_tokenService = new TokenService(_tokenInfraMock.Object, _authRepositoryMock.Object);
_tokenService = new TokenService(_tokenInfraMock.Object, new Mock<IAuthRepository>().Object);
}
[Fact]

View File

@@ -2,7 +2,6 @@ using System.Data.Common;
using Dapper;
using Domain.Entities;
using Domain.Exceptions;
using Features.Auth.Dtos;
using Infrastructure.Sql;
using Microsoft.Data.SqlClient;
@@ -46,7 +45,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
INSERT INTO dbo.UserCredential (UserAccountId, Hash)
VALUES (@UserAccountId, @Hash);
""",
new { UserAccountId = userAccountId, Hash = ua.UserCredential?.Hash },
new { UserAccountId = userAccountId, ua.UserCredential?.Hash },
transaction
)
);

View File

@@ -1,5 +1,4 @@
using Domain.Entities;
using Features.Auth.Dtos;
namespace Features.Auth.Repository;

View File

@@ -62,10 +62,6 @@ public interface ITokenService
/// </summary>
string GenerateConfirmationToken(UserAccount user);
/// <summary>Generates a token of the type specified by <typeparamref name="T" />.</summary>
/// <typeparam name="T">Must be <see cref="TokenType" />.</typeparam>
string GenerateToken<T>(UserAccount user)
where T : struct, Enum;
/// <summary>Validates an access token's signature and expiration and extracts the caller's identity.</summary>
/// <exception cref="Domain.Exceptions.UnauthorizedException">

View File

@@ -88,30 +88,6 @@ public class TokenService : ITokenService
);
}
/// <inheritdoc />
/// <exception cref="InvalidOperationException">
/// Thrown when <typeparamref name="T" /> is not <see cref="TokenType" /> or does not resolve to a known token type.
/// </exception>
public string GenerateToken<T>(UserAccount user)
where T : struct, Enum
{
if (typeof(T) != typeof(TokenType))
throw new InvalidOperationException("Invalid token type");
string tokenTypeName = typeof(T).Name;
if (!Enum.TryParse(typeof(TokenType), tokenTypeName, out object? parsed))
throw new InvalidOperationException("Invalid token type");
TokenType tokenType = (TokenType)parsed;
return tokenType switch
{
TokenType.AccessToken => GenerateAccessToken(user),
TokenType.RefreshToken => GenerateRefreshToken(user),
TokenType.ConfirmationToken => GenerateConfirmationToken(user),
_ => throw new InvalidOperationException("Invalid token type"),
};
}
/// <inheritdoc />
public async Task<ValidatedToken> ValidateAccessTokenAsync(string token)
{

View File

@@ -29,7 +29,7 @@ public class GetBreweryByIdHandlerTests
);
result.Should().NotBeNull();
result!.BreweryPostId.Should().Be(brewery.BreweryPostId);
result.BreweryPostId.Should().Be(brewery.BreweryPostId);
}
[Fact]

View File

@@ -25,7 +25,6 @@ public class CreateBreweryValidator : AbstractValidator<CreateBreweryCommand>
RuleFor(x => x.Location.CityId)
.NotEmpty()
.When(x => x.Location is not null)
.WithMessage("CityId is required.");
RuleFor(x => x.Location.AddressLine1)
@@ -33,15 +32,12 @@ public class CreateBreweryValidator : AbstractValidator<CreateBreweryCommand>
.When(x => x.Location is not null)
.WithMessage("Address line 1 is required.")
.MaximumLength(256)
.When(x => x.Location is not null)
.WithMessage("Address line 1 cannot exceed 256 characters.");
RuleFor(x => x.Location.PostalCode)
.NotEmpty()
.When(x => x.Location is not null)
.WithMessage("Postal code is required.")
.MaximumLength(20)
.When(x => x.Location is not null)
.WithMessage("Postal code cannot exceed 20 characters.");
}
}

View File

@@ -27,11 +27,11 @@ public static class BreweryDtoMapper
CityName: brewery.Location.City?.CityName ?? string.Empty,
StateProvinceName: brewery.Location.City?.StateProvince?.StateProvinceName
?? string.Empty,
StateProvinceCode: brewery.Location.City?.StateProvince?.ISO3166_2
StateProvinceCode: brewery.Location.City?.StateProvince?.Iso31662
?? string.Empty,
CountryName: brewery.Location.City?.StateProvince?.Country?.CountryName
?? string.Empty,
CountryCode: brewery.Location.City?.StateProvince?.Country?.ISO3166_1
CountryCode: brewery.Location.City?.StateProvince?.Country?.Iso31661
?? string.Empty,
AddressLine1: brewery.Location.AddressLine1,
AddressLine2: brewery.Location.AddressLine2,

View File

@@ -36,7 +36,7 @@ public class EmailDispatcher(
confirmationLink
);
await emailProvider.SendAsync(email, "Welcome to The Biergarten App!", emailHtml, true);
await emailProvider.SendAsync(email, "Welcome to The Biergarten App!", emailHtml);
}
/// <inheritdoc />

View File

@@ -59,7 +59,7 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
""",
new
{
UserAccountId = userAccount.UserAccountId,
userAccount.UserAccountId,
userAccount.Username,
userAccount.FirstName,
userAccount.LastName,

View File

@@ -1,44 +1 @@
namespace Infrastructure.Sql;
/// <summary>
/// Custom SQL Server error numbers (50000+) that a stored procedure could use to signal
/// domain-level failures via <c>THROW</c>, for a repository to catch as
/// <see cref="Microsoft.Data.SqlClient.SqlException" /> and translate via <c>ex.Number</c> into
/// the corresponding <c>Domain.Exceptions</c> type.
/// </summary>
/// <remarks>
/// Currently unused: the schema has no stored procedures, and no repository throws or catches on
/// these numbers. Repositories instead run their own existence/concurrency checks and throw
/// <c>Domain.Exceptions</c> directly (see <c>docs/website/database.md</c> for the current
/// approach). This type is kept in case a future stored procedure reintroduces the pattern; each
/// number would
/// identify a category of error shared by every procedure that raises it, not a single call site.
/// </remarks>
public static class SqlErrorCodes
{
/// <summary>
/// A stored procedure's own input validation failed (e.g. a required parameter was <c>NULL</c>).
/// Maps to HTTP 400 Bad Request.
/// </summary>
public const int Validation = 50400;
/// <summary>
/// A stored procedure could not find a row it required, by primary key or foreign key. Maps to
/// HTTP 404 Not Found.
/// </summary>
public const int NotFound = 50404;
/// <summary>
/// A stored procedure detected a conflict: either a duplicate/already-exists row, or a stale
/// optimistic-concurrency token (row-version mismatch). Maps to HTTP 409 Conflict.
/// </summary>
public const int Conflict = 50409;
/// <summary>
/// A defensive check inside a stored procedure failed under conditions that should be
/// unreachable in normal operation (e.g. an <c>INSERT</c> that should always affect one row
/// affected zero). Left uncaught by repositories so it falls through to the generic
/// <see cref="Microsoft.Data.SqlClient.SqlException" /> handler. Maps to HTTP 500/503.
/// </summary>
public const int InternalFailure = 50500;
}
namespace Infrastructure.Sql;

9181
web/backend/report.sarif Normal file

File diff suppressed because it is too large Load Diff