Files
maraphon-app/tests/Marathon.Domain.Tests/ValueObjects/EventIdTests.cs
T
alexei.dolgolyov 0683e348ba fix(security): bound bet-notes length + harden EventId against path/control chars
Two defense-in-depth findings from the I-series security review (both safe today,
neither currently exploitable):
- AddBetForm.Notes was unbounded free-text into SQLite; add a 2000-char sanity cap
  in IsValid (covers both the add and edit paths), alongside the existing stake/rate
  caps.
- EventId only rejected empty/whitespace; now also reject path separators, '..'
  traversal, control/newline chars and over-length input so no current-or-future
  consumer that builds a path/filename/log line from an id can be tricked. The
  charset stays open for forward-compat with non-numeric bookmaker ids.
2026-05-29 14:14:12 +03:00

84 lines
2.5 KiB
C#

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<ArgumentException>()
.WithParameterName("value");
}
[Fact]
public void Constructor_ThrowsArgumentException_WhenValueIsNull()
{
var act = () => new EventId(null!);
act.Should().Throw<ArgumentException>()
.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<ArgumentException>()
.WithParameterName("value");
}
[Fact]
public void Constructor_ThrowsArgumentException_WhenValueExceedsMaxLength()
{
var act = () => new EventId(new string('1', 129));
act.Should().Throw<ArgumentException>()
.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);
}
}