From acec0977735a294e526dff1d1009f7f9a3302c3d Mon Sep 17 00:00:00 2001 From: XShine555 Date: Fri, 29 May 2026 15:44:20 +0200 Subject: [PATCH] Remove tests --- MusifyBackend.slnx | 4 - ...ify.Infrastructure.IntegrationTests.csproj | 28 ++++ .../SeaweedFsFixture.cs | 67 ++++++++ .../StorageServiceTests.cs | 145 ++++++++++++++++++ 4 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 Tests/Musify.Infrastructure.IntegrationTests/Musify.Infrastructure.IntegrationTests.csproj create mode 100644 Tests/Musify.Infrastructure.IntegrationTests/SeaweedFsFixture.cs create mode 100644 Tests/Musify.Infrastructure.IntegrationTests/StorageServiceTests.cs diff --git a/MusifyBackend.slnx b/MusifyBackend.slnx index 386f50d..c92b66b 100644 --- a/MusifyBackend.slnx +++ b/MusifyBackend.slnx @@ -1,8 +1,4 @@ - - - - diff --git a/Tests/Musify.Infrastructure.IntegrationTests/Musify.Infrastructure.IntegrationTests.csproj b/Tests/Musify.Infrastructure.IntegrationTests/Musify.Infrastructure.IntegrationTests.csproj new file mode 100644 index 0000000..beb1cda --- /dev/null +++ b/Tests/Musify.Infrastructure.IntegrationTests/Musify.Infrastructure.IntegrationTests.csproj @@ -0,0 +1,28 @@ + + + + net10.0 + enable + enable + false + Musify.Infrastructure.IntegrationTests + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Tests/Musify.Infrastructure.IntegrationTests/SeaweedFsFixture.cs b/Tests/Musify.Infrastructure.IntegrationTests/SeaweedFsFixture.cs new file mode 100644 index 0000000..fcca425 --- /dev/null +++ b/Tests/Musify.Infrastructure.IntegrationTests/SeaweedFsFixture.cs @@ -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 +{ + public const string Name = "SeaweedFS"; +} diff --git a/Tests/Musify.Infrastructure.IntegrationTests/StorageServiceTests.cs b/Tests/Musify.Infrastructure.IntegrationTests/StorageServiceTests.cs new file mode 100644 index 0000000..e3bbbbb --- /dev/null +++ b/Tests/Musify.Infrastructure.IntegrationTests/StorageServiceTests.cs @@ -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.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(); + 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(); + 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); + } +}