Enhance school creation and staffing management with native language support
ci / server (push) Failing after 3m44s
ci / client (push) Successful in 14s

- Updated protocol documentation to include `nativeLanguages` in `nameSets` and added `nativeLanguage` to school creation options.
- Enhanced the UI for school creation to allow selection of native languages, improving user experience.
- Revised API interfaces to accommodate new native language features, ensuring proper data handling.
- Improved localization strings to support new native language functionalities in both English and Russian.
- Updated tests to validate the new native language features and ensure robust functionality in staffing scenarios.
This commit is contained in:
Leonid Pershin
2026-08-19 22:22:31 +03:00
parent 21d79cb49b
commit 5a00ad7746
48 changed files with 2064 additions and 312 deletions
@@ -118,6 +118,73 @@ public class TimetableApiTests(AppHostFixture fixture)
&& lesson.Locked);
}
/// <summary>
/// The refusal codes the client switches on. `pin-rejected` is covered above; these five are
/// the rest of the documented contract, and none of them was exercised.
/// </summary>
[Fact]
public async Task PinAndUnpinRefusals_CarryTheDocumentedCodes()
{
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Расписание отказы", TuesdayMorning);
var empty = await GetTimetableAsync(client, school.Id);
var klass = empty.Classes[0];
var room = empty.Rooms.First(candidate => candidate.Id.StartsWith("classroom-", StringComparison.Ordinal));
// Nobody teaches anything yet, so a well-formed pin still has no teacher to place.
Assert.Equal("no-teacher", await PinCodeAsync(client, school.Id, klass.Id, "Mathematics", room.Id));
Assert.Equal("unknown-class", await PinCodeAsync(client, school.Id, "cZZ", "Mathematics", room.Id));
Assert.Equal("unknown-subject", await PinCodeAsync(client, school.Id, klass.Id, "Astrology", room.Id));
Assert.Equal("unknown-room", await PinCodeAsync(client, school.Id, klass.Id, "Mathematics", "no-such-room"));
await HireMathAsync(client, school.Id);
var table = await GetTimetableAsync(client, school.Id);
var lesson = table.Lessons.First(row => row.Subject == "Mathematics");
using var pin = await client.PostAsJsonAsync(
$"/api/schools/{school.Id}/timetable/pin",
new { lesson.ClassId, lesson.Subject, lesson.RoomId, lesson.Day, lesson.Period },
TestContext.Current.CancellationToken);
pin.EnsureSuccessStatusCode();
var unpinQuery = $"classId={Uri.EscapeDataString(lesson.ClassId)}&subject={lesson.Subject}"
+ $"&day={lesson.Day}&period={lesson.Period}";
using var unpin = await client.DeleteAsync(
$"/api/schools/{school.Id}/timetable/pin?{unpinQuery}",
TestContext.Current.CancellationToken);
unpin.EnsureSuccessStatusCode();
// The lock is gone; asking again is a 404, not a silent success.
using var again = await client.DeleteAsync(
$"/api/schools/{school.Id}/timetable/pin?{unpinQuery}",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, again.StatusCode);
Assert.Equal("unknown-lesson", await ProblemCodeAsync(again));
using var incomplete = await client.DeleteAsync(
$"/api/schools/{school.Id}/timetable/pin?classId={Uri.EscapeDataString(lesson.ClassId)}",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, incomplete.StatusCode);
Assert.Equal("invalid-query", await ProblemCodeAsync(incomplete));
}
private static async Task<string?> PinCodeAsync(
HttpClient client,
int schoolId,
string classId,
string subject,
string roomId)
{
using var response = await client.PostAsJsonAsync(
$"/api/schools/{schoolId}/timetable/pin",
new { classId, subject, roomId, day = 0, period = 1 },
TestContext.Current.CancellationToken);
Assert.False(response.IsSuccessStatusCode, $"pinning {subject} into {roomId} was expected to fail.");
return await ProblemCodeAsync(response);
}
[Fact]
public async Task Saturday_HasNoOccupancyOnTheMap()
{