Files
h-school/tests/HSchool.AppHost.Tests/PortraitApiTests.cs
T
Leonid Pershin 230563d5c5 Merge main into review/slice-7bc-climate-wear; journal 32-37.
Integrate stage A review from main; resolve reviewed.md; fix dressRulesPanel unused import, managementPanel test, PortraitApiTests URL.
2026-08-20 06:46:36 +03:00

165 lines
7.0 KiB
C#

using System.Net;
using System.Net.Http.Json;
namespace HSchool.AppHost.Tests;
[Collection(AppHostCollection.Name)]
public class PortraitApiTests(AppHostFixture fixture)
{
private static readonly DateTime Start = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
[Fact]
public async Task GetPortrait_WithoutFile_Returns404()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Портреты", Start);
var personId = await FirstPersonIdAsync(client, school.Id);
using var response = await client.GetAsync(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(personId)}/portrait?kind=avatar",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
Assert.Equal("portrait-missing", await SchoolApiTests.ProblemCodeAsync(response));
}
[Fact]
public async Task GetPortrait_WithFile_ReturnsPngAndCardFlags()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Портрет на диске", Start);
var personId = await FirstPersonIdAsync(client, school.Id);
var directory = await SavesDirectoryAsync(client);
var portraits = Path.Combine(directory, $"{school.Id}.portraits");
Directory.CreateDirectory(portraits);
var png = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
await File.WriteAllBytesAsync(Path.Combine(portraits, $"{personId}.avatar.png"), png, TestContext.Current.CancellationToken);
using var image = await client.GetAsync(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(personId)}/portrait?kind=avatar",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, image.StatusCode);
Assert.Equal("image/png", image.Content.Headers.ContentType?.MediaType);
var card = await client.GetFromJsonAsync<PersonCardResponse>(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(personId)}?lang=ru",
TestContext.Current.CancellationToken);
Assert.NotNull(card);
Assert.True(card!.HasAvatar);
Assert.False(card.HasFullBody);
}
[Fact]
public async Task PostPortrait_WhenSwarmNotConfigured_Returns503()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Swarm выкл", Start);
var personId = await FirstPersonIdAsync(client, school.Id);
using var response = await client.PostAsync(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(personId)}/portrait?kind=avatar",
null,
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
Assert.Equal("swarmui-not-configured", await SchoolApiTests.ProblemCodeAsync(response));
}
[Fact]
public async Task GetPortraitPrompt_ReturnsBuiltPositiveAndNegative()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Промпт", Start);
var personId = await FirstPersonIdAsync(client, school.Id);
using var response = await client.GetAsync(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(personId)}/portrait/prompt?kind=avatar",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<PortraitPromptPayload>(TestContext.Current.CancellationToken);
Assert.NotNull(payload);
Assert.Equal("avatar", payload!.Kind);
Assert.Contains("age", payload.Positive, StringComparison.OrdinalIgnoreCase);
Assert.False(string.IsNullOrWhiteSpace(payload.Negative));
Assert.False(string.IsNullOrWhiteSpace(payload.PresetId));
}
[Fact]
public async Task GetSwarmUiSettings_ReturnsPresets()
{
using var client = fixture.App.CreateHttpClient("server");
var settings = await client.GetFromJsonAsync<SwarmUiSettingsPayload>(
"/api/settings/swarmui",
TestContext.Current.CancellationToken);
Assert.NotNull(settings);
Assert.NotEmpty(settings!.Presets);
Assert.False(string.IsNullOrWhiteSpace(settings.ActivePresetId));
}
[Fact]
public async Task DeleteSchool_RemovesPortraitDirectory()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Удалить портреты", Start);
var personId = await FirstPersonIdAsync(client, school.Id);
var directory = await SavesDirectoryAsync(client);
var portraits = Path.Combine(directory, $"{school.Id}.portraits");
Directory.CreateDirectory(portraits);
await File.WriteAllBytesAsync(Path.Combine(portraits, $"{personId}.avatar.png"), [1, 2, 3], TestContext.Current.CancellationToken);
using var deleted = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, deleted.StatusCode);
Assert.False(Directory.Exists(portraits));
}
private static async Task<string> FirstPersonIdAsync(HttpClient client, int schoolId)
{
var page = await client.GetFromJsonAsync<PeopleListResponse>(
$"/api/schools/{schoolId}/people?pageSize=1",
TestContext.Current.CancellationToken);
Assert.NotNull(page);
Assert.NotEmpty(page!.People);
return page.People[0].Id;
}
private static async Task<string> SavesDirectoryAsync(HttpClient client)
{
var payload = await client.GetFromJsonAsync<SavesDirectoryResponse>(
"/api/dev/saves-directory",
TestContext.Current.CancellationToken);
return payload!.Path;
}
private sealed record PeopleListResponse(int Total, IReadOnlyList<PersonListItem> People);
private sealed record PersonListItem(string Id);
private sealed record PersonCardResponse(
string Id,
bool HasAvatar,
bool HasCustom,
bool HasFullBody,
string? CustomPortraitPrompt);
private sealed record PortraitPromptPayload(
string Kind,
string Positive,
string Negative,
string? PromptExtra,
string PresetId,
string PresetLabel);
private sealed record SwarmUiSettingsPayload(string ActivePresetId, IReadOnlyList<SwarmUiPresetPayload> Presets);
private sealed record SwarmUiPresetPayload(string Id, string Label);
private sealed record SavesDirectoryResponse(string Path);
}