using QuestionsApi.Services.Flashcards; using Xunit; namespace QuestionsApi.Tests; public sealed class FlashcardCsvTests { [Fact] public void ParseRows_HandlesQuotedMultilineFieldsAndEmbeddedCommas() { var csv = "stable_id,deck,type,prompt,answer,extra,tags,source_question_external_id\n" + "abc-1,Cardio,basic,\"What is the first line,\nsecond line?\",\"Answer, with comma\",,tag1;tag2,q-1\n"; var rows = FlashcardCsv.ParseRows(csv); Assert.Equal(2, rows.Count); var data = rows[1]; Assert.Equal("abc-1", data[0]); Assert.Equal("What is the first line,\nsecond line?", data[3]); Assert.Equal("Answer, with comma", data[4]); Assert.Equal("tag1;tag2", data[6]); } [Fact] public void ParseRows_HandlesEscapedDoubleQuotes() { var csv = "a,b\n\"she said \"\"hi\"\"\",plain\n"; var rows = FlashcardCsv.ParseRows(csv); Assert.Equal("she said \"hi\"", rows[1][0]); Assert.Equal("plain", rows[1][1]); } [Fact] public void ParseRows_SupportsUnicodeContent() { var csv = "a,b\néè中文,\U0001F600\n"; var rows = FlashcardCsv.ParseRows(csv); Assert.Equal("éè中文", rows[1][0]); Assert.Equal("😀", rows[1][1]); } [Fact] public void ParseRows_HandlesFileWithoutTrailingNewline() { var csv = "a,b\n1,2"; var rows = FlashcardCsv.ParseRows(csv); Assert.Equal(2, rows.Count); Assert.Equal(["1", "2"], rows[1]); } [Fact] public void WriteRow_QuotesFieldsContainingSpecialCharacters() { var row = FlashcardCsv.WriteRow(["plain", "has,comma", "has\"quote", "has\nnewline"]); Assert.Equal("plain,\"has,comma\",\"has\"\"quote\",\"has\nnewline\"", row); } [Fact] public void WriteThenParse_RoundTrips() { var original = new[] { "id-1", "Deck, Name", "basic", "prompt with \"quotes\" and\nnewline", "answer", "", "a;b", "q-1" }; var line = FlashcardCsv.WriteRow(original); var rows = FlashcardCsv.ParseRows("header\n" + line + "\n"); Assert.Equal(original, rows[1]); } }