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); } } }