package calendar import ( "strings" "testing" "time" ) func TestRenderICalRoundTrips(t *testing.T) { e := ReminderEvent(7, time.Date(2026, 8, 1, 18, 30, 0, 0, time.UTC), "позвонить маме", 0) if e.UID != "maven-reminder-7" { t.Errorf("UID = %q", e.UID) } if got := e.End.Sub(e.Start); got != DefaultReminderDuration { t.Errorf("duration = %v, want %v", got, DefaultReminderDuration) } if got, want := ReminderPath(7), "maven-reminder-7.ics"; got != want { t.Errorf("ReminderPath = %q, want %q", got, want) } body := RenderICal([]Event{e}) if !strings.HasPrefix(body, "BEGIN:VCALENDAR\r\n") || !strings.HasSuffix(body, "END:VCALENDAR\r\n") { t.Fatalf("not a VCALENDAR body:\n%s", body) } back := ParseICal([]byte(body), e.Start.Add(-time.Hour), e.Start.Add(time.Hour)) if len(back) != 1 { t.Fatalf("got %d events back, want 1:\n%s", len(back), body) } if back[0].UID != e.UID || back[0].Summary != e.Summary { t.Errorf("round trip lost identity: %+v", back[0]) } if !back[0].Start.Equal(e.Start) || !back[0].End.Equal(e.End) { t.Errorf("round trip lost times: %+v", back[0]) } } func TestRenderICalIsDeterministic(t *testing.T) { e := ReminderEvent(1, time.Date(2026, 8, 1, 9, 0, 0, 0, time.UTC), "выпить воды", 0) if RenderICal([]Event{e}) != RenderICal([]Event{e}) { t.Error("the same reminder must render byte-identically, or every poll re-PUTs it") } } // A reminder payload is owner-supplied text. It must not be able to close the // VEVENT and inject properties of its own. func TestRenderICalEscapesInjection(t *testing.T) { e := ReminderEvent(2, time.Date(2026, 8, 1, 9, 0, 0, 0, time.UTC), "обед\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nSUMMARY:injected", 0) body := RenderICal([]Event{e}) // Count line-initial occurrences: the escaped text still contains the // characters "BEGIN:VEVENT", it just can no longer start a line. if n := strings.Count(body, "\r\nBEGIN:VEVENT\r\n"); n != 1 { t.Fatalf("payload injected a second VEVENT (%d):\n%s", n, body) } if n := strings.Count(body, "\r\nEND:VEVENT\r\n"); n != 1 { t.Fatalf("payload closed the VEVENT early (%d):\n%s", n, body) } if !strings.Contains(body, `SUMMARY:обед\nEND:VEVENT`) { t.Errorf("newlines should be escaped, not dropped:\n%s", body) } } func TestReminderEventEmptyPayload(t *testing.T) { e := ReminderEvent(3, time.Date(2026, 8, 1, 9, 0, 0, 0, time.UTC), " ", 0) if e.Summary != "напоминание" { t.Errorf("Summary = %q, want the neutral RU fallback", e.Summary) } }