Files
dmolinari ded76fcdc7 feat(domain): WordCounterService + WordCountResult + ChargeableCharConfig entity + exceptions (PRC-001)
- WordCounterService: pure domain service, 7-step algorithm (null/empty fast path → length check → emoji detection via Rune.EnumerateRunes → count specials before replace → replace specials+hyphens → collapse whitespace → tokenize)
- WordCountResult: sealed record with TotalWords + IReadOnlyDictionary<string,int> SpecialCharCounts
- 4 domain exceptions extending DomainException: EmojiDetectedException, WordCountValidationException, ChargeableCharConfigInvalidException, ChargeableCharConfigForwardOnlyException
- ChargeableCharConfig: rich entity with Create factory (invariants), Rehydrate reconstructor, ScheduleNewPrice (forward-only, returns new entity), Deactivate (idempotent)
- ChargeableCharCategories: enum-as-string constants (Currency, Percentage, Exclamation, Question, Other)
- DomainTimeProviderExtensions: internal GetArgentinaToday helper (mirrors Application.Common without creating Domain→Application dependency)
- 60 new tests: 25 golden cases all GREEN, 12 entity invariant tests, 12 exception tests, 5 WordCountResult tests, 6 ChargeableCharConfig entity tests
2026-04-20 12:13:06 -03:00

57 lines
1.8 KiB
C#

using FluentAssertions;
using SIGCM2.Domain.Pricing.WordCounter;
namespace SIGCM2.Application.Tests.Domain.Pricing.WordCounter;
/// <summary>
/// PRC-001 — T2.2 Domain unit tests for WordCountResult value object.
/// </summary>
public sealed class WordCountResultTests
{
[Fact]
public void WordCountResult_RecordEquality_SameValues_AreEqual()
{
var dictA = new Dictionary<string, int> { ["Currency"] = 1 };
var dictB = new Dictionary<string, int> { ["Currency"] = 1 };
var a = new WordCountResult(3, dictA);
var b = new WordCountResult(3, dictB);
// Records compare by value — TotalWords and SpecialCharCounts ref equality
// (not deep dict equality for records), but TotalWords equality is guaranteed.
a.TotalWords.Should().Be(b.TotalWords);
}
[Fact]
public void WordCountResult_SpecialCharCounts_IsIReadOnlyDictionary()
{
var result = new WordCountResult(0, new Dictionary<string, int>());
result.SpecialCharCounts.Should().BeAssignableTo<IReadOnlyDictionary<string, int>>();
}
[Fact]
public void WordCountResult_MissingKey_ReturnsZeroViaGetValueOrDefault()
{
var result = new WordCountResult(3, new Dictionary<string, int> { ["Currency"] = 1 });
result.SpecialCharCounts.GetValueOrDefault("Percentage").Should().Be(0);
}
[Fact]
public void WordCountResult_EmptySpecialCharCounts_ReturnsEmpty()
{
var result = new WordCountResult(0, new Dictionary<string, int>());
result.SpecialCharCounts.Should().BeEmpty();
}
[Fact]
public void WordCountResult_TotalWords_IsCorrect()
{
var result = new WordCountResult(5, new Dictionary<string, int>());
result.TotalWords.Should().Be(5);
}
}