test(playwright): Templates duplicate-name + create-cancel edge cases (Wave 4)

This commit is contained in:
Joseph Doherty
2026-06-07 03:37:25 -04:00
parent 11ba61c39c
commit 64222cf596
@@ -120,4 +120,96 @@ public class TemplateCrudTests
}
}
}
[SkippableFact]
public async Task CreateTemplate_DuplicateName_ShowsInlineError()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
// CLI-seed an existing base template, then UI-attempt to create a duplicate.
// Base (non-derived) Template.Name has a unique index
// (HasIndex(t => t.Name).IsUnique().HasFilter("[IsDerived]=0")) and
// TemplateService.CreateTemplateAsync has no friendly duplicate pre-check,
// so the DB-constraint exception is caught into _formError and rendered inline
// in div.text-danger.small with no navigation (stays on /create).
// (Empirically confirmed: duplicate create surfaces inline; duplicate-name path used.)
var name = CliRunner.UniqueName("tmpl");
var seededId = await CliRunner.CreateTemplateAsync(name);
try
{
var page = await _fixture.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/templates/create");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// Fill the Name input with the duplicate name and click Create.
await page.Locator("div.mb-3:has(label:has-text('Name')) input.form-control").FillAsync(name);
await page.ClickAsync("button.btn.btn-success:has-text('Create')");
// Web-first assertions: the inline error becomes visible and we stay on /create.
// Do NOT assert a literal message — it is the DB-constraint exception text.
await Assertions.Expect(page.Locator("div.text-danger.small")).ToBeVisibleAsync();
await Assertions.Expect(page)
.ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/design/templates/create"));
}
finally
{
// Delete the seeded source template, then sweep any leftover by name.
await CliRunner.DeleteTemplateAsync(seededId);
try
{
foreach (var id in await CliRunner.ListTemplateIdsByNamePrefixAsync(name))
{
await CliRunner.DeleteTemplateAsync(id);
}
}
catch
{
// Best-effort — swallow to avoid masking the original test failure.
}
}
}
[SkippableFact]
public async Task CreateTemplate_Cancel_ReturnsToListWithoutCreating()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
var name = CliRunner.UniqueName("tmpl");
try
{
var page = await _fixture.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/templates/create");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// Fill the Name input, then click Cancel — Blazor navigates back to the list.
await page.Locator("div.mb-3:has(label:has-text('Name')) input.form-control").FillAsync(name);
await page.ClickAsync("button.btn.btn-outline-secondary:has-text('Cancel')");
// excludePath: "/create" rejects the /design/templates/create URL we came from.
await PlaywrightFixture.WaitForPathAsync(page, "/design/templates", excludePath: "/create");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// Nothing was created: no template exists with our unique name.
Assert.Empty(await CliRunner.ListTemplateIdsByNamePrefixAsync(name));
}
finally
{
// Defensive sweep by name in case of an unexpected create.
try
{
foreach (var id in await CliRunner.ListTemplateIdsByNamePrefixAsync(name))
{
await CliRunner.DeleteTemplateAsync(id);
}
}
catch
{
// Best-effort — swallow to avoid masking the original test failure.
}
}
}
}