feat: add CalendarEventBuilder with shared event/transport logic
This commit is contained in:
parent
839abe6cf4
commit
10ccd6fce0
2 changed files with 122 additions and 0 deletions
53
Services/CalendarEventBuilder.cs
Normal file
53
Services/CalendarEventBuilder.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using ShiftScheduler.Shared;
|
||||||
|
|
||||||
|
namespace ShiftScheduler.Services;
|
||||||
|
|
||||||
|
public record CalendarEventData(string Summary, string Description);
|
||||||
|
|
||||||
|
public static class CalendarEventBuilder
|
||||||
|
{
|
||||||
|
public static CalendarEventData BuildEventContent(Shift shift, TransportConnection? transport)
|
||||||
|
{
|
||||||
|
var description = transport != null
|
||||||
|
? $"Transport: {FormatTransportInfo(transport)}"
|
||||||
|
: string.Empty;
|
||||||
|
return new CalendarEventData(shift.Name, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormatTransportInfo(TransportConnection transport)
|
||||||
|
{
|
||||||
|
var departure = transport.DepartureTime.ToString("HH:mm");
|
||||||
|
var arrival = transport.ArrivalTime.ToString("HH:mm");
|
||||||
|
return $"{transport.Platform}: {departure} → {arrival} ({transport.Duration.TotalMinutes} Minutes)";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3)
|
||||||
|
{
|
||||||
|
var delay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
for (var attempt = 0; attempt <= maxRetries; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await operation();
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex) when (ex.Message.Contains("429") && attempt < maxRetries)
|
||||||
|
{
|
||||||
|
await Task.Delay(delay);
|
||||||
|
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex.Message.Contains("Rate Limit") && attempt < maxRetries)
|
||||||
|
{
|
||||||
|
await Task.Delay(delay);
|
||||||
|
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex.Message.Contains("Forbidden") && ex.Message.Contains("quota") && attempt < maxRetries)
|
||||||
|
{
|
||||||
|
await Task.Delay(delay);
|
||||||
|
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await operation();
|
||||||
|
}
|
||||||
|
}
|
||||||
69
ShiftScheduler.Services.Tests/CalendarEventBuilderTests.cs
Normal file
69
ShiftScheduler.Services.Tests/CalendarEventBuilderTests.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
using ShiftScheduler.Services;
|
||||||
|
using ShiftScheduler.Shared;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace ShiftScheduler.Services.Tests;
|
||||||
|
|
||||||
|
public class CalendarEventBuilderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BuildEventContent_WithTransport_IncludesTransportInDescription()
|
||||||
|
{
|
||||||
|
var shift = new Shift { Name = "Früh" };
|
||||||
|
var transport = new TransportConnection
|
||||||
|
{
|
||||||
|
Platform = "Gleis 3",
|
||||||
|
DepartureTime = DateTimeOffset.Parse("2026-04-01T05:30:00+02:00"),
|
||||||
|
ArrivalTime = DateTimeOffset.Parse("2026-04-01T05:55:00+02:00"),
|
||||||
|
Duration = TimeSpan.FromMinutes(25)
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = CalendarEventBuilder.BuildEventContent(shift, transport);
|
||||||
|
|
||||||
|
result.Summary.ShouldBe("Früh");
|
||||||
|
result.Description.ShouldBe("Transport: Gleis 3: 05:30 → 05:55 (25 Minutes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildEventContent_WithoutTransport_EmptyDescription()
|
||||||
|
{
|
||||||
|
var shift = new Shift { Name = "Spät" };
|
||||||
|
|
||||||
|
var result = CalendarEventBuilder.BuildEventContent(shift, null);
|
||||||
|
|
||||||
|
result.Summary.ShouldBe("Spät");
|
||||||
|
result.Description.ShouldBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FormatTransportInfo_FormatsCorrectly()
|
||||||
|
{
|
||||||
|
var transport = new TransportConnection
|
||||||
|
{
|
||||||
|
Platform = "Gleis 3",
|
||||||
|
DepartureTime = DateTimeOffset.Parse("2026-04-01T05:30:00+02:00"),
|
||||||
|
ArrivalTime = DateTimeOffset.Parse("2026-04-01T05:55:00+02:00"),
|
||||||
|
Duration = TimeSpan.FromMinutes(25)
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = CalendarEventBuilder.FormatTransportInfo(transport);
|
||||||
|
|
||||||
|
result.ShouldBe("Gleis 3: 05:30 → 05:55 (25 Minutes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FormatTransportInfo_WithNullPlatform_UsesEmptyString()
|
||||||
|
{
|
||||||
|
var transport = new TransportConnection
|
||||||
|
{
|
||||||
|
Platform = null,
|
||||||
|
DepartureTime = DateTimeOffset.Parse("2026-04-01T05:30:00+02:00"),
|
||||||
|
ArrivalTime = DateTimeOffset.Parse("2026-04-01T05:55:00+02:00"),
|
||||||
|
Duration = TimeSpan.FromMinutes(25)
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = CalendarEventBuilder.FormatTransportInfo(transport);
|
||||||
|
|
||||||
|
result.ShouldBe(": 05:30 → 05:55 (25 Minutes)");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue