Vanilla catalogs stay without crushes; a content pack ships the overlay so later mods can reuse it.
237 lines
11 KiB
C#
237 lines
11 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 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);
|
|
}
|