Enable more analyzers, drop log-and-rethrow in file activities and apply dotnet format

Use invariant culture when formatting ids, put cancellation tokens last in endpoint handlers, remove the
CS8618 pragmas and keep only the analyzer overrides that still apply.
This commit is contained in:
2026-09-24 12:57:53 +02:00
parent 2d0867e68a
commit 28a7c4a98c
39 changed files with 160 additions and 192 deletions
+16 -11
View File
@@ -41,6 +41,13 @@ dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_symbols.private_fields.required_modifiers =
dotnet_naming_style.camel_case.capitalization = camel_case dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_rule.private_static_readonly.symbols = private_static_readonly
dotnet_naming_rule.private_static_readonly.style = pascal_case
dotnet_naming_rule.private_static_readonly.severity = warning
dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly.required_modifiers = static, readonly
dotnet_naming_rule.private_constants.symbols = private_constants dotnet_naming_rule.private_constants.symbols = private_constants
dotnet_naming_rule.private_constants.style = pascal_case dotnet_naming_rule.private_constants.style = pascal_case
dotnet_naming_rule.private_constants.severity = warning dotnet_naming_rule.private_constants.severity = warning
@@ -48,18 +55,16 @@ dotnet_naming_symbols.private_constants.applicable_kinds = field
dotnet_naming_symbols.private_constants.required_modifiers = const dotnet_naming_symbols.private_constants.required_modifiers = const
dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.capitalization = pascal_case
# Analyzer rules that would need behaviour or API changes; downgraded in P0, revisit in P15. # CA1848/CA1873 would turn every log call into a [LoggerMessage] source-generated method; CA1716 flags the
# EF-style IDatabase.Set<T>() and the Musify.Application.Shared namespace, whose names are deliberate.
dotnet_diagnostic.CA1848.severity = suggestion dotnet_diagnostic.CA1848.severity = suggestion
dotnet_diagnostic.CA1873.severity = suggestion dotnet_diagnostic.CA1873.severity = suggestion
dotnet_diagnostic.CA1305.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1859.severity = suggestion
dotnet_diagnostic.CA1716.severity = suggestion dotnet_diagnostic.CA1716.severity = suggestion
dotnet_diagnostic.CA1000.severity = suggestion # The routing slip builders are stateless services resolved from DI; they stay instance members.
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1816.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1852.severity = suggestion
dotnet_diagnostic.CA1068.severity = suggestion # Test names follow Method_Scenario_Expected.
dotnet_diagnostic.CA1711.severity = suggestion [Tests/**.cs]
dotnet_diagnostic.CA1861.severity = suggestion dotnet_diagnostic.CA1707.severity = none
# xUnit collection fixtures are conventionally named ...Collection.
dotnet_diagnostic.CA1711.severity = none
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Musify.Application.Shared; using Musify.Application.Shared;
namespace Musify.Application.Configuration; namespace Musify.Application.Configuration;
@@ -41,10 +42,10 @@ public class PictureRoutes
public string BuildPicturePath(PictureSize size, string pictureName) => StorageKey.Combine(FolderPath(size), pictureName); public string BuildPicturePath(PictureSize size, string pictureName) => StorageKey.Combine(FolderPath(size), pictureName);
public string BuildOriginalPicturePath(long userId, string pictureName) => public string BuildOriginalPicturePath(long userId, string pictureName) =>
StorageKey.Combine(UploadsFolder, userId.ToString(), ParentFolder, OriginalPicturesFolder, pictureName); StorageKey.Combine(UploadsFolder, userId.ToString(CultureInfo.InvariantCulture), ParentFolder, OriginalPicturesFolder, pictureName);
public string BuildTempPath(string tempRootPrefix, long userId, string objectName) => public string BuildTempPath(string tempRootPrefix, long userId, string objectName) =>
StorageKey.Combine(tempRootPrefix, userId.ToString(), ParentFolder, objectName); StorageKey.Combine(tempRootPrefix, userId.ToString(CultureInfo.InvariantCulture), ParentFolder, objectName);
} }
public class PictureSizes public class PictureSizes
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Musify.Application.Shared; using Musify.Application.Shared;
namespace Musify.Application.Configuration; namespace Musify.Application.Configuration;
@@ -48,7 +49,7 @@ public class TrackRoutes : PictureRoutes
public string PresetLargePicturePath => StorageKey.Combine(ParentFolder, PresetLargePicture); public string PresetLargePicturePath => StorageKey.Combine(ParentFolder, PresetLargePicture);
public string BuildOriginalAudioPath(long userId, string audioName) => public string BuildOriginalAudioPath(long userId, string audioName) =>
StorageKey.Combine(UploadsFolder, userId.ToString(), ParentFolder, OriginalAudiosFolder, audioName); StorageKey.Combine(UploadsFolder, userId.ToString(CultureInfo.InvariantCulture), ParentFolder, OriginalAudiosFolder, audioName);
public string BuildProcessedAudioPath(string folderName) => public string BuildProcessedAudioPath(string folderName) =>
StorageKey.Combine(ParentFolder, ProcessedAudiosFolder, folderName); StorageKey.Combine(ParentFolder, ProcessedAudiosFolder, folderName);
@@ -1,6 +1,6 @@
using Mediator; using Mediator;
using Musify.Domain.ValueObjects;
using Musify.Application.Genres.Responses; using Musify.Application.Genres.Responses;
using Musify.Domain.ValueObjects;
namespace Musify.Application.Genres; namespace Musify.Application.Genres;
@@ -1,8 +1,8 @@
using Mediator; using Mediator;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
using Musify.Application.Genres.Responses; using Musify.Application.Genres.Responses;
using Musify.Domain.ValueObjects;
namespace Musify.Application.Genres; namespace Musify.Application.Genres;
@@ -79,7 +79,7 @@ public class GenerateMixesForUserCommandHandler(
private static List<Guid> Shuffle(IEnumerable<Guid> trackIds) => private static List<Guid> Shuffle(IEnumerable<Guid> trackIds) =>
[.. trackIds.OrderBy(_ => Guid.NewGuid())]; [.. trackIds.OrderBy(_ => Guid.NewGuid())];
private static List<Guid> Interleave(IReadOnlyList<Guid> first, IReadOnlyList<Guid> second) private static List<Guid> Interleave(List<Guid> first, List<Guid> second)
{ {
var merged = new List<Guid>(first.Count + second.Count); var merged = new List<Guid>(first.Count + second.Count);
for (var index = 0; index < Math.Max(first.Count, second.Count); index++) for (var index = 0; index < Math.Max(first.Count, second.Count); index++)
@@ -4,9 +4,9 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Musify.Application.Configuration; using Musify.Application.Configuration;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Application.Shared;
using Musify.Application.Events; using Musify.Application.Events;
using Musify.Application.Services; using Musify.Application.Services;
using Musify.Application.Shared;
using Musify.Application.Tracks.Responses; using Musify.Application.Tracks.Responses;
using Musify.Domain.Entities; using Musify.Domain.Entities;
using Musify.Domain.ValueObjects; using Musify.Domain.ValueObjects;
@@ -1,3 +1,4 @@
using System.Globalization;
using ErrorOr; using ErrorOr;
using Mediator; using Mediator;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -48,7 +49,7 @@ public class StartListeningCommandHandler(
listenId = listen.Id; listenId = listen.Id;
} }
logger.LogInformation("Issued stream ticket for track {TrackId} to user {UserId}", request.TrackId, request.UserId?.ToString() ?? "(anonymous)"); logger.LogInformation("Issued stream ticket for track {TrackId} to user {UserId}", request.TrackId, request.UserId?.ToString(CultureInfo.InvariantCulture) ?? "(anonymous)");
return streamIssuer.Issue(track.Audio.FolderName, request.UserId, listenId); return streamIssuer.Issue(track.Audio.FolderName, request.UserId, listenId);
} }
@@ -2,8 +2,8 @@ using ErrorOr;
using Mediator; using Mediator;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Application.Users.Responses;
using Musify.Application.Shared; using Musify.Application.Shared;
using Musify.Application.Users.Responses;
namespace Musify.Application.Users; namespace Musify.Application.Users;
+1 -2
View File
@@ -1,8 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Musify.Domain.ValueObjects;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Musify.Domain.Abstractions; using Musify.Domain.Abstractions;
using Musify.Domain.ValueObjects;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
namespace Musify.Domain.ValueObjects; namespace Musify.Domain.ValueObjects;
#pragma warning disable CS8618
public class TrackAudio public class TrackAudio
{ {
public string? OriginalName { get; set; } public string? OriginalName { get; set; }
@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
namespace Musify.Domain.ValueObjects; namespace Musify.Domain.ValueObjects;
#pragma warning disable CS8618
public class TrackPictures public class TrackPictures
{ {
public string? OriginalName { get; set; } public string? OriginalName { get; set; }
@@ -14,8 +14,6 @@ internal class CopyFileInBucketActivity(
public const string ExecuteEndpointName = "copy-file-in-bucket"; public const string ExecuteEndpointName = "copy-file-in-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<CopyFileInBucketArguments> executeContext) public async Task<ExecutionResult> Execute(ExecuteContext<CopyFileInBucketArguments> executeContext)
{
try
{ {
await storageService.CopyFileAsync( await storageService.CopyFileAsync(
executeContext.Arguments.SourceBucket, executeContext.Arguments.SourceBucket,
@@ -28,16 +26,6 @@ internal class CopyFileInBucketActivity(
executeContext.Arguments.DestinationBucket, executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey)); executeContext.Arguments.DestinationKey));
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to copy {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
executeContext.Arguments.SourceBucket,
executeContext.Arguments.SourceKey,
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey);
throw;
}
}
public async Task<CompensationResult> Compensate(CompensateContext<CopyFileInBucketLog> compensateContext) public async Task<CompensationResult> Compensate(CompensateContext<CopyFileInBucketLog> compensateContext)
{ {
@@ -18,8 +18,6 @@ internal class DownloadFileFromBucketActivity(
var destinationPath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable); var destinationPath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationPath); ArgumentNullException.ThrowIfNull(destinationPath);
try
{
using var fileStream = await storageService.GetFileAsync( using var fileStream = await storageService.GetFileAsync(
executeContext.Arguments.Bucket, executeContext.Arguments.Bucket,
executeContext.Arguments.Key, executeContext.Arguments.Key,
@@ -40,12 +38,6 @@ internal class DownloadFileFromBucketActivity(
executeContext.Arguments.Key, executeContext.Arguments.Key,
destinationPath)); destinationPath));
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to download file from bucket");
throw;
}
}
public Task<CompensationResult> Compensate(CompensateContext<DownloadFileFromBucketLog> compensateContext) public Task<CompensationResult> Compensate(CompensateContext<DownloadFileFromBucketLog> compensateContext)
{ {
@@ -34,12 +34,5 @@ internal class RemoveFileFromBucketActivity(
executeContext.Arguments.Bucket); executeContext.Arguments.Bucket);
return executeContext.Completed(); return executeContext.Completed();
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to remove {Key} from bucket {Bucket}",
executeContext.Arguments.Key,
executeContext.Arguments.Bucket);
throw;
}
} }
} }
@@ -20,8 +20,6 @@ internal class TransferFilesToBucketActivity(
var destinationKey = executeContext.Arguments.DestinationKey; var destinationKey = executeContext.Arguments.DestinationKey;
ArgumentNullException.ThrowIfNull(destinationKey); ArgumentNullException.ThrowIfNull(destinationKey);
try
{
var uploadedKeys = await storageService.TransferFilesAsync( var uploadedKeys = await storageService.TransferFilesAsync(
folderPath, folderPath,
executeContext.Arguments.DestinationBucket, executeContext.Arguments.DestinationBucket,
@@ -32,14 +30,6 @@ internal class TransferFilesToBucketActivity(
executeContext.Arguments.DestinationBucket, executeContext.Arguments.DestinationBucket,
uploadedKeys.ToArray())); uploadedKeys.ToArray()));
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to transfer files from {FolderPath} to {DestinationBucket}",
folderPath,
executeContext.Arguments.DestinationBucket);
throw;
}
}
public async Task<CompensationResult> Compensate(CompensateContext<TransferFilesToBucketLog> compensateContext) public async Task<CompensationResult> Compensate(CompensateContext<TransferFilesToBucketLog> compensateContext)
{ {
@@ -21,8 +21,6 @@ internal class UploadFileToBucketActivity(IStorageService storageService,
.Where(static s => !string.IsNullOrWhiteSpace(s)) .Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s.Trim().Trim('/', '\\'))); .Select(static s => s.Trim().Trim('/', '\\')));
try
{
await storageService.UploadFileAsync( await storageService.UploadFileAsync(
sourceFilePath, sourceFilePath,
executeContext.Arguments.DestinationBucket, executeContext.Arguments.DestinationBucket,
@@ -32,12 +30,6 @@ internal class UploadFileToBucketActivity(IStorageService storageService,
executeContext.Arguments.DestinationBucket, executeContext.Arguments.DestinationBucket,
destinationKey)); destinationKey));
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to upload file to bucket");
throw;
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UploadFileToBucketLog> compensateContext) public async Task<CompensationResult> Compensate(CompensateContext<UploadFileToBucketLog> compensateContext)
{ {
@@ -20,8 +20,6 @@ internal class ResizePictureActivity(
var destinationFilePath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable); var destinationFilePath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationFilePath); ArgumentNullException.ThrowIfNull(destinationFilePath);
try
{
if (!File.Exists(sourceFilePath)) if (!File.Exists(sourceFilePath))
{ {
logger.LogWarning("Source file not found: {SourceFilePath}", sourceFilePath); logger.LogWarning("Source file not found: {SourceFilePath}", sourceFilePath);
@@ -48,12 +46,6 @@ internal class ResizePictureActivity(
destinationFilePath); destinationFilePath);
return executeContext.Completed(new ResizePictureLog(destinationFilePath)); return executeContext.Completed(new ResizePictureLog(destinationFilePath));
} }
catch (Exception exception)
{
logger.LogError(exception, "Failed to resize picture {SourceFilePath}", sourceFilePath);
throw;
}
}
public Task<CompensationResult> Compensate(CompensateContext<ResizePictureLog> compensateContext) public Task<CompensationResult> Compensate(CompensateContext<ResizePictureLog> compensateContext)
{ {
@@ -12,11 +12,11 @@ public class RoutingSlipCleanUpConsumer(
{ {
public const string QueueName = "routing-slip-clean-up"; public const string QueueName = "routing-slip-clean-up";
public Task Consume(ConsumeContext<RoutingSlipCompleted> consumeContext) public Task Consume(ConsumeContext<RoutingSlipCompleted> context)
=> DeleteFolder(consumeContext.Message.Variables); => DeleteFolder(context.Message.Variables);
public Task Consume(ConsumeContext<RoutingSlipFaulted> consumeContext) public Task Consume(ConsumeContext<RoutingSlipFaulted> context)
=> DeleteFolder(consumeContext.Message.Variables); => DeleteFolder(context.Message.Variables);
private Task DeleteFolder(IDictionary<string, object> variables) private Task DeleteFolder(IDictionary<string, object> variables)
{ {
@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Musify.Infrastructure.MassTransit.Activities.Albums; using Musify.Infrastructure.MassTransit.Activities.Albums;
using Musify.Infrastructure.MassTransit.Activities.Audio; using Musify.Infrastructure.MassTransit.Activities.Audio;
using Musify.Infrastructure.MassTransit.Activities.Files; using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Activities.LifeCycle;
using Musify.Infrastructure.MassTransit.Activities.Pictures; using Musify.Infrastructure.MassTransit.Activities.Pictures;
using Musify.Infrastructure.MassTransit.Activities.PlayLists; using Musify.Infrastructure.MassTransit.Activities.PlayLists;
using Musify.Infrastructure.MassTransit.Activities.Tracks; using Musify.Infrastructure.MassTransit.Activities.Tracks;
@@ -11,7 +12,6 @@ using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers; using Musify.Infrastructure.MassTransit.Consumers;
using Musify.Infrastructure.MassTransit.Logs; using Musify.Infrastructure.MassTransit.Logs;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
using Musify.Infrastructure.MassTransit.Activities.LifeCycle;
namespace Musify.Infrastructure.MassTransit; namespace Musify.Infrastructure.MassTransit;
@@ -1,11 +1,11 @@
using MassTransit; using MassTransit;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Musify.Application.Configuration;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Infrastructure.Configuration; using Musify.Infrastructure.Configuration;
using Musify.Infrastructure.MassTransit.Sagas; using Musify.Infrastructure.MassTransit.Sagas;
using Musify.Infrastructure.Persistence; using Musify.Infrastructure.Persistence;
using Musify.Application.Configuration;
namespace Musify.Infrastructure.MassTransit; namespace Musify.Infrastructure.MassTransit;
@@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore;
using Musify.Application.Configuration; using Musify.Application.Configuration;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Domain.ValueObjects; using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Activities.LifeCycle; using Musify.Infrastructure.MassTransit.Activities.LifeCycle;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
@@ -1,12 +1,12 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Musify.Application.Configuration;
using Musify.Infrastructure.Configuration; using Musify.Infrastructure.Configuration;
using OpenTelemetry.Exporter; using OpenTelemetry.Exporter;
using OpenTelemetry.Logs; using OpenTelemetry.Logs;
using OpenTelemetry.Metrics; using OpenTelemetry.Metrics;
using OpenTelemetry.Resources; using OpenTelemetry.Resources;
using OpenTelemetry.Trace; using OpenTelemetry.Trace;
using Musify.Application.Configuration;
namespace Musify.Infrastructure.Observability; namespace Musify.Infrastructure.Observability;
@@ -1,8 +1,8 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Musify.Application.Configuration;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Infrastructure.Configuration; using Musify.Infrastructure.Configuration;
using Musify.Application.Configuration;
namespace Musify.Infrastructure.Persistence; namespace Musify.Infrastructure.Persistence;
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography; using System.Security.Cryptography;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@@ -30,9 +31,9 @@ public sealed class StreamTicketService : IStreamTicketService, IDisposable
var claims = new Dictionary<string, object> { ["prefix"] = keyPrefix }; var claims = new Dictionary<string, object> { ["prefix"] = keyPrefix };
if (userId != null) if (userId != null)
claims["sub"] = userId.Value.ToString(); claims["sub"] = userId.Value.ToString(CultureInfo.InvariantCulture);
if (maxBytes != null) if (maxBytes != null)
claims["maxBytes"] = maxBytes.Value.ToString(); claims["maxBytes"] = maxBytes.Value.ToString(CultureInfo.InvariantCulture);
var descriptor = new SecurityTokenDescriptor var descriptor = new SecurityTokenDescriptor
{ {
@@ -26,9 +26,9 @@ public sealed class JwtBearerEventsHandler(
[property: JsonPropertyName("family_name")] string? FamilyName, [property: JsonPropertyName("family_name")] string? FamilyName,
[property: JsonPropertyName("picture")] string? Picture); [property: JsonPropertyName("picture")] string? Picture);
public override async Task TokenValidated(TokenValidatedContext tokenValidatedContext) public override async Task TokenValidated(TokenValidatedContext context)
{ {
var principal = tokenValidatedContext.Principal!; var principal = context.Principal!;
var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier); var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (!long.TryParse(rawId, out var userId)) if (!long.TryParse(rawId, out var userId))
@@ -41,7 +41,7 @@ public sealed class JwtBearerEventsHandler(
if (cache.TryGetValue(cacheKey, out _)) if (cache.TryGetValue(cacheKey, out _))
return; return;
var userInfo = await FetchUserInfoAsync(tokenValidatedContext, tokenValidatedContext.HttpContext.RequestAborted); var userInfo = await FetchUserInfoAsync(context, context.HttpContext.RequestAborted);
var username = userInfo?.Name var username = userInfo?.Name
?? userInfo?.PreferredUsername ?? userInfo?.PreferredUsername
@@ -65,7 +65,7 @@ public sealed class JwtBearerEventsHandler(
{ {
await mediator.Send( await mediator.Send(
new SyncUserCommand(userId, username, firstName, lastName, profilePictureUrl), new SyncUserCommand(userId, username, firstName, lastName, profilePictureUrl),
tokenValidatedContext.HttpContext.RequestAborted); context.HttpContext.RequestAborted);
cache.Set(cacheKey, true, UserSyncCacheTtl); cache.Set(cacheKey, true, UserSyncCacheTtl);
} }
@@ -76,9 +76,9 @@ public sealed class JwtBearerEventsHandler(
} }
private async Task<UserInfoResponse?> FetchUserInfoAsync( private async Task<UserInfoResponse?> FetchUserInfoAsync(
TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) TokenValidatedContext context, CancellationToken cancellationToken)
{ {
var authorizationHeader = tokenValidatedContext.HttpContext.Request.Headers.Authorization.ToString(); var authorizationHeader = context.HttpContext.Request.Headers.Authorization.ToString();
if (!authorizationHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) if (!authorizationHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return null; return null;
@@ -88,7 +88,7 @@ public sealed class JwtBearerEventsHandler(
try try
{ {
var userInfoEndpoint = await ResolveUserInfoEndpointAsync(tokenValidatedContext, cancellationToken); var userInfoEndpoint = await ResolveUserInfoEndpointAsync(context, cancellationToken);
var client = httpClientFactory.CreateClient(); var client = httpClientFactory.CreateClient();
using var request = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint) using var request = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint)
@@ -113,9 +113,9 @@ public sealed class JwtBearerEventsHandler(
} }
private async Task<string> ResolveUserInfoEndpointAsync( private async Task<string> ResolveUserInfoEndpointAsync(
TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) TokenValidatedContext context, CancellationToken cancellationToken)
{ {
var configurationManager = tokenValidatedContext.Options.ConfigurationManager; var configurationManager = context.Options.ConfigurationManager;
if (configurationManager != null) if (configurationManager != null)
{ {
var configuration = await configurationManager.GetConfigurationAsync(cancellationToken); var configuration = await configurationManager.GetConfigurationAsync(cancellationToken);
@@ -129,9 +129,9 @@ public static class AlbumEndpoints
private static async Task<IResult> GetAlbums( private static async Task<IResult> GetAlbums(
IMediator mediator, IMediator mediator,
CancellationToken cancellationToken,
string? title, string? title,
[AsParameters] PageQuery page) [AsParameters] PageQuery page,
CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetAlbumsQuery(title, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetAlbumsQuery(title, page.PageNumber, page.PageSize), cancellationToken);
return Results.Ok(result); return Results.Ok(result);
@@ -149,8 +149,8 @@ public static class AlbumEndpoints
private static async Task<IResult> GetAlbumsByUserId( private static async Task<IResult> GetAlbumsByUserId(
IMediator mediator, IMediator mediator,
long userId, long userId,
CancellationToken cancellationToken, [AsParameters] PageQuery page,
[AsParameters] PageQuery page) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult(); return result.ToHttpResult();
@@ -177,8 +177,8 @@ public static class AlbumEndpoints
private static async Task<IResult> GetAlbumTracks( private static async Task<IResult> GetAlbumTracks(
IMediator mediator, IMediator mediator,
Guid id, Guid id,
CancellationToken cancellationToken, [AsParameters] PageQuery page,
[AsParameters] PageQuery page) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetAlbumTracksQuery(id, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetAlbumTracksQuery(id, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult(); return result.ToHttpResult();
@@ -38,8 +38,8 @@ public static class LikeEndpoints
private static async Task<IResult> GetLikedTracks( private static async Task<IResult> GetLikedTracks(
IMediator mediator, IMediator mediator,
CurrentUser currentUser, CurrentUser currentUser,
CancellationToken cancellationToken, [AsParameters] PageQuery page,
[AsParameters] PageQuery page) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetLikedTracksQuery(currentUser.RequiredId, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetLikedTracksQuery(currentUser.RequiredId, page.PageNumber, page.PageSize), cancellationToken);
return Results.Ok(result); return Results.Ok(result);
@@ -125,9 +125,9 @@ public static class PlayListEndpoints
IMediator mediator, IMediator mediator,
CurrentUser currentUser, CurrentUser currentUser,
long userId, long userId,
CancellationToken cancellationToken,
string? name, string? name,
[AsParameters] PageQuery page) [AsParameters] PageQuery page,
CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken); var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken);
return Results.Ok(result); return Results.Ok(result);
@@ -137,8 +137,8 @@ public static class PlayListEndpoints
IMediator mediator, IMediator mediator,
CurrentUser currentUser, CurrentUser currentUser,
Guid id, Guid id,
CancellationToken cancellationToken, [AsParameters] PageQuery page,
[AsParameters] PageQuery page) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetPlayListTracksQuery(id, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken); var result = await mediator.Send(new GetPlayListTracksQuery(id, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken);
return result.ToHttpResult(); return result.ToHttpResult();
@@ -97,10 +97,10 @@ public static class TrackEndpoints
private static async Task<IResult> GetTracks( private static async Task<IResult> GetTracks(
IMediator mediator, IMediator mediator,
CancellationToken cancellationToken,
string? name, string? name,
GenreParameter? genre, GenreParameter? genre,
[AsParameters] PageQuery page) [AsParameters] PageQuery page,
CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetTracksQuery(name, page.PageNumber, page.PageSize, genre?.Value), cancellationToken); var result = await mediator.Send(new GetTracksQuery(name, page.PageNumber, page.PageSize, genre?.Value), cancellationToken);
return Results.Ok(result); return Results.Ok(result);
@@ -152,9 +152,9 @@ public static class TrackEndpoints
private static async Task<IResult> GetTracksByUserId( private static async Task<IResult> GetTracksByUserId(
IMediator mediator, IMediator mediator,
long userId, long userId,
CancellationToken cancellationToken,
string? name, string? name,
[AsParameters] PageQuery page) [AsParameters] PageQuery page,
CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetTracksByUserIdQuery(userId, name, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetTracksByUserIdQuery(userId, name, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult(); return result.ToHttpResult();
@@ -100,9 +100,9 @@ public static class UserEndpoints
private static async Task<IResult> GetUsers( private static async Task<IResult> GetUsers(
IMediator mediator, IMediator mediator,
CurrentUser currentUser, CurrentUser currentUser,
CancellationToken cancellationToken,
string? usernameSearch, string? usernameSearch,
[AsParameters] PageQuery page) [AsParameters] PageQuery page,
CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetUsersQuery(page.PageNumber, page.PageSize, usernameSearch, currentUser.Id), cancellationToken); var result = await mediator.Send(new GetUsersQuery(page.PageNumber, page.PageSize, usernameSearch, currentUser.Id), cancellationToken);
return Results.Ok(result); return Results.Ok(result);
@@ -162,8 +162,8 @@ public static class UserEndpoints
IMediator mediator, IMediator mediator,
CurrentUser currentUser, CurrentUser currentUser,
long id, long id,
CancellationToken cancellationToken, [AsParameters] PageQuery page,
[AsParameters] PageQuery page) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetUserFollowersQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetUserFollowersQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult(); return result.ToHttpResult();
@@ -173,8 +173,8 @@ public static class UserEndpoints
IMediator mediator, IMediator mediator,
CurrentUser currentUser, CurrentUser currentUser,
long id, long id,
CancellationToken cancellationToken, [AsParameters] PageQuery page,
[AsParameters] PageQuery page) CancellationToken cancellationToken)
{ {
var result = await mediator.Send(new GetUserFollowingQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken); var result = await mediator.Send(new GetUserFollowingQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult(); return result.ToHttpResult();
@@ -4,8 +4,8 @@ using Musify.Api.DataTransferObjects;
using Musify.Api.DataTransferObjects.Albums; using Musify.Api.DataTransferObjects.Albums;
using Musify.Api.Tests.TestSupport; using Musify.Api.Tests.TestSupport;
using Musify.Application.Albums.Responses; using Musify.Application.Albums.Responses;
using Musify.Application.Pictures.Responses;
using Musify.Application.Contracts; using Musify.Application.Contracts;
using Musify.Application.Pictures.Responses;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
@@ -1,3 +1,4 @@
using System.Globalization;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Mvc.Testing;
@@ -64,7 +65,7 @@ public sealed class ApiTestFixture : WebApplicationFactory<Program>, IAsyncLifet
public HttpClient CreateAuthenticatedClient(long userId, string userName = "test-user") public HttpClient CreateAuthenticatedClient(long userId, string userName = "test-user")
{ {
var client = CreateClient(); var client = CreateClient();
client.DefaultRequestHeaders.Add(FakeAuthenticationHandler.UserIdHeader, userId.ToString()); client.DefaultRequestHeaders.Add(FakeAuthenticationHandler.UserIdHeader, userId.ToString(CultureInfo.InvariantCulture));
client.DefaultRequestHeaders.Add("X-Test-User-Name", userName); client.DefaultRequestHeaders.Add("X-Test-User-Name", userName);
return client; return client;
} }
@@ -1,7 +1,7 @@
using Musify.Application.Genres; using Musify.Application.Genres;
using Musify.Application.Genres.Responses;
using Musify.Domain.ValueObjects; using Musify.Domain.ValueObjects;
using Xunit; using Xunit;
using Musify.Application.Genres.Responses;
namespace Musify.Application.Tests.Genres; namespace Musify.Application.Tests.Genres;
@@ -12,7 +12,11 @@ public abstract class HandlerTestBase : IAsyncLifetime
public async ValueTask InitializeAsync() => database = await TestDatabase.CreateAsync(); public async ValueTask InitializeAsync() => database = await TestDatabase.CreateAsync();
public ValueTask DisposeAsync() => database.DisposeAsync(); public ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
return database.DisposeAsync();
}
protected static ILogger<T> NoOpLogger<T>() => NullLogger<T>.Instance; protected static ILogger<T> NoOpLogger<T>() => NullLogger<T>.Instance;
@@ -18,7 +18,10 @@ public sealed class DeleteRoutingSlipBuilderTests(InfrastructureTestFixture fixt
{ {
var album = await SeedAlbumAsync(new EntityPictures var album = await SeedAlbumAsync(new EntityPictures
{ {
OriginalName = "original.webp", SmallName = "small.webp", MediumName = "medium.webp", LargeName = "large.webp" OriginalName = "original.webp",
SmallName = "small.webp",
MediumName = "medium.webp",
LargeName = "large.webp"
}); });
await using var database = fixture.CreateDatabase(); await using var database = fixture.CreateDatabase();
@@ -116,7 +119,10 @@ public sealed class DeleteRoutingSlipBuilderTests(InfrastructureTestFixture fixt
var track = await SeedTrackAsync( var track = await SeedTrackAsync(
new TrackPictures new TrackPictures
{ {
OriginalName = "original.webp", SmallName = "s.webp", MediumName = "m.webp", LargeName = "l.webp", OriginalName = "original.webp",
SmallName = "s.webp",
MediumName = "m.webp",
LargeName = "l.webp",
ProcessingStatus = ProcessingStatus.Completed ProcessingStatus = ProcessingStatus.Completed
}, },
new TrackAudio { OriginalName = "audio.mp3", FolderName = "folder", TranscodeStatus = ProcessingStatus.Completed }); new TrackAudio { OriginalName = "audio.mp3", FolderName = "folder", TranscodeStatus = ProcessingStatus.Completed });
@@ -18,6 +18,8 @@ public sealed class InfrastructureTestFixture : IAsyncLifetime
public const string S3SecretKey = "musify-test-secret"; public const string S3SecretKey = "musify-test-secret";
public const string S3Bucket = "musify-test-bucket"; public const string S3Bucket = "musify-test-bucket";
private static readonly string[] S3Actions = ["Admin", "Read", "Write"];
private readonly PostgreSqlContainer postgres = new PostgreSqlBuilder("postgres:17-alpine") private readonly PostgreSqlContainer postgres = new PostgreSqlBuilder("postgres:17-alpine")
.WithDatabase("musify_test") .WithDatabase("musify_test")
.WithUsername("postgres") .WithUsername("postgres")
@@ -83,7 +85,7 @@ public sealed class InfrastructureTestFixture : IAsyncLifetime
{ {
name = "musify-test", name = "musify-test",
credentials = new[] { new { accessKey = S3AccessKey, secretKey = S3SecretKey } }, credentials = new[] { new { accessKey = S3AccessKey, secretKey = S3SecretKey } },
actions = new[] { "Admin", "Read", "Write" } actions = S3Actions
} }
} }
}); });
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography; using System.Security.Cryptography;
using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@@ -36,7 +37,7 @@ public sealed class TicketValidatorTests : IDisposable
{ {
var claims = new Dictionary<string, object> { ["prefix"] = prefix }; var claims = new Dictionary<string, object> { ["prefix"] = prefix };
if (maxBytes != null) if (maxBytes != null)
claims["maxBytes"] = maxBytes.Value.ToString(); claims["maxBytes"] = maxBytes.Value.ToString(CultureInfo.InvariantCulture);
var descriptor = new SecurityTokenDescriptor var descriptor = new SecurityTokenDescriptor
{ {
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography; using System.Security.Cryptography;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@@ -39,7 +40,7 @@ public sealed class TicketValidationMiddlewareTests : IDisposable
{ {
var claims = new Dictionary<string, object> { ["prefix"] = prefix }; var claims = new Dictionary<string, object> { ["prefix"] = prefix };
if (maxBytes != null) if (maxBytes != null)
claims["maxBytes"] = maxBytes.Value.ToString(); claims["maxBytes"] = maxBytes.Value.ToString(CultureInfo.InvariantCulture);
return new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor return new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor
{ {