Simplify infrastructure services and move all recurring jobs to Hangfire

This commit is contained in:
2026-09-24 12:18:57 +02:00
parent 0fa033faff
commit 0cba32f776
24 changed files with 361 additions and 413 deletions
@@ -17,7 +17,7 @@ public sealed class RequestAlbumPictureUploadCommandHandlerTests : HandlerTestBa
private RequestAlbumPictureUploadCommandHandler CreateHandler(UploadIntentConfiguration? uploadIntentConfig = null)
{
storageService
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<CancellationToken>())
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns("https://storage.musify.test/presigned-upload");
var config = uploadIntentConfig ?? TestConfigurations.UploadIntent();
@@ -17,9 +17,7 @@ public sealed class GenerateMixesForUserCommandHandlerTests : HandlerTestBase
var user = TestEntities.User();
await SeedAsync(user);
var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.Empty(await Database.Mixes.ToListAsync(TestContext.Current.CancellationToken));
}
@@ -32,9 +30,7 @@ public sealed class GenerateMixesForUserCommandHandlerTests : HandlerTestBase
user, track,
TestEntities.ListeningHistory(user.Id, track.Id));
var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
var mixes = await Database.Mixes.ToListAsync(TestContext.Current.CancellationToken);
Assert.Single(mixes);
Assert.Equal(MixKind.Daily, mixes[0].Kind);
@@ -54,9 +50,7 @@ public sealed class GenerateMixesForUserCommandHandlerTests : HandlerTestBase
user, listenedTrack, unheardTrack, staleMix, staleItem,
TestEntities.ListeningHistory(user.Id, listenedTrack.Id));
var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
var mixes = await Database.Mixes.ToListAsync(TestContext.Current.CancellationToken);
Assert.DoesNotContain(mixes, mix => mix.Id == staleMix.Id);
Assert.Contains(mixes, mix => mix.Kind == MixKind.Discovery);
@@ -17,7 +17,7 @@ public sealed class RequestPlayListPictureUploadCommandHandlerTests : HandlerTes
private RequestPlayListPictureUploadCommandHandler CreateHandler(UploadIntentConfiguration? uploadIntentConfig = null)
{
storageService
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<CancellationToken>())
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns("https://storage.musify.test/presigned-upload");
var config = uploadIntentConfig ?? TestConfigurations.UploadIntent();
@@ -18,7 +18,7 @@ public sealed class RequestTrackUploadUrlsCommandHandlerTests : HandlerTestBase
private RequestTrackUploadUrlsCommandHandler CreateHandler(UploadIntentConfiguration? uploadIntentConfig = null)
{
storageService
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<CancellationToken>())
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns("https://storage.musify.test/presigned-upload");
var config = uploadIntentConfig ?? TestConfigurations.UploadIntent();
@@ -0,0 +1,71 @@
using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
using Musify.Application.Tests.TestSupport;
using Musify.Application.UploadIntents;
using Musify.Domain.ValueObjects;
using NSubstitute;
using Xunit;
namespace Musify.Application.Tests.UploadIntents;
public sealed class UploadIntentJobsCommandHandlerTests : HandlerTestBase
{
[Fact]
public async Task ExpireUploadIntents_OverdueIssuedIntent_IsMarkedExpired()
{
var owner = TestEntities.User();
var overdue = TestEntities.UploadIntent(owner.Id, expiresAt: DateTime.UtcNow.AddMinutes(-5));
var active = TestEntities.UploadIntent(owner.Id, expiresAt: DateTime.UtcNow.AddMinutes(5));
await SeedAsync(owner, overdue, active);
var handler = new ExpireUploadIntentsCommandHandler(
Database, TestConfigurations.UploadIntent(), NoOpLogger<ExpireUploadIntentsCommandHandler>());
var changed = await handler.Handle(new ExpireUploadIntentsCommand(), TestContext.Current.CancellationToken);
Assert.Equal(1, changed);
var stored = await Database.UploadIntents.AsNoTracking().ToListAsync(TestContext.Current.CancellationToken);
Assert.Equal(UploadIntentStatus.Expired, stored.Single(intent => intent.Id == overdue.Id).Status);
Assert.Equal(UploadIntentStatus.Issued, stored.Single(intent => intent.Id == active.Id).Status);
}
[Fact]
public async Task ExpireUploadIntents_IntentExpiredLongAgo_IsDeleted()
{
var owner = TestEntities.User();
var old = TestEntities.UploadIntent(
owner.Id, status: UploadIntentStatus.Expired, expiresAt: DateTime.UtcNow.AddDays(-30));
await SeedAsync(owner, old);
var handler = new ExpireUploadIntentsCommandHandler(
Database, TestConfigurations.UploadIntent(), NoOpLogger<ExpireUploadIntentsCommandHandler>());
await handler.Handle(new ExpireUploadIntentsCommand(), TestContext.Current.CancellationToken);
Assert.Empty(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
}
[Fact]
public async Task DeleteStaleTempUploads_RemovesOnlyObjectsOlderThanTheRetention()
{
var storage = Substitute.For<IStorageService>();
storage.ListObjectsAsync("test-bucket", "temp", Arg.Any<CancellationToken>()).Returns(Objects(
new StorageObject("temp/old", DateTime.UtcNow.AddDays(-10)),
new StorageObject("temp/new", DateTime.UtcNow)));
var handler = new DeleteStaleTempUploadsCommandHandler(
storage, TestConfigurations.Storage("test-bucket"), TestConfigurations.UploadIntent(),
NoOpLogger<DeleteStaleTempUploadsCommandHandler>());
var deleted = await handler.Handle(new DeleteStaleTempUploadsCommand(), TestContext.Current.CancellationToken);
Assert.Equal(1, deleted);
await storage.Received(1).RemoveFileAsync("test-bucket", "temp/old", Arg.Any<CancellationToken>());
await storage.DidNotReceive().RemoveFileAsync("test-bucket", "temp/new", Arg.Any<CancellationToken>());
}
private static async IAsyncEnumerable<StorageObject> Objects(params StorageObject[] objects)
{
foreach (var item in objects)
yield return item;
await Task.CompletedTask;
}
}