using System.Security.Claims; using System.Text; using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Xunit; namespace JobTrackerApi.MariaDb.Tests; public sealed class DashboardGeometryPersistenceTests { private const string Geometry = "{\"version\":2,\"revision\":0,\"widgets\":[{\"id\":\"attention\",\"visible\":false,\"size\":\"standard\",\"x\":4,\"y\":17,\"w\":8,\"h\":10}]}"; private static AuthController Controller(JobTrackerContext db, string body, string owner = "rd21-owner") { var http = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, owner) }, "test")) }; http.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); return new(new ConfigurationBuilder().Build(), null!, null!, null!, null!, null!, null!, null!, db) { ControllerContext = new() { HttpContext = http } }; } [Fact] public async Task Geometry_is_durable_owner_scoped_and_stale_or_legacy_writes_preserve_other_preferences() { await using var database = await MariaDbDatabase.CreateAsync(); await using (var seed = database.Context()) { seed.Users.AddRange(new ApplicationUser { Id = "rd21-owner", UserName = "geometry", UiLanguage = "nb-NO" }, new ApplicationUser { Id = "other", UserName = "untouched" }); await seed.SaveChangesAsync(); } await using (var write = database.Context()) Assert.IsType(await Controller(write, Geometry).UpdateDashboardPreference(default)); await using var verify = database.Context(); var owner = await verify.Users.AsNoTracking().SingleAsync(u => u.Id == "rd21-owner"); var saved = DashboardLayout.Read(owner.DashboardLayoutJson, owner.DashboardLayoutRevision); Assert.Equal(1, saved.Revision); Assert.Equal(new DashboardWidget("attention", false, "standard", 4, 17, 8, 10), Assert.Single(saved.Widgets)); Assert.Equal("nb-NO", owner.UiLanguage); Assert.Null((await verify.Users.AsNoTracking().SingleAsync(u => u.Id == "other")).DashboardLayoutJson); Assert.IsType(await Controller(verify, Geometry).UpdateDashboardPreference(default)); Assert.IsType(await Controller(verify, "{\"version\":1,\"revision\":1,\"widgets\":[]}").UpdateDashboardPreference(default)); Assert.Equal(owner.DashboardLayoutJson, (await verify.Users.AsNoTracking().SingleAsync(u => u.Id == owner.Id)).DashboardLayoutJson); } [Fact] public async Task Concurrent_geometry_saves_have_one_winner_and_one_conflict() { await using var database = await MariaDbDatabase.CreateAsync(); await using (var seed = database.Context()) { seed.Users.Add(new() { Id = "rd21-owner", UserName = "concurrent-geometry" }); await seed.SaveChangesAsync(); } await using var first = database.Context(); await using var second = database.Context(); var results = await Task.WhenAll(Controller(first, Geometry).UpdateDashboardPreference(default), Controller(second, Geometry.Replace("\"h\":10", "\"h\":12")).UpdateDashboardPreference(default)); Assert.Single(results.OfType()); Assert.Single(results.OfType()); await using var readback = database.Context(); Assert.Equal(1, (await readback.Users.SingleAsync()).DashboardLayoutRevision); } }