Skip to content

Commit d6d4c53

Browse files
CopilotabeckDev
andcommitted
Add comprehensive timezone logic tests for date handling
Co-authored-by: abeckDev <8720854+abeckDev@users.noreply.github.com>
1 parent 9e53b68 commit d6d4c53

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

AbeckDev.DbTimetable.Mcp.Test/TimeTableServiceTests.cs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,4 +638,183 @@ public async Task FindTrainConnectionsAsync_WhenNoConnectionsFound_ReturnsNoConn
638638
Assert.Contains("- Trains may require a transfer", result);
639639
Assert.Contains("- Try a different time or date", result);
640640
}
641+
642+
[Fact]
643+
public async Task GetStationBoardAsync_WithProvidedDate_UsesDateDirectlyWithoutTimezoneConversion()
644+
{
645+
// Arrange
646+
var mockHandler = new Mock<HttpMessageHandler>();
647+
mockHandler.Protected()
648+
.Setup<Task<HttpResponseMessage>>(
649+
"SendAsync",
650+
ItExpr.IsAny<HttpRequestMessage>(),
651+
ItExpr.IsAny<CancellationToken>())
652+
.ReturnsAsync(new HttpResponseMessage
653+
{
654+
StatusCode = HttpStatusCode.OK,
655+
Content = new StringContent(_testXmlResponse)
656+
});
657+
658+
var httpClient = new HttpClient(mockHandler.Object)
659+
{
660+
BaseAddress = new Uri(_config.BaseUrl)
661+
};
662+
var service = new TimeTableService(httpClient, _mockOptions.Object);
663+
var evaNo = "8000105";
664+
665+
// Provide a specific German time: 2025-11-06 14:30
666+
var germanTime = new DateTime(2025, 11, 6, 14, 30, 0);
667+
668+
// Act
669+
var result = await service.GetStationBoardAsync(evaNo, germanTime);
670+
671+
// Assert
672+
Assert.Equal(_testXmlResponse, result);
673+
674+
// Verify the request path contains the exact date and hour from the provided date
675+
// without any timezone conversion
676+
mockHandler.Protected().Verify(
677+
"SendAsync",
678+
Times.Once(),
679+
ItExpr.Is<HttpRequestMessage>(req =>
680+
req.RequestUri!.PathAndQuery.Contains("plan/8000105/251106/14")),
681+
ItExpr.IsAny<CancellationToken>());
682+
}
683+
684+
[Fact]
685+
public async Task GetStationBoardAsync_WithoutDate_UsesGermanTimezone()
686+
{
687+
// Arrange
688+
var mockHandler = new Mock<HttpMessageHandler>();
689+
string? capturedPath = null;
690+
mockHandler.Protected()
691+
.Setup<Task<HttpResponseMessage>>(
692+
"SendAsync",
693+
ItExpr.IsAny<HttpRequestMessage>(),
694+
ItExpr.IsAny<CancellationToken>())
695+
.ReturnsAsync((HttpRequestMessage req, CancellationToken ct) =>
696+
{
697+
capturedPath = req.RequestUri?.PathAndQuery;
698+
return new HttpResponseMessage
699+
{
700+
StatusCode = HttpStatusCode.OK,
701+
Content = new StringContent(_testXmlResponse)
702+
};
703+
});
704+
705+
var httpClient = new HttpClient(mockHandler.Object)
706+
{
707+
BaseAddress = new Uri(_config.BaseUrl)
708+
};
709+
var service = new TimeTableService(httpClient, _mockOptions.Object);
710+
var evaNo = "8000105";
711+
712+
// Calculate expected German time
713+
var berlinTz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
714+
var expectedGermanTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, berlinTz);
715+
var expectedDate = expectedGermanTime.ToString("yyMMdd");
716+
var expectedHour = expectedGermanTime.ToString("HH");
717+
718+
// Act
719+
var result = await service.GetStationBoardAsync(evaNo);
720+
721+
// Assert
722+
Assert.Equal(_testXmlResponse, result);
723+
Assert.NotNull(capturedPath);
724+
Assert.Contains($"plan/{evaNo}/{expectedDate}/{expectedHour}", capturedPath);
725+
}
726+
727+
[Fact]
728+
public async Task GetStationBoardAsync_WhenTimezoneNotAvailable_UsesFallback()
729+
{
730+
// This test verifies the fallback behavior when Europe/Berlin timezone cannot be found
731+
// Note: This is difficult to test directly since we can't easily mock TimeZoneInfo.FindSystemTimeZoneById
732+
// In actual deployment, the Europe/Berlin timezone should always be available on the system
733+
// The fallback to DateTime.Now is a defensive measure for edge cases
734+
735+
// Arrange
736+
var mockHandler = new Mock<HttpMessageHandler>();
737+
string? capturedPath = null;
738+
mockHandler.Protected()
739+
.Setup<Task<HttpResponseMessage>>(
740+
"SendAsync",
741+
ItExpr.IsAny<HttpRequestMessage>(),
742+
ItExpr.IsAny<CancellationToken>())
743+
.ReturnsAsync((HttpRequestMessage req, CancellationToken ct) =>
744+
{
745+
capturedPath = req.RequestUri?.PathAndQuery;
746+
return new HttpResponseMessage
747+
{
748+
StatusCode = HttpStatusCode.OK,
749+
Content = new StringContent(_testXmlResponse)
750+
};
751+
});
752+
753+
var httpClient = new HttpClient(mockHandler.Object)
754+
{
755+
BaseAddress = new Uri(_config.BaseUrl)
756+
};
757+
var service = new TimeTableService(httpClient, _mockOptions.Object);
758+
var evaNo = "8000105";
759+
760+
// Act
761+
var result = await service.GetStationBoardAsync(evaNo);
762+
763+
// Assert
764+
// The method should succeed and return a result regardless of timezone availability
765+
Assert.Equal(_testXmlResponse, result);
766+
Assert.NotNull(capturedPath);
767+
// Verify that a valid path was created with date and hour components
768+
Assert.Matches(@"plan/\d+/\d{6}/\d{2}", capturedPath);
769+
}
770+
771+
[Fact]
772+
public async Task GetStationBoardAsync_WithMultipleCalls_UsesConsistentTimezoneLogic()
773+
{
774+
// Arrange
775+
var mockHandler = new Mock<HttpMessageHandler>();
776+
var capturedPaths = new List<string>();
777+
mockHandler.Protected()
778+
.Setup<Task<HttpResponseMessage>>(
779+
"SendAsync",
780+
ItExpr.IsAny<HttpRequestMessage>(),
781+
ItExpr.IsAny<CancellationToken>())
782+
.ReturnsAsync((HttpRequestMessage req, CancellationToken ct) =>
783+
{
784+
if (req.RequestUri?.PathAndQuery != null)
785+
capturedPaths.Add(req.RequestUri.PathAndQuery);
786+
return new HttpResponseMessage
787+
{
788+
StatusCode = HttpStatusCode.OK,
789+
Content = new StringContent(_testXmlResponse)
790+
};
791+
});
792+
793+
var httpClient = new HttpClient(mockHandler.Object)
794+
{
795+
BaseAddress = new Uri(_config.BaseUrl)
796+
};
797+
var service = new TimeTableService(httpClient, _mockOptions.Object);
798+
var evaNo = "8000105";
799+
var specificDate = new DateTime(2025, 12, 25, 10, 15, 0);
800+
801+
// Act
802+
// First call without date (should use German timezone)
803+
await service.GetStationBoardAsync(evaNo);
804+
// Second call with specific date (should use provided date directly)
805+
await service.GetStationBoardAsync(evaNo, specificDate);
806+
// Third call without date again (should use German timezone)
807+
await service.GetStationBoardAsync(evaNo);
808+
809+
// Assert
810+
Assert.Equal(3, capturedPaths.Count);
811+
812+
// Second call should use the exact provided date
813+
Assert.Contains("plan/8000105/251225/10", capturedPaths[1]);
814+
815+
// First and third calls should use current German time (so they should be similar)
816+
// Both should contain valid date patterns
817+
Assert.Matches(@"plan/\d+/\d{6}/\d{2}", capturedPaths[0]);
818+
Assert.Matches(@"plan/\d+/\d{6}/\d{2}", capturedPaths[2]);
819+
}
641820
}

0 commit comments

Comments
 (0)