Condition drops from catalog hours only while the person is at school; skip through the night issues the same fresh set as a lived night so the school does not walk in in rags. Co-authored-by: Cursor <cursoragent@cursor.com>
199 lines
8.0 KiB
C#
199 lines
8.0 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace HSchool.AppHost.Tests;
|
|
|
|
[Collection(AppHostCollection.Name)]
|
|
public class InventoryApiTests(AppHostFixture fixture)
|
|
{
|
|
private static readonly DateTime Start = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
|
|
|
|
[Fact]
|
|
public async Task Card_IncludesWornAndCarried_ListDoesNot()
|
|
{
|
|
using var client = fixture.App.CreateHttpClient("server");
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var school = await SchoolApiTests.CreateAsync(client, "Гардероб карточка", Start, seed: 31);
|
|
|
|
var page = await client.GetFromJsonAsync<PeoplePage>(
|
|
$"/api/schools/{school.Id}/people?role=student&year=5&pageSize=20",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.NotNull(page);
|
|
var girl = page.People.First(person => person.Female);
|
|
|
|
using var listResponse = await client.GetAsync(
|
|
$"/api/schools/{school.Id}/people?pageSize=1",
|
|
TestContext.Current.CancellationToken);
|
|
var listJson = await listResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
|
Assert.DoesNotContain("carryMass", listJson, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("\"worn\"", listJson, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("\"carried\"", listJson, StringComparison.Ordinal);
|
|
|
|
var card = await client.GetFromJsonAsync<Card>(
|
|
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(girl.Id)}?lang=ru",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.NotNull(card);
|
|
Assert.NotEmpty(card.Worn);
|
|
Assert.Contains(card.Worn, item => item.Layers.Count > 0 && item.ColorLabel is { Length: > 0 });
|
|
Assert.Contains(card.Worn, item => item.Condition == 1 && item.ConditionLabel == "целая");
|
|
Assert.True(card.CarryCapacity > 0);
|
|
Assert.True(card.CarryMass <= card.CarryCapacity);
|
|
Assert.Contains(card.Carried, item => item.Mass > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SameSeed_YieldsTheSameShirt()
|
|
{
|
|
using var client = fixture.App.CreateHttpClient("server");
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var first = await SchoolApiTests.CreateAsync(client, "Синяя рубашка 1", Start, seed: 31);
|
|
var shirt = await FirstGirlTopAsync(client, first.Id);
|
|
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var second = await SchoolApiTests.CreateAsync(client, "Синяя рубашка 2", Start, seed: 31);
|
|
var again = await FirstGirlTopAsync(client, second.Id);
|
|
|
|
Assert.Equal(shirt.DefName, again.DefName);
|
|
Assert.Equal(shirt.Color, again.Color);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PeopleJsonWithoutInventory_IsDressedOnReloadAndRewritten()
|
|
{
|
|
using var client = fixture.App.CreateHttpClient("server");
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var school = await SchoolApiTests.CreateAsync(client, "Старый сейв без одежды", Start, seed: 31);
|
|
var before = await FirstGirlTopAsync(client, school.Id);
|
|
|
|
var directory = await SavesDirectoryAsync(client);
|
|
var path = Path.Combine(directory, $"{school.Id}.people.json");
|
|
StripItems(path);
|
|
Assert.DoesNotContain("\"items\"", File.ReadAllText(path), StringComparison.Ordinal);
|
|
|
|
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
|
reload.EnsureSuccessStatusCode();
|
|
|
|
var after = await FirstGirlTopAsync(client, school.Id);
|
|
Assert.Equal(before.DefName, after.DefName);
|
|
Assert.Equal(before.Color, after.Color);
|
|
Assert.Contains("\"items\"", File.ReadAllText(path), StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Card_ConditionLabel_FollowsTheSavedThreshold()
|
|
{
|
|
using var client = fixture.App.CreateHttpClient("server");
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var school = await SchoolApiTests.CreateAsync(client, "Ветхая рубашка", Start, seed: 31);
|
|
var girl = await FirstGirlTopAsync(client, school.Id);
|
|
Assert.Equal("целая", girl.ConditionLabel);
|
|
|
|
var directory = await SavesDirectoryAsync(client);
|
|
var path = Path.Combine(directory, $"{school.Id}.people.json");
|
|
SetFirstWornCondition(path, 0.1f);
|
|
|
|
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
|
reload.EnsureSuccessStatusCode();
|
|
|
|
var after = await FirstGirlTopAsync(client, school.Id);
|
|
Assert.Equal(0.1f, after.Condition, precision: 4);
|
|
Assert.Equal("висит лохмотьями", after.ConditionLabel);
|
|
}
|
|
|
|
private static async Task<WornItem> FirstGirlTopAsync(HttpClient client, int schoolId)
|
|
{
|
|
var page = await client.GetFromJsonAsync<PeoplePage>(
|
|
$"/api/schools/{schoolId}/people?role=student&sex=female&pageSize=20",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.NotNull(page);
|
|
Assert.NotEmpty(page.People);
|
|
var girl = page.People[0];
|
|
var card = await client.GetFromJsonAsync<Card>(
|
|
$"/api/schools/{schoolId}/people/{Uri.EscapeDataString(girl.Id)}?lang=ru",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.NotNull(card);
|
|
var top = card.Worn.FirstOrDefault(item =>
|
|
item.Layers.Any(layer => layer.DefName.Equals("Top", StringComparison.Ordinal)));
|
|
Assert.NotNull(top);
|
|
return top;
|
|
}
|
|
|
|
private static async Task<string> SavesDirectoryAsync(HttpClient client)
|
|
{
|
|
var payload = await client.GetFromJsonAsync<SavesDirectoryResponse>(
|
|
"/api/dev/saves-directory",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.NotNull(payload);
|
|
return payload.Path;
|
|
}
|
|
|
|
private static void StripItems(string path)
|
|
{
|
|
var root = JsonNode.Parse(File.ReadAllText(path))
|
|
?? throw new InvalidOperationException("People file parsed to nothing.");
|
|
foreach (var person in root["people"]?.AsArray() ?? [])
|
|
{
|
|
person?.AsObject().Remove("items");
|
|
}
|
|
|
|
var applicants = root["applicants"]?["applicants"]?.AsArray();
|
|
if (applicants is not null)
|
|
{
|
|
foreach (var applicant in applicants)
|
|
{
|
|
applicant?["person"]?.AsObject()?.Remove("items");
|
|
}
|
|
}
|
|
|
|
File.WriteAllText(
|
|
path,
|
|
root.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
|
|
}
|
|
|
|
private static void SetFirstWornCondition(string path, float condition)
|
|
{
|
|
var root = JsonNode.Parse(File.ReadAllText(path))
|
|
?? throw new InvalidOperationException("People file parsed to nothing.");
|
|
foreach (var person in root["people"]?.AsArray() ?? [])
|
|
{
|
|
foreach (var item in person?["items"]?.AsArray() ?? [])
|
|
{
|
|
if (item?["location"]?.GetValue<string>() == "worn")
|
|
{
|
|
item["condition"] = condition;
|
|
}
|
|
}
|
|
}
|
|
|
|
File.WriteAllText(
|
|
path,
|
|
root.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
|
|
}
|
|
|
|
private sealed record SavesDirectoryResponse(string Path);
|
|
|
|
private sealed record PeoplePage(IReadOnlyList<PersonRow> People);
|
|
|
|
private sealed record PersonRow(string Id, string FullName, bool Female);
|
|
|
|
private sealed record Card(
|
|
IReadOnlyList<WornItem> Worn,
|
|
IReadOnlyList<CarriedItem> Carried,
|
|
float CarryMass,
|
|
float CarryCapacity);
|
|
|
|
private sealed record WornItem(
|
|
string DefName,
|
|
string Label,
|
|
string? Color,
|
|
string? ColorLabel,
|
|
IReadOnlyList<Layer> Layers,
|
|
float Condition,
|
|
string ConditionLabel);
|
|
|
|
private sealed record Layer(string DefName, string Label);
|
|
|
|
private sealed record CarriedItem(string DefName, string Label, float Mass);
|
|
}
|