Files
h-school/tests/HSchool.AppHost.Tests/PeopleApiTests.cs
T
Leonid Pershin 3c54f981b7
ci / server (push) Failing after 3m44s
ci / client (push) Successful in 15s
Update wire protocol to version 7 and enhance presence management features
- Bumped the wire protocol version to 7, reflecting significant changes in the communication structure.
- Introduced a new presence message type for real-time occupancy updates, including node activity and individual presence states.
- Updated the API to include a directory endpoint for fetching short id→name mappings, improving client-side name resolution.
- Revised the map snapshot structure to be static, with people and current lessons now handled through the presence stream.
- Enhanced client-side handling of presence updates, including UI adjustments to display live occupancy and activity.
- Updated documentation to reflect the new protocol features and changes in presence management.
- Added tests to validate the new presence functionalities and ensure robust handling of real-time data.
2026-08-19 17:00:53 +03:00

238 lines
9.4 KiB
C#

using System.Net.Http.Json;
namespace HSchool.AppHost.Tests;
[Collection(AppHostCollection.Name)]
public class PeopleApiTests(AppHostFixture fixture)
{
private static readonly DateTime Start = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
[Fact]
public async Task List_FiltersByRoleAndYear()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Люди фильтр", Start);
var page = await GetPeopleAsync(client, school.Id, "role=student&year=5&pageSize=100");
Assert.NotEmpty(page.People);
Assert.Equal(page.People.Count, page.Total);
Assert.All(page.People, person =>
{
Assert.Contains("student", person.Roles);
Assert.Equal(5, person.ClassYear);
});
Assert.Contains(5, page.Filters.Years);
}
[Fact]
public async Task List_SortsBySurname()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Люди сорт", Start);
var page = await GetPeopleAsync(client, school.Id, "sort=surname&pageSize=20");
var surnames = page.People.Select(person => person.Surname).ToArray();
Assert.Equal(surnames.OrderBy(name => name, StringComparer.Ordinal), surnames);
}
[Fact]
public async Task List_PageBounds()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Люди страницы", Start);
var first = await GetPeopleAsync(client, school.Id, "page=1&pageSize=10");
Assert.True(first.Total > 10);
Assert.Equal(10, first.People.Count);
Assert.Equal(1, first.Page);
Assert.Equal(10, first.PageSize);
var past = await GetPeopleAsync(client, school.Id, "page=999&pageSize=10");
Assert.Equal(first.Total, past.Total);
Assert.Empty(past.People);
using var zero = await client.GetAsync($"/api/schools/{school.Id}/people?page=0", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, zero.StatusCode);
Assert.Equal("invalid-query", await ProblemCodeAsync(zero));
using var huge = await client.GetAsync($"/api/schools/{school.Id}/people?pageSize=101", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, huge.StatusCode);
Assert.Equal("invalid-query", await ProblemCodeAsync(huge));
}
[Fact]
public async Task List_HasPupilsAndParentsButNoStaff()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Пустой штат", Start);
var students = await GetPeopleAsync(client, school.Id, "role=student&pageSize=10");
var parents = await GetPeopleAsync(client, school.Id, "role=parent&pageSize=10");
var staff = await GetPeopleAsync(client, school.Id, "role=staff&pageSize=10");
Assert.NotEmpty(students.People);
Assert.NotEmpty(parents.People);
Assert.Equal(0, staff.Total);
Assert.Empty(staff.People);
}
[Fact]
public async Task Directory_ListsRosterIdsAndNames()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Справочник", Start);
var directory = await client.GetFromJsonAsync<DirectoryResponse>(
$"/api/schools/{school.Id}/directory",
TestContext.Current.CancellationToken);
Assert.NotNull(directory);
Assert.NotEmpty(directory.People);
Assert.All(directory.People, person =>
{
Assert.False(string.IsNullOrWhiteSpace(person.Id));
Assert.False(string.IsNullOrWhiteSpace(person.FullName));
});
var page = await GetPeopleAsync(client, school.Id, "pageSize=10");
Assert.Contains(directory.People, person => person.Id == page.People[0].Id && person.FullName == page.People[0].FullName);
using var missing = await client.GetAsync("/api/schools/999999/directory", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, missing.StatusCode);
Assert.Equal("unknown-school", await ProblemCodeAsync(missing));
}
[Fact]
public async Task List_UnknownSchool_IsNotFound()
{
using var client = fixture.App.CreateHttpClient("server");
using var response = await client.GetAsync("/api/schools/999999/people", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
Assert.Equal("unknown-school", await ProblemCodeAsync(response));
}
[Fact]
public async Task Card_UnknownPerson_IsNotFound()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Люди карточка", Start);
using var response = await client.GetAsync(
$"/api/schools/{school.Id}/people/nobody",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
Assert.Equal("unknown-person", await ProblemCodeAsync(response));
}
[Fact]
public async Task Card_IncludesFamilyAndLiveNeeds()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Люди семья", Start);
var page = await GetPeopleAsync(client, school.Id, "role=student&year=5&pageSize=10");
var pupil = page.People[0];
var card = await client.GetFromJsonAsync<PersonCardResponse>(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(pupil.Id)}?lang=ru",
TestContext.Current.CancellationToken);
Assert.NotNull(card);
Assert.Equal(pupil.Id, card.Id);
Assert.Equal(2, card.Family.Parents.Count);
Assert.Contains(card.Needs, need => need.Id == "Sleep" && need.Value == 1f);
Assert.NotEmpty(card.Body);
Assert.NotEmpty(card.Skills);
var mother = card.Family.Parents.Single(parent => parent.Female);
var motherCard = await client.GetFromJsonAsync<PersonCardResponse>(
$"/api/schools/{school.Id}/people/{Uri.EscapeDataString(mother.Id)}?lang=ru",
TestContext.Current.CancellationToken);
Assert.NotNull(motherCard);
Assert.Contains(motherCard.Family.Children, child => child.Id == pupil.Id);
}
private static async Task<PeopleListResponse> GetPeopleAsync(HttpClient client, int schoolId, string query)
{
var page = await client.GetFromJsonAsync<PeopleListResponse>(
$"/api/schools/{schoolId}/people?{query}",
TestContext.Current.CancellationToken);
Assert.NotNull(page);
return page;
}
private static async Task<string?> ProblemCodeAsync(HttpResponseMessage response)
{
var problem = await response.Content.ReadFromJsonAsync<ProblemResponse>(TestContext.Current.CancellationToken);
return problem?.Code;
}
private sealed record ProblemResponse(string? Code);
private sealed record PeopleListResponse(
int Total,
int Page,
int PageSize,
IReadOnlyList<PersonListItemResponse> People,
PeopleFilterOptionsResponse Filters);
private sealed record PeopleFilterOptionsResponse(
IReadOnlyList<int> Years,
IReadOnlyList<string> Letters,
IReadOnlyList<DefLabelResponse> Positions);
private sealed record DefLabelResponse(string DefName, string Label);
private sealed record PersonListItemResponse(
string Id,
string FullName,
string Surname,
string Given,
string Patronymic,
bool Female,
int Age,
IReadOnlyList<string> Roles,
int? ClassYear,
string? ClassLetter,
string? Position,
string? PositionLabel);
private sealed record PersonCardResponse(
string Id,
string FullName,
bool Female,
int Age,
DateTime BirthDate,
IReadOnlyList<string> Roles,
int? ClassYear,
string? ClassLetter,
IReadOnlyList<LabeledStatResponse> Body,
IReadOnlyList<LabeledStatResponse> Skills,
IReadOnlyList<DefLabelResponse> Traits,
IReadOnlyList<NeedStatResponse> Needs,
PersonFamilyResponse Family);
private sealed record LabeledStatResponse(string Id, string Label, string Value);
private sealed record NeedStatResponse(string Id, string Label, float Value);
private sealed record PersonFamilyResponse(
IReadOnlyList<PersonRelResponse> Parents,
IReadOnlyList<PersonRelResponse> Children,
IReadOnlyList<PersonRelResponse> Siblings,
IReadOnlyList<PersonRelResponse> Partners);
private sealed record PersonRelResponse(string Id, string FullName, bool Female);
private sealed record DirectoryResponse(IReadOnlyList<DirectoryPersonResponse> People);
private sealed record DirectoryPersonResponse(string Id, string FullName);
}