This commit updates the EventOccurrenceParser to ensure that high school (HS) events are not incorrectly associated with middle school (MS) events during parsing. The logic now gracefully skips HS section headers, preventing any fuzzy matching from leading to incorrect associations. Additionally, a new unit test has been added to verify that HS occurrences are correctly excluded from MS event occurrences, ensuring the integrity of the parsing process.
721 lines
33 KiB
C#
721 lines
33 KiB
C#
using Core.Entities;
|
||
using Core.Models;
|
||
using Core.Parsers;
|
||
|
||
namespace Tests.Parsers;
|
||
|
||
/// <summary>
|
||
/// Integration tests for EventOccurrenceParser using real test data files.
|
||
/// </summary>
|
||
public class EventOccurrenceParser_Tests
|
||
{
|
||
#region Helper Methods
|
||
|
||
/// <summary>
|
||
/// Checks if a line contains a High School event marker.
|
||
/// </summary>
|
||
private static bool IsHighSchoolEvent(string line)
|
||
{
|
||
return line.Contains("– HS", StringComparison.OrdinalIgnoreCase) ||
|
||
line.Contains(" - HS", StringComparison.OrdinalIgnoreCase) ||
|
||
line.Contains("- HS", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks if a line contains a Middle School event marker.
|
||
/// </summary>
|
||
private static bool IsMiddleSchoolEvent(string line)
|
||
{
|
||
return line.Contains("– MS", StringComparison.OrdinalIgnoreCase) ||
|
||
line.Contains(" - MS", StringComparison.OrdinalIgnoreCase) ||
|
||
line.Contains("- MS", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines if an issue is expected (HS-related) or fixable.
|
||
/// </summary>
|
||
private static bool IsExpectedIssue(ParsingIssue issue, List<string> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Categorizes issues into expected (HS-related) and fixable.
|
||
/// </summary>
|
||
private static (List<ParsingIssue> Expected, List<ParsingIssue> Fixable) CategorizeIssues(
|
||
List<ParsingIssue> issues, List<string> fileLines)
|
||
{
|
||
var expected = new List<ParsingIssue>();
|
||
var fixable = new List<ParsingIssue>();
|
||
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets sample lines for a list of issues.
|
||
/// </summary>
|
||
private static List<string> GetSampleLines(List<ParsingIssue> issues, int count = 5)
|
||
{
|
||
return issues
|
||
.Take(count)
|
||
.Select(i => $" Line {i.LineNumber}: {i.LineContent}")
|
||
.ToList();
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Counts HS vs MS event sections in the file.
|
||
/// </summary>
|
||
private static (int HighSchool, int MiddleSchool) CountEventSections(List<string> 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;
|
||
|
||
// Verify HS events are skipped gracefully (no issues should be created for them)
|
||
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 tracked in SkippedHSSectionHeaders
|
||
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)");
|
||
// HS events should not create issues - they should be skipped gracefully
|
||
Assert.That(hsIssues, Has.Count.EqualTo(0), "HS events should be skipped gracefully without creating issues");
|
||
// HS section headers should be tracked
|
||
Assert.That(skippedHSHeaders, Has.Count.GreaterThanOrEqualTo(2), "Should track at least 2 HS section headers (Coding - HS, CAD Architecture - HS, CAD Engineering - HS)");
|
||
// 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");
|
||
|
||
// MS occurrences should be parsed
|
||
if (result.Occurrences.ContainsKey(biotechnology))
|
||
{
|
||
var msOccurrences = result.Occurrences[biotechnology];
|
||
Assert.That(msOccurrences, Has.Count.EqualTo(2),
|
||
"Should have 2 MS occurrences (Submit Entry and Judging)");
|
||
|
||
// Verify MS occurrences have correct names
|
||
var msNames = msOccurrences.Select(o => o.Name).ToList();
|
||
Assert.That(msNames, Does.Contain("Submit Entry"), "MS should have Submit Entry");
|
||
Assert.That(msNames, Does.Contain("Judging"), "MS should have Judging");
|
||
}
|
||
|
||
// HS section header should be skipped (note: normalized to regular hyphen)
|
||
Assert.That(result.SkippedHSSectionHeaders, Does.Contain("Biotechnology Design - HS"),
|
||
"HS section header should be in SkippedHSSectionHeaders");
|
||
|
||
// HS occurrences should NOT be associated with Biotechnology MS event
|
||
if (result.Occurrences.ContainsKey(biotechnology))
|
||
{
|
||
var allOccurrences = result.Occurrences[biotechnology];
|
||
var hsOccurrenceNames = new[] { "Submit Entry", "Judging", "Pick-up" };
|
||
var hsOccurrencesFound = allOccurrences.Where(o =>
|
||
o.Name == "Submit Entry" && o.Date.Contains("April 3") ||
|
||
o.Name == "Judging" && o.Date.Contains("April 3") ||
|
||
o.Name == "Pick-up" && o.Date.Contains("April 4")).ToList();
|
||
|
||
// The HS occurrences should not be in the MS event's occurrences
|
||
// We expect only 2 MS occurrences, not 5 (2 MS + 3 HS)
|
||
Assert.That(allOccurrences, Has.Count.LessThanOrEqualTo(2),
|
||
"HS occurrences should not be associated with MS event. " +
|
||
$"Found {allOccurrences.Count} occurrences, expected 2 MS only.");
|
||
}
|
||
|
||
Assert.Pass("HS events correctly skipped and not associated with MS events");
|
||
}
|
||
finally
|
||
{
|
||
EventOccurrenceParserTestHelpers.CleanupTempFile(tempFile);
|
||
}
|
||
}
|
||
} |