Add editorconfig and analyzers, format backend code
This commit is contained in:
+80
-81
@@ -5,108 +5,107 @@ using Musify.Application.Tests.TestSupport;
|
||||
using Musify.Domain.Entities;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
|
||||
private AddTrackToAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<AddTrackToAlbumCommandHandler>());
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_FirstTrack_AddsItAtNumberOne()
|
||||
{
|
||||
private AddTrackToAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<AddTrackToAlbumCommandHandler>());
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, album, track);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_FirstTrack_AddsItAtNumberOne()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, album, track);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
var link = Assert.Single(await Database.AlbumHasTracks.ToListAsync(TestContext.Current.CancellationToken));
|
||||
Assert.Equal(1, link.TrackNumber);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
var link = Assert.Single(await Database.AlbumHasTracks.ToListAsync(TestContext.Current.CancellationToken));
|
||||
Assert.Equal(1, link.TrackNumber);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, track);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, track);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, Guid.NewGuid(), track.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, Guid.NewGuid(), track.Id), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, stranger, album, track);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, stranger, album, track);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(stranger.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(stranger.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_TrackMissing_ReturnsNotFound()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TrackMissing_ReturnsNotFound()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_TrackOwnedByAnotherUser_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var otherOwner = TestEntities.User(2, "other");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var foreignTrack = TestEntities.Track(otherOwner);
|
||||
await SeedAsync(owner, otherOwner, album, foreignTrack);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TrackOwnedByAnotherUser_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var otherOwner = TestEntities.User(2, "other");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var foreignTrack = TestEntities.Track(otherOwner);
|
||||
await SeedAsync(owner, otherOwner, album, foreignTrack);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, foreignTrack.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, foreignTrack.Id), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlreadyInAlbum_ReturnsConflict()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 };
|
||||
await SeedAsync(owner, album, track, link);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlreadyInAlbum_ReturnsConflict()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 };
|
||||
await SeedAsync(owner, album, track, link);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumAlreadyHasTracks_AppendsAtNextNumber()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var firstTrack = TestEntities.Track(owner, "First");
|
||||
var secondTrack = TestEntities.Track(owner, "Second");
|
||||
var existingLink = new AlbumHasTrack { AlbumId = album.Id, TrackId = firstTrack.Id, TrackNumber = 3 };
|
||||
await SeedAsync(owner, album, firstTrack, secondTrack, existingLink);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumAlreadyHasTracks_AppendsAtNextNumber()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var firstTrack = TestEntities.Track(owner, "First");
|
||||
var secondTrack = TestEntities.Track(owner, "Second");
|
||||
var existingLink = new AlbumHasTrack { AlbumId = album.Id, TrackId = firstTrack.Id, TrackNumber = 3 };
|
||||
await SeedAsync(owner, album, firstTrack, secondTrack, existingLink);
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, secondTrack.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, secondTrack.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.False(result.IsError);
|
||||
var newLink = await Database.AlbumHasTracks.SingleAsync(l => l.TrackId == secondTrack.Id, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(4, newLink.TrackNumber);
|
||||
}
|
||||
Assert.False(result.IsError);
|
||||
var newLink = await Database.AlbumHasTracks.SingleAsync(l => l.TrackId == secondTrack.Id, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(4, newLink.TrackNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,72 +6,71 @@ using Musify.Application.Tests.TestSupport;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
|
||||
private readonly IEventBus eventBus = Substitute.For<IEventBus>();
|
||||
private readonly IStorageService storageService = Substitute.For<IStorageService>();
|
||||
|
||||
private CreateAlbumCommandHandler CreateHandler() => new(
|
||||
eventBus,
|
||||
Database,
|
||||
new UploadIntentValidator(Database, storageService),
|
||||
NoOpLogger<CreateAlbumCommandHandler>(),
|
||||
TestConfigurations.Storage(),
|
||||
TestConfigurations.Album(),
|
||||
TestConfigurations.UploadIntent());
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPictureIntent_CreatesAlbumAndPublishesResourcesEvent()
|
||||
{
|
||||
private readonly IEventBus eventBus = Substitute.For<IEventBus>();
|
||||
private readonly IStorageService storageService = Substitute.For<IStorageService>();
|
||||
storageService
|
||||
.HeadObjectAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ObjectMetaData("image/webp", 1024));
|
||||
|
||||
private CreateAlbumCommandHandler CreateHandler() => new(
|
||||
eventBus,
|
||||
Database,
|
||||
new UploadIntentValidator(Database, storageService),
|
||||
NoOpLogger<CreateAlbumCommandHandler>(),
|
||||
TestConfigurations.Storage(),
|
||||
TestConfigurations.Album(),
|
||||
TestConfigurations.UploadIntent());
|
||||
var owner = TestEntities.User();
|
||||
var intent = TestEntities.UploadIntent(owner.Id, Musify.Domain.ValueObjects.UploadIntentPurpose.AlbumPicture, objectName: "cover.webp");
|
||||
await SeedAsync(owner, intent);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPictureIntent_CreatesAlbumAndPublishesResourcesEvent()
|
||||
{
|
||||
storageService
|
||||
.HeadObjectAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ObjectMetaData("image/webp", 1024));
|
||||
var command = new CreateAlbumCommand(owner.Id, " My Album ", "A description", 2024, intent.Id);
|
||||
|
||||
var owner = TestEntities.User();
|
||||
var intent = TestEntities.UploadIntent(owner.Id, Musify.Domain.ValueObjects.UploadIntentPurpose.AlbumPicture, objectName: "cover.webp");
|
||||
await SeedAsync(owner, intent);
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var command = new CreateAlbumCommand(owner.Id, " My Album ", "A description", 2024, intent.Id);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("My Album", result.Value.Title);
|
||||
Assert.Equal("A description", result.Value.Description);
|
||||
Assert.Equal(2024, result.Value.ReleaseYear);
|
||||
Assert.Equal(owner.Id, result.Value.OwnerUserId);
|
||||
Assert.Equal(0, result.Value.TrackCount);
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
var stored = await Database.Albums.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("MY ALBUM", stored.NormalizedTitle);
|
||||
Assert.Equal("cover.webp", stored.Pictures?.OriginalName);
|
||||
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.CreateAlbumResourcesEvent>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("My Album", result.Value.Title);
|
||||
Assert.Equal("A description", result.Value.Description);
|
||||
Assert.Equal(2024, result.Value.ReleaseYear);
|
||||
Assert.Equal(owner.Id, result.Value.OwnerUserId);
|
||||
Assert.Equal(0, result.Value.TrackCount);
|
||||
[Fact]
|
||||
public async Task Handle_UserDoesNotExist_ReturnsNotFound()
|
||||
{
|
||||
var command = new CreateAlbumCommand(404, "Orphan Album", null, null, Guid.NewGuid());
|
||||
|
||||
var stored = await Database.Albums.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("MY ALBUM", stored.NormalizedTitle);
|
||||
Assert.Equal("cover.webp", stored.Pictures?.OriginalName);
|
||||
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.CreateAlbumResourcesEvent>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserDoesNotExist_ReturnsNotFound()
|
||||
{
|
||||
var command = new CreateAlbumCommand(404, "Orphan Album", null, null, Guid.NewGuid());
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
[Fact]
|
||||
public async Task Handle_PictureIntentNotFound_ReturnsError()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
var command = new CreateAlbumCommand(owner.Id, "My Album", null, null, Guid.NewGuid());
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PictureIntentNotFound_ReturnsError()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner);
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var command = new CreateAlbumCommand(owner.Id, "My Album", null, null, Guid.NewGuid());
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(result.IsError);
|
||||
}
|
||||
Assert.True(result.IsError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,45 +3,44 @@ using Musify.Application.Albums;
|
||||
using Musify.Application.Tests.TestSupport;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class DeleteAlbumCommandHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class DeleteAlbumCommandHandlerTests : HandlerTestBase
|
||||
private DeleteAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<DeleteAlbumCommandHandler>());
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_Owner_RemovesAlbum()
|
||||
{
|
||||
private DeleteAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<DeleteAlbumCommandHandler>());
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_Owner_RemovesAlbum()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
var result = await CreateHandler().Handle(new DeleteAlbumCommand(owner.Id, album.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new DeleteAlbumCommand(owner.Id, album.Id), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Null(await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Null(await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken));
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new DeleteAlbumCommand(1, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new DeleteAlbumCommand(1, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorizedAndKeepsAlbum()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, stranger, album);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorizedAndKeepsAlbum()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, stranger, album);
|
||||
var result = await CreateHandler().Handle(new DeleteAlbumCommand(stranger.Id, album.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new DeleteAlbumCommand(stranger.Id, album.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
Assert.NotNull(await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken));
|
||||
}
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
Assert.NotNull(await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,33 @@ using Musify.Application.Tests.TestSupport;
|
||||
using Musify.Domain.Entities;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class GetAlbumByIdQueryHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class GetAlbumByIdQueryHandlerTests : HandlerTestBase
|
||||
private GetAlbumByIdQueryHandler CreateHandler() => new(Database);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ExistingAlbum_ReturnsItWithTrackCountAndCovers()
|
||||
{
|
||||
private GetAlbumByIdQueryHandler CreateHandler() => new(Database);
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, album, track, new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 });
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ExistingAlbum_ReturnsItWithTrackCountAndCovers()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var track = TestEntities.Track(owner);
|
||||
await SeedAsync(owner, album, track, new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 });
|
||||
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(album.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(album.Id), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(album.Id, result.Value.Id);
|
||||
Assert.Equal(1, result.Value.TrackCount);
|
||||
Assert.Equal([track.Id], result.Value.CoverTrackIds);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(album.Id, result.Value.Id);
|
||||
Assert.Equal(1, result.Value.TrackCount);
|
||||
Assert.Equal([track.Id], result.Value.CoverTrackIds);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,36 +3,35 @@ using Musify.Application.Albums;
|
||||
using Musify.Application.Tests.TestSupport;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class GetAlbumCoverQueryHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class GetAlbumCoverQueryHandlerTests : HandlerTestBase
|
||||
private GetAlbumCoverQueryHandler CreateHandler() =>
|
||||
new(Database, TestConfigurations.Storage("covers-bucket"), TestConfigurations.Album());
|
||||
|
||||
[Theory]
|
||||
[InlineData("small", "small.webp")]
|
||||
[InlineData("large", "large.webp")]
|
||||
[InlineData("medium", "medium.webp")]
|
||||
public async Task Handle_ExistingAlbum_ReturnsRequestedSize(string size, string expectedFileName)
|
||||
{
|
||||
private GetAlbumCoverQueryHandler CreateHandler() =>
|
||||
new(Database, TestConfigurations.Storage("covers-bucket"), TestConfigurations.Album());
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
|
||||
[Theory]
|
||||
[InlineData("small", "small.webp")]
|
||||
[InlineData("large", "large.webp")]
|
||||
[InlineData("medium", "medium.webp")]
|
||||
public async Task Handle_ExistingAlbum_ReturnsRequestedSize(string size, string expectedFileName)
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
var result = await CreateHandler().Handle(new GetAlbumCoverQuery(album.Id, size), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumCoverQuery(album.Id, size), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("covers-bucket", result.Value.Bucket);
|
||||
Assert.EndsWith(expectedFileName, result.Value.Key);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("covers-bucket", result.Value.Bucket);
|
||||
Assert.EndsWith(expectedFileName, result.Value.Key);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumCoverQuery(Guid.NewGuid(), "small"), TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumCoverQuery(Guid.NewGuid(), "small"), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,36 +4,35 @@ using Musify.Application.Tests.TestSupport;
|
||||
using Musify.Domain.Entities;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class GetAlbumTracksQueryHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class GetAlbumTracksQueryHandlerTests : HandlerTestBase
|
||||
private GetAlbumTracksQueryHandler CreateHandler() => new(Database);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumWithTracks_ReturnsThemOrderedByTrackNumber()
|
||||
{
|
||||
private GetAlbumTracksQueryHandler CreateHandler() => new(Database);
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var second = TestEntities.Track(owner, "Second");
|
||||
var first = TestEntities.Track(owner, "First");
|
||||
await SeedAsync(
|
||||
owner, album, second, first,
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 },
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = first.Id, TrackNumber = 1 });
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumWithTracks_ReturnsThemOrderedByTrackNumber()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var second = TestEntities.Track(owner, "Second");
|
||||
var first = TestEntities.Track(owner, "First");
|
||||
await SeedAsync(
|
||||
owner, album, second, first,
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 },
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = first.Id, TrackNumber = 1 });
|
||||
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(album.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(album.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(Guid.NewGuid(), PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(Guid.NewGuid(), PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-21
@@ -3,32 +3,31 @@ using Musify.Application.Albums;
|
||||
using Musify.Application.Tests.TestSupport;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class GetAlbumsByUserIdQueryHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class GetAlbumsByUserIdQueryHandlerTests : HandlerTestBase
|
||||
private GetAlbumsByUserIdQueryHandler CreateHandler() => new(Database);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserWithAlbums_ReturnsOnlyTheirAlbums()
|
||||
{
|
||||
private GetAlbumsByUserIdQueryHandler CreateHandler() => new(Database);
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var other = TestEntities.User(2, "other");
|
||||
await SeedAsync(owner, other, TestEntities.Album(owner.Id, "Mine"), TestEntities.Album(other.Id, "Theirs"));
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserWithAlbums_ReturnsOnlyTheirAlbums()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var other = TestEntities.User(2, "other");
|
||||
await SeedAsync(owner, other, TestEntities.Album(owner.Id, "Mine"), TestEntities.Album(other.Id, "Theirs"));
|
||||
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(owner.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(owner.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
var album = Assert.Single(result.Value.Items);
|
||||
Assert.Equal("Mine", album.Title);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
var album = Assert.Single(result.Value.Items);
|
||||
Assert.Equal("Mine", album.Title);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_UserMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(404, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(404, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,49 +2,48 @@ using Musify.Application.Albums;
|
||||
using Musify.Application.Tests.TestSupport;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
|
||||
private GetAlbumsQueryHandler CreateHandler() => new(Database);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NoTitleFilter_ReturnsAllAlbumsPaged()
|
||||
{
|
||||
private GetAlbumsQueryHandler CreateHandler() => new(Database);
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner, TestEntities.Album(owner.Id, "Alpha"), TestEntities.Album(owner.Id, "Beta"));
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NoTitleFilter_ReturnsAllAlbumsPaged()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner, TestEntities.Album(owner.Id, "Alpha"), TestEntities.Album(owner.Id, "Beta"));
|
||||
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(2, result.Value.TotalItemCount);
|
||||
Assert.All(result.Value.Items, item => Assert.NotNull(item.Album));
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(2, result.Value.TotalItemCount);
|
||||
Assert.All(result.Value.Items, item => Assert.NotNull(item.Album));
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_TitleFilter_ReturnsMatchingAlbumsOnly()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner, TestEntities.Album(owner.Id, "Rock Classics"), TestEntities.Album(owner.Id, "Jazz Favorites"));
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TitleFilter_ReturnsMatchingAlbumsOnly()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner, TestEntities.Album(owner.Id, "Rock Classics"), TestEntities.Album(owner.Id, "Jazz Favorites"));
|
||||
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: "rock", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: "rock", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
var item = Assert.Single(result.Value.Items);
|
||||
Assert.Equal("Rock Classics", item.Album.Title);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
var item = Assert.Single(result.Value.Items);
|
||||
Assert.Equal("Rock Classics", item.Album.Title);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_PageSizeSmallerThanTotal_ReportsHasNextPage()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner, TestEntities.Album(owner.Id, "One"), TestEntities.Album(owner.Id, "Two"), TestEntities.Album(owner.Id, "Three"));
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_PageSizeSmallerThanTotal_ReportsHasNextPage()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner, TestEntities.Album(owner.Id, "One"), TestEntities.Album(owner.Id, "Two"), TestEntities.Album(owner.Id, "Three"));
|
||||
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 2), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 2), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(2, result.Value.Items.Count);
|
||||
Assert.True(result.Value.HasNextPage);
|
||||
}
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(2, result.Value.Items.Count);
|
||||
Assert.True(result.Value.HasNextPage);
|
||||
}
|
||||
}
|
||||
|
||||
+60
-61
@@ -3,79 +3,78 @@ using Musify.Application.Tests.TestSupport;
|
||||
using Musify.Domain.Entities;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
|
||||
private GetRecentlyListenedAlbumsQueryHandler CreateHandler() => new(Database);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ListenedTracks_ReturnsTheirAlbumsMostRecentFirst()
|
||||
{
|
||||
private GetRecentlyListenedAlbumsQueryHandler CreateHandler() => new(Database);
|
||||
var owner = TestEntities.User();
|
||||
var olderAlbum = TestEntities.Album(owner.Id, "Older");
|
||||
var newerAlbum = TestEntities.Album(owner.Id, "Newer");
|
||||
var olderTrack = TestEntities.Track(owner, "In older album");
|
||||
var newerTrack = TestEntities.Track(owner, "In newer album");
|
||||
await SeedAsync(
|
||||
owner, olderAlbum, newerAlbum, olderTrack, newerTrack,
|
||||
new AlbumHasTrack { AlbumId = olderAlbum.Id, TrackId = olderTrack.Id, TrackNumber = 1 },
|
||||
new AlbumHasTrack { AlbumId = newerAlbum.Id, TrackId = newerTrack.Id, TrackNumber = 1 },
|
||||
TestEntities.ListeningHistory(owner.Id, olderTrack.Id, DateTime.UtcNow.AddDays(-2)),
|
||||
TestEntities.ListeningHistory(owner.Id, newerTrack.Id, DateTime.UtcNow.AddDays(-1)));
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ListenedTracks_ReturnsTheirAlbumsMostRecentFirst()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var olderAlbum = TestEntities.Album(owner.Id, "Older");
|
||||
var newerAlbum = TestEntities.Album(owner.Id, "Newer");
|
||||
var olderTrack = TestEntities.Track(owner, "In older album");
|
||||
var newerTrack = TestEntities.Track(owner, "In newer album");
|
||||
await SeedAsync(
|
||||
owner, olderAlbum, newerAlbum, olderTrack, newerTrack,
|
||||
new AlbumHasTrack { AlbumId = olderAlbum.Id, TrackId = olderTrack.Id, TrackNumber = 1 },
|
||||
new AlbumHasTrack { AlbumId = newerAlbum.Id, TrackId = newerTrack.Id, TrackNumber = 1 },
|
||||
TestEntities.ListeningHistory(owner.Id, olderTrack.Id, DateTime.UtcNow.AddDays(-2)),
|
||||
TestEntities.ListeningHistory(owner.Id, newerTrack.Id, DateTime.UtcNow.AddDays(-1)));
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(["Newer", "Older"], result.Value.Select(album => album.Title));
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(["Newer", "Older"], result.Value.Select(album => album.Title));
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_NoListeningHistory_ReturnsEmptyList()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NoListeningHistory_ReturnsEmptyList()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
await SeedAsync(owner);
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Empty(result.Value);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Empty(result.Value);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_LimitLowerThanCandidates_ReturnsOnlyThatManyAlbums()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var albums = Enumerable.Range(1, 3).Select(i => TestEntities.Album(owner.Id, $"Album {i}")).ToList();
|
||||
var tracks = albums.Select((_, i) => TestEntities.Track(owner, $"Track {i}")).ToList();
|
||||
var links = albums.Zip(tracks, (album, track) => new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 }).ToList();
|
||||
var histories = tracks.Select((track, i) => TestEntities.ListeningHistory(owner.Id, track.Id, DateTime.UtcNow.AddMinutes(-i))).ToList();
|
||||
await SeedAsync([owner, .. albums, .. tracks, .. links, .. histories]);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_LimitLowerThanCandidates_ReturnsOnlyThatManyAlbums()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var albums = Enumerable.Range(1, 3).Select(i => TestEntities.Album(owner.Id, $"Album {i}")).ToList();
|
||||
var tracks = albums.Select((_, i) => TestEntities.Track(owner, $"Track {i}")).ToList();
|
||||
var links = albums.Zip(tracks, (album, track) => new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 }).ToList();
|
||||
var histories = tracks.Select((track, i) => TestEntities.ListeningHistory(owner.Id, track.Id, DateTime.UtcNow.AddMinutes(-i))).ToList();
|
||||
await SeedAsync([owner, .. albums, .. tracks, .. links, .. histories]);
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 2), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 2), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(2, result.Value.Count);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal(2, result.Value.Count);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_UncountedListens_AreIgnored()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var countedAlbum = TestEntities.Album(owner.Id, "Counted album");
|
||||
var skippedAlbum = TestEntities.Album(owner.Id, "Skipped album");
|
||||
var countedTrack = TestEntities.Track(owner, "In counted");
|
||||
var skippedTrack = TestEntities.Track(owner, "In skipped");
|
||||
await SeedAsync(
|
||||
owner, countedAlbum, skippedAlbum, countedTrack, skippedTrack,
|
||||
new AlbumHasTrack { AlbumId = countedAlbum.Id, TrackId = countedTrack.Id, TrackNumber = 1 },
|
||||
new AlbumHasTrack { AlbumId = skippedAlbum.Id, TrackId = skippedTrack.Id, TrackNumber = 1 },
|
||||
TestEntities.ListeningHistory(owner.Id, countedTrack.Id, DateTime.UtcNow.AddDays(-1), playedSeconds: 120),
|
||||
TestEntities.ListeningHistory(owner.Id, skippedTrack.Id, DateTime.UtcNow, playedSeconds: 1, isCounted: false));
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UncountedListens_AreIgnored()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var countedAlbum = TestEntities.Album(owner.Id, "Counted album");
|
||||
var skippedAlbum = TestEntities.Album(owner.Id, "Skipped album");
|
||||
var countedTrack = TestEntities.Track(owner, "In counted");
|
||||
var skippedTrack = TestEntities.Track(owner, "In skipped");
|
||||
await SeedAsync(
|
||||
owner, countedAlbum, skippedAlbum, countedTrack, skippedTrack,
|
||||
new AlbumHasTrack { AlbumId = countedAlbum.Id, TrackId = countedTrack.Id, TrackNumber = 1 },
|
||||
new AlbumHasTrack { AlbumId = skippedAlbum.Id, TrackId = skippedTrack.Id, TrackNumber = 1 },
|
||||
TestEntities.ListeningHistory(owner.Id, countedTrack.Id, DateTime.UtcNow.AddDays(-1), playedSeconds: 120),
|
||||
TestEntities.ListeningHistory(owner.Id, skippedTrack.Id, DateTime.UtcNow, playedSeconds: 1, isCounted: false));
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(["Counted album"], result.Value.Select(album => album.Title));
|
||||
}
|
||||
Assert.Equal(["Counted album"], result.Value.Select(album => album.Title));
|
||||
}
|
||||
}
|
||||
|
||||
+51
-52
@@ -5,69 +5,68 @@ using Musify.Application.Tests.TestSupport;
|
||||
using Musify.Domain.Entities;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
|
||||
private RemoveTrackFromAlbumCommandHandler CreateHandler() =>
|
||||
new(Database, NoOpLogger<RemoveTrackFromAlbumCommandHandler>());
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TrackInAlbum_RemovesItAndRenumbersTheRest()
|
||||
{
|
||||
private RemoveTrackFromAlbumCommandHandler CreateHandler() =>
|
||||
new(Database, NoOpLogger<RemoveTrackFromAlbumCommandHandler>());
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var first = TestEntities.Track(owner, "First");
|
||||
var second = TestEntities.Track(owner, "Second");
|
||||
var third = TestEntities.Track(owner, "Third");
|
||||
await SeedAsync(
|
||||
owner, album, first, second, third,
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = first.Id, TrackNumber = 1 },
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 },
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = third.Id, TrackNumber = 3 });
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TrackInAlbum_RemovesItAndRenumbersTheRest()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
var first = TestEntities.Track(owner, "First");
|
||||
var second = TestEntities.Track(owner, "Second");
|
||||
var third = TestEntities.Track(owner, "Third");
|
||||
await SeedAsync(
|
||||
owner, album, first, second, third,
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = first.Id, TrackNumber = 1 },
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 },
|
||||
new AlbumHasTrack { AlbumId = album.Id, TrackId = third.Id, TrackNumber = 3 });
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, second.Id), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, second.Id), TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
var remaining = await Database.AlbumHasTracks
|
||||
.Where(link => link.AlbumId == album.Id)
|
||||
.OrderBy(link => link.TrackNumber)
|
||||
.ToListAsync(TestContext.Current.CancellationToken);
|
||||
Assert.Equal([first.Id, third.Id], remaining.Select(link => link.TrackId));
|
||||
Assert.Equal([1, 2], remaining.Select(link => link.TrackNumber));
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
var remaining = await Database.AlbumHasTracks
|
||||
.Where(link => link.AlbumId == album.Id)
|
||||
.OrderBy(link => link.TrackNumber)
|
||||
.ToListAsync(TestContext.Current.CancellationToken);
|
||||
Assert.Equal([first.Id, third.Id], remaining.Select(link => link.TrackId));
|
||||
Assert.Equal([1, 2], remaining.Select(link => link.TrackNumber));
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(1, Guid.NewGuid(), Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(1, Guid.NewGuid(), Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, stranger, album);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, stranger, album);
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(stranger.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(stranger.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_TrackNotInAlbum_ReturnsNotFound()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_TrackNotInAlbum_ReturnsNotFound()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, album);
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
}
|
||||
|
||||
+47
-48
@@ -8,68 +8,67 @@ using Musify.Application.Tests.TestSupport;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class RequestAlbumPictureUploadCommandHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class RequestAlbumPictureUploadCommandHandlerTests : HandlerTestBase
|
||||
private readonly IStorageService storageService = Substitute.For<IStorageService>();
|
||||
|
||||
private RequestAlbumPictureUploadCommandHandler CreateHandler(UploadIntentConfiguration? uploadIntentConfig = null)
|
||||
{
|
||||
private readonly IStorageService storageService = Substitute.For<IStorageService>();
|
||||
storageService
|
||||
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://storage.musify.test/presigned-upload");
|
||||
|
||||
private RequestAlbumPictureUploadCommandHandler CreateHandler(UploadIntentConfiguration? uploadIntentConfig = null)
|
||||
{
|
||||
storageService
|
||||
.GetUploadUrlAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://storage.musify.test/presigned-upload");
|
||||
return new RequestAlbumPictureUploadCommandHandler(
|
||||
Database,
|
||||
new UploadIntentValidator(Database, storageService),
|
||||
storageService,
|
||||
NoOpLogger<RequestAlbumPictureUploadCommandHandler>(),
|
||||
TestConfigurations.Storage(),
|
||||
TestConfigurations.Album(),
|
||||
uploadIntentConfig ?? TestConfigurations.UploadIntent());
|
||||
}
|
||||
|
||||
return new RequestAlbumPictureUploadCommandHandler(
|
||||
Database,
|
||||
new UploadIntentValidator(Database, storageService),
|
||||
storageService,
|
||||
NoOpLogger<RequestAlbumPictureUploadCommandHandler>(),
|
||||
TestConfigurations.Storage(),
|
||||
TestConfigurations.Album(),
|
||||
uploadIntentConfig ?? TestConfigurations.UploadIntent());
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_UserExists_IssuesUploadUrlAndCreatesIntent()
|
||||
{
|
||||
var user = TestEntities.User();
|
||||
await SeedAsync(user);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserExists_IssuesUploadUrlAndCreatesIntent()
|
||||
{
|
||||
var user = TestEntities.User();
|
||||
await SeedAsync(user);
|
||||
var command = new RequestAlbumPictureUploadCommand(user.Id, "webp", "image/webp");
|
||||
|
||||
var command = new RequestAlbumPictureUploadCommand(user.Id, "webp", "image/webp");
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.UploadUrl);
|
||||
Assert.Single(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.UploadUrl);
|
||||
Assert.Single(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_UserMissing_ReturnsNotFound()
|
||||
{
|
||||
var command = new RequestAlbumPictureUploadCommand(404, "webp", "image/webp");
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserMissing_ReturnsNotFound()
|
||||
{
|
||||
var command = new RequestAlbumPictureUploadCommand(404, "webp", "image/webp");
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
[Fact]
|
||||
public async Task Handle_UserOverByteQuota_ReturnsValidationErrorAndCreatesNoIntent()
|
||||
{
|
||||
var config = TestConfigurations.UploadIntent();
|
||||
config.MaxActiveUploadBytesPerUser = 100;
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_UserOverByteQuota_ReturnsValidationErrorAndCreatesNoIntent()
|
||||
{
|
||||
var config = TestConfigurations.UploadIntent();
|
||||
config.MaxActiveUploadBytesPerUser = 100;
|
||||
var user = TestEntities.User();
|
||||
await SeedAsync(user);
|
||||
|
||||
var user = TestEntities.User();
|
||||
await SeedAsync(user);
|
||||
var command = new RequestAlbumPictureUploadCommand(user.Id, "webp", "image/webp", ExpectedSizeBytes: 1_000_000);
|
||||
|
||||
var command = new RequestAlbumPictureUploadCommand(user.Id, "webp", "image/webp", ExpectedSizeBytes: 1_000_000);
|
||||
var result = await CreateHandler(config).Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await CreateHandler(config).Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.Validation, result.FirstError.Type);
|
||||
Assert.Empty(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
|
||||
}
|
||||
Assert.Equal(ErrorType.Validation, result.FirstError.Type);
|
||||
Assert.Empty(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,89 +6,88 @@ using Musify.Application.Tests.TestSupport;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Musify.Application.Tests.Albums
|
||||
namespace Musify.Application.Tests.Albums;
|
||||
|
||||
public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
|
||||
{
|
||||
public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
|
||||
private readonly IEventBus eventBus = Substitute.For<IEventBus>();
|
||||
private readonly IStorageService storageService = Substitute.For<IStorageService>();
|
||||
|
||||
private UpdateAlbumCommandHandler CreateHandler() => new(
|
||||
eventBus,
|
||||
Database,
|
||||
new UploadIntentValidator(Database, storageService),
|
||||
NoOpLogger<UpdateAlbumCommandHandler>(),
|
||||
TestConfigurations.Storage(),
|
||||
TestConfigurations.Album(),
|
||||
TestConfigurations.UploadIntent());
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_Owner_UpdatesTitleDescriptionAndReleaseYear()
|
||||
{
|
||||
private readonly IEventBus eventBus = Substitute.For<IEventBus>();
|
||||
private readonly IStorageService storageService = Substitute.For<IStorageService>();
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id, title: "Old Title", description: "Old", releaseYear: 2020);
|
||||
await SeedAsync(owner, album);
|
||||
|
||||
private UpdateAlbumCommandHandler CreateHandler() => new(
|
||||
eventBus,
|
||||
Database,
|
||||
new UploadIntentValidator(Database, storageService),
|
||||
NoOpLogger<UpdateAlbumCommandHandler>(),
|
||||
TestConfigurations.Storage(),
|
||||
TestConfigurations.Album(),
|
||||
TestConfigurations.UploadIntent());
|
||||
var command = new UpdateAlbumCommand(owner.Id, album.Id, " New Title ", "New description", 2025, null);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_Owner_UpdatesTitleDescriptionAndReleaseYear()
|
||||
{
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id, title: "Old Title", description: "Old", releaseYear: 2020);
|
||||
await SeedAsync(owner, album);
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var command = new UpdateAlbumCommand(owner.Id, album.Id, " New Title ", "New description", 2025, null);
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("New Title", result.Value.Title);
|
||||
Assert.Equal("New description", result.Value.Description);
|
||||
Assert.Equal(2025, result.Value.ReleaseYear);
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
var stored = await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("NEW TITLE", stored.NormalizedTitle);
|
||||
}
|
||||
|
||||
Assert.False(result.IsError);
|
||||
Assert.Equal("New Title", result.Value.Title);
|
||||
Assert.Equal("New description", result.Value.Description);
|
||||
Assert.Equal(2025, result.Value.ReleaseYear);
|
||||
[Fact]
|
||||
public async Task Handle_ValidPictureIntent_UpdatesPictureAndPublishesSourceEvent()
|
||||
{
|
||||
storageService
|
||||
.HeadObjectAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ObjectMetaData("image/webp", 1024));
|
||||
|
||||
var stored = await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("NEW TITLE", stored.NormalizedTitle);
|
||||
}
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id, title: "Old Title");
|
||||
var intent = TestEntities.UploadIntent(owner.Id, Musify.Domain.ValueObjects.UploadIntentPurpose.AlbumPicture, objectName: "new-cover.webp");
|
||||
await SeedAsync(owner, album, intent);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ValidPictureIntent_UpdatesPictureAndPublishesSourceEvent()
|
||||
{
|
||||
storageService
|
||||
.HeadObjectAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ObjectMetaData("image/webp", 1024));
|
||||
var command = new UpdateAlbumCommand(owner.Id, album.Id, "Old Title", null, null, intent.Id);
|
||||
|
||||
var owner = TestEntities.User();
|
||||
var album = TestEntities.Album(owner.Id, title: "Old Title");
|
||||
var intent = TestEntities.UploadIntent(owner.Id, Musify.Domain.ValueObjects.UploadIntentPurpose.AlbumPicture, objectName: "new-cover.webp");
|
||||
await SeedAsync(owner, album, intent);
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var command = new UpdateAlbumCommand(owner.Id, album.Id, "Old Title", null, null, intent.Id);
|
||||
Assert.False(result.IsError);
|
||||
var stored = await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("new-cover.webp", stored.Pictures?.OriginalName);
|
||||
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.UpdateAlbumPictureSourceEvent>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var command = new UpdateAlbumCommand(1, Guid.NewGuid(), "Title", null, null, null);
|
||||
|
||||
Assert.False(result.IsError);
|
||||
var stored = await Database.Albums.FindAsync([album.Id], TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal("new-cover.webp", stored.Pictures?.OriginalName);
|
||||
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.UpdateAlbumPictureSourceEvent>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_AlbumMissing_ReturnsNotFound()
|
||||
{
|
||||
var command = new UpdateAlbumCommand(1, Guid.NewGuid(), "Title", null, null, null);
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, stranger, album);
|
||||
|
||||
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
|
||||
}
|
||||
var command = new UpdateAlbumCommand(stranger.Id, album.Id, "Hijacked", null, null, null);
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_NotOwner_ReturnsUnauthorized()
|
||||
{
|
||||
var owner = TestEntities.User(1, "owner");
|
||||
var stranger = TestEntities.User(2, "stranger");
|
||||
var album = TestEntities.Album(owner.Id);
|
||||
await SeedAsync(owner, stranger, album);
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
var command = new UpdateAlbumCommand(stranger.Id, album.Id, "Hijacked", null, null, null);
|
||||
|
||||
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user