Files
chapter-organizer/Tests/Parsers/EventOccurrenceParser_Tests.cs
T
poprhythm f400813667 Update EventOccurrence parsing to use EventOccurrenceParseGroup for improved data structure
This commit refactors the EventOccurrence parsing logic to utilize the EventOccurrenceParseGroup class, enhancing the organization of parsed occurrences by grouping them based on event definitions and optional section levels. The changes include updates to the EventOccurrenceParseResult, EventOccurrenceParser, and EventOccurrenceParserService to accommodate the new grouping structure. Additionally, tests are modified to reflect these changes, ensuring that the parsing functionality remains intact and accurate. This refactor improves data handling and aligns with the overall architecture of the application.
2026-04-04 21:55:44 -04:00

613 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 Constants
private const int MaxLookbackLines = 20;
private const int TopUnmatchedPatternsCount = 10;
private const int SampleIssuesCount = 5;
private const int SectionTestStartLineIndex = 63; // Line 64 (1-based) = index 63 (0-based)
private const int SectionTestLineCount = 29; // Lines 64-92 inclusive
#endregion
#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) return false;
// Look backwards to find the most recent section header
for (int i = currentLineIndex - 1; i >= 0 && i >= currentLineIndex - MaxLookbackLines; i--)
{
if (i >= fileLines.Count) continue;
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 = SampleIssuesCount)
{
return issues
.Take(count)
.Select(i => $" Line {i.LineNumber}: {i.LineContent}")
.ToList();
}
/// <summary>
/// Writes special events summary to console.
/// </summary>
private static void WriteSpecialEventsSummary(IDictionary<EventOccurrenceParseGroup, List<Core.Entities.EventOccurrence>> occurrences)
{
Console.WriteLine($"\n--- Special Events Found ---");
static int CountFor(IDictionary<EventOccurrenceParseGroup, List<Core.Entities.EventOccurrence>> occ, EventDefinition def) =>
occ.Where(kvp => ReferenceEquals(kvp.Key.EventDefinition, def)).Sum(kvp => kvp.Value.Count);
var gs = CountFor(occurrences, EventDefinition.GeneralSchedule);
if (gs > 0)
Console.WriteLine($" GeneralSchedule: {gs} occurrences");
var mtc = CountFor(occurrences, EventDefinition.MeetTheCandidates);
if (mtc > 0)
Console.WriteLine($" MeetTheCandidates: {mtc} occurrences");
var com = CountFor(occurrences, EventDefinition.ChapterOfficerMeeting);
if (com > 0)
Console.WriteLine($" ChapterOfficerMeeting: {com} occurrences");
var vdm = CountFor(occurrences, EventDefinition.VotingDelegateMeeting);
if (vdm > 0)
Console.WriteLine($" VotingDelegateMeeting: {vdm} occurrences");
}
/// <summary>
/// Writes issue breakdown by type to console.
/// </summary>
private static void WriteIssueBreakdown(
Dictionary<ParsingIssueType, List<ParsingIssue>> issuesByType,
Dictionary<ParsingIssueType, int> expectedByType,
Dictionary<ParsingIssueType, int> fixableByType,
List<ParsingIssue> fixableIssues)
{
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, SampleIssuesCount);
Console.WriteLine($" Sample fixable issues:");
foreach (var sample in samples)
{
Console.WriteLine(sample);
}
}
}
}
/// <summary>
/// Writes unmatched line analysis to console.
/// </summary>
private static void WriteUnmatchedLineAnalysis(List<ParsingIssue> fixableIssues)
{
var unmatchedLines = fixableIssues.Where(i => i.IssueType == ParsingIssueType.UnmatchedLine).ToList();
if (!unmatchedLines.Any()) return;
Console.WriteLine($"\n--- Unmatched Line Analysis ---");
var unmatchedPatterns = unmatchedLines
.GroupBy(i => i.LineContent.Trim())
.OrderByDescending(g => g.Count())
.Take(TopUnmatchedPatternsCount);
Console.WriteLine($"Top unmatched line formats:");
foreach (var pattern in unmatchedPatterns)
{
Console.WriteLine($" \"{pattern.Key}\" (appears {pattern.Count()} times)");
}
}
/// <summary>
/// Analyzes and reports parsing results for a file.
/// </summary>
private static void AnalyzeParsingResults(
EventOccurrenceParserResult result,
FileInfo fileInfo,
string title)
{
// 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=== {title} ===");
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}");
WriteSpecialEventsSummary(result.Occurrences);
WriteIssueBreakdown(issuesByType, expectedByType, fixableByType, fixableIssues);
WriteUnmatchedLineAnalysis(fixableIssues);
}
/// <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);
}
/// <summary>
/// Writes special events to console output.
/// </summary>
private static void WriteSpecialEvents(IDictionary<EventOccurrenceParseGroup, List<Core.Entities.EventOccurrence>> occurrences)
{
static List<Core.Entities.EventOccurrence> ListFor(IDictionary<EventOccurrenceParseGroup, List<Core.Entities.EventOccurrence>> occ, EventDefinition def) =>
occ.Where(kvp => ReferenceEquals(kvp.Key.EventDefinition, def)).SelectMany(kvp => kvp.Value).ToList();
Console.WriteLine("General Schedule");
foreach (var eo in ListFor(occurrences, EventDefinition.GeneralSchedule).OrderBy(o => o.StartTime))
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
Console.WriteLine("Meet the Candidates");
foreach (var eo in ListFor(occurrences, EventDefinition.MeetTheCandidates))
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
Console.WriteLine("Chapter Officer Meeting");
foreach (var eo in ListFor(occurrences, EventDefinition.ChapterOfficerMeeting))
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
Console.WriteLine("Voting Delegate Meeting");
foreach (var eo in ListFor(occurrences, EventDefinition.VotingDelegateMeeting))
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
#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}");
var eventOccurrences = dictionary
.Where(kvp => ReferenceEquals(kvp.Key.EventDefinition, @event))
.SelectMany(kvp => kvp.Value)
.ToList();
if (eventOccurrences.Count == 0)
{
Console.WriteLine($"!!! eventDefinition not found {@event.Name}");
continue;
}
foreach (var eo in eventOccurrences)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
WriteSpecialEvents(dictionary);
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}");
var eventOccurrences = dictionary
.Where(kvp => ReferenceEquals(kvp.Key.EventDefinition, @event))
.SelectMany(kvp => kvp.Value)
.ToList();
if (eventOccurrences.Count == 0)
{
Console.WriteLine($"!!! eventDefinition not found {@event.Name}");
continue;
}
foreach (var eo in eventOccurrences)
{
Console.WriteLine($"\t{eo.StartTime.DayOfWeek} {eo.Time}, {eo.Name}, {eo.Location}");
}
}
WriteSpecialEvents(dictionary);
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");
AnalyzeParsingResults(result, fileInfo, "2025 TSA Nationals Competition Event Times Analysis");
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var (_, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// 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");
AnalyzeParsingResults(result, fileInfo, "2025 TN TSA State Competition Event Times Analysis");
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var (_, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// 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");
AnalyzeParsingResults(result, fileInfo, "2024 TN TSA State Competition Event Times Analysis");
var fileLines = File.ReadAllLines(fileInfo.FullName).ToList();
var (_, fixableIssues) = CategorizeIssues(result.Issues, fileLines);
var totalParsed = result.Occurrences.Values.Sum(list => list.Count);
// 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(SectionTestStartLineIndex).Take(SectionTestLineCount).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.TryGetValue(new EventOccurrenceParseGroup(childrensStories, SchoolLevel.MiddleSchool), out var csOccurrences))
msEventCount += csOccurrences.Count;
if (coding != null && result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(coding, SchoolLevel.MiddleSchool), out var codingOccurrences))
msEventCount += codingOccurrences.Count;
if (communityServiceVideo != null && result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(communityServiceVideo, SchoolLevel.MiddleSchool), out var csvOccurrences))
msEventCount += csvOccurrences.Count;
if (constructionChallenge != null && result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(constructionChallenge, SchoolLevel.MiddleSchool), out var ccOccurrences))
msEventCount += ccOccurrences.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 SkippedSectionHeaders when no school level is set
var skippedHeaders = result.SkippedSectionHeaders;
// 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 section headers: {skippedHeaders.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(skippedHeaders, Has.Count.EqualTo(0), "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.TryGetValue(new EventOccurrenceParseGroup(childrensStories, SchoolLevel.MiddleSchool), out var childrensStoriesOccurrences))
{
var locations = childrensStoriesOccurrences
.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.SkippedSectionHeaders, Does.Not.Contain("Biotechnology Design - HS"),
"HS section header should NOT be in SkippedSectionHeaders when no school level is set");
// With no school level filtering, both MS and HS events are processed
result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(biotechnology!, SchoolLevel.MiddleSchool), out var msOccurrences);
result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(biotechnology!, SchoolLevel.HighSchool), out var hsOccurrences);
msOccurrences ??= [];
hsOccurrences ??= [];
Assert.That(msOccurrences, Has.Count.EqualTo(2), "MS section should have 2 occurrences");
Assert.That(hsOccurrences, Has.Count.EqualTo(3), "HS section should have 3 occurrences");
var allNames = msOccurrences.Concat(hsOccurrences).Select(o => o.Name).ToList();
Assert.That(allNames, Does.Contain("Submit Entry"));
Assert.That(allNames, Does.Contain("Judging"));
Assert.That(allNames, Does.Contain("Pick-up"));
Assert.Pass("All events processed when no school level is set");
}
finally
{
EventOccurrenceParserTestHelpers.CleanupTempFile(tempFile);
}
}
[Test]
public void Parse_SameEvent_HS_then_MS_ProducesTwoGroups()
{
var testContent = "Prepared Speech - HS\n" +
"Extemporaneous Speech Presentation Room (Heat 1) April 10 10 a.m. - 12:30 p.m. Meeting Room 4\n" +
"Prepared Speech - MS\n" +
"Prelims Presentation Room April 10 10:30 a.m. - 12:30 p.m. Meeting Room 10";
var tempFile = EventOccurrenceParserTestHelpers.CreateTempFile(testContent);
var events = new[] { EventOccurrenceParserTestHelpers.CreateTestEvent("Prepared Speech") };
var parser = new EventOccurrenceParser(tempFile, events);
try
{
var result = parser.Parse();
Assert.That(result.Issues, Has.Count.EqualTo(0));
var def = events[0];
Assert.That(result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(def, SchoolLevel.HighSchool), out var hsList), Is.True);
Assert.That(result.Occurrences.TryGetValue(new EventOccurrenceParseGroup(def, SchoolLevel.MiddleSchool), out var msList), Is.True);
Assert.That(hsList, Has.Count.EqualTo(1));
Assert.That(msList, Has.Count.EqualTo(1));
Assert.That(hsList![0].Name, Does.Contain("Extemporaneous"));
Assert.That(msList![0].Name, Does.Contain("Prelims"));
}
finally
{
EventOccurrenceParserTestHelpers.CleanupTempFile(tempFile);
}
}
}