Files
h-school/tests/HSchool.Server.Tests/SwarmUiClientTests.cs
T
Leonid Pershin 5a400be792 Add SwarmUI settings management and portrait generation enhancements
- Introduced new API endpoints for managing SwarmUI settings, including fetching and saving presets and age rules.
- Updated the portrait generation logic to utilize the new settings structure, allowing for dynamic preset selection based on age.
- Enhanced UI components to support SwarmUI settings, including localization for new strings and improved styling for settings sections.
- Added tests to verify the functionality of new settings endpoints and portrait generation behavior.

This commit lays the groundwork for more flexible and user-friendly portrait generation options.
2026-08-20 06:06:45 +03:00

189 lines
7.6 KiB
C#

using System.Net;
using System.Text;
using System.Text.Json;
using HSchool.Server.Game;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace HSchool.Server.Tests;
public class SwarmUiClientTests
{
[Fact]
public async Task GenerateAsync_UsesSessionAndReturnsImageBytes()
{
var handler = new FakeHandler();
var http = new HttpClient(handler) { BaseAddress = new Uri("http://swarm.test/") };
var client = new SwarmUiClient(
http,
Options.Create(new SwarmUiOptions { BaseUrl = "http://swarm.test", TimeoutSeconds = 30 }),
NullLogger<SwarmUiClient>.Instance);
var preset = SwarmUiPresetDefinition.CreateDefault();
var profile = preset.ToProfile(PortraitKind.Avatar);
var bytes = await client.GenerateAsync("a student", "bad", profile, CancellationToken.None);
Assert.Equal([0x89, 0x50, 0x4E, 0x47], bytes.Take(4));
Assert.Contains("/API/GetNewSession", handler.Requests[0]);
Assert.Contains("/API/GenerateText2Image", handler.Requests[1]);
}
[Fact]
public async Task ProbeAsync_ReturnsTrueWhenSessionOpens()
{
var handler = new FakeHandler();
var http = new HttpClient(handler) { BaseAddress = new Uri("http://swarm.test/") };
var client = new SwarmUiClient(
http,
Options.Create(new SwarmUiOptions { BaseUrl = "http://swarm.test", TimeoutSeconds = 30 }),
NullLogger<SwarmUiClient>.Instance);
var ok = await client.ProbeAsync(TimeSpan.FromSeconds(5), CancellationToken.None);
Assert.True(ok);
Assert.Single(handler.Requests);
Assert.Contains("/API/GetNewSession", handler.Requests[0]);
}
[Fact]
public async Task ProbeAsync_ReturnsFalseWhenUnreachable()
{
var handler = new FailingHandler();
var http = new HttpClient(handler) { BaseAddress = new Uri("http://swarm.test/") };
var client = new SwarmUiClient(
http,
Options.Create(new SwarmUiOptions { BaseUrl = "http://swarm.test", TimeoutSeconds = 30 }),
NullLogger<SwarmUiClient>.Instance);
var ok = await client.ProbeAsync(TimeSpan.FromSeconds(5), CancellationToken.None);
Assert.False(ok);
}
private sealed class FailingHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
Task.FromResult(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable));
}
[Fact]
public async Task GenerateAsync_SendsClipStopAtLayerForClipSkip()
{
var handler = new CapturingHandler();
var http = new HttpClient(handler) { BaseAddress = new Uri("http://swarm.test/") };
var client = new SwarmUiClient(
http,
Options.Create(new SwarmUiOptions { BaseUrl = "http://swarm.test", TimeoutSeconds = 30 }),
NullLogger<SwarmUiClient>.Instance);
var preset = SwarmUiPresetDefinition.CreateDefault();
preset.Steps = 4;
preset.CfgScale = 2;
preset.ClipSkip = 2;
preset.Sampler = "dpmpp_sde";
preset.Scheduler = "karras";
var profile = preset.ToProfile(PortraitKind.Avatar);
await client.GenerateAsync("a student", "bad", profile, CancellationToken.None);
using var document = JsonDocument.Parse(handler.GenerateBody!);
Assert.Equal(-2, document.RootElement.GetProperty("clipstopatlayer").GetInt32());
Assert.False(document.RootElement.TryGetProperty("clipskip", out _));
}
[Fact]
public void Registration_StripsStandardResilienceHandler()
{
var services = new ServiceCollection();
services.AddLogging();
services.ConfigureHttpClientDefaults(http => http.AddStandardResilienceHandler());
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["SwarmUi:BaseUrl"] = "http://127.0.0.1:7801",
["SwarmUi:TimeoutSeconds"] = "180",
})
.Build();
services.AddSwarmUi(configuration);
using var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IHttpMessageHandlerFactory>();
using var handler = factory.CreateHandler(nameof(SwarmUiClient));
Assert.DoesNotContain(
HandlerTypeNames(handler),
name => name.Contains("Resilience", StringComparison.Ordinal));
}
private static IEnumerable<string> HandlerTypeNames(HttpMessageHandler handler)
{
for (HttpMessageHandler? current = handler; current is not null; current = (current as DelegatingHandler)?.InnerHandler)
{
yield return current.GetType().FullName ?? current.GetType().Name;
}
}
private sealed class CapturingHandler : HttpMessageHandler
{
public string? GenerateBody { get; private set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri!.AbsolutePath.Contains("GetNewSession", StringComparison.Ordinal))
{
var session = JsonSerializer.Serialize(new { session_id = "sess-1" });
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(session, Encoding.UTF8, "application/json"),
};
}
if (request.RequestUri.AbsolutePath.Contains("GenerateText2Image", StringComparison.Ordinal))
{
GenerateBody = request.Content is null ? null : await request.Content.ReadAsStringAsync(cancellationToken);
var payload = JsonSerializer.Serialize(new { images = new[] { "data:image/png;base64,iVBORw0KGgo=" } });
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(payload, Encoding.UTF8, "application/json"),
};
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
private sealed class FakeHandler : HttpMessageHandler
{
public List<string> Requests { get; } = [];
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Requests.Add(request.RequestUri!.AbsolutePath);
if (request.RequestUri.AbsolutePath.Contains("GetNewSession", StringComparison.Ordinal))
{
var session = JsonSerializer.Serialize(new { session_id = "sess-1" });
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(session, Encoding.UTF8, "application/json"),
});
}
if (request.RequestUri.AbsolutePath.Contains("GenerateText2Image", StringComparison.Ordinal))
{
var payload = JsonSerializer.Serialize(new { images = new[] { "data:image/png;base64,iVBORw0KGgo=" } });
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(payload, Encoding.UTF8, "application/json"),
});
}
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
}