using LehrerApp.Core.Services;
using Xunit;
namespace LehrerApp.Tests;
/// Synthetische Fixtures nach dem am 23.08.2026 geprüften ClassyPlan-Export.
public sealed class AnnualPlanIcsParserTests
{
private static string Calendar(params string[] events) =>
"BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:ClassyPlan\n" +
string.Join("", events) + "END:VCALENDAR\n";
[Fact]
public void Parse_Ganztagstermin_NutztExklusivesDtendAlsInklusivesEnddatum()
{
var ics = Calendar(
"BEGIN:VEVENT\nUID:100-ClassyPlan.app\n" +
"DTSTART;VALUE=DATE:20260615\nDTEND;VALUE=DATE:20260620\n" +
"SUMMARY:Klassenfahrt 6a 6b 6d\nX-GROUPNAME:Lehrkräfte\nX-COLOR:#4B9FD8\n" +
"END:VEVENT\n");
var entry = Assert.Single(AnnualPlanIcsParser.Parse(ics));
Assert.True(entry.IsAllDay);
Assert.Equal(new DateOnly(2026, 6, 15), entry.StartDate);
Assert.Equal(new DateOnly(2026, 6, 19), entry.EndDate);
Assert.Null(entry.StartTime);
Assert.Equal("Lehrkräfte", entry.CalendarGroup);
}
[Fact]
public void Parse_UtcTermin_WirdUnterBeruecksichtigungDerSommerzeitNachBerlinKonvertiert()
{
var ics = Calendar(
"BEGIN:VEVENT\nUID:200-ClassyPlan.app\n" +
"DTSTART:20260929T113000Z\nDTEND:20260929T130000Z\n" +
"LAST-MODIFIED:20260814T095236Z\nSEQUENCE:2\nSTATUS:CONFIRMED\n" +
"SUMMARY:Teams 5–10\nLOCATION:R 101\nX-GROUPNAME:Öffentlich\nEND:VEVENT\n");
var entry = Assert.Single(AnnualPlanIcsParser.Parse(ics));
Assert.False(entry.IsAllDay);
Assert.Equal(new TimeOnly(13, 30), entry.StartTime);
Assert.Equal(new TimeOnly(15, 0), entry.EndTime);
Assert.Equal(2, entry.Sequence);
Assert.Equal(new DateTime(2026, 8, 14, 9, 52, 36, DateTimeKind.Utc),
entry.SourceLastModifiedUtc);
}
[Fact]
public void Parse_EscapingUndZeilenfaltung_WerdenAufgeloest()
{
var ics = Calendar(
"BEGIN:VEVENT\nUID:300-ClassyPlan.app\nDTSTART;VALUE=DATE:20260823\n" +
"DESCRIPTION:Erste Zeile\\nZweite Zeile mit sehr langem \n Text\\, Komma\\; Semikolon\n" +
"SUMMARY:Fortbildung\nEND:VEVENT\n");
var entry = Assert.Single(AnnualPlanIcsParser.Parse(ics));
Assert.Equal("Erste Zeile\nZweite Zeile mit sehr langem Text, Komma; Semikolon",
entry.Description);
}
[Fact]
public void Parse_EinDefektesVevent_LaesstNichtUnbemerktEinenTeilimportZu()
{
var ics = Calendar(
"BEGIN:VEVENT\nUID:gueltig\nDTSTART;VALUE=DATE:20260823\nEND:VEVENT\n",
"BEGIN:VEVENT\nUID:defekt\nSUMMARY:Ohne Datum\nEND:VEVENT\n");
var error = Assert.Throws(() => AnnualPlanIcsParser.Parse(ics));
Assert.Contains("DTSTART fehlt", error.Message);
}
[Fact]
public void Parse_DoppelteUid_WirdAbgelehnt()
{
var ics = Calendar(
"BEGIN:VEVENT\nUID:doppelt\nDTSTART;VALUE=DATE:20260823\nEND:VEVENT\n",
"BEGIN:VEVENT\nUID:doppelt\nDTSTART;VALUE=DATE:20260824\nEND:VEVENT\n");
Assert.Throws(() => AnnualPlanIcsParser.Parse(ics));
}
}