Maak slug in CMS zichtbaar en inline aanpasbaar
De CMS-admin toonde het nieuwe Slug-veld nog niet — dat bestond alleen in de API-respons. Voegt een read-only slugweergave onder het titelveld toe met een potlood-icoon om 'm inline te bewerken. Backend: Create/UpdateOfferingRequest accepteren nu een optionele Slug-override. Zonder expliciete waarde blijft het bestaande gedrag (auto-genereren uit titel, regenereren bij titelwijziging). Met een expliciete waarde wordt die genormaliseerd en op uniekheid gecontroleerd; een botsing geeft 409 Conflict (RFC 9457 ProblemDetails, naar het patroon van MasterControlledAvailabilityException). Frontend: de slug wordt alleen meegestuurd als de admin 'm daadwerkelijk bewerkt heeft (dirtyFields.slug), zodat een ongemoeide slug bij een titelwijziging gewoon blijft auto-regenereren. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,64 @@ public class OfferingsServiceTests
|
||||
await _repo.DidNotReceive().ExistsBySlugAsync(Arg.Any<string>(), Arg.Any<Guid?>());
|
||||
}
|
||||
|
||||
// --- Explicit slug override (admin-supplied via CMS) ---
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_NormalizesExplicitSlug()
|
||||
{
|
||||
_repo.GetMaxDisplayOrderAsync().Returns(-1);
|
||||
_repo.ExistsBySlugAsync("my-custom-slug", null).Returns(false);
|
||||
|
||||
var request = new CreateOfferingRequest(
|
||||
"Title", "Description", "€ 100", "per month", ["Feature 1"], "Contact", Slug: " My Custom Slug!! ");
|
||||
var dto = await CreateSut().CreateAsync(request, Guid.NewGuid());
|
||||
|
||||
dto.Slug.Should().Be("my-custom-slug");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Throws_WhenExplicitSlugAlreadyTaken()
|
||||
{
|
||||
_repo.GetMaxDisplayOrderAsync().Returns(-1);
|
||||
_repo.ExistsBySlugAsync("taken", null).Returns(true);
|
||||
|
||||
var request = new CreateOfferingRequest(
|
||||
"Title", "Description", "€ 100", "per month", ["Feature 1"], "Contact", Slug: "taken");
|
||||
|
||||
var act = () => CreateSut().CreateAsync(request, Guid.NewGuid());
|
||||
|
||||
await act.Should().ThrowAsync<OfferingSlugConflictException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_UsesExplicitSlug_EvenWhenTitleUnchanged()
|
||||
{
|
||||
var target = ExistingOffering();
|
||||
_repo.GetByIdAsync(target.Id).Returns(target);
|
||||
_repo.ExistsBySlugAsync("manually-set", target.Id).Returns(false);
|
||||
|
||||
var request = new UpdateOfferingRequest(
|
||||
target.Title, "Description", "€ 100", "per month", ["Feature 1"], "Contact", Slug: "manually-set");
|
||||
var dto = await CreateSut().UpdateAsync(target.Id, request, Guid.NewGuid());
|
||||
|
||||
dto!.Slug.Should().Be("manually-set");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_Throws_WhenExplicitSlugTakenByAnotherOffering()
|
||||
{
|
||||
var target = ExistingOffering();
|
||||
_repo.GetByIdAsync(target.Id).Returns(target);
|
||||
_repo.ExistsBySlugAsync("taken", target.Id).Returns(true);
|
||||
|
||||
var request = new UpdateOfferingRequest(
|
||||
target.Title, "Description", "€ 100", "per month", ["Feature 1"], "Contact", Slug: "taken");
|
||||
|
||||
var act = () => CreateSut().UpdateAsync(target.Id, request, Guid.NewGuid());
|
||||
|
||||
await act.Should().ThrowAsync<OfferingSlugConflictException>();
|
||||
}
|
||||
|
||||
// --- UpdateAsync ---
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user