Remove tests

This commit is contained in:
2026-05-29 15:44:20 +02:00
parent 51dd877a44
commit acec097773
4 changed files with 240 additions and 4 deletions
-4
View File
@@ -1,8 +1,4 @@
<Solution>
<Folder Name="/Tests/">
<Project Path="Tests/Musify.Application.Tests/Musify.Application.Tests.csproj" />
<Project Path="Tests/Musify.Infrastructure.Tests/Musify.Infrastructure.Tests.csproj" />
</Folder>
<Project Path="Application/Application.csproj" />
<Project Path="Infrastructure/Infrastructure.csproj" />
<Project Path="Domain/Domain.csproj" />
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>Musify.Infrastructure.IntegrationTests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Testcontainers" Version="4.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\Musify.Application.Tests\Musify.Application.Tests.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,67 @@
using Amazon.S3;
using Amazon.S3.Model;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using Musify.Infrastructure.Configuration;
using Xunit;
namespace Musify.Infrastructure.IntegrationTests;
public sealed class SeaweedFsFixture : IAsyncLifetime
{
private const int S3Port = 8333;
private const string AccessKey = "admin_access_key";
private const string SecretKey = "admin_secret_key";
public const string Bucket = "webapi-storage";
private readonly IContainer _container = new ContainerBuilder()
.WithImage("chrislusf/seaweedfs:latest")
.WithCommand("server", "-s3", "-dir=/data", "-ip.bind=0.0.0.0")
.WithPortBinding(S3Port, true)
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Start Seaweed S3 API Server"))
.Build();
public string ServiceUrl { get; private set; } = string.Empty;
public IAmazonS3 CreateClient()
{
var config = new AmazonS3Config
{
ServiceURL = ServiceUrl,
ForcePathStyle = true,
UseHttp = true,
AuthenticationRegion = "us-east-1",
};
return new AmazonS3Client(AccessKey, SecretKey, config);
}
public InfrastructureStorageConfiguration StorageConfig() => new()
{
Address = ServiceUrl,
AccessKey = AccessKey,
SecretAccessKey = SecretKey,
ForcePathStyle = true,
UseHttp = true,
};
public async Task InitializeAsync()
{
await _container.StartAsync();
ServiceUrl = $"http://{_container.Hostname}:{_container.GetMappedPublicPort(S3Port)}";
using var client = CreateClient();
await client.PutBucketAsync(new PutBucketRequest { BucketName = Bucket });
}
public async Task DisposeAsync()
{
await _container.DisposeAsync();
}
}
[CollectionDefinition(SeaweedFsCollection.Name)]
public sealed class SeaweedFsCollection : ICollectionFixture<SeaweedFsFixture>
{
public const string Name = "SeaweedFS";
}
@@ -0,0 +1,145 @@
using System.Text;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Musify.Application.Contracts;
using Musify.Application.Tests.Infrastructure;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.Services;
using Xunit;
namespace Musify.Infrastructure.IntegrationTests;
[Collection(SeaweedFsCollection.Name)]
public sealed class StorageServiceTests(SeaweedFsFixture seaweedFs)
{
private const string Bucket = SeaweedFsFixture.Bucket;
private (StorageService service, SqliteTestContext db) CreateService()
{
var db = new SqliteTestContext();
var service = new StorageService(
db.Database,
seaweedFs.CreateClient(),
NullLogger<StorageService>.Instance,
seaweedFs.StorageConfig());
return (service, db);
}
private static MemoryStream TextStream(string content) => new(Encoding.UTF8.GetBytes(content));
[Fact]
public async Task Upload_then_head_and_get_roundtrips_content()
{
var (service, db) = CreateService();
using var _ = db;
var key = $"tests/{Guid.NewGuid()}.txt";
await service.UploadFileAsync(TextStream("hello seaweed"), "text/plain", Bucket, key, CancellationToken.None);
var metadata = await service.HeadObjectAsync(Bucket, key, CancellationToken.None);
metadata.Should().NotBeNull();
metadata!.ContentLength.Should().Be("hello seaweed".Length);
using var stream = await service.GetFileAsync(Bucket, key, CancellationToken.None);
using var reader = new StreamReader(stream);
(await reader.ReadToEndAsync()).Should().Be("hello seaweed");
}
[Fact]
public async Task Upload_persists_a_successful_upload_record()
{
var (service, db) = CreateService();
using var _ = db;
var key = $"tests/{Guid.NewGuid()}.txt";
await service.UploadFileAsync(TextStream("data"), "text/plain", Bucket, key, CancellationToken.None);
var upload = db.NewContext().Uploads.Single(u => u.Key == key);
upload.State.Should().Be(UploadState.Successful);
upload.Bucket.Should().Be(Bucket);
}
[Fact]
public async Task HeadObject_returns_null_for_missing_object()
{
var (service, db) = CreateService();
using var _ = db;
var metadata = await service.HeadObjectAsync(Bucket, $"tests/missing-{Guid.NewGuid()}", CancellationToken.None);
metadata.Should().BeNull();
}
[Fact]
public async Task CopyFile_duplicates_the_object()
{
var (service, db) = CreateService();
using var _ = db;
var source = $"tests/{Guid.NewGuid()}-src.txt";
var destination = $"tests/{Guid.NewGuid()}-dst.txt";
await service.UploadFileAsync(TextStream("copy me"), "text/plain", Bucket, source, CancellationToken.None);
await service.CopyFileAsync(Bucket, source, Bucket, destination, CancellationToken.None);
(await service.HeadObjectAsync(Bucket, destination, CancellationToken.None)).Should().NotBeNull();
(await service.HeadObjectAsync(Bucket, source, CancellationToken.None)).Should().NotBeNull();
}
[Fact]
public async Task RemoveFile_deletes_the_object()
{
var (service, db) = CreateService();
using var _ = db;
var key = $"tests/{Guid.NewGuid()}.txt";
await service.UploadFileAsync(TextStream("delete me"), "text/plain", Bucket, key, CancellationToken.None);
await service.RemoveFileAsync(Bucket, key, CancellationToken.None);
(await service.HeadObjectAsync(Bucket, key, CancellationToken.None)).Should().BeNull();
}
[Fact]
public async Task ListObjects_returns_objects_under_prefix()
{
var (service, db) = CreateService();
using var _ = db;
var prefix = $"listing/{Guid.NewGuid()}";
await service.UploadFileAsync(TextStream("a"), "text/plain", Bucket, $"{prefix}/a.txt", CancellationToken.None);
await service.UploadFileAsync(TextStream("b"), "text/plain", Bucket, $"{prefix}/b.txt", CancellationToken.None);
var keys = new List<string>();
await foreach (var obj in service.ListObjectsAsync(Bucket, prefix, CancellationToken.None))
keys.Add(obj.Key);
keys.Should().Contain($"{prefix}/a.txt").And.Contain($"{prefix}/b.txt");
}
[Fact]
public async Task RemoveFolder_deletes_every_object_under_prefix()
{
var (service, db) = CreateService();
using var _ = db;
var prefix = $"folder/{Guid.NewGuid()}";
await service.UploadFileAsync(TextStream("a"), "text/plain", Bucket, $"{prefix}/a.txt", CancellationToken.None);
await service.UploadFileAsync(TextStream("b"), "text/plain", Bucket, $"{prefix}/nested/b.txt", CancellationToken.None);
await service.RemoveFolderAsync(Bucket, prefix, CancellationToken.None);
var remaining = new List<StorageObject>();
await foreach (var obj in service.ListObjectsAsync(Bucket, prefix, CancellationToken.None))
remaining.Add(obj);
remaining.Should().BeEmpty();
}
[Fact]
public async Task GetUploadUrl_returns_a_presigned_url()
{
var (service, db) = CreateService();
using var _ = db;
var url = await service.GetUploadUrlAsync(
Bucket, $"tests/{Guid.NewGuid()}.txt", "text/plain", TimeSpan.FromMinutes(5), CancellationToken.None);
url.Should().StartWith(seaweedFs.ServiceUrl);
}
}