Add alpha session gate with cookie auth and login UI.

This commit is contained in:
Leonid Pershin
2026-08-20 07:17:18 +03:00
parent 2a55a025f4
commit a444fc011e
24 changed files with 1005 additions and 27 deletions
@@ -110,6 +110,7 @@ public class SchoolApiTests(AppHostFixture fixture)
public async Task DeleteSchool_ThatDoesNotExist_IsNotFound()
{
using var client = fixture.App.CreateHttpClient("server");
await LoginAsync(client);
using var response = await client.DeleteAsync("/api/schools/999999", TestContext.Current.CancellationToken);
@@ -486,6 +487,7 @@ public class SchoolApiTests(AppHostFixture fixture)
public async Task Status_ReportsTheLoopAndTheLimit()
{
using var client = fixture.App.CreateHttpClient("server");
await LoginAsync(client);
var status = await client.GetFromJsonAsync<StatusResponse>("/api/status", TestContext.Current.CancellationToken);
@@ -510,8 +512,72 @@ public class SchoolApiTests(AppHostFixture fixture)
Assert.True(restored.Running);
}
internal const string TestPassword = "test-alpha";
internal const string TestUserName = "TestPlayer";
internal static async Task LoginAsync(HttpClient client, string userName = TestUserName)
{
using var current = await client.GetAsync("/api/session", TestContext.Current.CancellationToken);
if (current.IsSuccessStatusCode)
{
return;
}
_ = await LoginAndGetCookieAsync(client, userName);
}
internal static async Task<string> LoginAndGetCookieAsync(HttpClient client, string userName = TestUserName)
{
using var first = await client.PostAsJsonAsync(
"/api/session",
new { password = TestPassword, userName },
TestContext.Current.CancellationToken);
if (first.IsSuccessStatusCode)
{
return ExtractSessionCookie(first);
}
if (first.StatusCode == HttpStatusCode.Conflict)
{
userName = $"T-{Guid.NewGuid():N}"[..10];
using var retry = await client.PostAsJsonAsync(
"/api/session",
new { password = TestPassword, userName },
TestContext.Current.CancellationToken);
retry.EnsureSuccessStatusCode();
return ExtractSessionCookie(retry);
}
first.EnsureSuccessStatusCode();
return ExtractSessionCookie(first);
}
internal static string ExtractSessionCookie(HttpResponseMessage response)
{
if (!response.Headers.TryGetValues("Set-Cookie", out var values))
{
throw new InvalidOperationException("Login did not return a session cookie.");
}
foreach (var value in values)
{
if (!value.StartsWith("hschool.session=", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var end = value.IndexOf(';');
return end >= 0 ? value[..end] : value;
}
throw new InvalidOperationException("Login did not return a session cookie.");
}
internal static async Task ResetAsync(HttpClient client)
{
await LoginAsync(client);
var state = await GetSchoolsAsync(client);
foreach (var school in state.Schools)