Added initial test cases. Fixed circle search provider logic.

This commit is contained in:
2025-08-28 00:50:26 -04:00
parent 22b8513e34
commit f221deea36
9 changed files with 313 additions and 59 deletions

View File

@@ -0,0 +1,56 @@
using JSMR.Application.Circles.Contracts;
using JSMR.Application.Circles.Queries.Search;
using JSMR.Application.Common.Search;
using JSMR.Infrastructure.Data;
using JSMR.Infrastructure.Data.Repositories.Circles;
namespace JSMR.Tests.Integration;
public class CircleSearchProviderTests(MariaDbFixture fixture) : IClassFixture<MariaDbFixture>
{
[Fact]
public async Task Search_ByName_Filters_And_Sorts()
{
await fixture.ResetAsync();
await using AppDbContext context = fixture.CreateDbContext();
await Seed.SeedCirclesWithWorksAsync(context);
CircleSearchProvider provider = new(context);
var options = new SearchOptions<CircleSearchCriteria, CircleSortField>
{
PageNumber = 1,
PageSize = 50,
SortOptions = [new SortOption<CircleSortField>(CircleSortField.Name, SortDirection.Ascending)],
Criteria = new CircleSearchCriteria { Name = "Circle" }
};
var result = await provider.SearchAsync(options, CancellationToken.None);
Assert.True(result.TotalItems >= 2);
Assert.Equal("Circle A", result.Items[0].Name);
Assert.Equal("Circle B", result.Items[1].Name);
}
//[Fact]
//public async Task Search_Status_Favorited_Only()
//{
// await fixture.ResetAsync();
// await using var db = fixture.CreateDbContext();
// await Seed.SeedCirclesWithWorksAsync(db);
// var provider = new CircleSearchProvider(db);
// var options = new SearchOptions<CircleSearchCriteria, CircleSortField>
// {
// PageNumber = 1,
// PageSize = 50,
// SortOptions = Array.Empty<SortOption<CircleSortField>>(),
// Criteria = new CircleSearchCriteria { Status = Application.Circles.Queries.Search.CircleStatus.Favorited }
// };
// var result = await provider.SearchAsync(options, CancellationToken.None);
// Assert.All(result.Items, i => Assert.True(i.Favorite));
//}
}

View File

@@ -0,0 +1,89 @@
using DotNet.Testcontainers.Builders;
using JSMR.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Testcontainers.MariaDb;
namespace JSMR.Tests.Integration;
public sealed class MariaDbFixture : IAsyncLifetime
{
public MariaDbContainer? MariaDbContainer { get; private set; }
public string ConnectionString { get; private set; } = default!;
public async Task InitializeAsync()
{
MariaDbContainer = new MariaDbBuilder()
.WithImage("mariadb:10.11.6")
.WithEnvironment("MARIADB_ROOT_PASSWORD", "rootpwd")
.WithEnvironment("MARIADB_DATABASE", "appdb")
.WithPortBinding(3307, 3306) // host:container; avoid conflicts
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(3306))
.Build();
await MariaDbContainer.StartAsync();
ConnectionString =
"Server=localhost;Port=3307;Database=appdb;User=root;Password=rootpwd;SslMode=None;AllowPublicKeyRetrieval=True;";
//ConnectionString = MariaDbContainer.GetConnectionString();
// Run migrations here to create schema
await using AppDbContext context = CreateDbContext();
await context.Database.EnsureCreatedAsync();
//await context.Database.MigrateAsync();
}
public async Task DisposeAsync()
{
if (MariaDbContainer is not null)
{
await MariaDbContainer.StopAsync();
await MariaDbContainer.DisposeAsync();
}
}
public AppDbContext CreateDbContext()
{
MySqlServerVersion serverVersion = new(new Version(10, 11, 6));
DbContextOptions<AppDbContext> options = new DbContextOptionsBuilder<AppDbContext>()
.UseMySql(ConnectionString, serverVersion,
o => o.EnableRetryOnFailure())
.EnableSensitiveDataLogging()
.Options;
return new AppDbContext(options);
}
/// <summary>Clean tables between tests; use Respawn or manual TRUNCATE in correct FK order.</summary>
public async Task ResetAsync()
{
await using AppDbContext context = CreateDbContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
//await using var connection = context.Database.GetDbConnection();
//await connection.OpenAsync();
//using var cmd = connection.CreateCommand();
//cmd.CommandText = "SELECT DATABASE()";
//var dbName = (string?)await cmd.ExecuteScalarAsync();
//Console.WriteLine($"[TEST] Connected to DB: {dbName}");
// Fast reset (example): disable FK checks, truncate, re-enable
//await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS = 0;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_work_creators;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_work_tags;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE english_tags;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE english_voice_works;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_work_searches;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_works;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE creators;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE tags;");
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE circles;");
//await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS = 1;");
}
}

View File

@@ -0,0 +1,45 @@
using JSMR.Domain.Entities;
using JSMR.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace JSMR.Tests.Integration;
public static class Seed
{
public static async Task SeedBasicTagsAsync(AppDbContext context)
{
if (await context.Tags.AnyAsync())
return;
context.Tags.AddRange(
new() { TagId = 1, Name = "OL", Favorite = false, Blacklisted = false },
new() { TagId = 2, Name = "ほのぼの", Favorite = true, Blacklisted = false },
new() { TagId = 3, Name = "ツンデレ", Favorite = false, Blacklisted = true }
);
context.EnglishTags.AddRange(
new() { EnglishTagId = 1, TagId = 1, Name = "Office Lady" },
new() { EnglishTagId = 2, TagId = 2, Name = "Heartwarming" },
new() { EnglishTagId = 3, TagId = 3, Name = "Tsundere" }
);
await context.SaveChangesAsync();
}
public static async Task SeedCirclesWithWorksAsync(AppDbContext context)
{
var c1 = new Circle { Name = "Circle A", MakerId = "mk001", Favorite = false, Blacklisted = false, Spam = false };
var c2 = new Circle { Name = "Circle B", MakerId = "mk002", Favorite = true, Blacklisted = false, Spam = false };
context.Circles.AddRange(c1, c2);
await context.SaveChangesAsync();
context.VoiceWorks.AddRange(
new VoiceWork { CircleId = c1.CircleId, ProductId = "R-1", ProductName = "Work 1", Downloads = 100, SalesDate = new DateTime(2024, 1, 1), HasImage = true },
new VoiceWork { CircleId = c1.CircleId, ProductId = "R-10", ProductName = "Work 10", Downloads = 50, SalesDate = new DateTime(2024, 2, 1), HasImage = false },
new VoiceWork { CircleId = c2.CircleId, ProductId = "R-2", ProductName = "Work 2", Downloads = 200, SalesDate = new DateTime(2024, 3, 1), HasImage = true }
);
await context.SaveChangesAsync();
}
}