148 lines
6.8 KiB
C#
148 lines
6.8 KiB
C#
using System.Net;
|
|
using System.Text.Json;
|
|
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
|
|
|
namespace ItWorx.KnowledgeHub.IntegrationTests.Platform;
|
|
|
|
[Collection(PostgresTestGroup.Name)]
|
|
public sealed class UserPreferencesApiTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
|
{
|
|
private sealed record PreferencesPayload(
|
|
string Theme,
|
|
string Density,
|
|
bool PrimaryNavigationCollapsed,
|
|
bool ContextPanelOpen,
|
|
string[] NavigationOrder,
|
|
string[] DashboardWidgetOrder,
|
|
string[] HiddenDashboardWidgets,
|
|
string DefaultLandingPage,
|
|
string CatalogView,
|
|
int ContextPanelWidth,
|
|
int NavigationWidth);
|
|
|
|
private static PreferencesPayload DefaultPreferences(string theme = "system") => new(
|
|
theme,
|
|
"comfortable",
|
|
false,
|
|
false,
|
|
["/", "/infrastructure", "/infrastructure/services", "/projects", "/runbooks", "/knowledge", "/activity", "/search"],
|
|
["attention", "server", "projects", "activity", "runbooks", "favorites", "quick-actions"],
|
|
[],
|
|
"/",
|
|
"list",
|
|
304,
|
|
224);
|
|
|
|
private async Task<HttpClient> AuthenticatedClientAsync()
|
|
{
|
|
var client = Factory.CreateClient();
|
|
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
|
return client;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Missing_preferences_return_the_shared_defaults_without_persisting_a_row()
|
|
{
|
|
using var client = await AuthenticatedClientAsync();
|
|
|
|
using var first = await client.GetAsync("/api/v1/me/preferences");
|
|
using var firstJson = JsonDocument.Parse(await first.Content.ReadAsStringAsync());
|
|
using var second = await client.GetAsync("/api/v1/me/preferences");
|
|
using var secondJson = JsonDocument.Parse(await second.Content.ReadAsStringAsync());
|
|
|
|
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
|
|
Assert.Equal("\"0\"", first.Headers.ETag?.Tag);
|
|
Assert.Equal("0", firstJson.RootElement.GetProperty("eTag").GetString());
|
|
Assert.True(firstJson.RootElement.GetProperty("isDefault").GetBoolean());
|
|
Assert.Equal(1, firstJson.RootElement.GetProperty("schemaVersion").GetInt32());
|
|
Assert.Equal("system", firstJson.RootElement.GetProperty("theme").GetString());
|
|
Assert.Equal("comfortable", firstJson.RootElement.GetProperty("density").GetString());
|
|
Assert.Equal(304, firstJson.RootElement.GetProperty("contextPanelWidth").GetInt32());
|
|
Assert.Equal(224, firstJson.RootElement.GetProperty("navigationWidth").GetInt32());
|
|
Assert.Equal("0", secondJson.RootElement.GetProperty("eTag").GetString());
|
|
Assert.True(secondJson.RootElement.GetProperty("isDefault").GetBoolean());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Full_replacement_with_the_current_etag_persists_for_the_signed_in_user()
|
|
{
|
|
using var client = await AuthenticatedClientAsync();
|
|
var request = new PreferencesPayload(
|
|
"dark", "compact", true, true,
|
|
["/projects", "/", "/knowledge"], ["projects", "attention", "server"], ["server"],
|
|
"/projects", "grid", 420, 248);
|
|
|
|
using var updated = await SetupHelper.PutWithCsrfAsync(client, "/api/v1/me/preferences", request, "0");
|
|
using var updatedJson = JsonDocument.Parse(await updated.Content.ReadAsStringAsync());
|
|
using var fetched = await client.GetAsync("/api/v1/me/preferences");
|
|
using var fetchedJson = JsonDocument.Parse(await fetched.Content.ReadAsStringAsync());
|
|
|
|
Assert.Equal(HttpStatusCode.OK, updated.StatusCode);
|
|
Assert.Equal("\"1\"", updated.Headers.ETag?.Tag);
|
|
Assert.Equal("1", updatedJson.RootElement.GetProperty("eTag").GetString());
|
|
Assert.False(updatedJson.RootElement.GetProperty("isDefault").GetBoolean());
|
|
Assert.Equal("dark", fetchedJson.RootElement.GetProperty("theme").GetString());
|
|
Assert.Equal("/projects", fetchedJson.RootElement.GetProperty("defaultLandingPage").GetString());
|
|
Assert.Equal(420, fetchedJson.RootElement.GetProperty("contextPanelWidth").GetInt32());
|
|
Assert.Equal("1", fetchedJson.RootElement.GetProperty("eTag").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_missing_if_match_is_rejected_before_updating_preferences()
|
|
{
|
|
using var client = await AuthenticatedClientAsync();
|
|
var request = DefaultPreferences("light");
|
|
|
|
using var response = await SetupHelper.PutWithCsrfAsync(client, "/api/v1/me/preferences", request);
|
|
|
|
Assert.Equal((HttpStatusCode)428, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_stale_if_match_is_rejected_without_overwriting_a_newer_preference_version()
|
|
{
|
|
using var client = await AuthenticatedClientAsync();
|
|
var request = DefaultPreferences("dark");
|
|
|
|
using var first = await SetupHelper.PutWithCsrfAsync(client, "/api/v1/me/preferences", request, "0");
|
|
using var stale = await SetupHelper.PutWithCsrfAsync(client, "/api/v1/me/preferences", request with { Theme = "light" }, "0");
|
|
using var fetched = await client.GetAsync("/api/v1/me/preferences");
|
|
using var fetchedJson = JsonDocument.Parse(await fetched.Content.ReadAsStringAsync());
|
|
|
|
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
|
|
Assert.Equal(HttpStatusCode.PreconditionFailed, stale.StatusCode);
|
|
Assert.Equal("dark", fetchedJson.RootElement.GetProperty("theme").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Preferences_reject_unknown_navigation_and_widget_ids()
|
|
{
|
|
using var client = await AuthenticatedClientAsync();
|
|
var request = DefaultPreferences() with
|
|
{
|
|
NavigationOrder = ["/admin/users/delete"],
|
|
DashboardWidgetOrder = ["unexpected-widget"],
|
|
DefaultLandingPage = "/admin/users/delete"
|
|
};
|
|
|
|
using var response = await SetupHelper.PutWithCsrfAsync(client, "/api/v1/me/preferences", request, "0");
|
|
|
|
Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Concurrent_first_writes_with_the_default_etag_have_one_winner()
|
|
{
|
|
using var firstClient = await AuthenticatedClientAsync();
|
|
using var secondClient = await AuthenticatedClientAsync();
|
|
var firstWrite = SetupHelper.PutWithCsrfAsync(firstClient, "/api/v1/me/preferences", DefaultPreferences("dark"), "0");
|
|
var secondWrite = SetupHelper.PutWithCsrfAsync(secondClient, "/api/v1/me/preferences", DefaultPreferences("light"), "0");
|
|
|
|
using var firstResponse = await firstWrite;
|
|
using var secondResponse = await secondWrite;
|
|
|
|
Assert.Equal(1, new[] { firstResponse.StatusCode, secondResponse.StatusCode }.Count(status => status == HttpStatusCode.OK));
|
|
Assert.Equal(1, new[] { firstResponse.StatusCode, secondResponse.StatusCode }.Count(status => status == HttpStatusCode.PreconditionFailed));
|
|
}
|
|
}
|