using FluentAssertions; using Marathon.Domain.ValueObjects; namespace Marathon.Domain.Tests.ValueObjects; public sealed class EventIdTests { [Fact] public void Constructor_CreatesInstance_WhenValueIsNonEmpty() { var id = new EventId("26456117"); id.Value.Should().Be("26456117"); } [Theory] [InlineData("")] [InlineData(" ")] [InlineData("\t")] public void Constructor_ThrowsArgumentException_WhenValueIsEmptyOrWhitespace(string value) { var act = () => new EventId(value); act.Should().Throw() .WithParameterName("value"); } [Fact] public void Constructor_ThrowsArgumentException_WhenValueIsNull() { var act = () => new EventId(null!); act.Should().Throw() .WithParameterName("value"); } [Theory] [InlineData("a/b")] // forward slash (path separator) [InlineData("a\\b")] // back slash (path separator) [InlineData("..")] // parent-dir traversal [InlineData("../etc/passwd")] [InlineData("evt\n1")] // control char (newline) [InlineData("evt\r1")] // control char (CR) [InlineData("evt\0id")] // control char (NUL) public void Constructor_ThrowsArgumentException_WhenValueHasDangerousCharacters(string value) { var act = () => new EventId(value); act.Should().Throw() .WithParameterName("value"); } [Fact] public void Constructor_ThrowsArgumentException_WhenValueExceedsMaxLength() { var act = () => new EventId(new string('1', 129)); act.Should().Throw() .WithParameterName("value"); } [Theory] [InlineData("26456117")] // numeric (marathonbet.by) [InlineData("evt-1")] // hyphenated [InlineData("event_1")] // underscore [InlineData("evt.1")] // single dot is fine — only ".." is rejected [InlineData("AB12cd34")] // mixed-case alphanumeric (forward-compat) public void Constructor_Accepts_ValidAndForwardCompatIds(string value) { var id = new EventId(value); id.Value.Should().Be(value); } [Fact] public void ToString_ReturnsValue() { var id = new EventId("12345"); id.ToString().Should().Be("12345"); } [Fact] public void Equality_IsValueBased() { var a = new EventId("26456117"); var b = new EventId("26456117"); a.Should().Be(b); } }