Resuelve 4 de los followups creados post-archive de PRC-001: #55 — Decisión de negocio (2026-04-21): emojis NO se permiten en Symbol config. - WordCounterService.ContainsEmoji(string): helper publico que reutiliza los rangos Unicode de IsEmojiRune (Emoticons, Pictographs, Dingbats, VS-16, ZWJ). - CreateChargeableCharConfigCommandValidator: regla .Must que rechaza emoji en Symbol con mensaje claro. Defensiva: cubre clientes directos al API (Postman, adversariales) mas alla del SymbolInput blocker del frontend. - Tests: 5 emojis positivos (smile/car/fire/heart VS-16/sun) + 8 plain symbols ($, %, !, ¡, @, €, ##, ABCD) + actualizacion del Api test E2E (Post_WithEmojiSymbol). #57 — Alineacion FluentValidation con opt-in billing (CK_Price_NonNegative >= 0). - CreateChargeableCharConfigCommandValidator.PricePerUnit: GreaterThan(0) -> GreaterThanOrEqualTo(0). Mensaje explica el significado: 0 = no cobra. - Tests actualizados: PricePerUnit_Zero ahora Passes (era Fails). Negative sigue fallando. Api e2e usa -1 para el caso de rechazo. #58 — tsconfig ignoreDeprecations + MSW handler (parte a). - src/web/tsconfig.json: agrega "ignoreDeprecations": "6.0" para silenciar el warning TS5101 del baseUrl deprecated en TS 6.x. - (El MSW handler de /api/v1/admin/product-types no aplica — los tests ya mockean ProductTypeSelect directamente; warning residual no existe.) #54 — Seeder demo de overrides ficticios per-ProductType (V025). - database/migrations/V025__seed_chargeable_char_overrides_demo.sql: MERGE idempotente que crea overrides de ChargeableCharConfig para ProductTypes Clasificado/Notables/Fúnebres si existen en la DB. Precios ficticios ($ 5-8, % 3-5, ! 2-4, ¡ 2-4). No-op si los tipos no estan seedados (sera cuando PRD-008 haga seed de los 12 legacy). - V025_ROLLBACK.sql: elimina overrides demo preservando globales. - Aplicado en SIGCM2, SIGCM2_Test_App, SIGCM2_Test_Api. - database/README.md: V025 agregada al indice. Tests: 1659 .NET (1310 Application + 349 Api) + 510 vitest — todos GREEN. Closes #54, #55, #57, #58.
This commit is contained in:
@@ -243,9 +243,11 @@ public sealed class ChargeableCharConfigControllerTests : IAsyncLifetime
|
||||
[Fact]
|
||||
public async Task Post_InvalidPrice_Returns400ValidationFailure()
|
||||
{
|
||||
// PRC-001 followup #57: PricePerUnit >= 0 is now valid (opt-in billing).
|
||||
// Use -1 to still exercise the negative rejection path.
|
||||
var token = GetAdminToken();
|
||||
using var req = BuildRequest(HttpMethod.Post, "/api/v1/admin/chargeable-chars",
|
||||
body: new { productTypeId = (long?)null, symbol = "£", category = "Currency", pricePerUnit = 0m, validFrom = TomorrowStr() },
|
||||
body: new { productTypeId = (long?)null, symbol = "£", category = "Currency", pricePerUnit = -1m, validFrom = TomorrowStr() },
|
||||
token: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
resp.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||
@@ -276,25 +278,22 @@ public sealed class ChargeableCharConfigControllerTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PRC-001-R2.7 — Emoji symbols are explicitly DEFERRED per spec.
|
||||
/// The ChargeableCharConfig Symbol field accepts any 1–4 char value including emojis.
|
||||
/// "😀" in C# has string.Length = 2 (UTF-16 surrogate pair), so it passes MaximumLength(4).
|
||||
/// This test documents the deferred behavior: emoji in Symbol is accepted at config level.
|
||||
/// The EmojiDetectedException applies only to WordCounterService (ad text, not config symbols).
|
||||
/// PRC-001 followup #55 — Business decision (2026-04-21): emoji Symbols are NOT allowed.
|
||||
/// Validator delegates to WordCounterService.ContainsEmoji which checks every rune against
|
||||
/// the Unicode emoji ranges (Emoticons, Pictographs, Dingbats, VS-16, ZWJ, etc.).
|
||||
/// This provides a defensive check beyond the frontend SymbolInput blocker — direct API
|
||||
/// calls (Postman, adversarial clients) can't bypass it.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Post_WithEmojiSymbol_Returns201_BecauseEmojiRejectionIsDeferred()
|
||||
public async Task Post_WithEmojiSymbol_Returns400()
|
||||
{
|
||||
var token = GetAdminToken();
|
||||
// "😀" has C# string.Length == 2 (UTF-16 surrogate pair) — passes MaximumLength(4).
|
||||
// Emoji rejection for config Symbols is deferred to PRC-002+ per spec R2.7.
|
||||
using var req = BuildRequest(HttpMethod.Post, "/api/v1/admin/chargeable-chars",
|
||||
body: new { productTypeId = (long?)null, symbol = "😀", category = "Currency", pricePerUnit = 1.0m, validFrom = TomorrowStr() },
|
||||
token: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
// Accepted: emoji symbols deferred per spec. If business later rejects them, update validator + this test.
|
||||
resp.StatusCode.Should().Be(HttpStatusCode.Created,
|
||||
because: "emoji symbol rejection is deferred (spec R2.7). Symbol '😀' has length 2 in C# (UTF-16) → passes MaximumLength(4)");
|
||||
resp.StatusCode.Should().Be(HttpStatusCode.BadRequest,
|
||||
because: "emoji symbols are rejected by validator via WordCounterService.ContainsEmoji (#55)");
|
||||
}
|
||||
|
||||
// ── PUT /api/v1/admin/chargeable-chars/{id}/price ────────────────────────
|
||||
|
||||
@@ -92,10 +92,13 @@ public class CreateChargeableCharConfigCommandValidatorTests
|
||||
// ── PricePerUnit ─────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void PricePerUnit_Zero_FailsValidation()
|
||||
public void PricePerUnit_Zero_Passes_OptInBilling()
|
||||
{
|
||||
// PRC-001 followup #57: opt-in billing — 0 is a valid price meaning
|
||||
// "this Symbol exists but does NOT charge extra for this ProductType".
|
||||
// Aligned with DB check constraint CK_ChargeableCharConfig_Price_NonNegative (>= 0).
|
||||
var cmd = ValidCmd() with { PricePerUnit = 0m };
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PricePerUnit);
|
||||
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.PricePerUnit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -112,6 +115,35 @@ public class CreateChargeableCharConfigCommandValidatorTests
|
||||
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.PricePerUnit);
|
||||
}
|
||||
|
||||
// ── Symbol emoji rejection (PRC-001 followup #55) ───────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData("😀")] // smiley face (U+1F600, Emoticons block)
|
||||
[InlineData("🚗")] // car (U+1F697, Transport block)
|
||||
[InlineData("🔥")] // fire (U+1F525, Misc Symbols & Pictographs)
|
||||
[InlineData("❤️")] // red heart + VS-16 (U+2764 U+FE0F)
|
||||
[InlineData("☀️")] // sun (U+2600 in Misc Symbols block)
|
||||
public void Symbol_WithEmoji_FailsValidation(string emojiSymbol)
|
||||
{
|
||||
var cmd = ValidCmd() with { Symbol = emojiSymbol };
|
||||
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Symbol);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("$")]
|
||||
[InlineData("%")]
|
||||
[InlineData("!")]
|
||||
[InlineData("¡")]
|
||||
[InlineData("@")]
|
||||
[InlineData("€")]
|
||||
[InlineData("##")]
|
||||
[InlineData("ABCD")]
|
||||
public void Symbol_WithoutEmoji_Passes(string plainSymbol)
|
||||
{
|
||||
var cmd = ValidCmd() with { Symbol = plainSymbol };
|
||||
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.Symbol);
|
||||
}
|
||||
|
||||
// ── ValidFrom ────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user