Files
step3/api/QuestionsApi.Tests/FlashcardImportExportModelTests.cs
hz 48a8c8361d Add flashcards feature with spaced repetition scheduling
Adds a full flashcards system: FSRS-based scheduling, cloze/basic
card types, CSV import/export, media storage, and study UI, wired
into navigation and the dashboard alongside existing question flows.
2026-07-21 15:08:52 -05:00

93 lines
3.7 KiB
C#

using System.Text.Json;
using QuestionsApi.Models;
using QuestionsApi.Services.Flashcards;
using Xunit;
namespace QuestionsApi.Tests;
public sealed class FlashcardImportExportModelTests
{
[Fact]
public void JsonExport_RoundTrips_NotesCardsAndReviewHistory()
{
var export = new FlashcardJsonExport
{
SchemaVersion = 1,
Settings = new FlashcardSettings { UserId = "user-1", NewCardsPerDay = 20, ReviewsPerDay = 200 },
Decks = [new FlashcardDeck { Id = "deck-1", UserId = "user-1", Name = "Cardio", IsDefault = true }],
Notes =
[
new FlashcardJsonExportNote
{
Note = new FlashcardNote
{
Id = "note-1", UserId = "user-1", DeckId = "deck-1", StableId = "stable-1",
Type = FlashcardTypes.Cloze, Prompt = "{{c1::Paris}} is the capital of France",
Tags = ["geography"]
},
Cards =
[
new FlashcardSchedule
{
Id = "card-1", UserId = "user-1", NoteId = "note-1", Variant = 1,
State = FlashcardScheduleStates.Review, Stability = 12.3, Difficulty = 4.5,
DueUtc = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc)
}
],
ReviewLogs =
[
new FlashcardReviewLog
{
Id = "log-1", UserId = "user-1", CardId = "card-1", Rating = FlashcardRatings.Good,
ReviewedUtc = new DateTime(2025, 12, 31, 0, 0, 0, DateTimeKind.Utc)
}
],
Images = [new FlashcardJsonExportImage { AssetId = "asset-1", FileName = "x.png", ContentType = "image/png", Base64Bytes = Convert.ToBase64String([1, 2, 3]) }]
}
]
};
var json = JsonSerializer.Serialize(export);
var roundTripped = JsonSerializer.Deserialize<FlashcardJsonExport>(json);
Assert.NotNull(roundTripped);
var note = Assert.Single(roundTripped!.Notes);
Assert.Equal("stable-1", note.Note.StableId);
Assert.Equal(FlashcardTypes.Cloze, note.Note.Type);
var card = Assert.Single(note.Cards);
Assert.Equal(1, card.Variant);
Assert.Equal(12.3, card.Stability);
var log = Assert.Single(note.ReviewLogs);
Assert.Equal(FlashcardRatings.Good, log.Rating);
var image = Assert.Single(note.Images);
Assert.Equal([1, 2, 3], Convert.FromBase64String(image.Base64Bytes));
}
[Fact]
public void Fingerprint_IsStable_ForSameTypePromptAndAnswer_IgnoringSurroundingWhitespace()
{
var a = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, " What is 2+2? ", "4");
var b = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "What is 2+2?", "4");
Assert.Equal(a, b);
}
[Fact]
public void Fingerprint_DiffersWhenAnswerDiffers()
{
var a = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "What is 2+2?", "4");
var b = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "What is 2+2?", "five");
Assert.NotEqual(a, b);
}
[Fact]
public void Fingerprint_DiffersByType_ForSamePromptAndAnswer()
{
var basic = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "text", "text");
var cloze = FlashcardNoteStore.Fingerprint(FlashcardTypes.Cloze, "text", "text");
Assert.NotEqual(basic, cloze);
}
}