package services import ( "bytes" "strings" "testing" "time" ) func sampleLessons() []LessonExport { return []LessonExport{ {DayOfWeek: 1, PeriodIndex: 1, StartTime: "08:00", EndTime: "08:45", ClassName: "5a", SubjectName: "Mathe", SubjectCode: "M", TeacherName: "Schmidt, Anna", RoomName: "A101", Pinned: false}, {DayOfWeek: 2, PeriodIndex: 1, StartTime: "08:00", EndTime: "08:45", ClassName: "5a", SubjectName: "Deutsch, Klasse 5", SubjectCode: "D", TeacherName: "Mueller, Bob", RoomName: "", Pinned: true}, } } func TestWriteCSV_HeaderAndRows(t *testing.T) { var buf bytes.Buffer if err := WriteCSV(&buf, sampleLessons()); err != nil { t.Fatalf("WriteCSV failed: %v", err) } out := buf.String() wantHeader := "day_of_week,period_index,start_time,end_time,class,subject,subject_code,teacher,room,pinned" if !strings.Contains(out, wantHeader) { t.Errorf("CSV missing header line; got:\n%s", out) } if !strings.Contains(out, "1,1,08:00,08:45,5a,Mathe,M,\"Schmidt, Anna\",A101,false") { t.Errorf("CSV missing first row; got:\n%s", out) } // Commas inside subject name must be quoted. if !strings.Contains(out, "\"Deutsch, Klasse 5\"") { t.Errorf("CSV should quote comma in subject name; got:\n%s", out) } if !strings.Contains(out, ",true") { t.Errorf("Pinned flag should serialise as 'true'; got:\n%s", out) } } func TestWriteICS_StructureAndDates(t *testing.T) { weekStart := time.Date(2026, 8, 24, 0, 0, 0, 0, time.UTC) // a Monday var buf bytes.Buffer if err := WriteICS(&buf, sampleLessons(), weekStart, "Schuljahr 26/27"); err != nil { t.Fatalf("WriteICS failed: %v", err) } out := buf.String() for _, want := range []string{ "BEGIN:VCALENDAR\r\n", "VERSION:2.0\r\n", "PRODID:-//BreakPilot//Timetable//DE\r\n", "BEGIN:VEVENT\r\n", "END:VEVENT\r\n", "END:VCALENDAR\r\n", "DTSTART:20260824T080000", "DTSTART:20260825T080000", "SUMMARY:Mathe (5a)", "LOCATION:A101", "Schuljahr 26/27", } { if !strings.Contains(out, want) { t.Errorf("ICS missing %q in output", want) } } } func TestICSEscape_SpecialChars(t *testing.T) { tests := []struct { in, want string }{ {"plain", "plain"}, {"a,b", "a\\,b"}, {"x;y", "x\\;y"}, {"line1\nline2", "line1\\nline2"}, } for _, tt := range tests { got := icsEscape(tt.in) if got != tt.want { t.Errorf("icsEscape(%q) = %q, want %q", tt.in, got, tt.want) } } } func TestNextMonday(t *testing.T) { // Verify the offset arithmetic for every weekday — 2026-08-22 is a Saturday. cases := []struct { ref time.Time wantMo string }{ {time.Date(2026, 8, 24, 0, 0, 0, 0, time.UTC), "2026-08-24"}, // Mon → same day {time.Date(2026, 8, 25, 0, 0, 0, 0, time.UTC), "2026-08-31"}, // Tue → next Mon {time.Date(2026, 8, 23, 0, 0, 0, 0, time.UTC), "2026-08-24"}, // Sun → next Mon {time.Date(2026, 8, 22, 0, 0, 0, 0, time.UTC), "2026-08-24"}, // Sat → next Mon } for _, tt := range cases { got := NextMonday(tt.ref).Format("2006-01-02") if got != tt.wantMo { t.Errorf("NextMonday(%s) = %s, want %s", tt.ref.Format("2006-01-02"), got, tt.wantMo) } } }