Files
chapter-organizer/Tests/Parsers/StudentNotesFieldParser_Tests.cs
T
poprhythmandCursor 4cfd85b902 feat: import leftover student fields into notes and show them on the roster
Store leftover CSV columns on hidden student notes, move catalog import to /events/import, and persist Students index columns from Chapter Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 23:55:03 -04:00

197 lines
7.0 KiB
C#

using System.Text;
using Core.Notes;
using Core.Parsers;
using Tests.Builders;
namespace Tests.Parsers;
[TestFixture]
public class StudentNotesFieldParser_Tests
{
[SetUp]
public void SetUp()
{
BuilderExtensions.ResetAllBuilders();
}
[Test]
public void Parse_MatchesStudentAndReportsUnmatched()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Student Name,Interview Time,Application
"Chittenden, Aria",3:20-3:35,x
Nobody Here,1:00-1:15,x
""";
var result = Parse(csv, [aria]);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Matches, Has.Count.EqualTo(1));
Assert.That(result.Matches[0].Student, Is.SameAs(aria));
Assert.That(result.Matches[0].Merge.Changed, Is.True);
Assert.That(result.Issues, Has.Count.EqualTo(1));
Assert.That(result.Issues[0].RawStudentName, Is.EqualTo("Nobody Here"));
}
[Test]
public void Parse_SameExistingNote_HasNoChanges()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
var existing = ImportedFieldsTable.Merge(null,
[
new ImportedField("Interview Time", "3:20-3:35"),
new ImportedField("Application", "Yes")
]).Markdown;
const string csv = """
Student Name,Interview Time,Application
Aria Chittenden,3:20-3:35,x
""";
var result = Parse(csv, [aria], new Dictionary<int, string?> { [aria.Id] = existing });
Assert.That(result.Matches, Has.Count.EqualTo(1));
Assert.That(result.Matches[0].Merge.Changed, Is.False);
Assert.That(result.StudentsWithChanges, Is.EqualTo(0));
}
[Test]
public void Parse_MissingStudentNameColumn_IsError()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Name,Interview Time
Aria Chittenden,3:20-3:35
""";
var result = Parse(csv, [aria]);
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Errors, Has.Some.Contains("Student Name"));
Assert.That(result.Matches, Is.Empty);
}
[Test]
public void Parse_PreservesFieldsNotInCsv()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
var existing = ImportedFieldsTable.Merge(null,
[
new ImportedField("Interview Time", "1:00-1:15"),
new ImportedField("Teacher Rec 1", "Fuqua")
]).Markdown;
const string csv = """
Student Name,Interview Time
Aria Chittenden,3:20-3:35
""";
var result = Parse(csv, [aria], new Dictionary<int, string?> { [aria.Id] = existing });
var fields = ImportedFieldsTable.ParseFields(result.Matches[0].Merge.Markdown);
Assert.That(result.Matches[0].Merge.Changed, Is.True);
Assert.That(fields.Single(f => f.Name == "Interview Time").Value, Is.EqualTo("3:20-3:35"));
Assert.That(fields.Single(f => f.Name == "Teacher Rec 1").Value, Is.EqualTo("Fuqua"));
}
[Test]
public void Parse_X_BecomesYes_AndBlankFieldsRoundTrip()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Student Name,Application,Teacher Rec 3
Aria Chittenden,x,
""";
var first = Parse(csv, [aria]);
var markdown = first.Matches[0].Merge.Markdown;
Assert.That(ImportedFieldsTable.GetFieldValue(markdown, "Application"), Is.EqualTo("Yes"));
Assert.That(ImportedFieldsTable.GetFieldValue(markdown, "Teacher Rec 3"), Is.EqualTo(string.Empty));
var second = Parse(csv, [aria], new Dictionary<int, string?> { [aria.Id] = markdown });
Assert.That(second.Matches[0].Merge.Changed, Is.False);
Assert.That(second.StudentsWithChanges, Is.EqualTo(0));
}
[Test]
public void Parse_DuplicateStudentRows_LastValueWins_OneMatch()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Student Name,Interview Time,Teacher Rec 1
Aria Chittenden,1:00-1:15,Fuqua
"Chittenden, Aria",3:20-3:35,Young
""";
var result = Parse(csv, [aria]);
var fields = ImportedFieldsTable.ParseFields(result.Matches[0].Merge.Markdown);
Assert.That(result.Matches, Has.Count.EqualTo(1));
Assert.That(fields.Single(f => f.Name == "Interview Time").Value, Is.EqualTo("3:20-3:35"));
Assert.That(fields.Single(f => f.Name == "Teacher Rec 1").Value, Is.EqualTo("Young"));
}
[Test]
public void Parse_NameOnlyCsv_WarnsAndMatchesWithoutFieldChanges()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Student Name
Aria Chittenden
""";
var result = Parse(csv, [aria]);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Warnings, Has.Some.Contains("No leftover field columns"));
Assert.That(result.Matches, Has.Count.EqualTo(1));
Assert.That(result.Matches[0].Merge.Changed, Is.False);
}
[Test]
public void Parse_ExcludesRosterAndRankingColumns()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Student Name,Grade,TSA year,State ID,Regional ID,National ID,Officer,1,2,TOTAL # OF EVENTS,Interview Time,Application
Aria Chittenden,6,1st,,,,,Coding,JSS,2,3:20-3:35,x
""";
var result = Parse(csv, [aria]);
var fields = ImportedFieldsTable.ParseFields(result.Matches[0].Merge.Markdown);
Assert.That(result.FieldNames, Is.EquivalentTo(["Interview Time", "Application"]));
Assert.That(fields.Select(f => f.Name), Is.EquivalentTo(["Interview Time", "Application"]));
Assert.That(fields.Single(f => f.Name == "Application").Value, Is.EqualTo("Yes"));
Assert.That(fields.Any(f => f.Name is "Grade" or "1" or "Officer"), Is.False);
}
[Test]
public void Parse_RosterOnlyCsv_HasNoLeftoverFields()
{
var aria = StudentBuilder.Create("Aria", "Chittenden").Build();
const string csv = """
Student Name,Grade,TSA year,State ID,Regional ID,National ID
Aria Chittenden,6,1st,,,
""";
var result = Parse(csv, [aria]);
Assert.That(result.FieldNames, Is.Empty);
Assert.That(result.Matches[0].Merge.Changed, Is.False);
Assert.That(result.Warnings, Has.Some.Contains("No leftover field columns"));
}
private static Core.Models.StudentNotesImportResult Parse(
string csv,
ICollection<Core.Entities.Student> students,
IReadOnlyDictionary<int, string?>? notes = null)
{
using var reader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(csv)));
using var parser = new StudentNotesFieldParser(reader);
return parser.Parse(students, notes ?? new Dictionary<int, string?>());
}
}