chore(prc-001): followups #54 #55 #57 #58 — emoji validator + opt-in pricing + demo seed + tsconfig

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:
2026-04-21 13:27:54 -03:00
parent 0eab947975
commit 3e7c4bfde9
8 changed files with 190 additions and 17 deletions

View File

@@ -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 14 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 ────────────────────────