Skip to content
38 changes: 29 additions & 9 deletions Darling/Darling.Tests/DarlingCustomViewsLiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ public sealed class DarlingCustomViewsLiveTests
private const string SampleDefinition = "{\"panels\":[{\"read\":\"get_wait_stats\",\"viz\":\"table\",\"span\":1}]}";
private const string SampleDefinitionV2 = "{\"panels\":[{\"read\":\"get_cpu_utilization\",\"viz\":\"line\",\"span\":2}]}";

/// <summary>
/// The create's <c>updated_by</c>, and deliberately NOT the <c>web</c> constant. A fixture that passes the
/// constant and asserts the constant comes back is satisfied by a store that ignores the argument and
/// hardcodes it, so it cannot discriminate the parameter binding it exists to prove. This column carries an
/// authenticated subject (#2550), so the fixture carries a subject-shaped value — a placeholder, never a
/// real address.
/// </summary>
private const string CreatePrincipal = "placeholder-author@example.invalid";

/// <summary>
/// The update's <c>updated_by</c>, DIFFERENT from the create's so the update is shown to REPLACE the stamp
/// rather than leave the creator's. That difference is what makes
/// <see cref="CustomViewStore.UpdateSql"/>'s <c>$5</c> provable end-to-end: one value shared with the
/// create lets an update that never binds it read back the expected string anyway.
/// </summary>
private const string UpdatePrincipal = "placeholder-editor@example.invalid";

private static string RequireLivePostgres()
{
var connectionString = Environment.GetEnvironmentVariable("DARLING_TEST_PG");
Expand Down Expand Up @@ -81,12 +98,12 @@ public async Task CrudRoundTrip_Create_Get_List_Update_StaleConflict_DupConflict
{
/* create -> Ok, version 1, fields round-trip. */
var created = Assert.IsType<CustomViewResult.Ok>(
await store.CreateAsync(name1, "first", SampleDefinition, "web", ct));
await store.CreateAsync(name1, "first", SampleDefinition, CreatePrincipal, ct));
var view = created.View!;
Assert.Equal(name1, view.Name);
Assert.Equal("first", view.Description);
Assert.Equal(1, view.Version);
Assert.Equal("web", view.UpdatedBy);
Assert.Equal(CreatePrincipal, view.UpdatedBy);
Assert.Contains("panels", view.DefinitionJson, StringComparison.Ordinal);

/* get -> Ok, matches. */
Expand All @@ -100,29 +117,32 @@ public async Task CrudRoundTrip_Create_Get_List_Update_StaleConflict_DupConflict
Assert.Contains(list, s => s.Id == view.Id && s.Name == name1 && s.Version == 1);
Assert.Null(list.Single(s => s.Id == view.Id).Kind);

/* update at the correct version -> Ok, version bumped to 2. */
/* update at the correct version -> Ok, version bumped to 2, and the stamp REPLACED. A different
principal from the create's is what makes the last assertion able to fail: with one shared value
an update that never writes $5 at all still reads back the expected string. */
var updated = Assert.IsType<CustomViewResult.Ok>(
await store.UpdateAsync(view.Id, name1, "second", SampleDefinitionV2, 1, "web", ct));
await store.UpdateAsync(view.Id, name1, "second", SampleDefinitionV2, 1, UpdatePrincipal, ct));
Assert.Equal(2, updated.View!.Version);
Assert.Equal("second", updated.View.Description);
Assert.Equal(UpdatePrincipal, updated.View.UpdatedBy);

/* stale update (still presenting version 1) -> Conflict, not a silent clobber. */
Assert.IsType<CustomViewResult.Conflict>(
await store.UpdateAsync(view.Id, name1, "third", SampleDefinitionV2, 1, "web", ct));
await store.UpdateAsync(view.Id, name1, "third", SampleDefinitionV2, 1, CreatePrincipal, ct));

/* duplicate name on CREATE -> Conflict. */
Assert.IsType<CustomViewResult.Conflict>(
await store.CreateAsync(name1, null, SampleDefinition, "web", ct));
await store.CreateAsync(name1, null, SampleDefinition, CreatePrincipal, ct));

/* duplicate name on UPDATE: a second view renamed onto the first's name -> Conflict. */
var second = Assert.IsType<CustomViewResult.Ok>(
await store.CreateAsync(name2, null, SampleDefinition, "web", ct));
await store.CreateAsync(name2, null, SampleDefinition, CreatePrincipal, ct));
Assert.IsType<CustomViewResult.Conflict>(
await store.UpdateAsync(second.View!.Id, name1, null, SampleDefinition, 1, "web", ct));
await store.UpdateAsync(second.View!.Id, name1, null, SampleDefinition, 1, CreatePrincipal, ct));

/* update a non-existent id -> NotFound. */
Assert.IsType<CustomViewResult.NotFound>(
await store.UpdateAsync(-999999, name1, null, SampleDefinition, 1, "web", ct));
await store.UpdateAsync(-999999, name1, null, SampleDefinition, 1, CreatePrincipal, ct));

/* delete -> Ok; then get + delete-again -> NotFound. */
Assert.IsType<CustomViewResult.Ok>(await store.DeleteAsync(view.Id, ct));
Expand Down
Loading
Loading