Files
SIG-CM2.0/tests/SIGCM2.Application.Tests/Pricing/ChargeableChars/CreateChargeableCharConfigCommandValidatorTests.cs

178 lines
6.7 KiB
C#
Raw Permalink Normal View History

using FluentValidation.TestHelper;
using Microsoft.Extensions.Time.Testing;
using SIGCM2.Application.Pricing.ChargeableChars.Create;
using SIGCM2.Domain.Pricing.ChargeableChars;
namespace SIGCM2.Application.Tests.Pricing.ChargeableChars;
/// <summary>
/// PRC-001 — Validator tests for CreateChargeableCharConfigCommand.
/// Covers: Symbol length, Category enum, PricePerUnit > 0, ValidFrom >= today_AR.
/// </summary>
public class CreateChargeableCharConfigCommandValidatorTests
{
private readonly FakeTimeProvider _time = new(new DateTimeOffset(2026, 4, 20, 12, 0, 0, TimeSpan.Zero));
private readonly CreateChargeableCharConfigCommandValidator _validator;
private static readonly DateOnly Today = new(2026, 4, 20);
private static readonly DateOnly Yesterday = new(2026, 4, 19);
private static readonly DateOnly Tomorrow = new(2026, 4, 21);
public CreateChargeableCharConfigCommandValidatorTests()
{
_validator = new CreateChargeableCharConfigCommandValidator(_time);
}
private static CreateChargeableCharConfigCommand ValidCmd() => new(
ProductTypeId: null,
Symbol: "$",
Category: ChargeableCharCategories.Currency,
PricePerUnit: 1.0m,
ValidFrom: Today);
// ── Symbol ───────────────────────────────────────────────────────────────────
[Fact]
public void Symbol_Empty_FailsValidation()
{
var cmd = ValidCmd() with { Symbol = "" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Symbol);
}
[Fact]
public void Symbol_TooLong_FailsValidation()
{
// max 4 chars
var cmd = ValidCmd() with { Symbol = "ABCDE" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Symbol);
}
[Fact]
public void Symbol_SingleChar_Passes()
{
var cmd = ValidCmd() with { Symbol = "$" };
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.Symbol);
}
[Fact]
public void Symbol_FourChars_Passes()
{
var cmd = ValidCmd() with { Symbol = "ABCD" };
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.Symbol);
}
// ── Category ─────────────────────────────────────────────────────────────────
[Fact]
public void Category_Invalid_FailsValidation()
{
var cmd = ValidCmd() with { Category = "Unknown" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Category);
}
[Fact]
public void Category_Empty_FailsValidation()
{
var cmd = ValidCmd() with { Category = "" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Category);
}
[Theory]
[InlineData("Currency")]
[InlineData("Percentage")]
[InlineData("Exclamation")]
[InlineData("Question")]
[InlineData("Other")]
public void Category_ValidValues_Pass(string category)
{
var cmd = ValidCmd() with { Category = category };
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.Category);
}
// ── PricePerUnit ─────────────────────────────────────────────────────────────
[Fact]
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.
2026-04-21 13:27:54 -03:00
public void PricePerUnit_Zero_Passes_OptInBilling()
{
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.
2026-04-21 13:27:54 -03:00
// 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 };
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.
2026-04-21 13:27:54 -03:00
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.PricePerUnit);
}
[Fact]
public void PricePerUnit_Negative_FailsValidation()
{
var cmd = ValidCmd() with { PricePerUnit = -1m };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PricePerUnit);
}
[Fact]
public void PricePerUnit_Positive_Passes()
{
var cmd = ValidCmd() with { PricePerUnit = 0.01m };
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.PricePerUnit);
}
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.
2026-04-21 13:27:54 -03:00
// ── 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]
public void ValidFrom_InPast_FailsValidation()
{
var cmd = ValidCmd() with { ValidFrom = Yesterday };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.ValidFrom);
}
[Fact]
public void ValidFrom_Today_Passes()
{
var cmd = ValidCmd() with { ValidFrom = Today };
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.ValidFrom);
}
[Fact]
public void ValidFrom_Future_Passes()
{
var cmd = ValidCmd() with { ValidFrom = Tomorrow };
_validator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.ValidFrom);
}
// ── Happy path ────────────────────────────────────────────────────────────────
[Fact]
public void ValidCommand_PassesAllRules()
{
_validator.TestValidate(ValidCmd()).ShouldNotHaveAnyValidationErrors();
}
}