Design Prot Merge #1

Merged
NotS1mp merged 217 commits from design-prot into master 2026-09-21 15:48:16 +00:00
62 changed files with 286 additions and 272 deletions
Showing only changes of commit 98e6dfc4c4 - Show all commits
+2 -2
View File
@@ -59,8 +59,8 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="NSubstitute" Version="5.3.0" /> <PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" /> <PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="xunit" Version="2.9.3" /> <PackageVersion Include="xunit.v3" Version="4.0.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" /> <PackageVersion Include="xunit.runner.visualstudio" Version="4.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.8" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.8" />
<PackageVersion Include="Testcontainers" Version="4.15.0" /> <PackageVersion Include="Testcontainers" Version="4.15.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.15.0" /> <PackageVersion Include="Testcontainers.PostgreSql" Version="4.15.0" />
@@ -24,7 +24,7 @@ public sealed class AlbumEndpointsTests(ApiTestFixture fixture)
public async Task PostAlbums_NoAuthHeader_ReturnsUnauthorized() public async Task PostAlbums_NoAuthHeader_ReturnsUnauthorized()
{ {
var response = await fixture.CreateAnonymousClient() var response = await fixture.CreateAnonymousClient()
.PostAsJsonAsync("/albums", new CreateAlbumRequest("Unauthorized Album", null, null)); .PostAsJsonAsync("/albums", new CreateAlbumRequest("Unauthorized Album", null, null), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
} }
@@ -35,10 +35,10 @@ public sealed class AlbumEndpointsTests(ApiTestFixture fixture)
var userId = await CreateUserAsync(); var userId = await CreateUserAsync();
var client = fixture.CreateAuthenticatedClient(userId); var client = fixture.CreateAuthenticatedClient(userId);
var response = await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("My Album", "desc", 2024)); var response = await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("My Album", "desc", 2024), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, response.StatusCode); Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<AlbumApplicationResponse>(); var body = await response.Content.ReadFromJsonAsync<AlbumApplicationResponse>(TestContext.Current.CancellationToken);
Assert.NotNull(body); Assert.NotNull(body);
Assert.Equal("My Album", body.Title); Assert.Equal("My Album", body.Title);
Assert.Equal(userId, body.OwnerUserId); Assert.Equal(userId, body.OwnerUserId);
@@ -50,7 +50,7 @@ public sealed class AlbumEndpointsTests(ApiTestFixture fixture)
var userId = await CreateUserAsync(); var userId = await CreateUserAsync();
var client = fixture.CreateAuthenticatedClient(userId); var client = fixture.CreateAuthenticatedClient(userId);
var response = await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("", null, null)); var response = await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("", null, null), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
@@ -60,10 +60,10 @@ public sealed class AlbumEndpointsTests(ApiTestFixture fixture)
{ {
var userId = await CreateUserAsync(); var userId = await CreateUserAsync();
var client = fixture.CreateAuthenticatedClient(userId); var client = fixture.CreateAuthenticatedClient(userId);
var created = await (await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("Findable", null, null))) var created = await (await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("Findable", null, null), TestContext.Current.CancellationToken))
.Content.ReadFromJsonAsync<AlbumApplicationResponse>(); .Content.ReadFromJsonAsync<AlbumApplicationResponse>(TestContext.Current.CancellationToken);
var response = await fixture.CreateAnonymousClient().GetAsync($"/albums/{created!.Id}"); var response = await fixture.CreateAnonymousClient().GetAsync($"/albums/{created!.Id}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
} }
@@ -73,10 +73,10 @@ public sealed class AlbumEndpointsTests(ApiTestFixture fixture)
{ {
var owner = await CreateUserAsync(); var owner = await CreateUserAsync();
var stranger = await CreateUserAsync(); var stranger = await CreateUserAsync();
var created = await (await fixture.CreateAuthenticatedClient(owner).PostAsJsonAsync("/albums", new CreateAlbumRequest("Owned", null, null))) var created = await (await fixture.CreateAuthenticatedClient(owner).PostAsJsonAsync("/albums", new CreateAlbumRequest("Owned", null, null), TestContext.Current.CancellationToken))
.Content.ReadFromJsonAsync<AlbumApplicationResponse>(); .Content.ReadFromJsonAsync<AlbumApplicationResponse>(TestContext.Current.CancellationToken);
var response = await fixture.CreateAuthenticatedClient(stranger).DeleteAsync($"/albums/{created!.Id}"); var response = await fixture.CreateAuthenticatedClient(stranger).DeleteAsync($"/albums/{created!.Id}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
} }
@@ -86,11 +86,11 @@ public sealed class AlbumEndpointsTests(ApiTestFixture fixture)
{ {
var userId = await CreateUserAsync(); var userId = await CreateUserAsync();
var client = fixture.CreateAuthenticatedClient(userId); var client = fixture.CreateAuthenticatedClient(userId);
var created = await (await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("Deletable", null, null))) var created = await (await client.PostAsJsonAsync("/albums", new CreateAlbumRequest("Deletable", null, null), TestContext.Current.CancellationToken))
.Content.ReadFromJsonAsync<AlbumApplicationResponse>(); .Content.ReadFromJsonAsync<AlbumApplicationResponse>(TestContext.Current.CancellationToken);
var deleteResponse = await client.DeleteAsync($"/albums/{created!.Id}"); var deleteResponse = await client.DeleteAsync($"/albums/{created!.Id}", TestContext.Current.CancellationToken);
var getResponse = await fixture.CreateAnonymousClient().GetAsync($"/albums/{created.Id}"); var getResponse = await fixture.CreateAnonymousClient().GetAsync($"/albums/{created.Id}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode); Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode);
Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode); Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
@@ -16,11 +16,11 @@ public sealed class UserEndpointsTests(ApiTestFixture fixture)
var client = fixture.CreateAnonymousClient(); var client = fixture.CreateAnonymousClient();
var userId = Random.Shared.NextInt64(1, long.MaxValue); var userId = Random.Shared.NextInt64(1, long.MaxValue);
var response = await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "Jane Doe", "Jane", "Doe")); var response = await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "Jane Doe", "Jane", "Doe"), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, response.StatusCode); Assert.Equal(HttpStatusCode.Created, response.StatusCode);
Assert.Equal($"/users/{userId}", response.Headers.Location?.OriginalString); Assert.Equal($"/users/{userId}", response.Headers.Location?.OriginalString);
var body = await response.Content.ReadFromJsonAsync<UserApplicationResponse>(); var body = await response.Content.ReadFromJsonAsync<UserApplicationResponse>(TestContext.Current.CancellationToken);
Assert.NotNull(body); Assert.NotNull(body);
Assert.Equal("Jane Doe", body.Name); Assert.Equal("Jane Doe", body.Name);
} }
@@ -30,7 +30,7 @@ public sealed class UserEndpointsTests(ApiTestFixture fixture)
{ {
var client = fixture.CreateAnonymousClient(); var client = fixture.CreateAnonymousClient();
var response = await client.PostAsJsonAsync("/users", new CreateUserRequest(Random.Shared.NextInt64(1, long.MaxValue), "", null, null)); var response = await client.PostAsJsonAsync("/users", new CreateUserRequest(Random.Shared.NextInt64(1, long.MaxValue), "", null, null), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
@@ -40,9 +40,9 @@ public sealed class UserEndpointsTests(ApiTestFixture fixture)
{ {
var client = fixture.CreateAnonymousClient(); var client = fixture.CreateAnonymousClient();
var userId = Random.Shared.NextInt64(1, long.MaxValue); var userId = Random.Shared.NextInt64(1, long.MaxValue);
await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "First", null, null)); await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "First", null, null), TestContext.Current.CancellationToken);
var response = await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "Second", null, null)); var response = await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "Second", null, null), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
} }
@@ -52,12 +52,12 @@ public sealed class UserEndpointsTests(ApiTestFixture fixture)
{ {
var client = fixture.CreateAnonymousClient(); var client = fixture.CreateAnonymousClient();
var userId = Random.Shared.NextInt64(1, long.MaxValue); var userId = Random.Shared.NextInt64(1, long.MaxValue);
await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "Findable User", null, null)); await client.PostAsJsonAsync("/users", new CreateUserRequest(userId, "Findable User", null, null), TestContext.Current.CancellationToken);
var response = await client.GetAsync($"/users/{userId}"); var response = await client.GetAsync($"/users/{userId}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<UserApplicationResponse>(); var body = await response.Content.ReadFromJsonAsync<UserApplicationResponse>(TestContext.Current.CancellationToken);
Assert.Equal("Findable User", body?.Name); Assert.Equal("Findable User", body?.Name);
} }
@@ -66,7 +66,7 @@ public sealed class UserEndpointsTests(ApiTestFixture fixture)
{ {
var client = fixture.CreateAnonymousClient(); var client = fixture.CreateAnonymousClient();
var response = await client.GetAsync($"/users/{Random.Shared.NextInt64(1, long.MaxValue)}"); var response = await client.GetAsync($"/users/{Random.Shared.NextInt64(1, long.MaxValue)}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
@@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -10,7 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" /> <PackageReference Include="NSubstitute" />
<PackageReference Include="Testcontainers.PostgreSql" /> <PackageReference Include="Testcontainers.PostgreSql" />
<PackageReference Include="xunit" /> <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio"> <PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -37,7 +37,7 @@ public sealed class ApiTestFixture : WebApplicationFactory<Program>, IAsyncLifet
public IStreamTicketService StreamTicketService { get; } = Substitute.For<IStreamTicketService>(); public IStreamTicketService StreamTicketService { get; } = Substitute.For<IStreamTicketService>();
public async Task InitializeAsync() public async ValueTask InitializeAsync()
{ {
await postgres.StartAsync(); await postgres.StartAsync();
@@ -46,7 +46,11 @@ public sealed class ApiTestFixture : WebApplicationFactory<Program>, IAsyncLifet
await database.Database.MigrateAsync(); await database.Database.MigrateAsync();
} }
async Task IAsyncLifetime.DisposeAsync() // xUnit v3's IAsyncLifetime IS System.IAsyncDisposable (rather than declaring its own
// Task-returning DisposeAsync as in v2), so there is a single DisposeAsync slot to fill —
// override the one WebApplicationFactory already provides instead of implementing the
// interface explicitly alongside it.
public override async ValueTask DisposeAsync()
{ {
await base.DisposeAsync(); await base.DisposeAsync();
await postgres.DisposeAsync(); await postgres.DisposeAsync();
@@ -19,10 +19,10 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, album, track); await SeedAsync(owner, album, track);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var link = Assert.Single(await Database.AlbumHasTracks.ToListAsync()); var link = Assert.Single(await Database.AlbumHasTracks.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(1, link.TrackNumber); Assert.Equal(1, link.TrackNumber);
} }
@@ -33,7 +33,7 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, Guid.NewGuid(), track.Id), CancellationToken.None); 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);
} }
@@ -47,7 +47,7 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, stranger, album, track); await SeedAsync(owner, stranger, album, track);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(stranger.Id, album.Id, track.Id), CancellationToken.None); 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);
} }
@@ -59,7 +59,7 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var album = TestEntities.UserAlbum(owner.Id); var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album); await SeedAsync(owner, album);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), CancellationToken.None); 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);
} }
@@ -73,7 +73,7 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var foreignTrack = TestEntities.LocalTrack(otherOwner); var foreignTrack = TestEntities.LocalTrack(otherOwner);
await SeedAsync(owner, otherOwner, album, foreignTrack); await SeedAsync(owner, otherOwner, album, foreignTrack);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, foreignTrack.Id), CancellationToken.None); 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);
} }
@@ -87,7 +87,7 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 }; var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 };
await SeedAsync(owner, album, track, link); await SeedAsync(owner, album, track, link);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, track.Id), CancellationToken.None); 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);
} }
@@ -102,10 +102,10 @@ public sealed class AddTrackToAlbumCommandHandlerTests : HandlerTestBase
var existingLink = new AlbumHasTrack { AlbumId = album.Id, TrackId = firstTrack.Id, TrackNumber = 3 }; var existingLink = new AlbumHasTrack { AlbumId = album.Id, TrackId = firstTrack.Id, TrackNumber = 3 };
await SeedAsync(owner, album, firstTrack, secondTrack, existingLink); await SeedAsync(owner, album, firstTrack, secondTrack, existingLink);
var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, secondTrack.Id), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToAlbumCommand(owner.Id, album.Id, secondTrack.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var newLink = await Database.AlbumHasTracks.SingleAsync(l => l.TrackId == secondTrack.Id); var newLink = await Database.AlbumHasTracks.SingleAsync(l => l.TrackId == secondTrack.Id, TestContext.Current.CancellationToken);
Assert.Equal(4, newLink.TrackNumber); Assert.Equal(4, newLink.TrackNumber);
} }
} }
@@ -17,7 +17,7 @@ public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
var command = new CreateAlbumCommand(owner.Id, " My Album ", "A description", 2024); var command = new CreateAlbumCommand(owner.Id, " My Album ", "A description", 2024);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("My Album", result.Value.Title); Assert.Equal("My Album", result.Value.Title);
@@ -26,7 +26,7 @@ public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
Assert.Equal(owner.Id, result.Value.OwnerUserId); Assert.Equal(owner.Id, result.Value.OwnerUserId);
Assert.Equal(0, result.Value.TrackCount); Assert.Equal(0, result.Value.TrackCount);
var stored = await Database.UserAlbums.FindAsync(result.Value.Id); var stored = await Database.UserAlbums.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal("MY ALBUM", stored.NormalizedTitle); Assert.Equal("MY ALBUM", stored.NormalizedTitle);
} }
@@ -36,7 +36,7 @@ public sealed class CreateAlbumCommandHandlerTests : HandlerTestBase
{ {
var command = new CreateAlbumCommand(UserId: 404, "Orphan Album", Description: null, ReleaseYear: null); var command = new CreateAlbumCommand(UserId: 404, "Orphan Album", Description: null, ReleaseYear: null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -16,16 +16,16 @@ public sealed class DeleteAlbumCommandHandlerTests : HandlerTestBase
var album = TestEntities.UserAlbum(owner.Id); var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album); await SeedAsync(owner, album);
var result = await CreateHandler().Handle(new DeleteAlbumCommand(owner.Id, album.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteAlbumCommand(owner.Id, album.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Null(await Database.UserAlbums.FindAsync(album.Id)); Assert.Null(await Database.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound() public async Task Handle_AlbumMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new DeleteAlbumCommand(1, Guid.NewGuid()), CancellationToken.None); 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);
} }
@@ -38,9 +38,9 @@ public sealed class DeleteAlbumCommandHandlerTests : HandlerTestBase
var album = TestEntities.UserAlbum(owner.Id); var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, stranger, album); await SeedAsync(owner, stranger, album);
var result = await CreateHandler().Handle(new DeleteAlbumCommand(stranger.Id, album.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteAlbumCommand(stranger.Id, album.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
Assert.NotNull(await Database.UserAlbums.FindAsync(album.Id)); Assert.NotNull(await Database.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken));
} }
} }
@@ -18,7 +18,7 @@ public sealed class GetAlbumByIdQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, album, track, new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 }); await SeedAsync(owner, album, track, new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 });
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(album.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetAlbumByIdQuery(album.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(album.Id, result.Value.Id); Assert.Equal(album.Id, result.Value.Id);
@@ -29,7 +29,7 @@ public sealed class GetAlbumByIdQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound() public async Task Handle_AlbumMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetAlbumByIdQuery(Guid.NewGuid()), CancellationToken.None); 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);
} }
@@ -22,7 +22,7 @@ public sealed class GetAlbumTracksQueryHandlerTests : HandlerTestBase
new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 }, new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 },
new AlbumHasTrack { AlbumId = album.Id, TrackId = first.Id, TrackNumber = 1 }); new AlbumHasTrack { AlbumId = album.Id, TrackId = first.Id, TrackNumber = 1 });
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(album.Id, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetAlbumTracksQuery(album.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title)); Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
@@ -31,7 +31,7 @@ public sealed class GetAlbumTracksQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound() public async Task Handle_AlbumMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetAlbumTracksQuery(Guid.NewGuid(), PageNumber: 1, PageSize: 10), CancellationToken.None); 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);
} }
@@ -16,7 +16,7 @@ public sealed class GetAlbumsByUserIdQueryHandlerTests : HandlerTestBase
var other = TestEntities.User(2, "other"); var other = TestEntities.User(2, "other");
await SeedAsync(owner, other, TestEntities.UserAlbum(owner.Id, "Mine"), TestEntities.UserAlbum(other.Id, "Theirs")); await SeedAsync(owner, other, TestEntities.UserAlbum(owner.Id, "Mine"), TestEntities.UserAlbum(other.Id, "Theirs"));
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(owner.Id, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(owner.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var album = Assert.Single(result.Value.Items); var album = Assert.Single(result.Value.Items);
@@ -26,7 +26,7 @@ public sealed class GetAlbumsByUserIdQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_UserMissing_ReturnsNotFound() public async Task Handle_UserMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetAlbumsByUserIdQuery(404, PageNumber: 1, PageSize: 10), CancellationToken.None); 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);
} }
@@ -14,7 +14,7 @@ public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Alpha"), TestEntities.UserAlbum(owner.Id, "Beta")); await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Alpha"), TestEntities.UserAlbum(owner.Id, "Beta"));
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount); Assert.Equal(2, result.Value.TotalItemCount);
@@ -26,7 +26,7 @@ public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Rock Classics"), TestEntities.UserAlbum(owner.Id, "Jazz Favorites")); await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "Rock Classics"), TestEntities.UserAlbum(owner.Id, "Jazz Favorites"));
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: "rock", PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: "rock", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var album = Assert.Single(result.Value.Items); var album = Assert.Single(result.Value.Items);
@@ -39,7 +39,7 @@ public sealed class GetAlbumsQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "One"), TestEntities.UserAlbum(owner.Id, "Two"), TestEntities.UserAlbum(owner.Id, "Three")); await SeedAsync(owner, TestEntities.UserAlbum(owner.Id, "One"), TestEntities.UserAlbum(owner.Id, "Two"), TestEntities.UserAlbum(owner.Id, "Three"));
var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 2), CancellationToken.None); var result = await CreateHandler().Handle(new GetAlbumsQuery(Title: null, PageNumber: 1, PageSize: 2), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(2, result.Value.Items.Count); Assert.Equal(2, result.Value.Items.Count);
@@ -24,7 +24,7 @@ public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
TestEntities.ListeningHistory(owner.Id, olderTrack.Id, DateTime.UtcNow.AddDays(-2)), TestEntities.ListeningHistory(owner.Id, olderTrack.Id, DateTime.UtcNow.AddDays(-2)),
TestEntities.ListeningHistory(owner.Id, newerTrack.Id, DateTime.UtcNow.AddDays(-1))); TestEntities.ListeningHistory(owner.Id, newerTrack.Id, DateTime.UtcNow.AddDays(-1)));
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(["Newer", "Older"], result.Value.Select(album => album.Title)); Assert.Equal(["Newer", "Older"], result.Value.Select(album => album.Title));
@@ -36,7 +36,7 @@ public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner); await SeedAsync(owner);
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Empty(result.Value); Assert.Empty(result.Value);
@@ -52,7 +52,7 @@ public sealed class GetRecentlyListenedAlbumsQueryHandlerTests : HandlerTestBase
var histories = tracks.Select((track, i) => TestEntities.ListeningHistory(owner.Id, track.Id, DateTime.UtcNow.AddMinutes(-i))).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]); await SeedAsync([owner, .. albums, .. tracks, .. links, .. histories]);
var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 2), CancellationToken.None); var result = await CreateHandler().Handle(new GetRecentlyListenedAlbumsQuery(owner.Id, Limit: 2), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(2, result.Value.Count); Assert.Equal(2, result.Value.Count);
@@ -26,13 +26,13 @@ public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 }, new AlbumHasTrack { AlbumId = album.Id, TrackId = second.Id, TrackNumber = 2 },
new AlbumHasTrack { AlbumId = album.Id, TrackId = third.Id, TrackNumber = 3 }); new AlbumHasTrack { AlbumId = album.Id, TrackId = third.Id, TrackNumber = 3 });
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, second.Id), CancellationToken.None); var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, second.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var remaining = await Database.AlbumHasTracks var remaining = await Database.AlbumHasTracks
.Where(link => link.AlbumId == album.Id) .Where(link => link.AlbumId == album.Id)
.OrderBy(link => link.TrackNumber) .OrderBy(link => link.TrackNumber)
.ToListAsync(); .ToListAsync(TestContext.Current.CancellationToken);
Assert.Equal([first.Id, third.Id], remaining.Select(link => link.TrackId)); Assert.Equal([first.Id, third.Id], remaining.Select(link => link.TrackId));
Assert.Equal([1, 2], remaining.Select(link => link.TrackNumber)); Assert.Equal([1, 2], remaining.Select(link => link.TrackNumber));
} }
@@ -40,7 +40,7 @@ public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_AlbumMissing_ReturnsNotFound() public async Task Handle_AlbumMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(1, Guid.NewGuid(), Guid.NewGuid()), CancellationToken.None); 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);
} }
@@ -53,7 +53,7 @@ public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
var album = TestEntities.UserAlbum(owner.Id); var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, stranger, album); await SeedAsync(owner, stranger, album);
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(stranger.Id, album.Id, Guid.NewGuid()), CancellationToken.None); 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);
} }
@@ -65,7 +65,7 @@ public sealed class RemoveTrackFromAlbumCommandHandlerTests : HandlerTestBase
var album = TestEntities.UserAlbum(owner.Id); var album = TestEntities.UserAlbum(owner.Id);
await SeedAsync(owner, album); await SeedAsync(owner, album);
var result = await CreateHandler().Handle(new RemoveTrackFromAlbumCommand(owner.Id, album.Id, Guid.NewGuid()), CancellationToken.None); 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);
} }
@@ -18,14 +18,14 @@ public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
var command = new UpdateAlbumCommand(owner.Id, album.Id, " New Title ", "New description", 2025); var command = new UpdateAlbumCommand(owner.Id, album.Id, " New Title ", "New description", 2025);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("New Title", result.Value.Title); Assert.Equal("New Title", result.Value.Title);
Assert.Equal("New description", result.Value.Description); Assert.Equal("New description", result.Value.Description);
Assert.Equal(2025, result.Value.ReleaseYear); Assert.Equal(2025, result.Value.ReleaseYear);
var stored = await Database.UserAlbums.FindAsync(album.Id); var stored = await Database.UserAlbums.FindAsync([album.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal("NEW TITLE", stored.NormalizedTitle); Assert.Equal("NEW TITLE", stored.NormalizedTitle);
} }
@@ -35,7 +35,7 @@ public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
{ {
var command = new UpdateAlbumCommand(1, Guid.NewGuid(), "Title", null, null); var command = new UpdateAlbumCommand(1, Guid.NewGuid(), "Title", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -50,7 +50,7 @@ public sealed class UpdateAlbumCommandHandlerTests : HandlerTestBase
var command = new UpdateAlbumCommand(stranger.Id, album.Id, "Hijacked", null, null); var command = new UpdateAlbumCommand(stranger.Id, album.Id, "Hijacked", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -21,10 +21,10 @@ public sealed class GenerateMixesForUserCommandHandlerTests : HandlerTestBase
var user = TestEntities.User(); var user = TestEntities.User();
await SeedAsync(user); await SeedAsync(user);
var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Empty(await Database.Mixes.ToListAsync()); Assert.Empty(await Database.Mixes.ToListAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -34,10 +34,10 @@ public sealed class GenerateMixesForUserCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(user); var track = TestEntities.LocalTrack(user);
await SeedAsync(user, track, TestEntities.ListeningHistory(user.Id, track.Id)); await SeedAsync(user, track, TestEntities.ListeningHistory(user.Id, track.Id));
var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Empty(await Database.Mixes.ToListAsync()); Assert.Empty(await Database.Mixes.ToListAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -64,12 +64,12 @@ public sealed class GenerateMixesForUserCommandHandlerTests : HandlerTestBase
new TrackArtist { TrackId = otherTrackByArtist.Id, ArtistId = artist.Id, Position = 0 }, new TrackArtist { TrackId = otherTrackByArtist.Id, ArtistId = artist.Id, Position = 0 },
TestEntities.ListeningHistory(user.Id, listenedTrack.Id)); TestEntities.ListeningHistory(user.Id, listenedTrack.Id));
var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GenerateMixesForUserCommand(user.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var mixes = await Database.Mixes.ToListAsync(); var mixes = await Database.Mixes.ToListAsync(TestContext.Current.CancellationToken);
Assert.NotEmpty(mixes); Assert.NotEmpty(mixes);
Assert.DoesNotContain(mixes, mix => mix.Title == "Stale Mix"); Assert.DoesNotContain(mixes, mix => mix.Title == "Stale Mix");
Assert.Empty(await Database.MixItems.Where(item => item.MixId == staleMix.Id).ToListAsync()); Assert.Empty(await Database.MixItems.Where(item => item.MixId == staleMix.Id).ToListAsync(TestContext.Current.CancellationToken));
} }
} }
@@ -18,7 +18,7 @@ public sealed class GetMixByIdQueryHandlerTests : HandlerTestBase
var first = TestEntities.MixItem(mix.Id, position: 0, title: "First"); var first = TestEntities.MixItem(mix.Id, position: 0, title: "First");
await SeedAsync(owner, mix, second, first); await SeedAsync(owner, mix, second, first);
var result = await CreateHandler().Handle(new GetMixByIdQuery(owner.Id, mix.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetMixByIdQuery(owner.Id, mix.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("Discovery", result.Value.Title); Assert.Equal("Discovery", result.Value.Title);
@@ -28,7 +28,7 @@ public sealed class GetMixByIdQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_MixMissing_ReturnsNotFound() public async Task Handle_MixMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetMixByIdQuery(1, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new GetMixByIdQuery(1, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -40,7 +40,7 @@ public sealed class GetMixByIdQueryHandlerTests : HandlerTestBase
var mix = TestEntities.Mix(owner.Id); var mix = TestEntities.Mix(owner.Id);
await SeedAsync(owner, mix); await SeedAsync(owner, mix);
var result = await CreateHandler().Handle(new GetMixByIdQuery(2, mix.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetMixByIdQuery(2, mix.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -18,7 +18,7 @@ public sealed class GetMixesByUserIdQueryHandlerTests : HandlerTestBase
var theirs = TestEntities.Mix(other.Id, "Theirs"); var theirs = TestEntities.Mix(other.Id, "Theirs");
await SeedAsync(owner, other, second, first, theirs); await SeedAsync(owner, other, second, first, theirs);
var result = await CreateHandler().Handle(new GetMixesByUserIdQuery(owner.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetMixesByUserIdQuery(owner.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(["First", "Second"], result.Value.Select(mix => mix.Title)); Assert.Equal(["First", "Second"], result.Value.Select(mix => mix.Title));
@@ -30,7 +30,7 @@ public sealed class GetMixesByUserIdQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner); await SeedAsync(owner);
var result = await CreateHandler().Handle(new GetMixesByUserIdQuery(owner.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetMixesByUserIdQuery(owner.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Empty(result.Value); Assert.Empty(result.Value);
@@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -10,7 +11,7 @@
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" /> <PackageReference Include="NSubstitute" />
<PackageReference Include="xunit" /> <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio"> <PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -19,10 +19,10 @@ public sealed class AddTrackToPlayListCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, playList, track); await SeedAsync(owner, playList, track);
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, track.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var link = Assert.Single(await Database.PlayListHasTracks.ToListAsync()); var link = Assert.Single(await Database.PlayListHasTracks.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(0, link.Position); Assert.Equal(0, link.Position);
} }
@@ -37,17 +37,17 @@ public sealed class AddTrackToPlayListCommandHandlerTests : HandlerTestBase
owner, playList, existingTrack, newTrack, owner, playList, existingTrack, newTrack,
new PlayListHasTrack { PlayListId = playList.Id, TrackId = existingTrack.Id, Position = 0 }); new PlayListHasTrack { PlayListId = playList.Id, TrackId = existingTrack.Id, Position = 0 });
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, newTrack.Id), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, newTrack.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var newLink = await Database.PlayListHasTracks.SingleAsync(link => link.TrackId == newTrack.Id); var newLink = await Database.PlayListHasTracks.SingleAsync(link => link.TrackId == newTrack.Id, TestContext.Current.CancellationToken);
Assert.Equal(1, newLink.Position); Assert.Equal(1, newLink.Position);
} }
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(1, Guid.NewGuid(), Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(1, Guid.NewGuid(), Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -60,7 +60,7 @@ public sealed class AddTrackToPlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, stranger, playList); await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(stranger.Id, playList.Id, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(stranger.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -72,7 +72,7 @@ public sealed class AddTrackToPlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -85,7 +85,7 @@ public sealed class AddTrackToPlayListCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 }); await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new AddTrackToPlayListCommand(owner.Id, playList.Id, track.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -42,10 +42,10 @@ public sealed class AddYouTubeTrackToPlayListCommandHandlerTests : HandlerTestBa
var track = TestEntities.ExternalTrack(externalId: "abc123"); var track = TestEntities.ExternalTrack(externalId: "abc123");
await SeedAsync(owner, playList, track); await SeedAsync(owner, playList, track);
var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(owner.Id, playList.Id, "abc123"), CancellationToken.None); var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(owner.Id, playList.Id, "abc123"), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Single(await Database.PlayListHasTracks.ToListAsync()); Assert.Single(await Database.PlayListHasTracks.ToListAsync(TestContext.Current.CancellationToken));
await eventBus.DidNotReceive().PublishAsync(Arg.Any<Musify.Application.Events.DownloadYouTubeTrackEvent>(), Arg.Any<CancellationToken>()); await eventBus.DidNotReceive().PublishAsync(Arg.Any<Musify.Application.Events.DownloadYouTubeTrackEvent>(), Arg.Any<CancellationToken>());
} }
@@ -59,11 +59,11 @@ public sealed class AddYouTubeTrackToPlayListCommandHandlerTests : HandlerTestBa
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(owner.Id, playList.Id, "new-video"), CancellationToken.None); var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(owner.Id, playList.Id, "new-video"), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("Song Title", result.Value.Title); Assert.Equal("Song Title", result.Value.Title);
var track = await Database.ExternalTracks.SingleAsync(t => t.ExternalId == "new-video"); var track = await Database.ExternalTracks.SingleAsync(t => t.ExternalId == "new-video", TestContext.Current.CancellationToken);
Assert.True(track.Audio.DownloadRequested); Assert.True(track.Audio.DownloadRequested);
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.DownloadYouTubeTrackEvent>(), Arg.Any<CancellationToken>()); await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.DownloadYouTubeTrackEvent>(), Arg.Any<CancellationToken>());
} }
@@ -71,7 +71,7 @@ public sealed class AddYouTubeTrackToPlayListCommandHandlerTests : HandlerTestBa
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(1, Guid.NewGuid(), "abc123"), CancellationToken.None); var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(1, Guid.NewGuid(), "abc123"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -84,7 +84,7 @@ public sealed class AddYouTubeTrackToPlayListCommandHandlerTests : HandlerTestBa
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, stranger, playList); await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(stranger.Id, playList.Id, "abc123"), CancellationToken.None); var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(stranger.Id, playList.Id, "abc123"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -97,7 +97,7 @@ public sealed class AddYouTubeTrackToPlayListCommandHandlerTests : HandlerTestBa
var track = TestEntities.ExternalTrack(externalId: "abc123"); var track = TestEntities.ExternalTrack(externalId: "abc123");
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 }); await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(owner.Id, playList.Id, "abc123"), CancellationToken.None); var result = await CreateHandler().Handle(new AddYouTubeTrackToPlayListCommand(owner.Id, playList.Id, "abc123"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -30,7 +30,7 @@ public sealed class CreatePlayListCommandHandlerTests : HandlerTestBase
var command = new CreatePlayListCommand(user.Id, "My Playlist", "A description", PictureIntentId: null); var command = new CreatePlayListCommand(user.Id, "My Playlist", "A description", PictureIntentId: null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("My Playlist", result.Value.Name); Assert.Equal("My Playlist", result.Value.Name);
@@ -51,10 +51,10 @@ public sealed class CreatePlayListCommandHandlerTests : HandlerTestBase
var command = new CreatePlayListCommand(user.Id, "My Playlist", null, intent.Id); var command = new CreatePlayListCommand(user.Id, "My Playlist", null, intent.Id);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var stored = await Database.PlayLists.FindAsync(result.Value.Id); var stored = await Database.PlayLists.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal("cover.webp", stored.Pictures.OriginalName); Assert.Equal("cover.webp", stored.Pictures.OriginalName);
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.CreatePlayListResourcesEvent>(), Arg.Any<CancellationToken>()); await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.CreatePlayListResourcesEvent>(), Arg.Any<CancellationToken>());
@@ -65,7 +65,7 @@ public sealed class CreatePlayListCommandHandlerTests : HandlerTestBase
{ {
var command = new CreatePlayListCommand(404, "Orphan Playlist", null, null); var command = new CreatePlayListCommand(404, "Orphan Playlist", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -78,7 +78,7 @@ public sealed class CreatePlayListCommandHandlerTests : HandlerTestBase
var command = new CreatePlayListCommand(user.Id, "My Playlist", null, Guid.NewGuid()); var command = new CreatePlayListCommand(user.Id, "My Playlist", null, Guid.NewGuid());
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.True(result.IsError); Assert.True(result.IsError);
} }
@@ -21,10 +21,10 @@ public sealed class DeletePlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new DeletePlayListCommand(owner.Id, playList.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeletePlayListCommand(owner.Id, playList.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var stored = await Database.PlayLists.FindAsync(playList.Id); var stored = await Database.PlayLists.FindAsync([playList.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal(LifeCycleStatus.Removing, stored.LifeCycleStatus); Assert.Equal(LifeCycleStatus.Removing, stored.LifeCycleStatus);
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.DeletePlayListEvent>(), Arg.Any<CancellationToken>()); await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.DeletePlayListEvent>(), Arg.Any<CancellationToken>());
@@ -33,7 +33,7 @@ public sealed class DeletePlayListCommandHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new DeletePlayListCommand(1, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new DeletePlayListCommand(1, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -46,7 +46,7 @@ public sealed class DeletePlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, stranger, playList); await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new DeletePlayListCommand(stranger.Id, playList.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeletePlayListCommand(stranger.Id, playList.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -18,7 +18,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 }); await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(playList.Name, result.Value.Name); Assert.Equal(playList.Name, result.Value.Name);
@@ -28,7 +28,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListByIdQuery(Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -20,7 +20,7 @@ public sealed class GetPlayListCoverQueryHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListCoverQuery(playList.Id, size), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListCoverQuery(playList.Id, size), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("covers-bucket", result.Value.Bucket); Assert.Equal("covers-bucket", result.Value.Bucket);
@@ -30,7 +30,7 @@ public sealed class GetPlayListCoverQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetPlayListCoverQuery(Guid.NewGuid(), "small"), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListCoverQuery(Guid.NewGuid(), "small"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -22,7 +22,7 @@ public sealed class GetPlayListTracksQueryHandlerTests : HandlerTestBase
new PlayListHasTrack { PlayListId = playList.Id, TrackId = second.Id, Position = 1 }, new PlayListHasTrack { PlayListId = playList.Id, TrackId = second.Id, Position = 1 },
new PlayListHasTrack { PlayListId = playList.Id, TrackId = first.Id, Position = 0 }); new PlayListHasTrack { PlayListId = playList.Id, TrackId = first.Id, Position = 0 });
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title)); Assert.Equal(["First", "Second"], result.Value.Items.Select(track => track.Title));
@@ -31,7 +31,7 @@ public sealed class GetPlayListTracksQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(Guid.NewGuid(), PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListTracksQuery(Guid.NewGuid(), PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -20,7 +20,7 @@ public sealed class GetPlayListsByUserIdQueryHandlerTests : HandlerTestBase
TestEntities.PlayList(owner.Id, "Being removed", lifeCycleStatus: LifeCycleStatus.Removing), TestEntities.PlayList(owner.Id, "Being removed", lifeCycleStatus: LifeCycleStatus.Removing),
TestEntities.PlayList(other.Id, "Theirs")); TestEntities.PlayList(other.Id, "Theirs"));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var playList = Assert.Single(result.Value.Items); var playList = Assert.Single(result.Value.Items);
@@ -33,7 +33,7 @@ public sealed class GetPlayListsByUserIdQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.PlayList(owner.Id, "Road Trip"), TestEntities.PlayList(owner.Id, "Study Focus")); await SeedAsync(owner, TestEntities.PlayList(owner.Id, "Road Trip"), TestEntities.PlayList(owner.Id, "Study Focus"));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: "road", PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: "road", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var playList = Assert.Single(result.Value.Items); var playList = Assert.Single(result.Value.Items);
@@ -14,7 +14,7 @@ public sealed class GetPlayListsQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner, TestEntities.PlayList(owner.Id, "One"), TestEntities.PlayList(owner.Id, "Two")); await SeedAsync(owner, TestEntities.PlayList(owner.Id, "One"), TestEntities.PlayList(owner.Id, "Two"));
var result = await CreateHandler().Handle(new GetPlayListsQuery(PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListsQuery(PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount); Assert.Equal(2, result.Value.TotalItemCount);
@@ -23,7 +23,7 @@ public sealed class GetPlayListsQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_NoPlayLists_ReturnsEmptyPage() public async Task Handle_NoPlayLists_ReturnsEmptyPage()
{ {
var result = await CreateHandler().Handle(new GetPlayListsQuery(PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetPlayListsQuery(PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Empty(result.Value.Items); Assert.Empty(result.Value.Items);
@@ -18,7 +18,7 @@ public sealed class RemoveTrackFromPlayListCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 }); await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(owner.Id, playList.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(owner.Id, playList.Id, track.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Empty(Database.PlayListHasTracks); Assert.Empty(Database.PlayListHasTracks);
@@ -27,7 +27,7 @@ public sealed class RemoveTrackFromPlayListCommandHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_PlayListMissing_ReturnsNotFound() public async Task Handle_PlayListMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(1, Guid.NewGuid(), Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(1, Guid.NewGuid(), Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -40,7 +40,7 @@ public sealed class RemoveTrackFromPlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, stranger, playList); await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(stranger.Id, playList.Id, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(stranger.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -52,7 +52,7 @@ public sealed class RemoveTrackFromPlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(owner.Id, playList.Id, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new RemoveTrackFromPlayListCommand(owner.Id, playList.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -38,11 +38,11 @@ public sealed class RequestPlayListPictureUploadCommandHandlerTests : HandlerTes
var command = new RequestPlayListPictureUploadCommand(user.Id, "webp", "image/webp"); var command = new RequestPlayListPictureUploadCommand(user.Id, "webp", "image/webp");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.UploadUrl); Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.UploadUrl);
Assert.Single(await Database.UploadIntents.ToListAsync()); Assert.Single(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -50,7 +50,7 @@ public sealed class RequestPlayListPictureUploadCommandHandlerTests : HandlerTes
{ {
var command = new RequestPlayListPictureUploadCommand(404, "webp", "image/webp"); var command = new RequestPlayListPictureUploadCommand(404, "webp", "image/webp");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -66,9 +66,9 @@ public sealed class RequestPlayListPictureUploadCommandHandlerTests : HandlerTes
var command = new RequestPlayListPictureUploadCommand(user.Id, "webp", "image/webp", ExpectedSizeBytes: 1_000_000); var command = new RequestPlayListPictureUploadCommand(user.Id, "webp", "image/webp", ExpectedSizeBytes: 1_000_000);
var result = await CreateHandler(config).Handle(command, CancellationToken.None); var result = await CreateHandler(config).Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
Assert.Empty(await Database.UploadIntents.ToListAsync()); Assert.Empty(await Database.UploadIntents.ToListAsync(TestContext.Current.CancellationToken));
} }
} }
@@ -31,7 +31,7 @@ public sealed class UpdatePlayListCommandHandlerTests : HandlerTestBase
var command = new UpdatePlayListCommand(owner.Id, playList.Id, "New Name", null, null); var command = new UpdatePlayListCommand(owner.Id, playList.Id, "New Name", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("New Name", result.Value.Name); Assert.Equal("New Name", result.Value.Name);
@@ -42,7 +42,7 @@ public sealed class UpdatePlayListCommandHandlerTests : HandlerTestBase
{ {
var command = new UpdatePlayListCommand(1, Guid.NewGuid(), "New Name", null, null); var command = new UpdatePlayListCommand(1, Guid.NewGuid(), "New Name", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -57,7 +57,7 @@ public sealed class UpdatePlayListCommandHandlerTests : HandlerTestBase
var command = new UpdatePlayListCommand(stranger.Id, playList.Id, "Hijacked", null, null); var command = new UpdatePlayListCommand(stranger.Id, playList.Id, "Hijacked", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -71,7 +71,7 @@ public sealed class UpdatePlayListCommandHandlerTests : HandlerTestBase
var command = new UpdatePlayListCommand(owner.Id, playList.Id, " ", " ", null); var command = new UpdatePlayListCommand(owner.Id, playList.Id, " ", " ", null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("Original Name", result.Value.Name); Assert.Equal("Original Name", result.Value.Name);
@@ -22,7 +22,7 @@ public sealed class TrackStreamIssuerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track); await SeedAsync(owner, track);
var response = await CreateIssuer().IssueAsync(track.Id, "folder-42", userId: 7, CancellationToken.None); var response = await CreateIssuer().IssueAsync(track.Id, "folder-42", userId: 7, TestContext.Current.CancellationToken);
Assert.Equal("signed-token", response.Ticket); Assert.Equal("signed-token", response.Ticket);
Assert.Equal(120, response.ExpiresInSeconds); Assert.Equal(120, response.ExpiresInSeconds);
@@ -30,7 +30,7 @@ public sealed class TrackStreamIssuerTests : HandlerTestBase
"https://stream.musify.test/media/Tracks/ProcessedAudios/folder-42/audio.m4a", "https://stream.musify.test/media/Tracks/ProcessedAudios/folder-42/audio.m4a",
response.ManifestUrl); response.ManifestUrl);
var history = Assert.Single(await Database.ListeningHistories.ToListAsync()); var history = Assert.Single(await Database.ListeningHistories.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(7, history.UserId); Assert.Equal(7, history.UserId);
Assert.Equal(track.Id, history.TrackId); Assert.Equal(track.Id, history.TrackId);
} }
@@ -43,7 +43,7 @@ public sealed class TrackStreamIssuerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track); await SeedAsync(owner, track);
await CreateIssuer().IssueAsync(track.Id, "folder-42", userId: 1, CancellationToken.None); await CreateIssuer().IssueAsync(track.Id, "folder-42", userId: 1, TestContext.Current.CancellationToken);
ticketService.Received(1).IssueTicket(1, "Tracks/ProcessedAudios/folder-42/"); ticketService.Received(1).IssueTicket(1, "Tracks/ProcessedAudios/folder-42/");
} }
@@ -25,7 +25,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
var intent = TestEntities.UploadIntent(owner.Id, bucket: "bucket", key: "temp/object.webp"); var intent = TestEntities.UploadIntent(owner.Id, bucket: "bucket", key: "temp/object.webp");
await SeedAsync(owner, intent); await SeedAsync(owner, intent);
var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(intent.Id, result.Value.Id); Assert.Equal(intent.Id, result.Value.Id);
@@ -34,7 +34,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
[Fact] [Fact]
public async Task ValidateAndLoadAsync_IntentMissing_ReturnsNotFound() public async Task ValidateAndLoadAsync_IntentMissing_ReturnsNotFound()
{ {
var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), Guid.NewGuid(), owner.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), Guid.NewGuid(), owner.Id, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -46,7 +46,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
var intent = TestEntities.UploadIntent(owner.Id); var intent = TestEntities.UploadIntent(owner.Id);
await SeedAsync(owner, stranger, intent); await SeedAsync(owner, stranger, intent);
var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, stranger.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, stranger.Id, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -57,7 +57,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
var intent = TestEntities.UploadIntent(owner.Id, status: UploadIntentStatus.Consumed); var intent = TestEntities.UploadIntent(owner.Id, status: UploadIntentStatus.Consumed);
await SeedAsync(owner, intent); await SeedAsync(owner, intent);
var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -68,7 +68,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
var intent = TestEntities.UploadIntent(owner.Id, expiresAt: DateTime.UtcNow.AddMinutes(-1)); var intent = TestEntities.UploadIntent(owner.Id, expiresAt: DateTime.UtcNow.AddMinutes(-1));
await SeedAsync(owner, intent); await SeedAsync(owner, intent);
var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
} }
@@ -83,7 +83,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
var intent = TestEntities.UploadIntent(owner.Id); var intent = TestEntities.UploadIntent(owner.Id);
await SeedAsync(owner, intent); await SeedAsync(owner, intent);
var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(TestConfigurations.UploadIntent(), intent.Id, owner.Id, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -101,7 +101,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
var intent = TestEntities.UploadIntent(owner.Id); var intent = TestEntities.UploadIntent(owner.Id);
await SeedAsync(owner, intent); await SeedAsync(owner, intent);
var result = await CreateValidator().ValidateAndLoadAsync(config, intent.Id, owner.Id, CancellationToken.None); var result = await CreateValidator().ValidateAndLoadAsync(config, intent.Id, owner.Id, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
} }
@@ -111,7 +111,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
{ {
await SeedAsync(owner); await SeedAsync(owner);
var result = await CreateValidator().CheckQuotaAsync(TestConfigurations.UploadIntent(), owner.Id, requiredBytes: 1024, requiredIntentCount: 1, CancellationToken.None); var result = await CreateValidator().CheckQuotaAsync(TestConfigurations.UploadIntent(), owner.Id, requiredBytes: 1024, requiredIntentCount: 1, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
} }
@@ -123,7 +123,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
config.MaxActiveUploadIntentsPerUser = 1; config.MaxActiveUploadIntentsPerUser = 1;
await SeedAsync(owner, TestEntities.UploadIntent(owner.Id)); await SeedAsync(owner, TestEntities.UploadIntent(owner.Id));
var result = await CreateValidator().CheckQuotaAsync(config, owner.Id, requiredBytes: 1, requiredIntentCount: 1, CancellationToken.None); var result = await CreateValidator().CheckQuotaAsync(config, owner.Id, requiredBytes: 1, requiredIntentCount: 1, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
} }
@@ -138,7 +138,7 @@ public sealed class UploadIntentValidatorTests : HandlerTestBase
TestEntities.UploadIntent(owner.Id, status: UploadIntentStatus.Expired), TestEntities.UploadIntent(owner.Id, status: UploadIntentStatus.Expired),
TestEntities.UploadIntent(owner.Id, status: UploadIntentStatus.Consumed)); TestEntities.UploadIntent(owner.Id, status: UploadIntentStatus.Consumed));
var result = await CreateValidator().CheckQuotaAsync(config, owner.Id, requiredBytes: 1, requiredIntentCount: 1, CancellationToken.None); var result = await CreateValidator().CheckQuotaAsync(config, owner.Id, requiredBytes: 1, requiredIntentCount: 1, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
} }
@@ -17,7 +17,7 @@ public sealed class YouTubeTrackProvisionerTests : HandlerTestBase
[Fact] [Fact]
public async Task FindAsync_UnknownVideo_ReturnsNull() public async Task FindAsync_UnknownVideo_ReturnsNull()
{ {
var track = await CreateProvisioner().FindAsync("missing-video", CancellationToken.None); var track = await CreateProvisioner().FindAsync("missing-video", TestContext.Current.CancellationToken);
Assert.Null(track); Assert.Null(track);
} }
@@ -28,11 +28,11 @@ public sealed class YouTubeTrackProvisionerTests : HandlerTestBase
var existing = TestEntities.ExternalTrack(externalId: "known-video"); var existing = TestEntities.ExternalTrack(externalId: "known-video");
await SeedAsync(existing); await SeedAsync(existing);
var result = await CreateProvisioner().GetOrCreateAsync("known-video", CancellationToken.None); var result = await CreateProvisioner().GetOrCreateAsync("known-video", TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(existing.Id, result.Value.Track.Id); Assert.Equal(existing.Id, result.Value.Track.Id);
await youTubeMusicService.DidNotReceiveWithAnyArgs().GetSongAsync(default!, default); await youTubeMusicService.DidNotReceiveWithAnyArgs().GetSongAsync(default!, TestContext.Current.CancellationToken);
} }
[Fact] [Fact]
@@ -43,12 +43,12 @@ public sealed class YouTubeTrackProvisionerTests : HandlerTestBase
"new-video", "New Song", "New Artist", "Album", 210, "https://img/thumb.jpg", false, "new-video", "New Song", "New Artist", "Album", 210, "https://img/thumb.jpg", false,
[new YouTubeArtistRef(null, "New Artist"), new YouTubeArtistRef(null, "new artist")])); [new YouTubeArtistRef(null, "New Artist"), new YouTubeArtistRef(null, "new artist")]));
var result = await CreateProvisioner().GetOrCreateAsync("new-video", CancellationToken.None); var result = await CreateProvisioner().GetOrCreateAsync("new-video", TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("New Song", result.Value.Track.Title); Assert.Equal("New Song", result.Value.Track.Title);
Assert.Single(await Database.Artists.ToListAsync()); Assert.Single(await Database.Artists.ToListAsync(TestContext.Current.CancellationToken));
Assert.Single(await Database.TrackArtists.ToListAsync()); Assert.Single(await Database.TrackArtists.ToListAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -57,7 +57,7 @@ public sealed class YouTubeTrackProvisionerTests : HandlerTestBase
youTubeMusicService.GetSongAsync("bad-video", Arg.Any<CancellationToken>()) youTubeMusicService.GetSongAsync("bad-video", Arg.Any<CancellationToken>())
.Returns(ErrorOr.Error.NotFound(description: "Video not found")); .Returns(ErrorOr.Error.NotFound(description: "Video not found"));
var result = await CreateProvisioner().GetOrCreateAsync("bad-video", CancellationToken.None); var result = await CreateProvisioner().GetOrCreateAsync("bad-video", TestContext.Current.CancellationToken);
Assert.True(result.IsError); Assert.True(result.IsError);
} }
@@ -76,11 +76,11 @@ public sealed class YouTubeTrackProvisionerTests : HandlerTestBase
new YouTubeAlbumTrack("track-2", "Track Two", 200, 2, false) new YouTubeAlbumTrack("track-2", "Track Two", 200, 2, false)
]); ]);
await CreateProvisioner().MaterializeAlbumAsync("album-1", detail, CancellationToken.None); await CreateProvisioner().MaterializeAlbumAsync("album-1", detail, TestContext.Current.CancellationToken);
Assert.Equal(2, await Database.ExternalTracks.CountAsync()); Assert.Equal(2, await Database.ExternalTracks.CountAsync(TestContext.Current.CancellationToken));
Assert.Equal(2, await Database.AlbumHasTracks.CountAsync()); Assert.Equal(2, await Database.AlbumHasTracks.CountAsync(TestContext.Current.CancellationToken));
Assert.Single(await Database.ExternalAlbums.ToListAsync()); Assert.Single(await Database.ExternalAlbums.ToListAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -93,11 +93,11 @@ public sealed class YouTubeTrackProvisionerTests : HandlerTestBase
Tracks: [new YouTubeAlbumTrack("track-1", "Track One", 200, 1, false)]); Tracks: [new YouTubeAlbumTrack("track-1", "Track One", 200, 1, false)]);
var provisioner = CreateProvisioner(); var provisioner = CreateProvisioner();
await provisioner.MaterializeAlbumAsync("album-1", detail, CancellationToken.None); await provisioner.MaterializeAlbumAsync("album-1", detail, TestContext.Current.CancellationToken);
await provisioner.MaterializeAlbumAsync("album-1", detail, CancellationToken.None); await provisioner.MaterializeAlbumAsync("album-1", detail, TestContext.Current.CancellationToken);
Assert.Single(await Database.ExternalTracks.ToListAsync()); Assert.Single(await Database.ExternalTracks.ToListAsync(TestContext.Current.CancellationToken));
Assert.Single(await Database.AlbumHasTracks.ToListAsync()); Assert.Single(await Database.AlbumHasTracks.ToListAsync(TestContext.Current.CancellationToken));
Assert.Single(await Database.ExternalAlbums.ToListAsync()); Assert.Single(await Database.ExternalAlbums.ToListAsync(TestContext.Current.CancellationToken));
} }
} }
@@ -16,9 +16,9 @@ public abstract class HandlerTestBase : IAsyncLifetime
protected TestDatabase Database => database; protected TestDatabase Database => database;
public async Task InitializeAsync() => database = await TestDatabase.CreateAsync(); public async ValueTask InitializeAsync() => database = await TestDatabase.CreateAsync();
public Task DisposeAsync() => database.DisposeAsync().AsTask(); public ValueTask DisposeAsync() => database.DisposeAsync();
protected static ILogger<T> NoOpLogger<T>() => NullLogger<T>.Instance; protected static ILogger<T> NoOpLogger<T>() => NullLogger<T>.Instance;
@@ -43,14 +43,14 @@ public sealed class CreateTrackCommandHandlerTests : HandlerTestBase
var command = new CreateTrackCommand(user.Id, "My Song", picture.Id, audio.Id); var command = new CreateTrackCommand(user.Id, "My Song", picture.Id, audio.Id);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("My Song", result.Value.Title); Assert.Equal("My Song", result.Value.Title);
Assert.Equal(user.Id, result.Value.OwnerUserId); Assert.Equal(user.Id, result.Value.OwnerUserId);
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.CreateTrackResourcesEvent>(), Arg.Any<CancellationToken>()); await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.CreateTrackResourcesEvent>(), Arg.Any<CancellationToken>());
var stored = await Database.LocalTracks.FindAsync(result.Value.Id); var stored = await Database.LocalTracks.FindAsync([result.Value.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal(ProcessingStatus.Pending, stored.Audio.TranscodeStatus); Assert.Equal(ProcessingStatus.Pending, stored.Audio.TranscodeStatus);
} }
@@ -60,7 +60,7 @@ public sealed class CreateTrackCommandHandlerTests : HandlerTestBase
{ {
var command = new CreateTrackCommand(404, "Orphan Song", Guid.NewGuid(), Guid.NewGuid()); var command = new CreateTrackCommand(404, "Orphan Song", Guid.NewGuid(), Guid.NewGuid());
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -75,7 +75,7 @@ public sealed class CreateTrackCommandHandlerTests : HandlerTestBase
var command = new CreateTrackCommand(user.Id, "My Song", picture.Id, audio.Id); var command = new CreateTrackCommand(user.Id, "My Song", picture.Id, audio.Id);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.True(result.IsError); Assert.True(result.IsError);
await eventBus.DidNotReceive().PublishAsync(Arg.Any<Musify.Application.Events.CreateTrackResourcesEvent>(), Arg.Any<CancellationToken>()); await eventBus.DidNotReceive().PublishAsync(Arg.Any<Musify.Application.Events.CreateTrackResourcesEvent>(), Arg.Any<CancellationToken>());
@@ -91,7 +91,7 @@ public sealed class CreateTrackCommandHandlerTests : HandlerTestBase
var command = new CreateTrackCommand(user.Id, "My Song", picture.Id, audio.Id); var command = new CreateTrackCommand(user.Id, "My Song", picture.Id, audio.Id);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -21,10 +21,10 @@ public sealed class DeleteTrackCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new DeleteTrackCommand(owner.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteTrackCommand(owner.Id, track.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var stored = await Database.Tracks.FindAsync(track.Id); var stored = await Database.Tracks.FindAsync([track.Id], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal(LifeCycleStatus.Removing, stored.LifeCycleStatus); Assert.Equal(LifeCycleStatus.Removing, stored.LifeCycleStatus);
await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.DeleteTrackEvent>(), Arg.Any<CancellationToken>()); await eventBus.Received(1).PublishAsync(Arg.Any<Musify.Application.Events.DeleteTrackEvent>(), Arg.Any<CancellationToken>());
@@ -33,7 +33,7 @@ public sealed class DeleteTrackCommandHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_TrackMissing_ReturnsNotFound() public async Task Handle_TrackMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new DeleteTrackCommand(1, Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteTrackCommand(1, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -46,7 +46,7 @@ public sealed class DeleteTrackCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, stranger, track); await SeedAsync(owner, stranger, track);
var result = await CreateHandler().Handle(new DeleteTrackCommand(stranger.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteTrackCommand(stranger.Id, track.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -57,7 +57,7 @@ public sealed class DeleteTrackCommandHandlerTests : HandlerTestBase
var externalTrack = TestEntities.ExternalTrack(); var externalTrack = TestEntities.ExternalTrack();
await SeedAsync(externalTrack); await SeedAsync(externalTrack);
var result = await CreateHandler().Handle(new DeleteTrackCommand(1, externalTrack.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteTrackCommand(1, externalTrack.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type); Assert.Equal(ErrorType.Unauthorized, result.FirstError.Type);
} }
@@ -69,7 +69,7 @@ public sealed class DeleteTrackCommandHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner, audio: TestEntities.PendingAudio()); var track = TestEntities.LocalTrack(owner, audio: TestEntities.PendingAudio());
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new DeleteTrackCommand(owner.Id, track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new DeleteTrackCommand(owner.Id, track.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -20,7 +20,7 @@ public sealed class GetListeningHistoryQueryHandlerTests : HandlerTestBase
TestEntities.ListeningHistory(owner.Id, older.Id, DateTime.UtcNow.AddMinutes(-5)), TestEntities.ListeningHistory(owner.Id, older.Id, DateTime.UtcNow.AddMinutes(-5)),
TestEntities.ListeningHistory(owner.Id, newer.Id, DateTime.UtcNow)); TestEntities.ListeningHistory(owner.Id, newer.Id, DateTime.UtcNow));
var result = await CreateHandler().Handle(new GetListeningHistoryQuery(owner.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetListeningHistoryQuery(owner.Id), TestContext.Current.CancellationToken);
Assert.Equal(["Newer listen", "Older listen"], result.Select(track => track.Title)); Assert.Equal(["Newer listen", "Older listen"], result.Select(track => track.Title));
} }
@@ -31,7 +31,7 @@ public sealed class GetListeningHistoryQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync(owner); await SeedAsync(owner);
var result = await CreateHandler().Handle(new GetListeningHistoryQuery(owner.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetListeningHistoryQuery(owner.Id), TestContext.Current.CancellationToken);
Assert.Empty(result); Assert.Empty(result);
} }
@@ -16,7 +16,7 @@ public sealed class GetTrackByIdQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner, "My Song"); var track = TestEntities.LocalTrack(owner, "My Song");
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new GetTrackByIdQuery(track.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackByIdQuery(track.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("My Song", result.Value.Title); Assert.Equal("My Song", result.Value.Title);
@@ -27,7 +27,7 @@ public sealed class GetTrackByIdQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_TrackMissing_ReturnsNotFound() public async Task Handle_TrackMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetTrackByIdQuery(Guid.NewGuid()), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackByIdQuery(Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -21,7 +21,7 @@ public sealed class GetTrackCoverQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new GetTrackCoverQuery(track.Id, size), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackCoverQuery(track.Id, size), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("covers-bucket", result.Value.Bucket); Assert.Equal("covers-bucket", result.Value.Bucket);
@@ -32,7 +32,7 @@ public sealed class GetTrackCoverQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_TrackMissing_ReturnsNotFound() public async Task Handle_TrackMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetTrackCoverQuery(Guid.NewGuid(), "small"), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackCoverQuery(Guid.NewGuid(), "small"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -44,7 +44,7 @@ public sealed class GetTrackCoverQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner, pictures: TestEntities.PendingPictures()); var track = TestEntities.LocalTrack(owner, pictures: TestEntities.PendingPictures());
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new GetTrackCoverQuery(track.Id, "small"), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackCoverQuery(track.Id, "small"), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -33,14 +33,14 @@ public sealed class GetTrackStreamQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new GetTrackStreamQuery(track.Id, owner.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackStreamQuery(track.Id, owner.Id), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("ticket-token", result.Value.Ticket); Assert.Equal("ticket-token", result.Value.Ticket);
Assert.Equal(60, result.Value.ExpiresInSeconds); Assert.Equal(60, result.Value.ExpiresInSeconds);
Assert.Contains("audio-folder", result.Value.ManifestUrl); Assert.Contains("audio-folder", result.Value.ManifestUrl);
var history = Assert.Single(await Database.ListeningHistories.ToListAsync()); var history = Assert.Single(await Database.ListeningHistories.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(owner.Id, history.UserId); Assert.Equal(owner.Id, history.UserId);
Assert.Equal(track.Id, history.TrackId); Assert.Equal(track.Id, history.TrackId);
} }
@@ -48,7 +48,7 @@ public sealed class GetTrackStreamQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_TrackMissing_ReturnsNotFound() public async Task Handle_TrackMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetTrackStreamQuery(Guid.NewGuid(), 1), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackStreamQuery(Guid.NewGuid(), 1), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -60,7 +60,7 @@ public sealed class GetTrackStreamQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner, audio: TestEntities.PendingAudio()); var track = TestEntities.LocalTrack(owner, audio: TestEntities.PendingAudio());
await SeedAsync(owner, track); await SeedAsync(owner, track);
var result = await CreateHandler().Handle(new GetTrackStreamQuery(track.Id, owner.Id), CancellationToken.None); var result = await CreateHandler().Handle(new GetTrackStreamQuery(track.Id, owner.Id), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -21,7 +21,7 @@ public sealed class GetTracksByUserIdQueryHandlerTests : HandlerTestBase
new UserHasTrack { UserId = owner.Id, TrackId = mine.Id }, new UserHasTrack { UserId = owner.Id, TrackId = mine.Id },
new UserHasTrack { UserId = other.Id, TrackId = theirs.Id }); new UserHasTrack { UserId = other.Id, TrackId = theirs.Id });
var result = await CreateHandler().Handle(new GetTracksByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetTracksByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var track = Assert.Single(result.Value.Items); var track = Assert.Single(result.Value.Items);
@@ -37,7 +37,7 @@ public sealed class GetTracksByUserIdQueryHandlerTests : HandlerTestBase
var track = TestEntities.LocalTrack(owner); var track = TestEntities.LocalTrack(owner);
await SeedAsync(owner, track, new UserHasTrack { UserId = owner.Id, TrackId = track.Id }); await SeedAsync(owner, track, new UserHasTrack { UserId = owner.Id, TrackId = track.Id });
var result = await CreateHandler().Handle(new GetTracksByUserIdQuery(owner.Id, Name: null, PageNumber: requestedPageNumber, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetTracksByUserIdQuery(owner.Id, Name: null, PageNumber: requestedPageNumber, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(expectedPageNumber, result.Value.PageNumber); Assert.Equal(expectedPageNumber, result.Value.PageNumber);
@@ -19,7 +19,7 @@ public sealed class GetTracksQueryHandlerTests : HandlerTestBase
new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = trackA.Id }, new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = trackA.Id },
new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = trackB.Id }); new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = trackB.Id });
var result = await CreateHandler().Handle(new GetTracksQuery(Name: null, PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetTracksQuery(Name: null, PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount); Assert.Equal(2, result.Value.TotalItemCount);
@@ -36,7 +36,7 @@ public sealed class GetTracksQueryHandlerTests : HandlerTestBase
new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = match.Id }, new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = match.Id },
new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = other.Id }); new Musify.Domain.Entities.UserHasTrack { UserId = owner.Id, TrackId = other.Id });
var result = await CreateHandler().Handle(new GetTracksQuery(Name: "rhapsody", PageNumber: 1, PageSize: 10), CancellationToken.None); var result = await CreateHandler().Handle(new GetTracksQuery(Name: "rhapsody", PageNumber: 1, PageSize: 10), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var track = Assert.Single(result.Value.Items); var track = Assert.Single(result.Value.Items);
@@ -39,13 +39,13 @@ public sealed class RequestTrackUploadUrlsCommandHandlerTests : HandlerTestBase
var command = new RequestTrackUploadUrlsCommand(user.Id, "webp", "image/webp", "mp3", "audio/mpeg"); var command = new RequestTrackUploadUrlsCommand(user.Id, "webp", "image/webp", "mp3", "audio/mpeg");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.PictureUploadUrl); Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.PictureUploadUrl);
Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.AudioUploadUrl); Assert.Equal("https://storage.musify.test/presigned-upload", result.Value.AudioUploadUrl);
var intents = await Database.UploadIntents.Where(intent => intent.UserId == user.Id).ToListAsync(); var intents = await Database.UploadIntents.Where(intent => intent.UserId == user.Id).ToListAsync(TestContext.Current.CancellationToken);
Assert.Equal(2, intents.Count); Assert.Equal(2, intents.Count);
Assert.Contains(intents, intent => intent.Purpose == UploadIntentPurpose.TrackPicture); Assert.Contains(intents, intent => intent.Purpose == UploadIntentPurpose.TrackPicture);
Assert.Contains(intents, intent => intent.Purpose == UploadIntentPurpose.TrackAudio); Assert.Contains(intents, intent => intent.Purpose == UploadIntentPurpose.TrackAudio);
@@ -56,7 +56,7 @@ public sealed class RequestTrackUploadUrlsCommandHandlerTests : HandlerTestBase
{ {
var command = new RequestTrackUploadUrlsCommand(404, "webp", "image/webp", "mp3", "audio/mpeg"); var command = new RequestTrackUploadUrlsCommand(404, "webp", "image/webp", "mp3", "audio/mpeg");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -72,9 +72,9 @@ public sealed class RequestTrackUploadUrlsCommandHandlerTests : HandlerTestBase
var command = new RequestTrackUploadUrlsCommand(user.Id, "webp", "image/webp", "mp3", "audio/mpeg"); var command = new RequestTrackUploadUrlsCommand(user.Id, "webp", "image/webp", "mp3", "audio/mpeg");
var result = await CreateHandler(config).Handle(command, CancellationToken.None); var result = await CreateHandler(config).Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
Assert.Equal(1, await Database.UploadIntents.CountAsync()); Assert.Equal(1, await Database.UploadIntents.CountAsync(TestContext.Current.CancellationToken));
} }
} }
@@ -14,13 +14,13 @@ public sealed class CreateUserCommandHandlerTests : HandlerTestBase
{ {
var command = new CreateUserCommand(1, "Jane Doe", "Jane", "Doe"); var command = new CreateUserCommand(1, "Jane Doe", "Jane", "Doe");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("Jane Doe", result.Value.Name); Assert.Equal("Jane Doe", result.Value.Name);
Assert.Equal(1, result.Value.Id); Assert.Equal(1, result.Value.Id);
var stored = await Database.Users.FindAsync(1L); var stored = await Database.Users.FindAsync([1L], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal("JANE DOE", stored.NormalizedName); Assert.Equal("JANE DOE", stored.NormalizedName);
} }
@@ -33,7 +33,7 @@ public sealed class CreateUserCommandHandlerTests : HandlerTestBase
var command = new CreateUserCommand(1, "Someone Else", null, null); var command = new CreateUserCommand(1, "Someone Else", null, null);
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Conflict, result.FirstError.Type); Assert.Equal(ErrorType.Conflict, result.FirstError.Type);
} }
@@ -15,7 +15,7 @@ public sealed class GetUserByIdQueryHandlerTests : HandlerTestBase
var user = TestEntities.User(1, "Jane Doe"); var user = TestEntities.User(1, "Jane Doe");
await SeedAsync(user); await SeedAsync(user);
var result = await CreateHandler().Handle(new GetUserByIdQuery(1), CancellationToken.None); var result = await CreateHandler().Handle(new GetUserByIdQuery(1), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal("Jane Doe", result.Value.Name); Assert.Equal("Jane Doe", result.Value.Name);
@@ -24,7 +24,7 @@ public sealed class GetUserByIdQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_UserMissing_ReturnsNotFound() public async Task Handle_UserMissing_ReturnsNotFound()
{ {
var result = await CreateHandler().Handle(new GetUserByIdQuery(404), CancellationToken.None); var result = await CreateHandler().Handle(new GetUserByIdQuery(404), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.NotFound, result.FirstError.Type); Assert.Equal(ErrorType.NotFound, result.FirstError.Type);
} }
@@ -13,7 +13,7 @@ public sealed class GetUsersQueryHandlerTests : HandlerTestBase
{ {
await SeedAsync(TestEntities.User(1, "alice"), TestEntities.User(2, "bob")); await SeedAsync(TestEntities.User(1, "alice"), TestEntities.User(2, "bob"));
var result = await CreateHandler().Handle(new GetUsersQuery(PageNumber: 1, PageSize: 10, UsernameSearch: null), CancellationToken.None); var result = await CreateHandler().Handle(new GetUsersQuery(PageNumber: 1, PageSize: 10, UsernameSearch: null), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(2, result.Value.TotalItemCount); Assert.Equal(2, result.Value.TotalItemCount);
@@ -24,7 +24,7 @@ public sealed class GetUsersQueryHandlerTests : HandlerTestBase
{ {
await SeedAsync(TestEntities.User(1, "alice-wonderland"), TestEntities.User(2, "bob-builder")); await SeedAsync(TestEntities.User(1, "alice-wonderland"), TestEntities.User(2, "bob-builder"));
var result = await CreateHandler().Handle(new GetUsersQuery(PageNumber: 1, PageSize: 10, UsernameSearch: "alice"), CancellationToken.None); var result = await CreateHandler().Handle(new GetUsersQuery(PageNumber: 1, PageSize: 10, UsernameSearch: "alice"), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var user = Assert.Single(result.Value.Items); var user = Assert.Single(result.Value.Items);
@@ -13,10 +13,10 @@ public sealed class SyncUserCommandHandlerTests : HandlerTestBase
{ {
var command = new SyncUserCommand(1, "Jane Doe", "Jane", "Doe", "https://avatar.example/jane.png"); var command = new SyncUserCommand(1, "Jane Doe", "Jane", "Doe", "https://avatar.example/jane.png");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var stored = await Database.Users.FindAsync(1L); var stored = await Database.Users.FindAsync([1L], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal("Jane Doe", stored.Name); Assert.Equal("Jane Doe", stored.Name);
Assert.Equal("https://avatar.example/jane.png", stored.ProfilePictureUrl); Assert.Equal("https://avatar.example/jane.png", stored.ProfilePictureUrl);
@@ -30,10 +30,10 @@ public sealed class SyncUserCommandHandlerTests : HandlerTestBase
var command = new SyncUserCommand(1, "New Name", "New", "Name", "https://avatar.example/new.png"); var command = new SyncUserCommand(1, "New Name", "New", "Name", "https://avatar.example/new.png");
var result = await CreateHandler().Handle(command, CancellationToken.None); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
var stored = await Database.Users.FindAsync(1L); var stored = await Database.Users.FindAsync([1L], TestContext.Current.CancellationToken);
Assert.NotNull(stored); Assert.NotNull(stored);
Assert.Equal("New Name", stored.Name); Assert.Equal("New Name", stored.Name);
Assert.Equal("NEW NAME", stored.NormalizedName); Assert.Equal("NEW NAME", stored.NormalizedName);
@@ -30,7 +30,7 @@ public sealed class GetYouTubeAlbumQueryHandlerTests : HandlerTestBase
[Fact] [Fact]
public async Task Handle_EmptyAlbumId_ReturnsValidationError() public async Task Handle_EmptyAlbumId_ReturnsValidationError()
{ {
var result = await CreateHandler().Handle(new GetYouTubeAlbumQuery(string.Empty), CancellationToken.None); var result = await CreateHandler().Handle(new GetYouTubeAlbumQuery(string.Empty), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
} }
@@ -41,11 +41,11 @@ public sealed class GetYouTubeAlbumQueryHandlerTests : HandlerTestBase
var detail = BuildDetail(); var detail = BuildDetail();
youTubeMusicService.GetAlbumAsync("album-1", Arg.Any<CancellationToken>()).Returns(detail); youTubeMusicService.GetAlbumAsync("album-1", Arg.Any<CancellationToken>()).Returns(detail);
var result = await CreateHandler().Handle(new GetYouTubeAlbumQuery("album-1"), CancellationToken.None); var result = await CreateHandler().Handle(new GetYouTubeAlbumQuery("album-1"), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Same(detail, result.Value); Assert.Same(detail, result.Value);
Assert.NotEmpty(await Database.ExternalAlbums.ToListAsync()); Assert.NotEmpty(await Database.ExternalAlbums.ToListAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -54,7 +54,7 @@ public sealed class GetYouTubeAlbumQueryHandlerTests : HandlerTestBase
youTubeMusicService.GetAlbumAsync("missing", Arg.Any<CancellationToken>()) youTubeMusicService.GetAlbumAsync("missing", Arg.Any<CancellationToken>())
.Returns(Error.NotFound(description: "Album not found")); .Returns(Error.NotFound(description: "Album not found"));
var result = await CreateHandler().Handle(new GetYouTubeAlbumQuery("missing"), CancellationToken.None); var result = await CreateHandler().Handle(new GetYouTubeAlbumQuery("missing"), TestContext.Current.CancellationToken);
Assert.True(result.IsError); Assert.True(result.IsError);
} }
@@ -34,12 +34,12 @@ public sealed class ResolveYouTubeTrackStreamCommandHandlerTests : HandlerTestBa
var track = TestEntities.ExternalTrack(externalId: "abc123"); var track = TestEntities.ExternalTrack(externalId: "abc123");
await SeedAsync(user, track); await SeedAsync(user, track);
var result = await CreateHandler().Handle(new ResolveYouTubeTrackStreamCommand("abc123", 1), CancellationToken.None); var result = await CreateHandler().Handle(new ResolveYouTubeTrackStreamCommand("abc123", 1), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(YouTubeStreamMode.Server, result.Value.Mode); Assert.Equal(YouTubeStreamMode.Server, result.Value.Mode);
Assert.Equal("token", result.Value.Ticket); Assert.Equal("token", result.Value.Ticket);
await youTubeMusicService.DidNotReceiveWithAnyArgs().GetAudioStreamAsync(default!, default); await youTubeMusicService.DidNotReceiveWithAnyArgs().GetAudioStreamAsync(default!, TestContext.Current.CancellationToken);
} }
[Fact] [Fact]
@@ -52,14 +52,14 @@ public sealed class ResolveYouTubeTrackStreamCommandHandlerTests : HandlerTestBa
youTubeMusicService.ResolveArtworkUrl(Arg.Any<string>()).Returns(callInfo => callInfo.Arg<string>()); youTubeMusicService.ResolveArtworkUrl(Arg.Any<string>()).Returns(callInfo => callInfo.Arg<string>());
await SeedAsync(TestEntities.User(42)); await SeedAsync(TestEntities.User(42));
var result = await CreateHandler().Handle(new ResolveYouTubeTrackStreamCommand("new-video", 42), CancellationToken.None); var result = await CreateHandler().Handle(new ResolveYouTubeTrackStreamCommand("new-video", 42), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(YouTubeStreamMode.YouTube, result.Value.Mode); Assert.Equal(YouTubeStreamMode.YouTube, result.Value.Mode);
Assert.Equal("https://youtube.example/stream", result.Value.StreamUrl); Assert.Equal("https://youtube.example/stream", result.Value.StreamUrl);
Assert.NotNull(result.Value.TrackId); Assert.NotNull(result.Value.TrackId);
var history = Assert.Single(await Database.ListeningHistories.ToListAsync()); var history = Assert.Single(await Database.ListeningHistories.ToListAsync(TestContext.Current.CancellationToken));
Assert.Equal(42, history.UserId); Assert.Equal(42, history.UserId);
} }
} }
@@ -15,11 +15,11 @@ public sealed class SearchYouTubeAlbumsQueryHandlerTests
[Fact] [Fact]
public async Task Handle_EmptyQuery_ReturnsValidationErrorWithoutCallingTheService() public async Task Handle_EmptyQuery_ReturnsValidationErrorWithoutCallingTheService()
{ {
var result = await CreateHandler().Handle(new SearchYouTubeAlbumsQuery(string.Empty, string.Empty), CancellationToken.None); var result = await CreateHandler().Handle(new SearchYouTubeAlbumsQuery(string.Empty, string.Empty), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
await youTubeMusicService.DidNotReceiveWithAnyArgs() await youTubeMusicService.DidNotReceiveWithAnyArgs()
.SearchAlbumsAsync(default!, default!, default); .SearchAlbumsAsync(default!, default!, TestContext.Current.CancellationToken);
} }
[Fact] [Fact]
@@ -28,7 +28,7 @@ public sealed class SearchYouTubeAlbumsQueryHandlerTests
var expected = new YouTubeAlbumSearchResult([], "next-token"); var expected = new YouTubeAlbumSearchResult([], "next-token");
youTubeMusicService.SearchAlbumsAsync("query", "token", Arg.Any<CancellationToken>()).Returns(expected); youTubeMusicService.SearchAlbumsAsync("query", "token", Arg.Any<CancellationToken>()).Returns(expected);
var result = await CreateHandler().Handle(new SearchYouTubeAlbumsQuery("query", "token"), CancellationToken.None); var result = await CreateHandler().Handle(new SearchYouTubeAlbumsQuery("query", "token"), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Same(expected, result.Value); Assert.Same(expected, result.Value);
@@ -15,11 +15,11 @@ public sealed class SearchYouTubeTracksQueryHandlerTests
[Fact] [Fact]
public async Task Handle_EmptyQuery_ReturnsValidationErrorWithoutCallingTheService() public async Task Handle_EmptyQuery_ReturnsValidationErrorWithoutCallingTheService()
{ {
var result = await CreateHandler().Handle(new SearchYouTubeTracksQuery(string.Empty, string.Empty), CancellationToken.None); var result = await CreateHandler().Handle(new SearchYouTubeTracksQuery(string.Empty, string.Empty), TestContext.Current.CancellationToken);
Assert.Equal(ErrorType.Validation, result.FirstError.Type); Assert.Equal(ErrorType.Validation, result.FirstError.Type);
await youTubeMusicService.DidNotReceiveWithAnyArgs() await youTubeMusicService.DidNotReceiveWithAnyArgs()
.SearchSongsAsync(default!, default!, default); .SearchSongsAsync(default!, default!, TestContext.Current.CancellationToken);
} }
[Fact] [Fact]
@@ -28,7 +28,7 @@ public sealed class SearchYouTubeTracksQueryHandlerTests
var expected = new YouTubeSearchResult([], "next-token"); var expected = new YouTubeSearchResult([], "next-token");
youTubeMusicService.SearchSongsAsync("query", "token", Arg.Any<CancellationToken>()).Returns(expected); youTubeMusicService.SearchSongsAsync("query", "token", Arg.Any<CancellationToken>()).Returns(expected);
var result = await CreateHandler().Handle(new SearchYouTubeTracksQuery("query", "token"), CancellationToken.None); var result = await CreateHandler().Handle(new SearchYouTubeTracksQuery("query", "token"), TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Same(expected, result.Value); Assert.Same(expected, result.Value);
@@ -3,11 +3,12 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" /> <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio"> <PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -14,7 +15,7 @@
<PackageReference Include="NSubstitute" /> <PackageReference Include="NSubstitute" />
<PackageReference Include="Testcontainers" /> <PackageReference Include="Testcontainers" />
<PackageReference Include="Testcontainers.PostgreSql" /> <PackageReference Include="Testcontainers.PostgreSql" />
<PackageReference Include="xunit" /> <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio"> <PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -14,8 +14,8 @@ public sealed class AuditableEntityInterceptorTests(InfrastructureTestFixture fi
var before = DateTime.UtcNow; var before = DateTime.UtcNow;
var user = new User { Id = Random.Shared.NextInt64(1, long.MaxValue), Name = "audit-user", NormalizedName = "AUDIT-USER" }; var user = new User { Id = Random.Shared.NextInt64(1, long.MaxValue), Name = "audit-user", NormalizedName = "AUDIT-USER" };
await database.Users.AddAsync(user); await database.Users.AddAsync(user, TestContext.Current.CancellationToken);
await database.SaveChangesAsync(CancellationToken.None); await database.SaveChangesAsync(TestContext.Current.CancellationToken);
var after = DateTime.UtcNow; var after = DateTime.UtcNow;
@@ -29,14 +29,14 @@ public sealed class AuditableEntityInterceptorTests(InfrastructureTestFixture fi
await using var database = fixture.CreateDatabase(); await using var database = fixture.CreateDatabase();
var user = new User { Id = Random.Shared.NextInt64(1, long.MaxValue), Name = "audit-user-2", NormalizedName = "AUDIT-USER-2" }; var user = new User { Id = Random.Shared.NextInt64(1, long.MaxValue), Name = "audit-user-2", NormalizedName = "AUDIT-USER-2" };
await database.Users.AddAsync(user); await database.Users.AddAsync(user, TestContext.Current.CancellationToken);
await database.SaveChangesAsync(CancellationToken.None); await database.SaveChangesAsync(TestContext.Current.CancellationToken);
var originalCreatedAt = user.CreatedAt; var originalCreatedAt = user.CreatedAt;
await Task.Delay(TimeSpan.FromMilliseconds(50)); await Task.Delay(TimeSpan.FromMilliseconds(50), TestContext.Current.CancellationToken);
user.Name = "audit-user-2-renamed"; user.Name = "audit-user-2-renamed";
await database.SaveChangesAsync(CancellationToken.None); await database.SaveChangesAsync(TestContext.Current.CancellationToken);
Assert.Equal(originalCreatedAt, user.CreatedAt); Assert.Equal(originalCreatedAt, user.CreatedAt);
Assert.True(user.UpdatedAt > originalCreatedAt, Assert.True(user.UpdatedAt > originalCreatedAt,
@@ -15,7 +15,7 @@ public sealed class DatabaseTests(InfrastructureTestFixture fixture)
await using var database = fixture.CreateDatabase(); await using var database = fixture.CreateDatabase();
var user = new User { Id = Random.Shared.NextInt64(1, long.MaxValue), Name = "roundtrip-user", NormalizedName = "ROUNDTRIP-USER" }; var user = new User { Id = Random.Shared.NextInt64(1, long.MaxValue), Name = "roundtrip-user", NormalizedName = "ROUNDTRIP-USER" };
await database.Users.AddAsync(user); await database.Users.AddAsync(user, TestContext.Current.CancellationToken);
var track = new LocalTrack var track = new LocalTrack
{ {
@@ -27,11 +27,11 @@ public sealed class DatabaseTests(InfrastructureTestFixture fixture)
Pictures = new TrackPictures { OriginalName = "cover.webp", ProcessingStatus = ProcessingStatus.Pending }, Pictures = new TrackPictures { OriginalName = "cover.webp", ProcessingStatus = ProcessingStatus.Pending },
Audio = new TrackAudio { OriginalName = "song.mp3", TranscodeStatus = ProcessingStatus.Pending } Audio = new TrackAudio { OriginalName = "song.mp3", TranscodeStatus = ProcessingStatus.Pending }
}; };
await database.LocalTracks.AddAsync(track); await database.LocalTracks.AddAsync(track, TestContext.Current.CancellationToken);
await database.SaveChangesAsync(CancellationToken.None); await database.SaveChangesAsync(TestContext.Current.CancellationToken);
await using var reloaded = fixture.CreateDatabase(); await using var reloaded = fixture.CreateDatabase();
var stored = await reloaded.Tracks.AsNoTracking().SingleAsync(t => t.Id == track.Id); var stored = await reloaded.Tracks.AsNoTracking().SingleAsync(t => t.Id == track.Id, TestContext.Current.CancellationToken);
Assert.IsType<LocalTrack>(stored); Assert.IsType<LocalTrack>(stored);
Assert.Equal("Round Trip Track", stored.Title); Assert.Equal("Round Trip Track", stored.Title);
@@ -46,13 +46,13 @@ public sealed class DatabaseTests(InfrastructureTestFixture fixture)
await using var database = fixture.CreateDatabase(); await using var database = fixture.CreateDatabase();
var videoId = $"dup-{Guid.NewGuid():N}"; var videoId = $"dup-{Guid.NewGuid():N}";
await database.ExternalTracks.AddAsync(BuildExternalTrack(videoId)); await database.ExternalTracks.AddAsync(BuildExternalTrack(videoId), TestContext.Current.CancellationToken);
await database.SaveChangesAsync(CancellationToken.None); await database.SaveChangesAsync(TestContext.Current.CancellationToken);
await using var second = fixture.CreateDatabase(); await using var second = fixture.CreateDatabase();
await second.ExternalTracks.AddAsync(BuildExternalTrack(videoId)); await second.ExternalTracks.AddAsync(BuildExternalTrack(videoId), TestContext.Current.CancellationToken);
await Assert.ThrowsAsync<DbUpdateException>(() => second.SaveChangesAsync(CancellationToken.None)); await Assert.ThrowsAsync<DbUpdateException>(() => second.SaveChangesAsync(TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -66,16 +66,16 @@ public sealed class DatabaseTests(InfrastructureTestFixture fixture)
var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 }; var link = new AlbumHasTrack { AlbumId = album.Id, TrackId = track.Id, TrackNumber = 1 };
await database.AddRangeAsync(user, album, track, link); await database.AddRangeAsync(user, album, track, link);
await database.SaveChangesAsync(CancellationToken.None); await database.SaveChangesAsync(TestContext.Current.CancellationToken);
await using var deleter = fixture.CreateDatabase(); await using var deleter = fixture.CreateDatabase();
var albumToDelete = await deleter.UserAlbums.SingleAsync(a => a.Id == album.Id); var albumToDelete = await deleter.UserAlbums.SingleAsync(a => a.Id == album.Id, TestContext.Current.CancellationToken);
deleter.UserAlbums.Remove(albumToDelete); deleter.UserAlbums.Remove(albumToDelete);
await deleter.SaveChangesAsync(CancellationToken.None); await deleter.SaveChangesAsync(TestContext.Current.CancellationToken);
await using var verifier = fixture.CreateDatabase(); await using var verifier = fixture.CreateDatabase();
Assert.False(await verifier.AlbumHasTracks.AnyAsync(l => l.AlbumId == album.Id)); Assert.False(await verifier.AlbumHasTracks.AnyAsync(l => l.AlbumId == album.Id, TestContext.Current.CancellationToken));
Assert.True(await verifier.ExternalTracks.AnyAsync(t => t.Id == track.Id)); Assert.True(await verifier.ExternalTracks.AnyAsync(t => t.Id == track.Id, TestContext.Current.CancellationToken));
} }
private static ExternalTrack BuildExternalTrack(string externalId) => new() private static ExternalTrack BuildExternalTrack(string externalId) => new()
@@ -24,9 +24,9 @@ public sealed class PictureServiceTests
{ {
using var source = CreateSourceImage(800, 600); using var source = CreateSourceImage(800, 600);
await using var resized = await CreateService().ResizePictureAsWebpAsync(source, 200, 200, CancellationToken.None); await using var resized = await CreateService().ResizePictureAsWebpAsync(source, 200, 200, TestContext.Current.CancellationToken);
using var result = await Image.LoadAsync(resized); using var result = await Image.LoadAsync(resized, TestContext.Current.CancellationToken);
Assert.InRange(result.Width, 1, 200); Assert.InRange(result.Width, 1, 200);
Assert.InRange(result.Height, 1, 200); Assert.InRange(result.Height, 1, 200);
Assert.True(result.Width == 200 || result.Height == 200, "expected the longer side to land exactly on the target box"); Assert.True(result.Width == 200 || result.Height == 200, "expected the longer side to land exactly on the target box");
@@ -37,9 +37,9 @@ public sealed class PictureServiceTests
{ {
using var source = CreateSourceImage(100, 100); using var source = CreateSourceImage(100, 100);
await using var resized = await CreateService().ResizePictureAsWebpAsync(source, 50, 50, CancellationToken.None); await using var resized = await CreateService().ResizePictureAsWebpAsync(source, 50, 50, TestContext.Current.CancellationToken);
var format = await Image.DetectFormatAsync(resized); var format = await Image.DetectFormatAsync(resized, TestContext.Current.CancellationToken);
Assert.Equal("WEBP", format?.Name, ignoreCase: true); Assert.Equal("WEBP", format?.Name, ignoreCase: true);
} }
@@ -48,9 +48,9 @@ public sealed class PictureServiceTests
{ {
using var source = CreateSourceImage(400, 200); using var source = CreateSourceImage(400, 200);
await using var resized = await CreateService().ResizePictureAsWebpAsync(source, 100, 100, CancellationToken.None); await using var resized = await CreateService().ResizePictureAsWebpAsync(source, 100, 100, TestContext.Current.CancellationToken);
using var result = await Image.LoadAsync(resized); using var result = await Image.LoadAsync(resized, TestContext.Current.CancellationToken);
Assert.Equal(100, result.Width); Assert.Equal(100, result.Width);
Assert.Equal(50, result.Height); Assert.Equal(50, result.Height);
} }
@@ -45,7 +45,7 @@ public sealed class PlayListPresetSeederTests(InfrastructureTestFixture fixture)
private static async Task RunToCompletionAsync(PlayListPresetSeeder seeder) private static async Task RunToCompletionAsync(PlayListPresetSeeder seeder)
{ {
await seeder.StartAsync(CancellationToken.None); await seeder.StartAsync(TestContext.Current.CancellationToken);
await (seeder.ExecuteTask ?? Task.CompletedTask); await (seeder.ExecuteTask ?? Task.CompletedTask);
} }
@@ -66,7 +66,7 @@ public sealed class PlayListPresetSeederTests(InfrastructureTestFixture fixture)
routes.PresetLargePicturePath routes.PresetLargePicturePath
}) })
{ {
var metadata = await storage.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); var metadata = await storage.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
Assert.True(metadata is not null, $"expected {key} to have been seeded"); Assert.True(metadata is not null, $"expected {key} to have been seeded");
} }
} }
@@ -82,7 +82,7 @@ public sealed class PlayListPresetSeederTests(InfrastructureTestFixture fixture)
await RunToCompletionAsync(secondSeeder); await RunToCompletionAsync(secondSeeder);
var metadata = await storage.HeadObjectAsync( var metadata = await storage.HeadObjectAsync(
InfrastructureTestFixture.S3Bucket, configuration.Routes.PresetSmallPicturePath, CancellationToken.None); InfrastructureTestFixture.S3Bucket, configuration.Routes.PresetSmallPicturePath, TestContext.Current.CancellationToken);
Assert.NotNull(metadata); Assert.NotNull(metadata);
} }
@@ -96,7 +96,7 @@ public sealed class PlayListPresetSeederTests(InfrastructureTestFixture fixture)
await RunToCompletionAsync(seeder); await RunToCompletionAsync(seeder);
var metadata = await storage.HeadObjectAsync( var metadata = await storage.HeadObjectAsync(
InfrastructureTestFixture.S3Bucket, configuration.Routes.PresetSmallPicturePath, CancellationToken.None); InfrastructureTestFixture.S3Bucket, configuration.Routes.PresetSmallPicturePath, TestContext.Current.CancellationToken);
Assert.Null(metadata); Assert.Null(metadata);
} }
} }
@@ -16,7 +16,7 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
private Database database = null!; private Database database = null!;
private StorageService service = null!; private StorageService service = null!;
public Task InitializeAsync() public ValueTask InitializeAsync()
{ {
database = fixture.CreateDatabase(); database = fixture.CreateDatabase();
var storageConfiguration = new InfrastructureStorageConfiguration var storageConfiguration = new InfrastructureStorageConfiguration
@@ -28,10 +28,10 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
UseHttp = true, UseHttp = true,
}; };
service = new StorageService(database, fixture.CreateS3Client(), NullLogger<StorageService>.Instance, storageConfiguration); service = new StorageService(database, fixture.CreateS3Client(), NullLogger<StorageService>.Instance, storageConfiguration);
return Task.CompletedTask; return ValueTask.CompletedTask;
} }
public Task DisposeAsync() => database.DisposeAsync().AsTask(); public ValueTask DisposeAsync() => database.DisposeAsync();
[Fact] [Fact]
public async Task UploadThenGetFile_RoundTripsTheSameBytes() public async Task UploadThenGetFile_RoundTripsTheSameBytes()
@@ -39,11 +39,11 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
var key = $"tests/{Guid.NewGuid():N}/round-trip.txt"; var key = $"tests/{Guid.NewGuid():N}/round-trip.txt";
var content = "hello seaweedfs"u8.ToArray(); var content = "hello seaweedfs"u8.ToArray();
await service.UploadFileAsync(new MemoryStream(content), "text/plain", InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); await service.UploadFileAsync(new MemoryStream(content), "text/plain", InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
await using var stream = await service.GetFileAsync(InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); await using var stream = await service.GetFileAsync(InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
using var reader = new StreamReader(stream, Encoding.UTF8); using var reader = new StreamReader(stream, Encoding.UTF8);
var readBack = await reader.ReadToEndAsync(); var readBack = await reader.ReadToEndAsync(TestContext.Current.CancellationToken);
Assert.Equal("hello seaweedfs", readBack); Assert.Equal("hello seaweedfs", readBack);
} }
@@ -52,9 +52,9 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
public async Task HeadObjectAsync_ExistingObject_ReturnsMetadata() public async Task HeadObjectAsync_ExistingObject_ReturnsMetadata()
{ {
var key = $"tests/{Guid.NewGuid():N}/head.txt"; var key = $"tests/{Guid.NewGuid():N}/head.txt";
await service.UploadFileAsync(new MemoryStream("x"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); await service.UploadFileAsync(new MemoryStream("x"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
var metadata = await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); var metadata = await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
Assert.NotNull(metadata); Assert.NotNull(metadata);
Assert.Equal(1, metadata.ContentLength); Assert.Equal(1, metadata.ContentLength);
@@ -63,7 +63,7 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
[Fact] [Fact]
public async Task HeadObjectAsync_MissingObject_ReturnsNull() public async Task HeadObjectAsync_MissingObject_ReturnsNull()
{ {
var metadata = await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, $"tests/{Guid.NewGuid():N}/missing.txt", CancellationToken.None); var metadata = await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, $"tests/{Guid.NewGuid():N}/missing.txt", TestContext.Current.CancellationToken);
Assert.Null(metadata); Assert.Null(metadata);
} }
@@ -72,11 +72,11 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
public async Task RemoveFileAsync_ExistingObject_DeletesIt() public async Task RemoveFileAsync_ExistingObject_DeletesIt()
{ {
var key = $"tests/{Guid.NewGuid():N}/to-remove.txt"; var key = $"tests/{Guid.NewGuid():N}/to-remove.txt";
await service.UploadFileAsync(new MemoryStream("x"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); await service.UploadFileAsync(new MemoryStream("x"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
await service.RemoveFileAsync(InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); await service.RemoveFileAsync(InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
Assert.Null(await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, CancellationToken.None)); Assert.Null(await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
@@ -84,22 +84,22 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
{ {
var sourceKey = $"tests/{Guid.NewGuid():N}/source.txt"; var sourceKey = $"tests/{Guid.NewGuid():N}/source.txt";
var destinationKey = $"tests/{Guid.NewGuid():N}/destination.txt"; var destinationKey = $"tests/{Guid.NewGuid():N}/destination.txt";
await service.UploadFileAsync(new MemoryStream("copy-me"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, sourceKey, CancellationToken.None); await service.UploadFileAsync(new MemoryStream("copy-me"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, sourceKey, TestContext.Current.CancellationToken);
await service.CopyFileAsync(InfrastructureTestFixture.S3Bucket, sourceKey, InfrastructureTestFixture.S3Bucket, destinationKey, CancellationToken.None); await service.CopyFileAsync(InfrastructureTestFixture.S3Bucket, sourceKey, InfrastructureTestFixture.S3Bucket, destinationKey, TestContext.Current.CancellationToken);
Assert.NotNull(await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, destinationKey, CancellationToken.None)); Assert.NotNull(await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, destinationKey, TestContext.Current.CancellationToken));
} }
[Fact] [Fact]
public async Task ListObjectsAsync_ReturnsEveryObjectUnderThePrefix() public async Task ListObjectsAsync_ReturnsEveryObjectUnderThePrefix()
{ {
var prefix = $"tests/{Guid.NewGuid():N}"; var prefix = $"tests/{Guid.NewGuid():N}";
await service.UploadFileAsync(new MemoryStream("a"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/a.txt", CancellationToken.None); await service.UploadFileAsync(new MemoryStream("a"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/a.txt", TestContext.Current.CancellationToken);
await service.UploadFileAsync(new MemoryStream("b"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/b.txt", CancellationToken.None); await service.UploadFileAsync(new MemoryStream("b"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/b.txt", TestContext.Current.CancellationToken);
var keys = new List<string>(); var keys = new List<string>();
await foreach (var item in service.ListObjectsAsync(InfrastructureTestFixture.S3Bucket, prefix, CancellationToken.None)) await foreach (var item in service.ListObjectsAsync(InfrastructureTestFixture.S3Bucket, prefix, TestContext.Current.CancellationToken))
keys.Add(item.Key); keys.Add(item.Key);
Assert.Equal([$"{prefix}/a.txt", $"{prefix}/b.txt"], keys.Order()); Assert.Equal([$"{prefix}/a.txt", $"{prefix}/b.txt"], keys.Order());
@@ -109,13 +109,13 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
public async Task RemoveFolderAsync_DeletesEveryObjectUnderThePrefix() public async Task RemoveFolderAsync_DeletesEveryObjectUnderThePrefix()
{ {
var prefix = $"tests/{Guid.NewGuid():N}"; var prefix = $"tests/{Guid.NewGuid():N}";
await service.UploadFileAsync(new MemoryStream("a"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/a.txt", CancellationToken.None); await service.UploadFileAsync(new MemoryStream("a"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/a.txt", TestContext.Current.CancellationToken);
await service.UploadFileAsync(new MemoryStream("b"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/nested/b.txt", CancellationToken.None); await service.UploadFileAsync(new MemoryStream("b"u8.ToArray()), "text/plain", InfrastructureTestFixture.S3Bucket, $"{prefix}/nested/b.txt", TestContext.Current.CancellationToken);
await service.RemoveFolderAsync(InfrastructureTestFixture.S3Bucket, prefix, CancellationToken.None); await service.RemoveFolderAsync(InfrastructureTestFixture.S3Bucket, prefix, TestContext.Current.CancellationToken);
var remaining = new List<string>(); var remaining = new List<string>();
await foreach (var item in service.ListObjectsAsync(InfrastructureTestFixture.S3Bucket, prefix, CancellationToken.None)) await foreach (var item in service.ListObjectsAsync(InfrastructureTestFixture.S3Bucket, prefix, TestContext.Current.CancellationToken))
remaining.Add(item.Key); remaining.Add(item.Key);
Assert.Empty(remaining); Assert.Empty(remaining);
} }
@@ -126,7 +126,7 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
var key = $"tests/{Guid.NewGuid():N}/presigned.txt"; var key = $"tests/{Guid.NewGuid():N}/presigned.txt";
var uploadUrl = await service.GetUploadUrlAsync( var uploadUrl = await service.GetUploadUrlAsync(
InfrastructureTestFixture.S3Bucket, key, "text/plain", TimeSpan.FromMinutes(5), CancellationToken.None); InfrastructureTestFixture.S3Bucket, key, "text/plain", TimeSpan.FromMinutes(5), TestContext.Current.CancellationToken);
using var httpClient = new HttpClient(); using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Put, uploadUrl) using var request = new HttpRequestMessage(HttpMethod.Put, uploadUrl)
@@ -141,10 +141,10 @@ public sealed class StorageServiceTests(InfrastructureTestFixture fixture) : IAs
// this header (see StorageService.GetUploadUrlAsync) — the actual PUT has to send it too, // this header (see StorageService.GetUploadUrlAsync) — the actual PUT has to send it too,
// or the signature no longer matches and the server answers 403. // or the signature no longer matches and the server answers 403.
request.Headers.TryAddWithoutValidation("If-None-Match", "*"); request.Headers.TryAddWithoutValidation("If-None-Match", "*");
var response = await httpClient.SendAsync(request); var response = await httpClient.SendAsync(request, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var metadata = await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, CancellationToken.None); var metadata = await service.HeadObjectAsync(InfrastructureTestFixture.S3Bucket, key, TestContext.Current.CancellationToken);
Assert.NotNull(metadata); Assert.NotNull(metadata);
Assert.Equal("presigned content".Length, metadata.ContentLength); Assert.Equal("presigned content".Length, metadata.ContentLength);
} }
@@ -44,7 +44,7 @@ public sealed class InfrastructureTestFixture : IAsyncLifetime
public string S3ServiceUrl => $"http://{seaweedFs.Hostname}:{seaweedFs.GetMappedPublicPort(8333)}"; public string S3ServiceUrl => $"http://{seaweedFs.Hostname}:{seaweedFs.GetMappedPublicPort(8333)}";
public async Task InitializeAsync() public async ValueTask InitializeAsync()
{ {
await Task.WhenAll(postgres.StartAsync(), seaweedFs.StartAsync()); await Task.WhenAll(postgres.StartAsync(), seaweedFs.StartAsync());
@@ -56,7 +56,7 @@ public sealed class InfrastructureTestFixture : IAsyncLifetime
await s3.PutBucketAsync(S3Bucket); await s3.PutBucketAsync(S3Bucket);
} }
public async Task DisposeAsync() public async ValueTask DisposeAsync()
{ {
await postgres.DisposeAsync(); await postgres.DisposeAsync();
await seaweedFs.DisposeAsync(); await seaweedFs.DisposeAsync();
@@ -3,11 +3,12 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" /> <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio"> <PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
+5
View File
@@ -0,0 +1,5 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
}