Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions AbeckDev.DbTimetable.Mcp.Test/TimeTableServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,113 @@ public async Task GetStationInformation_SetsCorrectHeaders()
// Assert
VerifyHttpRequest(mockHandler, $"station/{pattern}", _config.ClientId, _config.ApiKey);
}

[Fact]
public async Task FindTrainConnectionsAsync_WithValidStations_ReturnsAnalysisReport()
{
// Arrange
var stationXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<stations>
<station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
</stations>";

var timetableXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<timetable station=""Frankfurt Hbf"">
<s id=""1"">
<tl c=""ICE"" n=""123"" f=""Berlin Hbf""/>
<dp pt=""2511061430"" pp=""7"" ppth=""Frankfurt Hbf|Mannheim|Heidelberg|Berlin Hbf""/>
</s>
</timetable>";

var changesXml = @"<?xml version=""1.0"" encoding=""UTF-8""?><timetable/>";

var mockHandler = new Mock<HttpMessageHandler>();
var requestCount = 0;
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() =>
{
requestCount++;
// First two calls are station lookups, third is timetable, fourth is changes
if (requestCount <= 2)
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(stationXml) };
if (requestCount == 3)
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(timetableXml) };
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(changesXml) };
});

var httpClient = new HttpClient(mockHandler.Object) { BaseAddress = new Uri(_config.BaseUrl) };
var service = new TimeTableService(httpClient, _mockOptions.Object);

// Act
var result = await service.FindTrainConnectionsAsync("Frankfurt", "Berlin");

// Assert
Assert.Contains("Train Connection Analysis", result);
Assert.Contains("Frankfurt Hbf", result);
Assert.Contains("EVA: 8000105", result);
}

[Fact]
public async Task FindTrainConnectionsAsync_WithInvalidStationA_ReturnsError()
{
// Arrange
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.NotFound,
Content = new StringContent("Not Found")
});

var httpClient = new HttpClient(mockHandler.Object) { BaseAddress = new Uri(_config.BaseUrl) };
var service = new TimeTableService(httpClient, _mockOptions.Object);

// Act
var result = await service.FindTrainConnectionsAsync("InvalidStation", "Berlin");

// Assert
Assert.Contains("Could not find station 'InvalidStation'", result);
}

[Fact]
public async Task FindTrainConnectionsAsync_WithInvalidStationB_ReturnsError()
{
// Arrange
var stationAXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<stations>
<station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
</stations>";

var mockHandler = new Mock<HttpMessageHandler>();
var requestCount = 0;
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() =>
{
requestCount++;
if (requestCount == 1)
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(stationAXml) };
return new HttpResponseMessage { StatusCode = HttpStatusCode.NotFound, Content = new StringContent("Not Found") };
});

var httpClient = new HttpClient(mockHandler.Object) { BaseAddress = new Uri(_config.BaseUrl) };
var service = new TimeTableService(httpClient, _mockOptions.Object);

// Act
var result = await service.FindTrainConnectionsAsync("Frankfurt", "InvalidStation");

// Assert
Assert.Contains("Could not find station 'InvalidStation'", result);
}
}
125 changes: 125 additions & 0 deletions AbeckDev.DbTimetable.Mcp.Test/TimetableToolsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,129 @@ public async Task GetStationDetails_WithGeneralException_ReturnsUnexpectedErrorM
Assert.Contains("Unexpected error:", result);
Assert.Contains(exceptionMessage, result);
}

[Fact]
public async Task FindTrainConnections_WithValidStations_ReturnsConnectionAnalysis()
{
// Arrange
var stationA = "Frankfurt";
var stationB = "Berlin";
var expectedResult = "=== Train Connection Analysis ===\nConnections found";

_mockService.Setup(s => s.FindTrainConnectionsAsync(stationA, stationB, null, default))
.ReturnsAsync(expectedResult);

var tools = new Tools.TimetableTools(_mockService.Object);

// Act
var result = await tools.FindTrainConnections(stationA, stationB, null);

// Assert
Assert.Equal(expectedResult, result);
_mockService.Verify(s => s.FindTrainConnectionsAsync(stationA, stationB, null, default), Times.Once);
}

[Fact]
public async Task FindTrainConnections_WithValidDateTime_ParsesAndCallsService()
{
// Arrange
var stationA = "Frankfurt";
var stationB = "Berlin";
var dateTimeString = "2025-11-06 14:30";
var parsedDateTime = DateTime.Parse(dateTimeString);
var expectedResult = "Connections found";

_mockService.Setup(s => s.FindTrainConnectionsAsync(stationA, stationB, parsedDateTime, default))
.ReturnsAsync(expectedResult);

var tools = new Tools.TimetableTools(_mockService.Object);

// Act
var result = await tools.FindTrainConnections(stationA, stationB, dateTimeString);

// Assert
Assert.Equal(expectedResult, result);
_mockService.Verify(s => s.FindTrainConnectionsAsync(stationA, stationB, parsedDateTime, default), Times.Once);
}

[Fact]
public async Task FindTrainConnections_WithInvalidDateTimeFormat_ReturnsErrorMessage()
{
// Arrange
var stationA = "Frankfurt";
var stationB = "Berlin";
var invalidDateTime = "invalid-date-format";

var tools = new Tools.TimetableTools(_mockService.Object);

// Act
var result = await tools.FindTrainConnections(stationA, stationB, invalidDateTime);

// Assert
Assert.Contains("Error: Invalid date format", result);
Assert.Contains("yyyy-MM-dd HH:mm", result);
}

[Fact]
public async Task FindTrainConnections_WithEmptyDateTime_CallsServiceWithNull()
{
// Arrange
var stationA = "Frankfurt";
var stationB = "Berlin";
var expectedResult = "Connections found";

_mockService.Setup(s => s.FindTrainConnectionsAsync(stationA, stationB, null, default))
.ReturnsAsync(expectedResult);

var tools = new Tools.TimetableTools(_mockService.Object);

// Act
var result = await tools.FindTrainConnections(stationA, stationB, "");

// Assert
Assert.Equal(expectedResult, result);
_mockService.Verify(s => s.FindTrainConnectionsAsync(stationA, stationB, null, default), Times.Once);
}

[Fact]
public async Task FindTrainConnections_WithHttpRequestException_ReturnsErrorMessage()
{
// Arrange
var stationA = "Frankfurt";
var stationB = "Berlin";
var exceptionMessage = "Network error";

_mockService.Setup(s => s.FindTrainConnectionsAsync(stationA, stationB, null, default))
.ThrowsAsync(new HttpRequestException(exceptionMessage));

var tools = new Tools.TimetableTools(_mockService.Object);

// Act
var result = await tools.FindTrainConnections(stationA, stationB, null);

// Assert
Assert.Contains("Error finding train connections:", result);
Assert.Contains(exceptionMessage, result);
}

[Fact]
public async Task FindTrainConnections_WithGeneralException_ReturnsUnexpectedErrorMessage()
{
// Arrange
var stationA = "Frankfurt";
var stationB = "Berlin";
var exceptionMessage = "Unexpected error";

_mockService.Setup(s => s.FindTrainConnectionsAsync(stationA, stationB, null, default))
.ThrowsAsync(new InvalidOperationException(exceptionMessage));

var tools = new Tools.TimetableTools(_mockService.Object);

// Act
var result = await tools.FindTrainConnections(stationA, stationB, null);

// Assert
Assert.Contains("Unexpected error:", result);
Assert.Contains(exceptionMessage, result);
}
}
5 changes: 5 additions & 0 deletions AbeckDev.DbTimetable.Mcp/Services/ITimeTableService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ public interface ITimeTableService
/// Get information about stations given either a station name (prefix), eva number, ds100/rl100 code, wildcard (*); doesn't seem to work with umlauten in station name (prefix)
/// </summary>
Task<string> GetStationInformation(string pattern, CancellationToken cancellationToken = default);

/// <summary>
/// Find train connections between two stations and assess their current status including delays and disruptions
/// </summary>
Task<string> FindTrainConnectionsAsync(string stationA, string stationB, DateTime? dateTime = null, CancellationToken cancellationToken = default);
}
Loading