Refactor event occurrence parsing by introducing modular components for improved maintainability

This commit restructures the EventOccurrenceParser by breaking down its functionality into modular components, including EventDefinitionResolver, LineClassifier, LocationPatternMatcher, SectionHeaderMatcher, TimeLocationParser, and TimeParser. This refactoring enhances code readability and maintainability, allowing for easier updates and testing. Additionally, the TextUtil class has been updated to include input sanitization methods. Comprehensive unit tests have been added to ensure the correctness of the new parsing logic and to validate the handling of various event occurrence scenarios.
This commit is contained in:
2026-01-08 20:23:57 -05:00
parent 7ddc55f672
commit f916cfad6b
16 changed files with 1631 additions and 342 deletions
@@ -0,0 +1,107 @@
using Core.Entities;
using Core.Parsers.EventOccurrence;
using NUnit.Framework;
namespace Tests.Parsers.EventOccurrence;
[TestFixture]
public class EventDefinitionResolver_Tests
{
[Test]
public void Resolve_SpecialEventPattern_MeetTheCandidates_ReturnsCorrectDefinition()
{
// Act
var result = EventDefinitionResolver.Resolve("Meet the Candidates Session 1", null);
// Assert
Assert.That(result, Is.EqualTo(EventDefinition.MeetTheCandidates));
}
[Test]
public void Resolve_SpecialEventPattern_ChapterOfficerMeeting_ReturnsCorrectDefinition()
{
// Act
var result = EventDefinitionResolver.Resolve("Chapter Officer Meeting - MS", null);
// Assert
Assert.That(result, Is.EqualTo(EventDefinition.ChapterOfficerMeeting));
}
[Test]
public void Resolve_SpecialEventPattern_VotingDelegateMeeting_ReturnsCorrectDefinition()
{
// Act
var result = EventDefinitionResolver.Resolve("Voting Delegate Meeting", null);
// Assert
Assert.That(result, Is.EqualTo(EventDefinition.VotingDelegateMeeting));
}
[Test]
public void Resolve_SpecialEventPattern_CaseInsensitive_Works()
{
// Act
var result = EventDefinitionResolver.Resolve("MEET THE CANDIDATES", null);
// Assert
Assert.That(result, Is.EqualTo(EventDefinition.MeetTheCandidates));
}
[Test]
public void Resolve_GeneralSchedule_CurrentEventGeneralSchedule_ReturnsGeneralSchedule()
{
// Act
var result = EventDefinitionResolver.Resolve("Some Event Name", EventDefinition.GeneralSchedule);
// Assert
Assert.That(result, Is.EqualTo(EventDefinition.GeneralSchedule));
}
[Test]
public void Resolve_CurrentEventDefinition_ReturnsCurrentEvent()
{
// Arrange
var currentEvent = new EventDefinition { Name = "Test Event" };
// Act
var result = EventDefinitionResolver.Resolve("Some Occurrence", currentEvent);
// Assert
Assert.That(result, Is.EqualTo(currentEvent));
}
[Test]
public void Resolve_SpecialEventPattern_TakesPrecedenceOverCurrentEvent()
{
// Arrange
var currentEvent = new EventDefinition { Name = "Test Event" };
// Act
var result = EventDefinitionResolver.Resolve("Meet the Candidates Session 1", currentEvent);
// Assert
Assert.That(result, Is.EqualTo(EventDefinition.MeetTheCandidates));
Assert.That(result, Is.Not.EqualTo(currentEvent));
}
[Test]
public void Resolve_NoMatch_ReturnsNull()
{
// Act
var result = EventDefinitionResolver.Resolve("Unknown Event Name", null);
// Assert
Assert.That(result, Is.Null);
}
[Test]
public void Resolve_NoMatch_CurrentEventNull_ReturnsNull()
{
// Act
var result = EventDefinitionResolver.Resolve("Unknown Event Name", null);
// Assert
Assert.That(result, Is.Null);
}
}
@@ -0,0 +1,99 @@
using Core.Parsers.EventOccurrence;
using NUnit.Framework;
namespace Tests.Parsers.EventOccurrence;
[TestFixture]
public class LineClassifier_Tests
{
[Test]
public void IsEmptyLine_EmptyString_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsEmptyLine(""), Is.True);
}
[Test]
public void IsEmptyLine_WhitespaceOnly_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsEmptyLine(" "), Is.True);
Assert.That(LineClassifier.IsEmptyLine("\t\n"), Is.True);
}
[Test]
public void IsEmptyLine_NonEmpty_ReturnsFalse()
{
// Act & Assert
Assert.That(LineClassifier.IsEmptyLine("Some text"), Is.False);
}
[Test]
public void IsCommentLine_StartsWithHash_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsCommentLine("# This is a comment"), Is.True);
}
[Test]
public void IsCommentLine_NoHash_ReturnsFalse()
{
// Act & Assert
Assert.That(LineClassifier.IsCommentLine("Not a comment"), Is.False);
}
[Test]
public void IsContinuationLine_ParentheticalNote_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("(Semifinalists only)"), Is.True);
Assert.That(LineClassifier.IsContinuationLine("(Note: Some information)"), Is.True);
}
[Test]
public void IsContinuationLine_StartsWithLowercaseContinuationWord_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("the event will be held"), Is.True);
Assert.That(LineClassifier.IsContinuationLine("and participants should arrive"), Is.True);
Assert.That(LineClassifier.IsContinuationLine("be sure to register"), Is.True);
Assert.That(LineClassifier.IsContinuationLine("or contact the coordinator"), Is.True);
}
[Test]
public void IsContinuationLine_StartsWithLowercase_NonContinuationWord_ReturnsFalse()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("important: bring materials"), Is.False);
}
[Test]
public void IsContinuationLine_StartsWithUppercase_ReturnsFalse()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("Important Event March 15"), Is.False);
}
[Test]
public void IsContinuationLine_ContainsSchedulePosted_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("Schedule Posted on website"), Is.True);
}
[Test]
public void IsContinuationLine_ContainsNote_ReturnsTrue()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("Note: Additional information"), Is.True);
Assert.That(LineClassifier.IsContinuationLine("*Note: Important details"), Is.True);
}
[Test]
public void IsContinuationLine_RegularEventLine_ReturnsFalse()
{
// Act & Assert
Assert.That(LineClassifier.IsContinuationLine("Test Event March 15 3:00 p.m. Room A"), Is.False);
}
}
@@ -0,0 +1,166 @@
using Core.Parsers.EventOccurrence;
using NUnit.Framework;
namespace Tests.Parsers.EventOccurrence;
[TestFixture]
public class LocationPatternMatcher_Tests
{
[Test]
public void Match_ExactMatch_ReturnsLocation()
{
// Arrange
var patterns = new List<string> { "Room A", "Room B", "Hall C" };
// Act
var result = LocationPatternMatcher.Match("Room A", patterns);
// Assert
Assert.That(result, Is.EqualTo("Room A"));
}
[Test]
public void Match_ExactMatch_CaseInsensitive_ReturnsLocation()
{
// Arrange
var patterns = new List<string> { "Room A" };
// Act
var result = LocationPatternMatcher.Match("room a", patterns);
// Assert
Assert.That(result, Is.EqualTo("room a"));
}
[Test]
public void Match_WildcardPattern_Matches_ReturnsLocation()
{
// Arrange
var patterns = new List<string> { "Room *", "Hall *" };
// Act
var result = LocationPatternMatcher.Match("Room 101", patterns);
// Assert
Assert.That(result, Is.EqualTo("Room 101"));
}
[Test]
public void Match_WildcardPattern_MultipleMatches_ReturnsFirstMatch()
{
// Arrange
var patterns = new List<string> { "Room *", "Exhibit Hall *" };
// Act
var result = LocationPatternMatcher.Match("Room 202", patterns);
// Assert
Assert.That(result, Is.EqualTo("Room 202"));
}
[Test]
public void Match_WildcardPattern_ExhibitHall_Matches()
{
// Arrange
var patterns = new List<string> { "Exhibit Hall *" };
// Act
var result = LocationPatternMatcher.Match("Exhibit Hall C", patterns);
// Assert
Assert.That(result, Is.EqualTo("Exhibit Hall C"));
}
[Test]
public void Match_WildcardPattern_MtgRoom_Matches()
{
// Arrange
var patterns = new List<string> { "Mtg. Room *" };
// Act
var result = LocationPatternMatcher.Match("Mtg. Room 14", patterns);
// Assert
Assert.That(result, Is.EqualTo("Mtg. Room 14"));
}
[Test]
public void Match_NoMatch_ReturnsEmpty()
{
// Arrange
var patterns = new List<string> { "Room *", "Hall *" };
// Act
var result = LocationPatternMatcher.Match("Unknown Location", patterns);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void Match_EmptyLocation_ReturnsEmpty()
{
// Arrange
var patterns = new List<string> { "Room *" };
// Act
var result = LocationPatternMatcher.Match("", patterns);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void Match_WhitespaceLocation_ReturnsEmpty()
{
// Arrange
var patterns = new List<string> { "Room *" };
// Act
var result = LocationPatternMatcher.Match(" ", patterns);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void Match_EmptyPatterns_ReturnsEmpty()
{
// Arrange
var patterns = new List<string>();
// Act
var result = LocationPatternMatcher.Match("Room A", patterns);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void Match_PatternWithSpecialCharacters_EscapesCorrectly()
{
// Arrange
var patterns = new List<string> { "Room (A)" };
// Act
var result = LocationPatternMatcher.Match("Room (A)", patterns);
// Assert
Assert.That(result, Is.EqualTo("Room (A)"));
}
[Test]
public void Match_LocationWithWhitespace_Trims_ReturnsLocation()
{
// Arrange
var patterns = new List<string> { "Room *" };
// Act
var result = LocationPatternMatcher.Match(" Room 101 ", patterns);
// Assert
// LocationPatternMatcher returns the matched location after normalization (trim)
Assert.That(result, Is.EqualTo("Room 101"));
}
}
@@ -0,0 +1,162 @@
using Core.Entities;
using Core.Parsers.EventOccurrence;
using FuzzySharp;
using NUnit.Framework;
namespace Tests.Parsers.EventOccurrence;
[TestFixture]
public class SectionHeaderMatcher_Tests
{
[Test]
public void IsGeneralSchedule_GeneralScheduleLine_ReturnsTrue()
{
// Act & Assert
Assert.That(SectionHeaderMatcher.IsGeneralSchedule("General Schedule"), Is.True);
Assert.That(SectionHeaderMatcher.IsGeneralSchedule("General Schedule - MS"), Is.True);
}
[Test]
public void IsGeneralSchedule_RegularEvent_ReturnsFalse()
{
// Act & Assert
Assert.That(SectionHeaderMatcher.IsGeneralSchedule("Test Event - MS"), Is.False);
}
[Test]
public void HasSchoolLevel_ContainsMS_ReturnsTrue()
{
// Act & Assert
Assert.That(SectionHeaderMatcher.HasSchoolLevel("Test Event - MS"), Is.True);
Assert.That(SectionHeaderMatcher.HasSchoolLevel("Test Event MS"), Is.True);
}
[Test]
public void HasSchoolLevel_ContainsHS_ReturnsTrue()
{
// Act & Assert
Assert.That(SectionHeaderMatcher.HasSchoolLevel("Test Event - HS"), Is.True);
Assert.That(SectionHeaderMatcher.HasSchoolLevel("Test Event HS"), Is.True);
}
[Test]
public void HasSchoolLevel_NoSchoolLevel_ReturnsFalse()
{
// Act & Assert
Assert.That(SectionHeaderMatcher.HasSchoolLevel("Test Event"), Is.False);
}
[Test]
public void MatchEventDefinition_ExactMatch_ReturnsEvent()
{
// Arrange
var events = new List<EventDefinition>
{
new() { Name = "Test Event" },
new() { Name = "Another Event" }
};
// Act
var result = SectionHeaderMatcher.MatchEventDefinition("Test Event", events);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result!.Name, Is.EqualTo("Test Event"));
}
[Test]
public void MatchEventDefinition_CloseMatch_ReturnsEvent()
{
// Arrange
var events = new List<EventDefinition>
{
new() { Name = "Test Event" },
new() { Name = "Another Event" }
};
// Act
var result = SectionHeaderMatcher.MatchEventDefinition("Test Evnt", events); // Typo but close
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result!.Name, Is.EqualTo("Test Event"));
}
[Test]
public void MatchEventDefinition_NoMatch_ReturnsNull()
{
// Arrange
var events = new List<EventDefinition>
{
new() { Name = "Test Event" }
};
// Act
var result = SectionHeaderMatcher.MatchEventDefinition("Completely Different Event", events);
// Assert
Assert.That(result, Is.Null);
}
[Test]
public void MatchEventDefinition_EmptyEvents_ReturnsNull()
{
// Arrange
var events = new List<EventDefinition>();
// Act
var result = SectionHeaderMatcher.MatchEventDefinition("Test Event", events);
// Assert
Assert.That(result, Is.Null);
}
[Test]
public void MatchEventDefinition_MultipleMatches_ReturnsBestMatch()
{
// Arrange
var events = new List<EventDefinition>
{
new() { Name = "Test Event" },
new() { Name = "Test Event Advanced" },
new() { Name = "Another Event" }
};
// Act
var result = SectionHeaderMatcher.MatchEventDefinition("Test Event", events);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result!.Name, Is.EqualTo("Test Event")); // Exact match should win
}
[Test]
public void GetBestMatchRatio_ValidEvents_ReturnsRatio()
{
// Arrange
var events = new List<EventDefinition>
{
new() { Name = "Test Event" }
};
// Act
var ratio = SectionHeaderMatcher.GetBestMatchRatio("Test Event", events);
// Assert
Assert.That(ratio, Is.GreaterThan(50)); // Should be high for exact match
}
[Test]
public void GetBestMatchRatio_EmptyEvents_ReturnsZero()
{
// Arrange
var events = new List<EventDefinition>();
// Act
var ratio = SectionHeaderMatcher.GetBestMatchRatio("Test Event", events);
// Assert
Assert.That(ratio, Is.EqualTo(0));
}
}
@@ -0,0 +1,206 @@
using Core.Models;
using Core.Parsers.EventOccurrence;
using NUnit.Framework;
namespace Tests.Parsers.EventOccurrence;
[TestFixture]
public class TimeLocationParser_Tests
{
[Test]
public void Parse_TimeAndLocation_ExtractsBoth()
{
// Arrange
var locationConfig = new LocationParsingConfiguration
{
LocationPatterns = new List<string> { "Room *" }
};
// Act
TimeLocationParser.Parse("10:30 a.m. Room 101", locationConfig,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("10:30 a.m."));
Assert.That(location, Is.EqualTo("Room 101"));
Assert.That(locationParseSuccess, Is.True);
}
[Test]
public void Parse_TimeRangeAndLocation_ExtractsTimeRangeAndLocation()
{
// Arrange
var locationConfig = new LocationParsingConfiguration
{
LocationPatterns = new List<string> { "Room *" }
};
// Act
TimeLocationParser.Parse("10:00 a.m. - 12:00 p.m. Room 202", locationConfig,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("10:00 a.m. - 12:00 p.m."));
Assert.That(location, Is.EqualTo("Room 202"));
Assert.That(locationParseSuccess, Is.True);
}
[Test]
public void Parse_NOONAndLocation_ExtractsBoth()
{
// Arrange
var locationConfig = new LocationParsingConfiguration
{
LocationPatterns = new List<string> { "Hall *" }
};
// Act
TimeLocationParser.Parse("NOON Hall C", locationConfig,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("NOON"));
Assert.That(location, Is.EqualTo("Hall C"));
Assert.That(locationParseSuccess, Is.True);
}
[Test]
public void Parse_TimeOnly_NoLocation()
{
// Arrange
var locationConfig = new LocationParsingConfiguration
{
LocationPatterns = new List<string> { "Room *" }
};
// Act
TimeLocationParser.Parse("3:00 p.m.", locationConfig,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("3:00 p.m."));
Assert.That(location, Is.Empty);
Assert.That(locationParseSuccess, Is.True); // No location is valid
}
[Test]
public void Parse_LocationNotMatchingPattern_StillReturnsLocation_ReportsFailure()
{
// Arrange
var locationConfig = new LocationParsingConfiguration
{
LocationPatterns = new List<string> { "Room *" }
};
// Act
TimeLocationParser.Parse("10:00 a.m. Unknown Location", locationConfig,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("10:00 a.m."));
Assert.That(location, Is.EqualTo("Unknown Location"));
Assert.That(locationParseSuccess, Is.False);
}
[Test]
public void Parse_LocationWithTimeComponent_CleansTimeComponent()
{
// Arrange
var locationConfig = new LocationParsingConfiguration
{
LocationPatterns = new List<string> { "Exhibit Hall *" }
};
// Act
TimeLocationParser.Parse("10:00 a.m. - 12:15 p.m. Exhibit Hall C", locationConfig,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("10:00 a.m. - 12:15 p.m."));
Assert.That(location, Is.EqualTo("Exhibit Hall C"));
}
[Test]
public void Parse_NoLocationConfig_StillExtractsTimeAndLocation()
{
// Act
TimeLocationParser.Parse("3:00 p.m. Room A", null,
out string time, out string location, out bool locationParseSuccess);
// Assert
Assert.That(time, Is.EqualTo("3:00 p.m."));
Assert.That(location, Is.EqualTo("Room A"));
Assert.That(locationParseSuccess, Is.False); // No patterns to match against
}
[Test]
public void CleanLocationText_RemovesTimeAtStart()
{
// Act
var result = TimeLocationParser.CleanLocationText("- 12:15 p.m. Exhibit Hall C");
// Assert
Assert.That(result, Is.EqualTo("Exhibit Hall C"));
}
[Test]
public void CleanLocationText_RemovesTimeWithoutDash()
{
// Act
var result = TimeLocationParser.CleanLocationText("12:15 p.m. Exhibit Hall C");
// Assert
Assert.That(result, Is.EqualTo("Exhibit Hall C"));
}
[Test]
public void CleanLocationText_RemovesNOONAtStart()
{
// Act
var result = TimeLocationParser.CleanLocationText("- NOON Exhibit Hall C");
// Assert
Assert.That(result, Is.EqualTo("Exhibit Hall C"));
}
[Test]
public void CleanLocationText_OnlyTime_ReturnsEmpty()
{
// Act
var result = TimeLocationParser.CleanLocationText("12:15 p.m.");
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void CleanLocationText_EmptyString_ReturnsEmpty()
{
// Act
var result = TimeLocationParser.CleanLocationText("");
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void CleanLocationText_WhitespaceOnly_ReturnsEmpty()
{
// Act
var result = TimeLocationParser.CleanLocationText(" ");
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void CleanLocationText_NoTimeComponent_ReturnsOriginal()
{
// Act
var result = TimeLocationParser.CleanLocationText("Exhibit Hall C");
// Assert
Assert.That(result, Is.EqualTo("Exhibit Hall C"));
}
}
@@ -0,0 +1,156 @@
using Core.Parsers.EventOccurrence;
using NUnit.Framework;
namespace Tests.Parsers.EventOccurrence;
[TestFixture]
public class TimeParser_Tests
{
[Test]
public void Parse_NOON_Returns12PM()
{
// Act
var result = TimeParser.Parse("NOON");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(12, 0, 0)));
}
[Test]
public void Parse_TBD_ReturnsMidnight()
{
// Act
var result = TimeParser.Parse("TBD");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(0, 0, 0)));
}
[Test]
public void Parse_TBD_CaseInsensitive_ReturnsMidnight()
{
// Act
var result = TimeParser.Parse("tbd");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(0, 0, 0)));
}
[Test]
public void Parse_AMTime_ReturnsCorrectTime()
{
// Act
var result = TimeParser.Parse("10:30 a.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(10, 30, 0)));
}
[Test]
public void Parse_PMTime_ReturnsCorrectTime()
{
// Act
var result = TimeParser.Parse("3:45 p.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(15, 45, 0)));
}
[Test]
public void Parse_TimeRange_ExtractsStartTime()
{
// Act
var result = TimeParser.Parse("10:00 a.m. - 12:00 p.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(10, 0, 0)));
}
[Test]
public void Parse_TimeRangeWithNOON_ExtractsStartTime()
{
// Act
var result = TimeParser.Parse("10:30 a.m. - NOON");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(10, 30, 0)));
}
[Test]
public void Parse_TimeWithoutMinutes_ReturnsCorrectTime()
{
// Act
var result = TimeParser.Parse("3 p.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(15, 0, 0)));
}
[Test]
public void Parse_TimeWithoutColon_ReturnsCorrectTime()
{
// Act
var result = TimeParser.Parse("1030 a.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(10, 30, 0)));
}
[Test]
public void Parse_12PM_Returns12PM_NotMidnight()
{
// Act
var result = TimeParser.Parse("12:00 p.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(12, 0, 0)));
}
[Test]
public void Parse_12AM_ReturnsMidnight()
{
// Act
var result = TimeParser.Parse("12:00 a.m.");
// Assert
Assert.That(result, Is.EqualTo(new TimeOnly(0, 0, 0)));
}
[Test]
public void Parse_InvalidFormat_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => TimeParser.Parse("invalid time format"));
}
[Test]
public void ExtractStartTime_Range_ReturnsStartTime()
{
// Act
var result = TimeParser.ExtractStartTime("10:00 a.m. - 12:00 p.m.");
// Assert
Assert.That(result, Is.EqualTo("10:00 a.m."));
}
[Test]
public void ExtractStartTime_NoRange_ReturnsOriginal()
{
// Act
var result = TimeParser.ExtractStartTime("3:00 p.m.");
// Assert
Assert.That(result, Is.EqualTo("3:00 p.m."));
}
[Test]
public void ExtractStartTime_RangeWithNOON_ReturnsStartTime()
{
// Act
var result = TimeParser.ExtractStartTime("10:30 a.m. - NOON");
// Assert
Assert.That(result, Is.EqualTo("10:30 a.m."));
}
}
+146
View File
@@ -0,0 +1,146 @@
using Core.Utility;
using NUnit.Framework;
namespace Tests.Utility;
[TestFixture]
public class TextUtil_Tests
{
[Test]
public void ParseDate_ValidInput_ReturnsCorrectDate()
{
// Arrange & Act
var result = TextUtil.ParseDate("January", "15", 2025);
// Assert
Assert.That(result, Is.EqualTo(new DateOnly(2025, 1, 15)));
}
[Test]
public void ParseDate_AllMonths_AreSupported()
{
// Arrange
var months = new[] { "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" };
var expectedDates = new[]
{
new DateOnly(2025, 1, 15),
new DateOnly(2025, 2, 15),
new DateOnly(2025, 3, 15),
new DateOnly(2025, 4, 15),
new DateOnly(2025, 5, 15),
new DateOnly(2025, 6, 15),
new DateOnly(2025, 7, 15),
new DateOnly(2025, 8, 15),
new DateOnly(2025, 9, 15),
new DateOnly(2025, 10, 15),
new DateOnly(2025, 11, 15),
new DateOnly(2025, 12, 15),
};
// Act & Assert
for (int i = 0; i < months.Length; i++)
{
var result = TextUtil.ParseDate(months[i], "15", 2025);
Assert.That(result, Is.EqualTo(expectedDates[i]),
$"Month {months[i]} should parse correctly");
}
}
[Test]
public void ParseDate_CaseInsensitive_Works()
{
// Arrange & Act
var result1 = TextUtil.ParseDate("JANUARY", "15", 2025);
var result2 = TextUtil.ParseDate("january", "15", 2025);
var result3 = TextUtil.ParseDate("JaNuArY", "15", 2025);
// Assert
Assert.That(result1, Is.EqualTo(new DateOnly(2025, 1, 15)));
Assert.That(result2, Is.EqualTo(new DateOnly(2025, 1, 15)));
Assert.That(result3, Is.EqualTo(new DateOnly(2025, 1, 15)));
}
[Test]
public void ParseDate_InvalidMonth_ThrowsArgumentException()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentException>(() => TextUtil.ParseDate("InvalidMonth", "15", 2025));
}
[Test]
public void ParseDate_InvalidDay_ThrowsFormatException()
{
// Arrange, Act & Assert
Assert.Throws<FormatException>(() => TextUtil.ParseDate("January", "abc", 2025));
}
[Test]
public void ParseDate_InvalidDate_ThrowsArgumentOutOfRangeException()
{
// Arrange, Act & Assert - February 30 doesn't exist
Assert.Throws<ArgumentOutOfRangeException>(() => TextUtil.ParseDate("February", "30", 2025));
}
[Test]
public void ParseDate_LeapYear_February29_Works()
{
// Arrange & Act
var result = TextUtil.ParseDate("February", "29", 2024); // 2024 is a leap year
// Assert
Assert.That(result, Is.EqualTo(new DateOnly(2024, 2, 29)));
}
[Test]
public void ParseDate_NonLeapYear_February29_Throws()
{
// Arrange, Act & Assert - 2025 is not a leap year
Assert.Throws<ArgumentOutOfRangeException>(() => TextUtil.ParseDate("February", "29", 2025));
}
[Test]
public void ParseDate_SingleDigitDay_Works()
{
// Arrange & Act
var result = TextUtil.ParseDate("March", "3", 2025);
// Assert
Assert.That(result, Is.EqualTo(new DateOnly(2025, 3, 3)));
}
[Test]
public void ParseDate_DifferentYears_Works()
{
// Arrange & Act
var result2024 = TextUtil.ParseDate("January", "1", 2024);
var result2025 = TextUtil.ParseDate("January", "1", 2025);
var result2026 = TextUtil.ParseDate("January", "1", 2026);
// Assert
Assert.That(result2024, Is.EqualTo(new DateOnly(2024, 1, 1)));
Assert.That(result2025, Is.EqualTo(new DateOnly(2025, 1, 1)));
Assert.That(result2026, Is.EqualTo(new DateOnly(2026, 1, 1)));
}
[Test]
public void ParseDate_FirstDayOfMonth_Works()
{
// Arrange & Act
var result = TextUtil.ParseDate("December", "1", 2025);
// Assert
Assert.That(result, Is.EqualTo(new DateOnly(2025, 12, 1)));
}
[Test]
public void ParseDate_LastDayOfMonth_Works()
{
// Arrange & Act
var result = TextUtil.ParseDate("January", "31", 2025);
// Assert
Assert.That(result, Is.EqualTo(new DateOnly(2025, 1, 31)));
}
}