feat(dashboard): add revision-safe interactive widget grid
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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<OkObjectResult>(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<ConflictObjectResult>(await Controller(verify, Geometry).UpdateDashboardPreference(default));
|
||||
Assert.IsType<ConflictObjectResult>(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<OkObjectResult>());
|
||||
Assert.Single(results.OfType<ConflictObjectResult>());
|
||||
await using var readback = database.Context();
|
||||
Assert.Equal(1, (await readback.Users.SingleAsync()).DashboardLayoutRevision);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user