Complete phase 47 romance pack on generic orientation and affinity hooks.
Vanilla catalogs stay without crushes; a content pack ships the overlay so later mods can reuse it.
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
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<CatalogResponse>("/api/catalog?lang=ru", TestContext.Current.CancellationToken);
|
||||
var example = await client.GetFromJsonAsync<CatalogResponse>(
|
||||
"/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<CatalogResponse>(
|
||||
"/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<PeoplePage>(
|
||||
$"/api/schools/{packed.Id}/people?role=student&pageSize=5",
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(page);
|
||||
var card = await client.GetFromJsonAsync<Card>(
|
||||
$"/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<PeoplePage>(
|
||||
$"/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<Card>(
|
||||
$"/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<StaffingResponse>(
|
||||
$"/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<PeoplePage>(
|
||||
$"/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<string>() == applicant.Id);
|
||||
var crushes = teacher!["bonds"]?["crushes"]?.AsArray() ?? [];
|
||||
Assert.DoesNotContain(crushes, node => node?.GetValue<string>() == pupilId);
|
||||
|
||||
var card = await client.GetFromJsonAsync<Card>(
|
||||
$"/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<PeoplePage>(
|
||||
$"/api/schools/{school.Id}/people?role=student&year=1&pageSize=5",
|
||||
TestContext.Current.CancellationToken);
|
||||
var eleventh = await client.GetFromJsonAsync<PeoplePage>(
|
||||
$"/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<Card>(
|
||||
$"/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<SchoolApiTests.SchoolResponse> 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<SchoolApiTests.SchoolResponse>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(created);
|
||||
return created;
|
||||
}
|
||||
|
||||
private static async Task<string> 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<JsonArray> 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<string>() == 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<TopicInfo> Topics);
|
||||
|
||||
private sealed record TopicInfo(string DefName, string Label, IReadOnlyList<string> Tags);
|
||||
|
||||
private sealed record PeoplePage(IReadOnlyList<PersonRow> 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<PersonLink>? Crushes,
|
||||
PersonLink? Pair);
|
||||
|
||||
private sealed record PersonLink(string Id);
|
||||
|
||||
private sealed record StaffingResponse(IReadOnlyList<ApplicantRow> Applicants);
|
||||
|
||||
private sealed record ApplicantRow(string Id, bool IsParent);
|
||||
}
|
||||
Reference in New Issue
Block a user