Merge branch 'backend-refactor-rev' into audit-merge-three

Adapt the refactored web player to the new backend API (id path params,
bulk track adds, flat list items, mix kind) and port AddAlbumToPlayList
to the backend conventions.
This commit is contained in:
2026-09-24 17:30:40 +02:00
444 changed files with 14432 additions and 9275 deletions
@@ -63,7 +63,7 @@ namespace Musify.Application.Tests.PlayLists
}
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorized()
public async Task Handle_NotOwner_ReturnsForbidden()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
@@ -73,7 +73,7 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(new AddAlbumToPlayListCommand(stranger.Id, playList.Id, album.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.Equal(ErrorType.Forbidden, result.FirstError.Type);
}
[Fact]
@@ -7,9 +7,9 @@ using Xunit;
namespace Musify.Application.Tests.PlayLists
{
public sealed class AddTrackToPlayListCommandHandlerTests : HandlerTestBase
public sealed class AddTracksToPlayListCommandHandlerTests : HandlerTestBase
{
private AddTrackToPlayListCommandHandler CreateHandler() => new(Database, NoOpLogger<AddTrackToPlayListCommandHandler>());
private AddTracksToPlayListCommandHandler CreateHandler() => new(Database, NoOpLogger<AddTracksToPlayListCommandHandler>());
[Fact]
public async Task Handle_FirstTrack_AddsItAtPositionZero()
@@ -19,7 +19,7 @@ namespace Musify.Application.Tests.PlayLists
var track = TestEntities.Track(owner);
await SeedAsync(owner, playList, track);
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, track.Id), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new AddTracksToPlayListCommand(owner.Id, playList.Id, [track.Id]), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var link = Assert.Single(await Database.PlayListHasTracks.ToListAsync(TestContext.Current.CancellationToken));
@@ -37,7 +37,7 @@ namespace Musify.Application.Tests.PlayLists
owner, playList, existingTrack, newTrack,
new PlayListHasTrack { PlayListId = playList.Id, TrackId = existingTrack.Id, Position = 0 });
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, newTrack.Id), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new AddTracksToPlayListCommand(owner.Id, playList.Id, [newTrack.Id]), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var newLink = await Database.PlayListHasTracks.SingleAsync(link => link.TrackId == newTrack.Id, TestContext.Current.CancellationToken);
@@ -47,22 +47,22 @@ namespace Musify.Application.Tests.PlayLists
[Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound()
{
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(1, Guid.NewGuid(), Guid.NewGuid()), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new AddTracksToPlayListCommand(1, Guid.NewGuid(), [Guid.NewGuid()]), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorized()
public async Task Handle_NotOwner_ReturnsForbidden()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(stranger.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new AddTracksToPlayListCommand(stranger.Id, playList.Id, [Guid.NewGuid()]), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.Equal(ErrorType.Forbidden, result.FirstError.Type);
}
[Fact]
@@ -72,22 +72,59 @@ namespace Musify.Application.Tests.PlayLists
var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new AddTracksToPlayListCommand(owner.Id, playList.Id, [Guid.NewGuid()]), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[Fact]
public async Task Handle_TrackAlreadyInPlayList_ReturnsConflict()
public async Task Handle_TrackAlreadyInPlayList_IsSkipped()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id);
var track = TestEntities.Track(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, track.Id), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new AddTracksToPlayListCommand(owner.Id, playList.Id, [track.Id]), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
Assert.False(result.IsError);
Assert.Single(await Database.PlayListHasTracks.ToListAsync(TestContext.Current.CancellationToken));
}
[Fact]
public async Task TrySaveChangesAsync_DuplicatePlayListTrack_MapsTheUniqueViolationToTheGivenError()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id);
var track = TestEntities.Track(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
Database.PlayListHasTracks.Add(new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 1 });
var result = await Database.TrySaveChangesAsync(Error.Conflict("Test.Duplicate", "duplicate"), TestContext.Current.CancellationToken);
Assert.Equal("Test.Duplicate", result.FirstError.Code);
}
[Fact]
public async Task Handle_SeveralTracks_AppendsThemInOrderSkippingExistingOnes()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id);
var existing = TestEntities.Track(owner, "Existing");
var first = TestEntities.Track(owner, "First");
var second = TestEntities.Track(owner, "Second");
await SeedAsync(
owner, playList, existing, first, second,
new PlayListHasTrack { PlayListId = playList.Id, TrackId = existing.Id, Position = 0 });
var result = await CreateHandler().Handle(
new AddTracksToPlayListCommand(owner.Id, playList.Id, [first.Id, existing.Id, second.Id, first.Id]),
TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var links = await Database.PlayListHasTracks.OrderBy(link => link.Position).ToListAsync(TestContext.Current.CancellationToken);
Assert.Equal([existing.Id, first.Id, second.Id], links.Select(link => link.TrackId));
Assert.Equal([0, 1, 2], links.Select(link => link.Position));
}
}
}
@@ -16,11 +16,9 @@ namespace Musify.Application.Tests.PlayLists
private CreatePlayListCommandHandler CreateHandler() => new(
eventBus,
Database,
new UploadIntentValidator(Database, storageService),
new UploadIntentValidator(Database, storageService, TestConfigurations.UploadIntent()),
NoOpLogger<CreatePlayListCommandHandler>(),
TestConfigurations.Storage(),
TestConfigurations.PlayList(),
TestConfigurations.UploadIntent());
TestConfigurations.PlayList());
[Fact]
public async Task Handle_NoPictureIntent_CreatesPlayListWithoutPictures()
@@ -71,7 +69,7 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Private, result.Value.Visibility);
Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Private, result.Value.Visibility);
}
[Fact]
@@ -80,12 +78,12 @@ namespace Musify.Application.Tests.PlayLists
var user = TestEntities.User();
await SeedAsync(user);
var command = new CreatePlayListCommand(user.Id, "My Playlist", null, PictureIntentId: null, Visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public);
var command = new CreatePlayListCommand(user.Id, "My Playlist", null, PictureIntentId: null, Visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Public, result.Value.Visibility);
Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Public, result.Value.Visibility);
}
[Fact]
@@ -39,7 +39,7 @@ namespace Musify.Application.Tests.PlayLists
}
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorized()
public async Task Handle_NotOwner_ReturnsForbidden()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
@@ -48,7 +48,7 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(new DeletePlayListCommand(stranger.Id, playList.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.Equal(ErrorType.Forbidden, result.FirstError.Type);
}
}
}
@@ -14,7 +14,7 @@ namespace Musify.Application.Tests.PlayLists
public async Task Handle_ExistingPlayList_ReturnsItWithCoverTrackIds()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public);
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
var track = TestEntities.Track(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
@@ -37,7 +37,7 @@ namespace Musify.Application.Tests.PlayLists
public async Task Handle_PrivatePlayList_RequestedByOwner_ReturnsIt()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Private);
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Private);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, owner.Id), TestContext.Current.CancellationToken);
@@ -50,7 +50,7 @@ namespace Musify.Application.Tests.PlayLists
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Private);
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Private);
await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, stranger.Id), TestContext.Current.CancellationToken);
@@ -62,7 +62,7 @@ namespace Musify.Application.Tests.PlayLists
public async Task Handle_PrivatePlayList_RequestedAnonymously_ReturnsNotFound()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Private);
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Private);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, RequestingUserId: null), TestContext.Current.CancellationToken);
@@ -74,7 +74,7 @@ namespace Musify.Application.Tests.PlayLists
public async Task Handle_PublicPlayList_RequestedAnonymously_ReturnsIt()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public);
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, RequestingUserId: null), TestContext.Current.CancellationToken);
@@ -1,51 +0,0 @@
using ErrorOr;
using Musify.Application.PlayLists;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.PlayLists
{
public sealed class GetPlayListCoverQueryHandlerTests : HandlerTestBase
{
private GetPlayListCoverQueryHandler CreateHandler() =>
new(Database, TestConfigurations.Storage("covers-bucket"), TestConfigurations.PlayList());
[Theory]
[InlineData("small", "small.webp")]
[InlineData("large", "large.webp")]
[InlineData("medium", "medium.webp")]
public async Task Handle_ExistingPlayList_ReturnsRequestedSize(string size, string expectedFileName)
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListCoverQuery(playList.Id, size), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal("covers-bucket", result.Value.Bucket);
Assert.EndsWith(expectedFileName, result.Value.Key);
}
[Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound()
{
var result = await CreateHandler().Handle(new GetPlayListCoverQuery(Guid.NewGuid(), "small"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[Fact]
public async Task Handle_PlayListWithoutPictures_ReturnsNotFound()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id);
playList.Pictures = null;
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListCoverQuery(playList.Id, "small"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
}
}
@@ -2,6 +2,7 @@ using ErrorOr;
using Musify.Application.PlayLists;
using Musify.Application.Tests.TestSupport;
using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
using Xunit;
namespace Musify.Application.Tests.PlayLists
@@ -22,7 +23,7 @@ namespace Musify.Application.Tests.PlayLists
new PlayListHasTrack { PlayListId = playList.Id, TrackId = second.Id, Position = 1 },
new PlayListHasTrack { PlayListId = playList.Id, TrackId = first.Id, Position = 0 });
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
@@ -35,5 +36,29 @@ namespace Musify.Application.Tests.PlayLists
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[Fact]
public async Task Handle_PrivatePlayListOfAnotherUser_ReturnsNotFound()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: PlayListVisibility.Private);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: 999), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[Fact]
public async Task Handle_PrivatePlayListOfViewer_ReturnsTracks()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: PlayListVisibility.Private);
await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
}
}
}
@@ -20,39 +20,46 @@ namespace Musify.Application.Tests.PlayLists
TestEntities.PlayList(owner.Id, "Being removed", lifeCycleStatus: LifeCycleStatus.Removing),
TestEntities.PlayList(other.Id, "Theirs"));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var playList = Assert.Single(result.Value.Items);
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
var playList = Assert.Single(result.Items);
Assert.Equal("Mine", playList.Name);
}
[Fact]
public async Task Handle_OnlyPublic_ReturnsOnlyPublicPlayLists()
public async Task Handle_AnotherViewer_ReturnsOnlyPublicPlayLists()
{
var owner = TestEntities.User();
await SeedAsync(
owner,
TestEntities.PlayList(owner.Id, "Public one", visibility: PlaylistVisibility.Public),
TestEntities.PlayList(owner.Id, "Private one", visibility: PlaylistVisibility.Private));
TestEntities.PlayList(owner.Id, "Public one", visibility: PlayListVisibility.Public),
TestEntities.PlayList(owner.Id, "Private one", visibility: PlayListVisibility.Private));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, OnlyPublic: true), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var playList = Assert.Single(result.Value.Items);
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: 999), TestContext.Current.CancellationToken);
var playList = Assert.Single(result.Items);
Assert.Equal("Public one", playList.Name);
}
[Fact]
public async Task Handle_Owner_SeesPrivatePlayLists()
{
var owner = TestEntities.User();
await SeedAsync(
owner,
TestEntities.PlayList(owner.Id, "Public one", visibility: PlayListVisibility.Public),
TestEntities.PlayList(owner.Id, "Private one", visibility: PlayListVisibility.Private));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
Assert.Equal(2, result.Items.Count);
}
[Fact]
public async Task Handle_NameFilter_ReturnsOnlyMatchingPlayLists()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.PlayList(owner.Id, "Road Trip"), TestEntities.PlayList(owner.Id, "Study Focus"));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: "road", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var playList = Assert.Single(result.Value.Items);
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: "road", PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
var playList = Assert.Single(result.Items);
Assert.Equal("Road Trip", playList.Name);
}
}
@@ -1,32 +0,0 @@
using Musify.Application.PlayLists;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.PlayLists
{
public sealed class GetPlayListsQueryHandlerTests : HandlerTestBase
{
private GetPlayListsQueryHandler CreateHandler() => new(Database);
[Fact]
public async Task Handle_ReturnsEveryPlayListPaged()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.PlayList(owner.Id, "One"), TestEntities.PlayList(owner.Id, "Two"));
var result = await CreateHandler().Handle(new GetPlayListsQuery(PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount);
}
[Fact]
public async Task Handle_NoPlayLists_ReturnsEmptyPage()
{
var result = await CreateHandler().Handle(new GetPlayListsQuery(PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Empty(result.Value.Items);
}
}
}
@@ -33,7 +33,7 @@ namespace Musify.Application.Tests.PlayLists
}
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorized()
public async Task Handle_NotOwner_ReturnsForbidden()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
@@ -42,7 +42,7 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(stranger.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.Equal(ErrorType.Forbidden, result.FirstError.Type);
}
[Fact]
@@ -56,5 +56,27 @@ namespace Musify.Application.Tests.PlayLists
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[Fact]
public async Task Handle_TrackInTheMiddle_RenumbersTheRemainingTracks()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id);
var first = TestEntities.Track(owner, "First");
var second = TestEntities.Track(owner, "Second");
var third = TestEntities.Track(owner, "Third");
await SeedAsync(
owner, playList, first, second, third,
new PlayListHasTrack { PlayListId = playList.Id, TrackId = first.Id, Position = 0 },
new PlayListHasTrack { PlayListId = playList.Id, TrackId = second.Id, Position = 1 },
new PlayListHasTrack { PlayListId = playList.Id, TrackId = third.Id, Position = 2 });
var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(owner.Id, playList.Id, second.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
var remaining = Database.PlayListHasTracks.OrderBy(link => link.Position).ToList();
Assert.Equal([first.Id, third.Id], remaining.Select(link => link.TrackId));
Assert.Equal([0, 1], remaining.Select(link => link.Position));
}
}
}
@@ -17,17 +17,20 @@ namespace Musify.Application.Tests.PlayLists
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();
return new RequestPlayListPictureUploadCommandHandler(
Database,
new UploadIntentValidator(Database, storageService),
storageService,
NoOpLogger<RequestPlayListPictureUploadCommandHandler>(),
TestConfigurations.Storage(),
TestConfigurations.PlayList(),
uploadIntentConfig ?? TestConfigurations.UploadIntent());
new UploadIntentIssuer(
Database,
storageService,
new UploadIntentValidator(Database, storageService, config),
TestConfigurations.Storage(),
config,
NoOpLogger<UploadIntentIssuer>()),
TestConfigurations.PlayList());
}
[Fact]
@@ -16,11 +16,9 @@ namespace Musify.Application.Tests.PlayLists
private UpdatePlayListCommandHandler CreateHandler() => new(
eventBus,
Database,
new UploadIntentValidator(Database, storageService),
new UploadIntentValidator(Database, storageService, TestConfigurations.UploadIntent()),
NoOpLogger<UpdatePlayListCommandHandler>(),
TestConfigurations.Storage(),
TestConfigurations.PlayList(),
TestConfigurations.UploadIntent());
TestConfigurations.PlayList());
[Fact]
public async Task Handle_Owner_RenamesPlayList()
@@ -48,7 +46,7 @@ namespace Musify.Application.Tests.PlayLists
}
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorized()
public async Task Handle_NotOwner_ReturnsForbidden()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
@@ -59,7 +57,7 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.Equal(ErrorType.Forbidden, result.FirstError.Type);
}
[Fact]
@@ -69,19 +67,19 @@ namespace Musify.Application.Tests.PlayLists
var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList);
var command = new UpdatePlayListCommand(owner.Id, playList.Id, null, null, null, Musify.Domain.ValueObjects.PlaylistVisibility.Public);
var command = new UpdatePlayListCommand(owner.Id, playList.Id, null, null, null, Musify.Domain.ValueObjects.PlayListVisibility.Public);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Public, result.Value.Visibility);
Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Public, result.Value.Visibility);
}
[Fact]
public async Task Handle_NoVisibilitySpecified_LeavesItUnchanged()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public);
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
await SeedAsync(owner, playList);
var command = new UpdatePlayListCommand(owner.Id, playList.Id, "New Name", null, null);
@@ -89,11 +87,11 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Public, result.Value.Visibility);
Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Public, result.Value.Visibility);
}
[Fact]
public async Task Handle_BlankNameAndDescription_LeavesThemUnchanged()
public async Task Handle_BlankName_LeavesItUnchangedAndBlankDescriptionClearsIt()
{
var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, "Original Name", "Original description");
@@ -105,7 +103,7 @@ namespace Musify.Application.Tests.PlayLists
Assert.False(result.IsError);
Assert.Equal("Original Name", result.Value.Name);
Assert.Equal("Original description", result.Value.Description);
Assert.Null(result.Value.Description);
}
[Fact]
@@ -126,7 +124,7 @@ namespace Musify.Application.Tests.PlayLists
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal("new-cover.webp", result.Value.SmallImageKeyName);
Assert.Null(result.Value.SmallImageKeyName);
var stored = await Database.PlayLists.FindAsync([playList.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored);
Assert.Equal("new-cover.webp", stored.Pictures?.OriginalName);