Remove tests
This commit is contained in:
@@ -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" />
|
||||
|
||||
+28
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user