using System.Reflection; using FluentAssertions; using Moq; using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Calendar; using Wino.Core.Integration.Processors; using Wino.Services; using Xunit; namespace Wino.Core.Tests.Synchronizers; public class CalDavEventTimeMappingTests { [Fact] public void ParseCalendarData_UtcEvent_AssignsUtcTimeZone() { const string ics = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Wino Mail//Tests//EN CALSCALE:GREGORIAN BEGIN:VEVENT UID:utc-event DTSTAMP:20260201T000000Z DTSTART:20260219T010000Z DTEND:20260219T020000Z SUMMARY:UTC Event END:VEVENT END:VCALENDAR """; var events = ParseEvents(ics); events.Should().ContainSingle(); events[0].StartTimeZone.Should().Be(TimeZoneInfo.Utc.Id); events[0].EndTimeZone.Should().Be(TimeZoneInfo.Utc.Id); } [Fact] public async Task ManageCalendarEventAsync_PersistsWallClockTimeForSourceTimeZone() { var calendar = new AccountCalendar { Id = Guid.NewGuid(), Name = "Calendar" }; var remoteEvent = new CalDavCalendarEvent { RemoteEventId = "event-1", Title = "Wall Clock Event", Start = new DateTimeOffset(2026, 2, 19, 1, 0, 0, TimeSpan.FromHours(1)), End = new DateTimeOffset(2026, 2, 19, 2, 0, 0, TimeSpan.FromHours(1)), StartTimeZone = "Europe/Berlin", EndTimeZone = "Europe/Berlin" }; CalendarItem? capturedItem = null; var calendarService = new Mock(); calendarService .Setup(x => x.GetCalendarItemAsync(calendar.Id, remoteEvent.RemoteEventId)) .ReturnsAsync((CalendarItem?)null); calendarService .Setup(x => x.CreateNewCalendarItemAsync(It.IsAny(), It.IsAny>())) .Callback>((item, _) => capturedItem = item) .Returns(Task.CompletedTask); calendarService .Setup(x => x.SaveRemindersAsync(It.IsAny(), It.IsAny>())) .Returns(Task.CompletedTask); var sut = new ImapChangeProcessor( Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), calendarService.Object, Mock.Of(), Mock.Of()); await sut.ManageCalendarEventAsync(remoteEvent, calendar, organizerAccount: null); capturedItem.Should().NotBeNull(); var savedItem = capturedItem!; savedItem.StartDate.Should().Be(new DateTime(2026, 2, 19, 1, 0, 0)); savedItem.DurationInSeconds.Should().Be(3600); savedItem.StartTimeZone.Should().Be("Europe/Berlin"); savedItem.EndTimeZone.Should().Be("Europe/Berlin"); } private static List ParseEvents(string icsContent) { var parseMethod = typeof(CalDavClient).GetMethod( "ParseCalendarData", BindingFlags.NonPublic | BindingFlags.Static); parseMethod.Should().NotBeNull(); var result = parseMethod!.Invoke( null, [ icsContent, "https://calendar.example.com/event.ics", "\"etag\"", new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2026, 12, 31, 23, 59, 59, TimeSpan.Zero) ]); return result.Should().BeOfType>().Subject; } }