Use block-scoped namespaces in the test suite

Converts all 73 test source files from file-scoped (namespace X;) to
block-scoped (namespace X { ... }) declarations, re-indenting their
contents accordingly. Matches the existing convention already used in
several production files (e.g. Musify.Infrastructure.Configuration,
Musify.Application.Contracts).

Mechanical change only, done with a script, verified with a full
rebuild (0 warnings, 0 errors) and test run (257/257 passing).
This commit is contained in:
2026-09-09 23:09:43 +02:00
parent 56e06ccd5f
commit b5700614c1
73 changed files with 3718 additions and 3645 deletions
@@ -5,107 +5,108 @@ using Musify.Application.Tests.TestSupport;
using Musify.Domain.Entities;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private AddTrackToAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<AddTrackToAlbumCommandHandler>());
[Fact]
public async Task Handle_FirstTrack_AddsItAtNumberOne()
public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, album, track);
private AddTrackToAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<AddTrackToAlbumCommandHandler>());
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_FirstTrack_AddsItAtNumberOne()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, album, track);
Assert.False(result.IsError);
var link = Assert.Single(await Database.AlbumHasTracks.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(1, link.TrackNumber);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound()
{
var owner = TestEntities.User();
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track);
Assert.False(result.IsError);
var link = Assert.Single(await Database.AlbumHasTracks.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(1, link.TrackNumber);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, Guid.NewGuid(), track.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound()
{
var owner = TestEntities.User();
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, Guid.NewGuid(), track.Id), 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.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, stranger, album, track);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(stranger.Id, album.Id, track.Id), 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.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, stranger, album, track);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(stranger.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackMissing_ReturnsNotFound()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackMissing_ReturnsNotFound()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackOwnedByAnotherUser_ReturnsUnauthorized()
{
var owner = TestEntities.User(1, "owner");
var otherOwner = TestEntities.User(2, "other");
var album = TestEntities.UserAlbum(owner.Id);
var foreignTrack = TestEntities.LocalTrack(otherOwner);
await SeedAsync(owner, otherOwner, album, foreignTrack);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, foreignTrack.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackOwnedByAnotherUser_ReturnsUnauthorized()
{
var owner = TestEntities.User(1, "owner");
var otherOwner = TestEntities.User(2, "other");
var album = TestEntities.UserAlbum(owner.Id);
var foreignTrack = TestEntities.LocalTrack(otherOwner);
await SeedAsync(owner, otherOwner, album, foreignTrack);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, foreignTrack.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlreadyInAlbum_ReturnsConflict()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 };
await SeedAsync(owner, album, track, link);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlreadyInAlbum_ReturnsConflict()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 };
await SeedAsync(owner, album, track, link);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlbumAlreadyHasTracks_AppendsAtNextNumber()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var firstTrack = TestEntities.LocalTrack(owner, "First");
var secondTrack = TestEntities.LocalTrack(owner, "Second");
var existingLink = new AlbumHasTrack { AlbumId = album.Id, TrackId = firstTrack.Id, TrackNumber = 3 };
await SeedAsync(owner, album, firstTrack, secondTrack, existingLink);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, secondTrack.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlbumAlreadyHasTracks_AppendsAtNextNumber()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var firstTrack = TestEntities.LocalTrack(owner, "First");
var secondTrack = TestEntities.LocalTrack(owner, "Second");
var existingLink = new AlbumHasTrack { AlbumId = album.Id, TrackId = firstTrack.Id, TrackNumber = 3 };
await SeedAsync(owner, album, firstTrack, secondTrack, existingLink);
Assert.False(result.IsError);
var newLink = await Database.AlbumHasTracks.SingleAsync(l => l.TrackId == secondTrack.Id, TestContext.Current.CancellationToken);
Assert.Equal(4, newLink.TrackNumber);
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);
}
}
}
@@ -3,41 +3,42 @@ using Musify.Application.Albums;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private CreateAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<CreateAlbumCommandHandler>());
[Fact]
public async Task Handle_UserExists_CreatesAlbumAndReturnsIt()
public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
await SeedAsync(owner);
private CreateAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<CreateAlbumCommandHandler>());
var command = new CreateAlbumCommand(owner.Id, " My Album ", "A description", 2024);
[Fact]
public async Task Handle_UserExists_CreatesAlbumAndReturnsIt()
{
var owner = TestEntities.User();
await SeedAsync(owner);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
var command = new CreateAlbumCommand(owner.Id, " My Album ", "A description", 2024);
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.UserAlbums.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored);
Assert.Equal("MY ALBUM", stored.NormalizedTitle);
}
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(UserId: 404, "Orphan Album", Description: null, ReleaseYear: null);
var stored = await Database.UserAlbums.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored);
Assert.Equal("MY ALBUM", stored.NormalizedTitle);
}
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_UserDoesNotExist_ReturnsNotFound()
{
var command = new CreateAlbumCommand(UserId: 404, "Orphan Album", Description: null, ReleaseYear: null);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
}
}
@@ -3,44 +3,45 @@ using Musify.Application.Albums;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class DeleteAlbumCommandHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private DeleteAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<DeleteAlbumCommandHandler>());
[Fact]
public async Task Handle_Owner_RemovesAlbum()
public sealed class DeleteAlbumCommandHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album);
private DeleteAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<DeleteAlbumCommandHandler>());
var result = await CreateHandler().Handle(new DeleteAlbumCommand(owner.Id, album.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_Owner_RemovesAlbum()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album);
Assert.False(result.IsError);
Assert.Null(await Database.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken));
}
var result = await CreateHandler().Handle(new DeleteAlbumCommand(owner.Id, 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);
Assert.False(result.IsError);
Assert.Null(await Database.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken));
}
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[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_NotOwner_ReturnsUnauthorizedAndKeepsAlbum()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, stranger, album);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new DeleteAlbumCommand(stranger.Id, album.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorizedAndKeepsAlbum()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, stranger, album);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.NotNull(await Database.UserAlbums.FindAsync([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.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken));
}
}
}
@@ -4,33 +4,34 @@ using Musify.Application.Tests.TestSupport;
using Musify.Domain.Entities;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class GetAlbumByIdQueryHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private GetAlbumByIdQueryHandler CreateHandler() => new(Database);
[Fact]
public async Task Handle_ExistingAlbum_ReturnsItWithTrackCountAndCovers()
public sealed class GetAlbumByIdQueryHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, album, track, new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 });
private GetAlbumByIdQueryHandler CreateHandler() => new(Database);
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(album.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_ExistingAlbum_ReturnsItWithTrackCountAndCovers()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, album, track, new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 });
Assert.False(result.IsError);
Assert.Equal(album.Id, result.Value.Id);
Assert.Equal(1, result.Value.TrackCount);
Assert.Equal([track.Id], result.Value.CoverTrackIds);
}
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(album.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound()
{
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(Guid.NewGuid()), 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.Equal(ErrorType.NotFound, result.FirstError.Type);
[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);
}
}
}
@@ -4,35 +4,36 @@ using Musify.Application.Tests.TestSupport;
using Musify.Domain.Entities;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class GetAlbumTracksQueryHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private GetAlbumTracksQueryHandler CreateHandler() => new(Database);
[Fact]
public async Task Handle_AlbumWithTracks_ReturnsThemOrderedByTrackNumber()
public sealed class GetAlbumTracksQueryHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var second = TestEntities.LocalTrack(owner, "Second");
var first = TestEntities.LocalTrack(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 });
private GetAlbumTracksQueryHandler CreateHandler() => new(Database);
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(album.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_AlbumWithTracks_ReturnsThemOrderedByTrackNumber()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var second = TestEntities.LocalTrack(owner, "Second");
var first = TestEntities.LocalTrack(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 });
Assert.False(result.IsError);
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
}
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(album.Id, 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.False(result.IsError);
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
}
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
[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);
}
}
}
@@ -3,31 +3,32 @@ using Musify.Application.Albums;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class GetAlbumsByUserIdQueryHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private GetAlbumsByUserIdQueryHandler CreateHandler() => new(Database);
[Fact]
public async Task Handle_UserWithAlbums_ReturnsOnlyTheirAlbums()
public sealed class GetAlbumsByUserIdQueryHandlerTests : HandlerTestBase
{
var owner = TestEntities.User(1, "owner");
var other = TestEntities.User(2, "other");
await SeedAsync(owner, other, TestEntities.UserAlbum(owner.Id, "Mine"), TestEntities.UserAlbum(other.Id, "Theirs"));
private GetAlbumsByUserIdQueryHandler CreateHandler() => new(Database);
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(owner.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_UserWithAlbums_ReturnsOnlyTheirAlbums()
{
var owner = TestEntities.User(1, "owner");
var other = TestEntities.User(2, "other");
await SeedAsync(owner, other, TestEntities.UserAlbum(owner.Id, "Mine"), TestEntities.UserAlbum(other.Id, "Theirs"));
Assert.False(result.IsError);
var album = Assert.Single(result.Value.Items);
Assert.Equal("Mine", album.Title);
}
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(owner.Id, 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.False(result.IsError);
var album = Assert.Single(result.Value.Items);
Assert.Equal("Mine", album.Title);
}
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
[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);
}
}
}
@@ -2,47 +2,48 @@ using Musify.Application.Albums;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private GetAlbumsQueryHandler CreateHandler() => new(Database);
[Fact]
public async Task Handle_NoTitleFilter_ReturnsAllAlbumsPaged()
public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Alpha"), TestEntities.UserAlbum(owner.Id, "Beta"));
private GetAlbumsQueryHandler CreateHandler() => new(Database);
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_NoTitleFilter_ReturnsAllAlbumsPaged()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Alpha"), TestEntities.UserAlbum(owner.Id, "Beta"));
Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount);
}
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TitleFilter_ReturnsOnlyMatchingAlbums()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Rock Classics"), TestEntities.UserAlbum(owner.Id, "Jazz Favorites"));
Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount);
}
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: "rock", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TitleFilter_ReturnsOnlyMatchingAlbums()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Rock Classics"), TestEntities.UserAlbum(owner.Id, "Jazz Favorites"));
Assert.False(result.IsError);
var album = Assert.Single(result.Value.Items);
Assert.Equal("Rock Classics", album.Title);
}
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: "rock", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_PageSizeSmallerThanTotal_ReportsHasNextPage()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "One"), TestEntities.UserAlbum(owner.Id, "Two"), TestEntities.UserAlbum(owner.Id, "Three"));
Assert.False(result.IsError);
var album = Assert.Single(result.Value.Items);
Assert.Equal("Rock Classics", album.Title);
}
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 2), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_PageSizeSmallerThanTotal_ReportsHasNextPage()
{
var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "One"), TestEntities.UserAlbum(owner.Id, "Two"), TestEntities.UserAlbum(owner.Id, "Three"));
Assert.False(result.IsError);
Assert.Equal(2, result.Value.Items.Count);
Assert.True(result.Value.HasNextPage);
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);
}
}
}
@@ -3,58 +3,59 @@ using Musify.Application.Tests.TestSupport;
using Musify.Domain.Entities;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private GetRecentlyListenedAlbumsQueryHandler CreateHandler() => new(Database);
[Fact]
public async Task Handle_ListenedTracks_ReturnsTheirAlbumsMostRecentFirst()
public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var olderAlbum = TestEntities.UserAlbum(owner.Id, "Older");
var newerAlbum = TestEntities.UserAlbum(owner.Id, "Newer");
var olderTrack = TestEntities.LocalTrack(owner, "In older album");
var newerTrack = TestEntities.LocalTrack(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)));
private GetRecentlyListenedAlbumsQueryHandler CreateHandler() => new(Database);
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_ListenedTracks_ReturnsTheirAlbumsMostRecentFirst()
{
var owner = TestEntities.User();
var olderAlbum = TestEntities.UserAlbum(owner.Id, "Older");
var newerAlbum = TestEntities.UserAlbum(owner.Id, "Newer");
var olderTrack = TestEntities.LocalTrack(owner, "In older album");
var newerTrack = TestEntities.LocalTrack(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)));
Assert.False(result.IsError);
Assert.Equal(["Newer", "Older"], result.Value.Select(album => album.Title));
}
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_NoListeningHistory_ReturnsEmptyList()
{
var owner = TestEntities.User();
await SeedAsync(owner);
Assert.False(result.IsError);
Assert.Equal(["Newer", "Older"], result.Value.Select(album => album.Title));
}
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_NoListeningHistory_ReturnsEmptyList()
{
var owner = TestEntities.User();
await SeedAsync(owner);
Assert.False(result.IsError);
Assert.Empty(result.Value);
}
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_LimitLowerThanCandidates_ReturnsOnlyThatManyAlbums()
{
var owner = TestEntities.User();
var albums = Enumerable.Range(1, 3).Select(i => TestEntities.UserAlbum(owner.Id, $"Album {i}")).ToList();
var tracks = albums.Select((_, i) => TestEntities.LocalTrack(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]);
Assert.False(result.IsError);
Assert.Empty(result.Value);
}
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 2), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_LimitLowerThanCandidates_ReturnsOnlyThatManyAlbums()
{
var owner = TestEntities.User();
var albums = Enumerable.Range(1, 3).Select(i => TestEntities.UserAlbum(owner.Id, $"Album {i}")).ToList();
var tracks = albums.Select((_, i) => TestEntities.LocalTrack(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]);
Assert.False(result.IsError);
Assert.Equal(2, result.Value.Count);
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 2), TestContext.Current.CancellationToken);
Assert.False(result.IsError);
Assert.Equal(2, result.Value.Count);
}
}
}
@@ -5,68 +5,69 @@ using Musify.Application.Tests.TestSupport;
using Musify.Domain.Entities;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private RemoveTrackFromAlbumCommandHandler CreateHandler() =>
new(Database, NoOpLogger<RemoveTrackFromAlbumCommandHandler>());
[Fact]
public async Task Handle_TrackInAlbum_RemovesItAndRenumbersTheRest()
public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var first = TestEntities.LocalTrack(owner, "First");
var second = TestEntities.LocalTrack(owner, "Second");
var third = TestEntities.LocalTrack(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 });
private RemoveTrackFromAlbumCommandHandler CreateHandler() =>
new(Database, NoOpLogger<RemoveTrackFromAlbumCommandHandler>());
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, second.Id), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackInAlbum_RemovesItAndRenumbersTheRest()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
var first = TestEntities.LocalTrack(owner, "First");
var second = TestEntities.LocalTrack(owner, "Second");
var third = TestEntities.LocalTrack(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 });
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));
}
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, second.Id), 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.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.Equal(ErrorType.NotFound, result.FirstError.Type);
}
[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_NotOwner_ReturnsUnauthorized()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, stranger, album);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(stranger.Id, album.Id, Guid.NewGuid()), 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.UserAlbum(owner.Id);
await SeedAsync(owner, stranger, album);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(stranger.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackNotInAlbum_ReturnsNotFound()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
[Fact]
public async Task Handle_TrackNotInAlbum_ReturnsNotFound()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
}
}
}
@@ -3,55 +3,56 @@ using Musify.Application.Albums;
using Musify.Application.Tests.TestSupport;
using Xunit;
namespace Musify.Application.Tests.Albums;
public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
namespace Musify.Application.Tests.Albums
{
private UpdateAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<UpdateAlbumCommandHandler>());
[Fact]
public async Task Handle_Owner_UpdatesTitleDescriptionAndReleaseYear()
public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(owner.Id, title: "Old Title", description: "Old", releaseYear: 2020);
await SeedAsync(owner, album);
private UpdateAlbumCommandHandler CreateHandler() => new(Database, NoOpLogger<UpdateAlbumCommandHandler>());
var command = new UpdateAlbumCommand(owner.Id, album.Id, " New Title ", "New description", 2025);
[Fact]
public async Task Handle_Owner_UpdatesTitleDescriptionAndReleaseYear()
{
var owner = TestEntities.User();
var album = TestEntities.UserAlbum(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);
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.UserAlbums.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_AlbumMissing_ReturnsNotFound()
{
var command = new UpdateAlbumCommand(1, Guid.NewGuid(), "Title", null, null);
var stored = await Database.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored);
Assert.Equal("NEW TITLE", stored.NormalizedTitle);
}
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);
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.UserAlbum(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);
[Fact]
public async Task Handle_NotOwner_ReturnsUnauthorized()
{
var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger");
var album = TestEntities.UserAlbum(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);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
}
}
}