using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; namespace HSchool.AppHost.Tests; [Collection(AppHostCollection.Name)] public class RomancePackTests(AppHostFixture fixture) { private static readonly DateTime Start = new(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc); [Fact] public async Task Catalog_WithoutRomance_HasNoRomanticTopics() { using var client = fixture.App.CreateHttpClient("server"); await SchoolApiTests.LoginAsync(client); var vanilla = await client.GetFromJsonAsync("/api/catalog?lang=ru", TestContext.Current.CancellationToken); var example = await client.GetFromJsonAsync( "/api/catalog?lang=ru&mods=example", TestContext.Current.CancellationToken); Assert.NotNull(vanilla); Assert.NotNull(example); Assert.DoesNotContain(vanilla.Topics, topic => topic.DefName is "TopicCrush" or "TopicCouple" or "TopicTease"); Assert.DoesNotContain(vanilla.Topics, topic => topic.Tags.Contains("romance", StringComparer.Ordinal)); Assert.DoesNotContain(example.Topics, topic => topic.DefName is "TopicCrush" or "TopicCouple" or "TopicTease"); Assert.DoesNotContain(example.Topics, topic => topic.Tags.Contains("romance", StringComparer.Ordinal)); } [Fact] public async Task Catalog_WithRomance_AddsPackTopics() { using var client = fixture.App.CreateHttpClient("server"); await SchoolApiTests.LoginAsync(client); var catalog = await client.GetFromJsonAsync( "/api/catalog?lang=ru&mods=romance", TestContext.Current.CancellationToken); Assert.NotNull(catalog); Assert.Contains(catalog.Topics, topic => topic.DefName == "TopicCrush"); Assert.Contains(catalog.Topics, topic => topic.Tags.Contains("romance", StringComparer.Ordinal)); } [Fact] public async Task Create_WithPack_WritesOrientation_WithoutPack_OmitsField() { using var client = fixture.App.CreateHttpClient("server"); await SchoolApiTests.ResetAsync(client); var vanilla = await SchoolApiTests.CreateAsync(client, "Без романтики", Start, seed: 47); var vanillaJson = await ReadPeopleJsonAsync(client, vanilla.Id); Assert.DoesNotContain("\"orientation\"", vanillaJson, StringComparison.Ordinal); var packed = await CreateWithModsAsync(client, "С романтикой", ["romance"]); var packedJson = await ReadPeopleJsonAsync(client, packed.Id); Assert.Contains("\"orientation\"", packedJson, StringComparison.Ordinal); var page = await client.GetFromJsonAsync( $"/api/schools/{packed.Id}/people?role=student&pageSize=5", TestContext.Current.CancellationToken); Assert.NotNull(page); var card = await client.GetFromJsonAsync( $"/api/schools/{packed.Id}/people/{Uri.EscapeDataString(page.People[0].Id)}?lang=ru", TestContext.Current.CancellationToken); Assert.NotNull(card); Assert.False(string.IsNullOrWhiteSpace(card.Orientation?.DefName)); } [Fact] public async Task SixteenYearOlds_DoNotPair_WhenOpinionsAreMax() { using var client = fixture.App.CreateHttpClient("server"); await SchoolApiTests.ResetAsync(client); var school = await CreateWithModsAsync(client, "Шестнадцать", ["romance"]); var page = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people?role=student&year=10&pageSize=40", TestContext.Current.CancellationToken); Assert.NotNull(page); Assert.True(page.People.Count >= 2, "need two tenth-years"); var first = page.People[0]; var second = page.People.First(person => person.Id != first.Id); await PatchPeopleAsync(client, school.Id, people => { Link(people, first.Id, second.Id, 100); Link(people, second.Id, first.Id, 100); }); var card = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people/{Uri.EscapeDataString(first.Id)}?lang=ru", TestContext.Current.CancellationToken); Assert.NotNull(card); Assert.Null(card.Connections?.Pair); Assert.DoesNotContain("\"partnerId\"", await ReadPeopleJsonAsync(client, school.Id), StringComparison.Ordinal); } [Fact] public async Task Teacher_DoesNotGainCrushOnStudent() { using var client = fixture.App.CreateHttpClient("server"); await SchoolApiTests.ResetAsync(client); var school = await CreateWithModsAsync(client, "Учитель", ["romance"]); var staffing = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/staffing", TestContext.Current.CancellationToken); Assert.NotNull(staffing); var applicant = staffing.Applicants.First(row => !row.IsParent); using var hire = await client.PostAsJsonAsync( $"/api/schools/{school.Id}/staff/hire", new { personId = applicant.Id, position = "Teacher" }, TestContext.Current.CancellationToken); hire.EnsureSuccessStatusCode(); var pupils = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people?role=student&pageSize=5", TestContext.Current.CancellationToken); Assert.NotNull(pupils); var pupilId = pupils.People[0].Id; await PatchPeopleAsync(client, school.Id, people => Link(people, applicant.Id, pupilId, 100)); var json = await ReadPeopleJsonAsync(client, school.Id); Assert.DoesNotContain($"\"partnerId\": \"{pupilId}\"", json, StringComparison.Ordinal); var teacher = JsonNode.Parse(json)!["people"]!.AsArray() .First(node => node!["id"]?.GetValue() == applicant.Id); var crushes = teacher!["bonds"]?["crushes"]?.AsArray() ?? []; Assert.DoesNotContain(crushes, node => node?.GetValue() == pupilId); var card = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people/{Uri.EscapeDataString(applicant.Id)}?lang=ru", TestContext.Current.CancellationToken); Assert.NotNull(card); Assert.DoesNotContain(card.Connections?.Crushes ?? [], row => row.Id == pupilId); } [Fact] public async Task FirstYear_DoesNotCrushEleventh() { using var client = fixture.App.CreateHttpClient("server"); await SchoolApiTests.ResetAsync(client); var school = await CreateWithModsAsync(client, "Параллели", ["romance"]); var first = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people?role=student&year=1&pageSize=5", TestContext.Current.CancellationToken); var eleventh = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people?role=student&year=11&pageSize=5", TestContext.Current.CancellationToken); Assert.NotNull(first); Assert.NotNull(eleventh); Assert.NotEmpty(first.People); Assert.NotEmpty(eleventh.People); var youngId = first.People[0].Id; var oldId = eleventh.People[0].Id; await PatchPeopleAsync(client, school.Id, people => Link(people, youngId, oldId, 100)); var card = await client.GetFromJsonAsync( $"/api/schools/{school.Id}/people/{Uri.EscapeDataString(youngId)}?lang=ru", TestContext.Current.CancellationToken); Assert.NotNull(card); Assert.DoesNotContain(card.Connections?.Crushes ?? [], row => row.Id == oldId); } private static async Task CreateWithModsAsync( HttpClient client, string name, string[] modIds) { using var response = await client.PostAsJsonAsync( "/api/schools", new { name, startDate = Start, modIds, seed = 47 }, TestContext.Current.CancellationToken); response.EnsureSuccessStatusCode(); var created = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(created); return created; } private static async Task ReadPeopleJsonAsync(HttpClient client, int schoolId) { var directory = await SchoolApiTests.SavesDirectoryAsync(client); return await File.ReadAllTextAsync( Path.Combine(directory, $"{schoolId}.people.json"), TestContext.Current.CancellationToken); } private static async Task PatchPeopleAsync(HttpClient client, int schoolId, Action patch) { var directory = await SchoolApiTests.SavesDirectoryAsync(client); var path = Path.Combine(directory, $"{schoolId}.people.json"); var doc = JsonNode.Parse(await File.ReadAllTextAsync(path, TestContext.Current.CancellationToken))!; var people = doc["people"]!.AsArray(); patch(people); await File.WriteAllTextAsync(path, doc.ToJsonString(new JsonSerializerOptions { WriteIndented = true }), TestContext.Current.CancellationToken); using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); reload.EnsureSuccessStatusCode(); } private static void Link(JsonArray people, string fromId, string toId, int opinion) { var from = people.First(node => node!["id"]?.GetValue() == fromId)!.AsObject(); from["orientation"] = "Bisexual"; var opinions = from["opinions"] as JsonObject ?? new JsonObject(); opinions[toId] = opinion; from["opinions"] = opinions; var bonds = from["bonds"] as JsonObject ?? new JsonObject(); bonds["crushes"] = new JsonArray(); from["bonds"] = bonds; } private sealed record CatalogResponse(IReadOnlyList Topics); private sealed record TopicInfo(string DefName, string Label, IReadOnlyList Tags); private sealed record PeoplePage(IReadOnlyList People); private sealed record PersonRow(string Id); private sealed record Card(string Id, DefLabel? Orientation, PersonConnections? Connections); private sealed record DefLabel(string DefName, string Label); private sealed record PersonConnections( IReadOnlyList? Crushes, PersonLink? Pair); private sealed record PersonLink(string Id); private sealed record StaffingResponse(IReadOnlyList Applicants); private sealed record ApplicantRow(string Id, bool IsParent); }