using Core.Entities;
using Core.Models;
using Core.Parsers;
namespace Tests.Parsers;
///
/// Integration tests for EventOccurrenceParser using real test data files.
///
public class EventOccurrenceParser_Tests
{
#region Helper Methods
///
/// Checks if a line contains a High School event marker.
///
private static bool IsHighSchoolEvent(string line)
{
return line.Contains("– HS", StringComparison.OrdinalIgnoreCase) ||
line.Contains(" - HS", StringComparison.OrdinalIgnoreCase) ||
line.Contains("- HS", StringComparison.OrdinalIgnoreCase);
}
///
/// Checks if a line contains a Middle School event marker.
///
private static bool IsMiddleSchoolEvent(string line)
{
return line.Contains("– MS", StringComparison.OrdinalIgnoreCase) ||
line.Contains(" - MS", StringComparison.OrdinalIgnoreCase) ||
line.Contains("- MS", StringComparison.OrdinalIgnoreCase);
}
///
/// Determines if an issue is expected (HS-related) or fixable.
///
private static bool IsExpectedIssue(ParsingIssue issue, List fileLines, int currentLineIndex)
{
// Check if the issue line itself is an HS event header
if (IsHighSchoolEvent(issue.LineContent))
return true;
// For MissingEventDefinition issues, check if we're in an HS section
if (issue.IssueType == ParsingIssueType.MissingEventDefinition)
{
// Look backwards to find the most recent section header
for (int i = currentLineIndex - 1; i >= 0 && i >= currentLineIndex - 20; i--)
{
if (i < fileLines.Count)
{
var line = fileLines[i].Trim();
if (IsHighSchoolEvent(line))
return true;
if (IsMiddleSchoolEvent(line))
return false; // Found MS section, so this is fixable
}
}
}
return false;
}
///
/// Categorizes issues into expected (HS-related) and fixable.
///
private static (List Expected, List Fixable) CategorizeIssues(
List issues, List fileLines)
{
var expected = new List();
var fixable = new List();
foreach (var issue in issues)
{
var lineIndex = issue.LineNumber - 1; // Convert to 0-based index
if (IsExpectedIssue(issue, fileLines, lineIndex))
{
expected.Add(issue);
}
else
{
fixable.Add(issue);
}
}
return (expected, fixable);
}
///
/// Gets sample lines for a list of issues.
///
private static List GetSampleLines(List issues, int count = 5)
{
return issues
.Take(count)
.Select(i => $" Line {i.LineNumber}: {i.LineContent}")
.ToList();
}
///
/// Counts HS vs MS event sections in the file.
///
private static (int HighSchool, int MiddleSchool) CountEventSections(List fileLines)
{
int hsCount = 0;
int msCount = 0;
foreach (var line in fileLines)
{
var trimmed = line.Trim();
if (IsHighSchoolEvent(trimmed))
hsCount++;
else if (IsMiddleSchoolEvent(trimmed))
msCount++;
}
return (hsCount, msCount);
}
#endregion
[Test]
public void ParseNationalsTest()
{
var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceNationalsFileInfo(), events);
var result = parser.Parse();
var dictionary = result.Occurrences;
Console.WriteLine($"Occurrence, Month, Date, Time, Location");
foreach (var @event in events)
{
Console.WriteLine($"{@event.Name}");
if (!dictionary.ContainsKey(@event))
{
Console.WriteLine("!!! eventDefinition not found " + @event.Name);
continue;
}
var eventOccurrences = dictionary[@event];
foreach (var eo in eventOccurrences)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("General Schedule");
if (dictionary.ContainsKey(EventDefinition.GeneralSchedule))
{
foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime))
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Meet the Candidates");
if (dictionary.ContainsKey(EventDefinition.MeetTheCandidates))
{
foreach (var eo in dictionary[EventDefinition.MeetTheCandidates])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Chapter Officer Meeting");
if (dictionary.ContainsKey(EventDefinition.ChapterOfficerMeeting))
{
foreach (var eo in dictionary[EventDefinition.ChapterOfficerMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Voting Delegate Meeting");
if (dictionary.ContainsKey(EventDefinition.VotingDelegateMeeting))
{
foreach (var eo in dictionary[EventDefinition.VotingDelegateMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Assert.Pass();
}
[Test]
public void ParseStatesTest()
{
var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(TestEntityHandler.GetEventOccurrenceStateFileInfo(), events);
var result = parser.Parse();
var dictionary = result.Occurrences;
Console.WriteLine($"Occurrence, Month, Date, Time, Location");
foreach (var @event in events)
{
Console.WriteLine($"{@event.Name}");
if (!dictionary.ContainsKey(@event))
{
Console.WriteLine("!!! eventDefinition not found " + @event.Name);
continue;
}
var eventOccurrences = dictionary[@event];
foreach (var eo in eventOccurrences)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("General Schedule");
if (dictionary.ContainsKey(EventDefinition.GeneralSchedule))
{
foreach (var eo in dictionary[EventDefinition.GeneralSchedule].OrderBy(occurrence => occurrence.StartTime))
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Meet the Candidates");
if (dictionary.ContainsKey(EventDefinition.MeetTheCandidates))
{
foreach (var eo in dictionary[EventDefinition.MeetTheCandidates])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Chapter Officer Meeting");
if (dictionary.ContainsKey(EventDefinition.ChapterOfficerMeeting))
{
foreach (var eo in dictionary[EventDefinition.ChapterOfficerMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Console.WriteLine("Voting Delegate Meeting");
if (dictionary.ContainsKey(EventDefinition.VotingDelegateMeeting))
{
foreach (var eo in dictionary[EventDefinition.VotingDelegateMeeting])
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
Assert.Pass();
}
[Test]
public void Analyze_2025Nationals_ParsingResults()
{
// Arrange
var events = TestEntityHandler.GetEvents();
var fileInfo = TestEntityHandler.GetEventOccurrenceNationalsFileInfo();
var parser = new EventOccurrenceParser(fileInfo, events);
// Act
var result = parser.Parse();
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Load file lines for analysis
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== 2025 TSA Nationals Competition Event Times Analysis ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
Console.WriteLine($"\n--- Special Events Found ---");
if (result.Occurrences.ContainsKey(EventDefinition.GeneralSchedule))
Console.WriteLine($" GeneralSchedule: {result.Occurrences[EventDefinition.GeneralSchedule].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.MeetTheCandidates))
Console.WriteLine($" MeetTheCandidates: {result.Occurrences[EventDefinition.MeetTheCandidates].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($" ChapterOfficerMeeting: {result.Occurrences[EventDefinition.ChapterOfficerMeeting].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.VotingDelegateMeeting))
Console.WriteLine($" VotingDelegateMeeting: {result.Occurrences[EventDefinition.VotingDelegateMeeting].Count} occurrences");
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, 5);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (unmatchedLines.Any())
{
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(10);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
// Test passes if no exceptions were thrown
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
}
[Test]
public void Analyze_2025State_ParsingResults()
{
// Arrange
var events = TestEntityHandler.GetEvents();
var fileInfo = TestEntityHandler.GetEventOccurrenceStateFileInfo();
var parser = new EventOccurrenceParser(fileInfo, events);
// Act
var result = parser.Parse();
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Load file lines for analysis
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== 2025 TN TSA State Competition Event Times Analysis ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
Console.WriteLine($"\n--- Special Events Found ---");
if (result.Occurrences.ContainsKey(EventDefinition.GeneralSchedule))
Console.WriteLine($" GeneralSchedule: {result.Occurrences[EventDefinition.GeneralSchedule].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.MeetTheCandidates))
Console.WriteLine($" MeetTheCandidates: {result.Occurrences[EventDefinition.MeetTheCandidates].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($" ChapterOfficerMeeting: {result.Occurrences[EventDefinition.ChapterOfficerMeeting].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.VotingDelegateMeeting))
Console.WriteLine($" VotingDelegateMeeting: {result.Occurrences[EventDefinition.VotingDelegateMeeting].Count} occurrences");
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, 5);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (unmatchedLines.Any())
{
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(10);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
// Test passes if no exceptions were thrown
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
}
[Test]
public void Analyze_2024State_ParsingResults()
{
// Arrange
var events = TestEntityHandler.GetEvents();
var fileInfo = TestEntityHandler.GetEventOccurrenceState2024FileInfo();
var parser = new EventOccurrenceParser(fileInfo, events);
// Act
var result = parser.Parse();
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Load file lines for analysis
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var totalLines = fileLines.Count;
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// Categorize issues
var (expectedIssues, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
// Count event sections
var (hsSections, msSections) = CountEventSections(fileLines);
// Group issues by type
var issuesByType = result.Issues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.ToList());
var expectedByType = expectedIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
var fixableByType = fixableIssues.GroupBy(i => i.IssueType).ToDictionary(g => g.Key, g => g.Count());
// Output analysis
Console.WriteLine($"\n=== 2024 TN TSA State Competition Event Times Analysis ===");
Console.WriteLine($"\n--- Summary Statistics ---");
Console.WriteLine($"Total lines in file: {totalLines}");
Console.WriteLine($"Total occurrences parsed: {totalParsed}");
Console.WriteLine($"Total issues found: {result.Issues.Count}");
Console.WriteLine($" Expected issues (HS-related): {expectedIssues.Count} ({100.0 * expectedIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($" Fixable issues: {fixableIssues.Count} ({100.0 * fixableIssues.Count / Math.Max(1, result.Issues.Count):F1}%)");
Console.WriteLine($"Event sections: HS={hsSections}, MS={msSections}");
Console.WriteLine($"Events with occurrences: {result.Occurrences.Count}");
Console.WriteLine($"\n--- Special Events Found ---");
if (result.Occurrences.ContainsKey(EventDefinition.GeneralSchedule))
Console.WriteLine($" GeneralSchedule: {result.Occurrences[EventDefinition.GeneralSchedule].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.MeetTheCandidates))
Console.WriteLine($" MeetTheCandidates: {result.Occurrences[EventDefinition.MeetTheCandidates].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($" ChapterOfficerMeeting: {result.Occurrences[EventDefinition.ChapterOfficerMeeting].Count} occurrences");
if (result.Occurrences.ContainsKey(EventDefinition.VotingDelegateMeeting))
Console.WriteLine($" VotingDelegateMeeting: {result.Occurrences[EventDefinition.VotingDelegateMeeting].Count} occurrences");
Console.WriteLine($"\n--- Issue Breakdown by Type ---");
foreach (var kvp in issuesByType.OrderByDescending(x => x.Value.Count))
{
var issueType = kvp.Key;
var allIssues = kvp.Value;
expectedByType.TryGetValue(issueType, out var expectedCount);
fixableByType.TryGetValue(issueType, out var fixableCount);
Console.WriteLine($"\n{issueType}:");
Console.WriteLine($" Total: {allIssues.Count} (Expected: {expectedCount}, Fixable: {fixableCount})");
if (fixableCount > 0)
{
var fixableOfType = fixableIssues.Where(i => i.IssueType == issueType).ToList();
var samples = GetSampleLines(fixableOfType, 5);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
// Pattern Analysis - LocationParseFailure issues are no longer created (pattern matching removed)
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (unmatchedLines.Any())
{
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(10);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
// Test passes if no exceptions were thrown
Assert.Pass($"Successfully parsed {totalParsed} occurrences with {result.Issues.Count} issues ({fixableIssues.Count} fixable)");
}
[Test]
public void Parse_Section_Lines64To92_ChildrensStoriesToConstructionChallenge()
{
// Arrange
// Extract lines 64-92 from the test file - contains MS and HS events with various formats
var allLines = File.ReadAllLines(TestEntityHandler.GetEventOccurrenceStateFileInfo().FullName);
var sectionLines = allLines.Skip(63).Take(29).ToArray(); // Lines 64-92 (0-indexed: 63-91)
var sectionContent = string.Join("\n", sectionLines);
var tempFile = EventOccurrenceParserTestHelpers.CreateTempFile(sectionContent);
var events = TestEntityHandler.GetEvents();
var parser = new EventOccurrenceParser(tempFile, events);
try
{
// Act
var result = parser.Parse();
// Assert - Should parse without exceptions
Assert.That(result, Is.Not.Null, "Parser should return a result");
// Count occurrences by event type
var totalOccurrences = result.Occurrences.Values.Sum(list => list.Count);
// Verify MS events are parsed
var childrensStories = events.FirstOrDefault(e => e.Name.Contains("Children's Stories", StringComparison.OrdinalIgnoreCase));
var coding = events.FirstOrDefault(e => e.Name == "Coding");
var communityServiceVideo = events.FirstOrDefault(e => e.Name.Contains("Community Service Video", StringComparison.OrdinalIgnoreCase));
var constructionChallenge = events.FirstOrDefault(e => e.Name.Contains("Construction Challenge", StringComparison.OrdinalIgnoreCase));
// Count expected MS occurrences:
// Children's Stories – MS: 5 occurrences (lines 65-69)
// Coding – MS: 2 occurrences (lines 76-77)
// Community Service Video – MS: 4 occurrences (lines 79-82)
// Construction Challenge – MS: 5 occurrences (lines 88-92)
// Total expected MS occurrences: 16
var msEventCount = 0;
if (childrensStories != null && result.Occurrences.ContainsKey(childrensStories))
msEventCount += result.Occurrences[childrensStories].Count;
if (coding != null && result.Occurrences.ContainsKey(coding))
msEventCount += result.Occurrences[coding].Count;
if (communityServiceVideo != null && result.Occurrences.ContainsKey(communityServiceVideo))
msEventCount += result.Occurrences[communityServiceVideo].Count;
if (constructionChallenge != null && result.Occurrences.ContainsKey(constructionChallenge))
msEventCount += result.Occurrences[constructionChallenge].Count;
// When no school level is set, HS events should be processed (not skipped)
// Verify HS events are processed or handled appropriately
var hsIssues = result.Issues.Where(i =>
i.LineContent.Contains("Coding – HS") ||
i.LineContent.Contains("CAD") && i.LineContent.Contains("HS") ||
i.LineNumber >= 72 && i.LineNumber <= 86 && IsHighSchoolEvent(i.LineContent)
).ToList();
// Verify HS section headers are NOT tracked in SkippedHSSectionHeaders when no school level is set
var skippedHSHeaders = result.SkippedHSSectionHeaders;
// Verify continuation lines are skipped
// Line 70 starts with "*The" - this enters continuation mode and both line 70 and 71 should be skipped
var continuationLineIssues = result.Issues.Where(i =>
i.LineContent.Contains("books of semifinalist teams") ||
i.LineContent.Contains("be returned to teams")
).ToList();
// Verify specific time formats are parsed correctly
var noonOccurrence = result.Occurrences.Values
.SelectMany(list => list)
.FirstOrDefault(eo => eo.Time.Contains("NOON", StringComparison.OrdinalIgnoreCase));
var lateTimeOccurrence = result.Occurrences.Values
.SelectMany(list => list)
.FirstOrDefault(eo => eo.Time.Contains("11:59", StringComparison.OrdinalIgnoreCase));
// Output detailed analysis
Console.WriteLine($"\n=== Section Lines 64-92 Parsing Results ===");
Console.WriteLine($"Total occurrences parsed: {totalOccurrences}");
Console.WriteLine($"MS event occurrences: {msEventCount}");
Console.WriteLine($"Total issues: {result.Issues.Count}");
Console.WriteLine($"HS-related issues: {hsIssues.Count}");
Console.WriteLine($"Skipped HS section headers: {skippedHSHeaders.Count}");
Console.WriteLine($"Continuation line issues: {continuationLineIssues.Count}");
Console.WriteLine($"\n--- Issue Types ---");
foreach (var issueType in result.Issues.GroupBy(i => i.IssueType))
{
Console.WriteLine($" {issueType.Key}: {issueType.Count()}");
}
// Assertions
Assert.That(totalOccurrences, Is.GreaterThan(0), "Should parse at least some occurrences");
Assert.That(msEventCount, Is.GreaterThanOrEqualTo(14), "Should parse most MS occurrences (at least 14 out of 16)");
// When no school level is set, HS events should be processed (not skipped)
// HS events may create issues if they don't match event definitions, which is expected
// HS section headers should NOT be tracked when no school level is set
Assert.That(skippedHSHeaders, Has.Count.EqualTo(0), "HS section headers should NOT be tracked when no school level is set");
// Line 70 (starts with "*The") enters continuation mode and both line 70 and 71 should be skipped without issues
Assert.That(continuationLineIssues, Has.Count.EqualTo(0),
"Continuation lines starting with '*' and subsequent lines should be skipped without issues");
Assert.That(noonOccurrence, Is.Not.Null, "Should parse NOON time format");
Assert.That(lateTimeOccurrence, Is.Not.Null, "Should parse 11:59 p.m. time format");
// Verify specific locations are parsed
if (childrensStories != null && result.Occurrences.ContainsKey(childrensStories))
{
var locations = result.Occurrences[childrensStories]
.Select(eo => eo.Location)
.Where(loc => !string.IsNullOrWhiteSpace(loc))
.ToList();
Assert.That(locations, Has.Count.GreaterThan(0), "Children's Stories should have locations parsed");
}
// Test passes with detailed information
Assert.Pass($"Successfully parsed section: {totalOccurrences} occurrences, {result.Issues.Count} issues, {msEventCount} MS events");
}
finally
{
EventOccurrenceParserTestHelpers.CleanupTempFile(tempFile);
}
}
[Test]
public void Parse_BiotechnologyMSAndHS_HSOccurrencesNotAssociatedWithMS()
{
// Arrange
// This test verifies that HS events (like "Biotechnology Design – HS") are not incorrectly
// associated with MS events (like "Biotechnology – MS") even if fuzzy matching finds a match
var testContent = "Biotechnology – MS\n" +
"Submit Entry April 3 8 a.m. – 9 a.m. Exhibit Hall C\n" +
"Judging April 3 9 a.m. – 5 p.m. Exhibit Hall C\n" +
"Biotechnology Design – HS\n" +
"Submit Entry April 3 8 a.m. – 9:00 a.m. Exhibit Hall C\n" +
"Judging April 3 9 a.m. – 5 p.m. Exhibit Hall C\n" +
"Pick-up April 4 5 p.m. – 5:30 p.m. Exhibit Hall C";
var tempFile = EventOccurrenceParserTestHelpers.CreateTempFile(testContent);
var events = new[] { EventOccurrenceParserTestHelpers.CreateTestEvent("Biotechnology") };
var parser = new EventOccurrenceParser(tempFile, events);
try
{
// Act
var result = parser.Parse();
// Assert
var biotechnology = events.FirstOrDefault(e => e.Name == "Biotechnology");
Assert.That(biotechnology, Is.Not.Null, "Biotechnology event should exist");
// When no school level is set, all events (MS and HS) should be processed
// HS section header should NOT be skipped (note: normalized to regular hyphen)
Assert.That(result.SkippedHSSectionHeaders, Does.Not.Contain("Biotechnology Design - HS"),
"HS section header should NOT be in SkippedHSSectionHeaders when no school level is set");
// With no school level filtering, both MS and HS events are processed
if (result.Occurrences.ContainsKey(biotechnology))
{
var allOccurrences = result.Occurrences[biotechnology];
// With no school level set, we process all occurrences (both MS and HS)
// Expected: 2 MS occurrences (Submit Entry, Judging) + 3 HS occurrences (Submit Entry, Judging, Pick-up) = 5 total
Assert.That(allOccurrences, Has.Count.EqualTo(5),
"Should have all 5 occurrences (2 MS + 3 HS) when no school level is set. " +
$"Found {allOccurrences.Count} occurrences total.");
// Verify all expected occurrence names are present
var occurrenceNames = allOccurrences.Select(o => o.Name).ToList();
Assert.That(occurrenceNames, Does.Contain("Submit Entry"), "Should have Submit Entry occurrences");
Assert.That(occurrenceNames, Does.Contain("Judging"), "Should have Judging occurrences");
Assert.That(occurrenceNames, Does.Contain("Pick-up"), "Should have Pick-up occurrence");
}
Assert.Pass("All events processed when no school level is set");
}
finally
{
EventOccurrenceParserTestHelpers.CleanupTempFile(tempFile);
}
}
}