@@ -360,4 +360,282 @@ public async Task GetStationInformation_SetsCorrectHeaders()
360360 // Assert
361361 VerifyHttpRequest ( mockHandler , $ "station/{ pattern } ", _config . ClientId , _config . ApiKey ) ;
362362 }
363+
364+ [ Fact ]
365+ public async Task FindTrainConnectionsAsync_WithValidStations_ReturnsAnalysisReport ( )
366+ {
367+ // Arrange
368+ var stationXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
369+ <stations>
370+ <station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
371+ </stations>" ;
372+
373+ var timetableXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
374+ <timetable station=""Frankfurt Hbf"">
375+ <s id=""1"">
376+ <tl c=""ICE"" n=""123"" f=""Berlin Hbf""/>
377+ <dp pt=""2511061430"" pp=""7"" ppth=""Frankfurt Hbf|Mannheim|Heidelberg|Berlin Hbf""/>
378+ </s>
379+ </timetable>" ;
380+
381+ var changesXml = @"<?xml version=""1.0"" encoding=""UTF-8""?><timetable/>" ;
382+
383+ var mockHandler = new Mock < HttpMessageHandler > ( ) ;
384+ var requestCount = 0 ;
385+ mockHandler . Protected ( )
386+ . Setup < Task < HttpResponseMessage > > (
387+ "SendAsync" ,
388+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
389+ ItExpr . IsAny < CancellationToken > ( ) )
390+ . ReturnsAsync ( ( ) =>
391+ {
392+ requestCount ++ ;
393+ // First two calls are station lookups, third is timetable, fourth is full changes
394+ if ( requestCount <= 2 )
395+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( stationXml ) } ;
396+ if ( requestCount == 3 )
397+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( timetableXml ) } ;
398+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( changesXml ) } ;
399+ } ) ;
400+
401+ var httpClient = new HttpClient ( mockHandler . Object ) { BaseAddress = new Uri ( _config . BaseUrl ) } ;
402+ var service = new TimeTableService ( httpClient , _mockOptions . Object ) ;
403+
404+ // Act
405+ var result = await service . FindTrainConnectionsAsync ( "Frankfurt" , "Berlin" ) ;
406+
407+ // Assert
408+ Assert . Contains ( "Train Connection Analysis" , result ) ;
409+ Assert . Contains ( "Frankfurt Hbf" , result ) ;
410+ Assert . Contains ( "EVA: 8000105" , result ) ;
411+ }
412+
413+ [ Fact ]
414+ public async Task FindTrainConnectionsAsync_WithLiveDelayData_ReturnsDelayInformation ( )
415+ {
416+ // Arrange
417+ var stationXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
418+ <stations>
419+ <station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
420+ </stations>" ;
421+
422+ var timetableXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
423+ <timetable station=""Frankfurt Hbf"">
424+ <s id=""123456"">
425+ <tl c=""ICE"" n=""123"" f=""Berlin Hbf""/>
426+ <dp pt=""2511061430"" pp=""7"" ppth=""Frankfurt Hbf|Mannheim|Berlin Hbf""/>
427+ </s>
428+ </timetable>" ;
429+
430+ // Changes XML with live delay data - train delayed by 10 minutes
431+ var changesXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
432+ <timetable>
433+ <s id=""123456"">
434+ <tl c=""ICE"" n=""123"" f=""Berlin Hbf""/>
435+ <dp pt=""2511061430"" ct=""2511061440"" pp=""7"" cp=""8""/>
436+ <m t=""Train delayed due to technical issues""/>
437+ </s>
438+ </timetable>" ;
439+
440+ var mockHandler = new Mock < HttpMessageHandler > ( ) ;
441+ var requestCount = 0 ;
442+ mockHandler . Protected ( )
443+ . Setup < Task < HttpResponseMessage > > (
444+ "SendAsync" ,
445+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
446+ ItExpr . IsAny < CancellationToken > ( ) )
447+ . ReturnsAsync ( ( ) =>
448+ {
449+ requestCount ++ ;
450+ // First two calls are station lookups, third is timetable, fourth is full changes
451+ if ( requestCount <= 2 )
452+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( stationXml ) } ;
453+ if ( requestCount == 3 )
454+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( timetableXml ) } ;
455+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( changesXml ) } ;
456+ } ) ;
457+
458+ var httpClient = new HttpClient ( mockHandler . Object ) { BaseAddress = new Uri ( _config . BaseUrl ) } ;
459+ var service = new TimeTableService ( httpClient , _mockOptions . Object ) ;
460+
461+ // Act
462+ var result = await service . FindTrainConnectionsAsync ( "Frankfurt" , "Berlin" ) ;
463+
464+ // Assert
465+ Assert . Contains ( "Train Connection Analysis" , result ) ;
466+ Assert . Contains ( "Delay: +10 minutes" , result ) ;
467+ Assert . Contains ( "Train delayed due to technical issues" , result ) ;
468+ Assert . Contains ( "Platform: 8" , result ) ; // Changed platform
469+ }
470+
471+ [ Fact ]
472+ public async Task FindTrainConnectionsAsync_WithInvalidStationA_ReturnsError ( )
473+ {
474+ // Arrange
475+ var mockHandler = new Mock < HttpMessageHandler > ( ) ;
476+ mockHandler . Protected ( )
477+ . Setup < Task < HttpResponseMessage > > (
478+ "SendAsync" ,
479+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
480+ ItExpr . IsAny < CancellationToken > ( ) )
481+ . ReturnsAsync ( new HttpResponseMessage
482+ {
483+ StatusCode = HttpStatusCode . NotFound ,
484+ Content = new StringContent ( "Not Found" )
485+ } ) ;
486+
487+ var httpClient = new HttpClient ( mockHandler . Object ) { BaseAddress = new Uri ( _config . BaseUrl ) } ;
488+ var service = new TimeTableService ( httpClient , _mockOptions . Object ) ;
489+
490+ // Act
491+ var result = await service . FindTrainConnectionsAsync ( "InvalidStation" , "Berlin" ) ;
492+
493+ // Assert
494+ Assert . Contains ( "Could not find station 'InvalidStation'" , result ) ;
495+ }
496+
497+ [ Fact ]
498+ public async Task FindTrainConnectionsAsync_WithInvalidStationB_ReturnsError ( )
499+ {
500+ // Arrange
501+ var stationAXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
502+ <stations>
503+ <station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
504+ </stations>" ;
505+
506+ var mockHandler = new Mock < HttpMessageHandler > ( ) ;
507+ var requestCount = 0 ;
508+ mockHandler . Protected ( )
509+ . Setup < Task < HttpResponseMessage > > (
510+ "SendAsync" ,
511+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
512+ ItExpr . IsAny < CancellationToken > ( ) )
513+ . ReturnsAsync ( ( ) =>
514+ {
515+ requestCount ++ ;
516+ if ( requestCount == 1 )
517+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( stationAXml ) } ;
518+ return new HttpResponseMessage { StatusCode = HttpStatusCode . NotFound , Content = new StringContent ( "Not Found" ) } ;
519+ } ) ;
520+
521+ var httpClient = new HttpClient ( mockHandler . Object ) { BaseAddress = new Uri ( _config . BaseUrl ) } ;
522+ var service = new TimeTableService ( httpClient , _mockOptions . Object ) ;
523+
524+ // Act
525+ var result = await service . FindTrainConnectionsAsync ( "Frankfurt" , "InvalidStation" ) ;
526+
527+ // Assert
528+ Assert . Contains ( "Could not find station 'InvalidStation'" , result ) ;
529+ }
530+
531+ [ Fact ]
532+ public async Task FindTrainConnectionsAsync_WhenNoChangesAvailable_ReturnsWarning ( )
533+ {
534+ // Arrange
535+ var stationXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
536+ <stations>
537+ <station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
538+ </stations>" ;
539+
540+ var timetableXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
541+ <timetable station=""Frankfurt Hbf"">
542+ <s id=""123456"">
543+ <tl c=""ICE"" n=""123"" f=""Berlin Hbf""/>
544+ <dp pt=""2511061430"" pp=""7"" ppth=""Frankfurt Hbf|Mannheim|Berlin Hbf""/>
545+ </s>
546+ </timetable>" ;
547+
548+ var mockHandler = new Mock < HttpMessageHandler > ( ) ;
549+ var requestCount = 0 ;
550+ mockHandler . Protected ( )
551+ . Setup < Task < HttpResponseMessage > > (
552+ "SendAsync" ,
553+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
554+ ItExpr . IsAny < CancellationToken > ( ) )
555+ . ReturnsAsync ( ( ) =>
556+ {
557+ requestCount ++ ;
558+ // First two calls are station lookups
559+ if ( requestCount <= 2 )
560+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( stationXml ) } ;
561+ // Third is timetable
562+ if ( requestCount == 3 )
563+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( timetableXml ) } ;
564+ // Fourth is full changes - simulate failure
565+ return new HttpResponseMessage { StatusCode = HttpStatusCode . InternalServerError , Content = new StringContent ( "Server Error" ) } ;
566+ } ) ;
567+
568+ var httpClient = new HttpClient ( mockHandler . Object ) { BaseAddress = new Uri ( _config . BaseUrl ) } ;
569+ var service = new TimeTableService ( httpClient , _mockOptions . Object ) ;
570+
571+ // Act
572+ var result = await service . FindTrainConnectionsAsync ( "Frankfurt" , "Berlin" ) ;
573+
574+ // Assert
575+ Assert . Contains ( "Train Connection Analysis" , result ) ;
576+ Assert . Contains ( "⚠ No changes available" , result ) ;
577+ }
578+
579+ [ Fact ]
580+ public async Task FindTrainConnectionsAsync_WhenNoConnectionsFound_ReturnsNoConnectionsMessage ( )
581+ {
582+ // Arrange
583+ var stationXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
584+ <stations>
585+ <station name=""Frankfurt Hbf"" eva=""8000105"" ds100=""FF""/>
586+ </stations>" ;
587+
588+ var stationBXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
589+ <stations>
590+ <station name=""Konstanz"" eva=""8003400"" ds100=""KN""/>
591+ </stations>" ;
592+
593+ // Timetable with a train that doesn't go to Konstanz (only to Munich)
594+ var timetableXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
595+ <timetable station=""Frankfurt Hbf"">
596+ <s id=""123456"">
597+ <tl c=""ICE"" n=""123"" f=""Munich Hbf""/>
598+ <dp pt=""2511061430"" pp=""7"" ppth=""Frankfurt Hbf|Stuttgart|Munich Hbf""/>
599+ </s>
600+ </timetable>" ;
601+
602+ var changesXml = @"<?xml version=""1.0"" encoding=""UTF-8""?><timetable/>" ;
603+
604+ var mockHandler = new Mock < HttpMessageHandler > ( ) ;
605+ var requestCount = 0 ;
606+ mockHandler . Protected ( )
607+ . Setup < Task < HttpResponseMessage > > (
608+ "SendAsync" ,
609+ ItExpr . IsAny < HttpRequestMessage > ( ) ,
610+ ItExpr . IsAny < CancellationToken > ( ) )
611+ . ReturnsAsync ( ( ) =>
612+ {
613+ requestCount ++ ;
614+ // First station lookup (Frankfurt)
615+ if ( requestCount == 1 )
616+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( stationXml ) } ;
617+ // Second station lookup (Konstanz)
618+ if ( requestCount == 2 )
619+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( stationBXml ) } ;
620+ // Third is timetable
621+ if ( requestCount == 3 )
622+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( timetableXml ) } ;
623+ // Fourth is full changes
624+ return new HttpResponseMessage { StatusCode = HttpStatusCode . OK , Content = new StringContent ( changesXml ) } ;
625+ } ) ;
626+
627+ var httpClient = new HttpClient ( mockHandler . Object ) { BaseAddress = new Uri ( _config . BaseUrl ) } ;
628+ var service = new TimeTableService ( httpClient , _mockOptions . Object ) ;
629+
630+ // Act
631+ var result = await service . FindTrainConnectionsAsync ( "Frankfurt" , "Konstanz" ) ;
632+
633+ // Assert
634+ Assert . Contains ( "Train Connection Analysis" , result ) ;
635+ Assert . Contains ( "⚠ No direct connections found in the current timetable." , result ) ;
636+ Assert . Contains ( "This could mean:" , result ) ;
637+ Assert . Contains ( "- No direct trains operate between these stations" , result ) ;
638+ Assert . Contains ( "- Trains may require a transfer" , result ) ;
639+ Assert . Contains ( "- Try a different time or date" , result ) ;
640+ }
363641}
0 commit comments