diff --git a/das_client/integration_test/app_test.dart b/das_client/integration_test/app_test.dart index 79db99df..e1b99603 100644 --- a/das_client/integration_test/app_test.dart +++ b/das_client/integration_test/app_test.dart @@ -1,5 +1,5 @@ -import 'package:das_client/flavor.dart'; import 'package:das_client/app/i18n/i18n.dart'; +import 'package:das_client/flavor.dart'; import 'package:das_client/main.dart'; import 'package:fimber/fimber.dart'; import 'package:flutter_gen/gen_l10n/app_localizations_de.dart'; @@ -7,8 +7,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'di.dart'; -import 'test/train_journey_test.dart' as train_journey_tests; import 'test/navigation_test.dart' as navigation_tests; +import 'test/train_journey_test.dart' as train_journey_tests; +import 'test/train_search_test.dart' as train_search_tests; AppLocalizations l10n = AppLocalizationsDe(); @@ -18,9 +19,14 @@ void main() { train_journey_tests.main(); navigation_tests.main(); + train_search_tests.main(); } Future prepareAndStartApp(WidgetTester tester) async { + // iOS workaround for enterText not working on some devices, if its the first element + // (https://github.com/leancodepl/patrol/issues/1868#issuecomment-1814241939) + tester.testTextInput.register(); + await IntegrationTestDI.init(Flavor.dev); runDasApp(); await tester.pumpAndSettle(const Duration(milliseconds: 500)); diff --git a/das_client/integration_test/di.dart b/das_client/integration_test/di.dart index 41626da8..8d21b49d 100644 --- a/das_client/integration_test/di.dart +++ b/das_client/integration_test/di.dart @@ -11,25 +11,20 @@ import 'auth/mqtt_client_user_connector.dart'; class IntegrationTestDI { const IntegrationTestDI._(); - static bool _initialized = false; - - static Future init(Flavor flavor) { - if (_initialized) { - return GetIt.I.allReady(); - } else { - Fimber.i('Initialize integration test dependency injection'); - GetIt.I.registerFlavor(flavor); - GetIt.I.registerTokenSpecProvider(); - GetIt.I.registerOidcClient(); - _registerIntegrationTestAuthenticator(); - GetIt.I.registerSferaComponents(); - GetIt.I.registerMqttComponent(); - - GetIt.I.unregister(); - _registerMqttClientConnector(); - - _initialized = true; - } + static Future init(Flavor flavor) async { + Fimber.i('Initialize integration test dependency injection'); + await GetIt.I.reset(); + + GetIt.I.registerFlavor(flavor); + GetIt.I.registerTokenSpecProvider(); + GetIt.I.registerOidcClient(); + _registerIntegrationTestAuthenticator(); + GetIt.I.registerSferaComponents(); + GetIt.I.registerMqttComponent(); + + GetIt.I.unregister(); + _registerMqttClientConnector(); + return GetIt.I.allReady(); } diff --git a/das_client/integration_test/test/train_journey_test.dart b/das_client/integration_test/test/train_journey_test.dart index d1437773..0ff8f2ce 100644 --- a/das_client/integration_test/test/train_journey_test.dart +++ b/das_client/integration_test/test/train_journey_test.dart @@ -9,13 +9,11 @@ void main() { // Load app widget. await prepareAndStartApp(tester); - await tester.pump(const Duration(seconds: 1)); - - // Verify we have trainnumber with 9232. + // Verify we have trainnumber with 7839. expect(find.text('7839'), findsOneWidget); - // Verify we have company with 1088. - expect(find.text('1085'), findsOneWidget); + // Verify we have ru SBB. + expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); // check that the primary button is enabled var primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; @@ -25,10 +23,12 @@ void main() { await tester.tap(primaryButton); // wait for train journey to load - await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.pumpAndSettle(); // check if station is present expect(find.text('SO_W'), findsOneWidget); + + await tester.pumpAndSettle(); }); }); } diff --git a/das_client/integration_test/test/train_search_test.dart b/das_client/integration_test/test/train_search_test.dart new file mode 100644 index 00000000..f5ef2003 --- /dev/null +++ b/das_client/integration_test/test/train_search_test.dart @@ -0,0 +1,154 @@ +import 'package:das_client/util/error_code.dart'; +import 'package:das_client/util/format.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../app_test.dart'; +import '../util/test_utils.dart'; + +void main() { + group('train search screen tests', () { + + testWidgets('test default values', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + // Verify we have ru SBB. + expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); + + // Verify that today is preselected + expect(find.text(Format.date(DateTime.now())), findsOneWidget); + }); + + testWidgets('test selecting ru values', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + // Verify we have ru SBB. + expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); + + await tapElement(tester, find.text(l10n.c_ru_sbb_p)); + + expect(find.text(l10n.c_ru_sbb_c), findsOneWidget); + expect(find.text(l10n.c_ru_bls_p), findsOneWidget); + expect(find.text(l10n.c_ru_bls_c), findsOneWidget); + expect(find.text(l10n.c_ru_sob), findsOneWidget); + + await tapElement(tester, find.text(l10n.c_ru_sob)); + + expect(find.text(l10n.c_ru_sob), findsOneWidget); + expect(find.text(l10n.c_ru_sbb_p), findsNothing); + }); + + testWidgets('test load button disabled when validation fails', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + // Verify we have ru SBB. + expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); + + // Verify that today is preselected + expect(find.text(Format.date(DateTime.now())), findsOneWidget); + + var trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + expect(trainNumberText, findsOneWidget); + + await enterText(tester, trainNumberText, ''); + + // check that the primary button is disabled + var primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + expect(tester.widget(primaryButton).onPressed, isNull); + + }); + + testWidgets('test can select yesterday', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + final today = DateTime.now(); + final yesterday = today.add(Duration(days: -1)); + + var todayDateTextFinder = find.text(Format.date(today)); + var yesterdayDateTextFinder = find.text(Format.date(yesterday)); + + // Verify that today is preselected + expect(todayDateTextFinder, findsOneWidget); + expect(yesterdayDateTextFinder, findsNothing); + + await tapElement(tester, todayDateTextFinder); + + final sbbDatePickerFinder = find.byWidgetPredicate((widget) => widget is SBBDatePicker); + final yesterdayFinder = find.descendant( + of: sbbDatePickerFinder, + matching: find.byWidgetPredicate((widget) => widget is Text && widget.data == '${(yesterday.day)}.')); + await tapElement(tester, yesterdayFinder); + + // tap outside dialog + await tester.tapAt(Offset(200, 200)); + await tester.pumpAndSettle(); + + expect(todayDateTextFinder, findsNothing); + expect(yesterdayDateTextFinder, findsOneWidget); + + }); + + testWidgets('test can not select day before yesterday', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + final today = DateTime.now(); + final yesterday = today.add(Duration(days: -1)); + final dayBeforeYesterday = today.add(Duration(days: -2)); + + var todayDateTextFinder = find.text(Format.date(today)); + var yesterdayDateTextFinder = find.text(Format.date(yesterday)); + var dayBeforeYesterdayDateTextFinder = find.text(Format.date(dayBeforeYesterday)); + + // Verify that today is preselected + expect(todayDateTextFinder, findsOneWidget); + expect(yesterdayDateTextFinder, findsNothing); + + await tapElement(tester, todayDateTextFinder); + + final sbbDatePickerFinder = find.byWidgetPredicate((widget) => widget is SBBDatePicker); + final yesterdayFinder = find.descendant( + of: sbbDatePickerFinder, + matching: find.byWidgetPredicate((widget) => widget is Text && widget.data == '${(dayBeforeYesterday.day)}.')); + await tapElement(tester, yesterdayFinder); + + // tap outside dialog + await tester.tapAt(Offset(200, 200)); + await tester.pumpAndSettle(); + + expect(todayDateTextFinder, findsNothing); + expect(yesterdayDateTextFinder, findsOneWidget); + expect(dayBeforeYesterdayDateTextFinder, findsNothing); + }); + + testWidgets('test error if JP unavailable', (tester) async { + // Load app widget. + await prepareAndStartApp(tester); + + // Verify we have ru SBB. + expect(find.text(l10n.c_ru_sbb_p), findsOneWidget); + + // Verify that today is preselected + expect(find.text(Format.date(DateTime.now())), findsOneWidget); + + var trainNumberText = findTextFieldByLabel(l10n.p_train_selection_trainnumber_description); + expect(trainNumberText, findsOneWidget); + + await enterText(tester, trainNumberText, '1234'); + + // check that the primary button is disabled + var primaryButton = find.byWidgetPredicate((widget) => widget is SBBPrimaryButton).first; + expect(tester.widget(primaryButton).onPressed, isNotNull); + + await tapElement(tester, primaryButton); + + expect(find.text('${ErrorCode.sferaJpUnavailable.code}: ${l10n.c_error_sfera_jp_unavailable}'), findsOneWidget); + }); + + }); +} diff --git a/das_client/integration_test/util/test_utils.dart b/das_client/integration_test/util/test_utils.dart index 2a490530..a4b0ae19 100644 --- a/das_client/integration_test/util/test_utils.dart +++ b/das_client/integration_test/util/test_utils.dart @@ -1,3 +1,4 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -10,10 +11,16 @@ Future openDrawer(WidgetTester tester) async { } Future tapElement(WidgetTester tester, FinderBase element) async { - var gestureDetector = find.ancestor(of: element, matching: find.byType(GestureDetector)).first; - await tester.tap(gestureDetector); - - await tester.pumpAndSettle(const Duration(milliseconds: 250)); + await tester.tap(element); + await tester.pumpAndSettle(); } +Future enterText(WidgetTester tester, FinderBase element, String text) async { + await tester.enterText(element, text); + await tester.pumpAndSettle(); +} +Finder findTextFieldByLabel(String label) { + var sbbTextField = find.byWidgetPredicate((widget) => widget is SBBTextField && widget.labelText == label); + return find.descendant(of: sbbTextField, matching: find.byType(TextField)); +} diff --git a/das_client/l10n/strings_de.arb b/das_client/l10n/strings_de.arb index 7ae1787c..71be7766 100644 --- a/das_client/l10n/strings_de.arb +++ b/das_client/l10n/strings_de.arb @@ -1,10 +1,10 @@ { "c_app_name": "DAS Client", - "p_train_selection_load": "Fahrbild laden", + "p_train_selection_load": "Übernehmen", "p_train_selection_trainnumber_description": "Zugnummer", - "p_train_selection_trainnumber_placeholder": "z.B. 711", - "p_train_selection_company_description": "Company code", - "p_train_selection_company_placeholder": "z.B. 0085", + "p_train_selection_ru_description": "EVU", + "p_train_selection_date_description": "Datum", + "p_train_selection_choose_date": "Datum wählen", "p_train_journey_header_button_dark_theme": "Nachtmodus", "p_train_journey_header_button_pause": "Pause", "w_navigation_drawer_fahrtinfo_title": "Fahrtinfo", @@ -15,5 +15,16 @@ "p_login_login_button_text": "Login", "p_login_login_button_description": "Mit Ihrem Account einloggen", "p_login_login_failed": "Login fehlgeschlagen", - "w_adl_notification_title": "ADL Meldung" + "w_adl_notification_title": "ADL Meldung", + "c_ru_sbb_p": "SBB", + "c_ru_sbb_c": "SBB Cargo", + "c_ru_bls_p": "BLS", + "c_ru_bls_c": "BLS Cargo", + "c_ru_sob": "SOB", + "c_error_connection_failed": "Verbindung fehlgeschlagen", + "c_error_sfera_validation_failed": "Validierung der Daten fehlgeschlagen", + "c_error_sfera_handshake_rejected": "Server hat die Verbindung abgelehnt", + "c_error_sfera_request_timeout": "Timeout bei der Anfrage", + "c_error_sfera_jp_unavailable": "Fahrordnung nicht vorhanden", + "c_error_sfera_sp_invalid": "Unvollständige Daten erhalten" } \ No newline at end of file diff --git a/das_client/lib/app/bloc/train_journey_cubit.dart b/das_client/lib/app/bloc/train_journey_cubit.dart index 2d0f8c93..7d3526aa 100644 --- a/das_client/lib/app/bloc/train_journey_cubit.dart +++ b/das_client/lib/app/bloc/train_journey_cubit.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:das_client/app/model/ru.dart'; import 'package:das_client/sfera/sfera_component.dart'; import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; @@ -12,7 +13,7 @@ class TrainJourneyCubit extends Cubit { TrainJourneyCubit({ required SferaService sferaService, }) : _sferaService = sferaService, - super(SelectingTrainJourneyState()); + super(SelectingTrainJourneyState(date: DateTime.now(), ru: Ru.sbbP)); final SferaService _sferaService; @@ -25,35 +26,35 @@ class TrainJourneyCubit extends Cubit { void loadTrainJourney() async { final currentState = state; if (currentState is SelectingTrainJourneyState) { - final now = DateTime.now(); - final company = currentState.company; + final date = currentState.date; + final evu = currentState.ru; final trainNumber = currentState.trainNumber; - if (company == null || trainNumber == null) { + if (evu == null || trainNumber == null) { Fimber.i('company or trainNumber null'); return; } - emit(ConnectingState(company, trainNumber, now)); + emit(ConnectingState(evu, trainNumber, currentState.date)); _stateSubscription?.cancel(); _stateSubscription = _sferaService.stateStream.listen((state) { switch (state) { case SferaServiceState.connected: - emit(TrainJourneyLoadedState(company, trainNumber, now)); + emit(TrainJourneyLoadedState(evu, trainNumber, date)); break; case SferaServiceState.connecting: case SferaServiceState.handshaking: case SferaServiceState.loadingJourney: case SferaServiceState.loadingSegments: - emit(ConnectingState(company, trainNumber, now)); + emit(ConnectingState(evu, trainNumber, date)); break; case SferaServiceState.disconnected: case SferaServiceState.offline: emit(SelectingTrainJourneyState( - company: company, trainNumber: trainNumber, errorCode: _sferaService.lastErrorCode)); + ru: evu, trainNumber: trainNumber, date: date, errorCode: _sferaService.lastErrorCode)); break; } }); - _sferaService.connect(OtnId.create(company, trainNumber, now)); + _sferaService.connect(OtnId.create(evu.companyCode, trainNumber, date)); } } @@ -61,16 +62,28 @@ class TrainJourneyCubit extends Cubit { if (state is SelectingTrainJourneyState) { emit(SelectingTrainJourneyState( trainNumber: trainNumber, - company: (state as SelectingTrainJourneyState).company, + ru: (state as SelectingTrainJourneyState).ru, + date: (state as SelectingTrainJourneyState).date, errorCode: (state as SelectingTrainJourneyState).errorCode)); } } - void updateCompany(String? company) { + void updateCompany(Ru? evu) { if (state is SelectingTrainJourneyState) { emit(SelectingTrainJourneyState( trainNumber: (state as SelectingTrainJourneyState).trainNumber, - company: company, + ru: evu, + date: (state as SelectingTrainJourneyState).date, + errorCode: (state as SelectingTrainJourneyState).errorCode)); + } + } + + void updateDate(DateTime date) { + if (state is SelectingTrainJourneyState) { + emit(SelectingTrainJourneyState( + trainNumber: (state as SelectingTrainJourneyState).trainNumber, + ru: (state as SelectingTrainJourneyState).ru, + date: date, errorCode: (state as SelectingTrainJourneyState).errorCode)); } } @@ -80,7 +93,8 @@ class TrainJourneyCubit extends Cubit { Fimber.i('Reseting TrainJourney cubit in state $state'); emit(SelectingTrainJourneyState( trainNumber: (state as BaseTrainJourneyState).trainNumber, - company: (state as BaseTrainJourneyState).company)); + date: DateTime.now(), + ru: (state as BaseTrainJourneyState).ru)); } } } diff --git a/das_client/lib/app/bloc/train_journey_state.dart b/das_client/lib/app/bloc/train_journey_state.dart index 46669527..f8abaf01 100644 --- a/das_client/lib/app/bloc/train_journey_state.dart +++ b/das_client/lib/app/bloc/train_journey_state.dart @@ -4,32 +4,33 @@ part of 'train_journey_cubit.dart'; sealed class TrainJourneyState {} final class SelectingTrainJourneyState extends TrainJourneyState { - SelectingTrainJourneyState({this.company, this.trainNumber, this.errorCode}); + SelectingTrainJourneyState({this.ru, this.trainNumber, required this.date, this.errorCode}); final String? trainNumber; - final String? company; + final Ru? ru; + final DateTime date; final ErrorCode? errorCode; } abstract class BaseTrainJourneyState extends TrainJourneyState { - BaseTrainJourneyState(this.company, this.trainNumber, this.date); + BaseTrainJourneyState(this.ru, this.trainNumber, this.date); - final String company; + final Ru ru; final String trainNumber; final DateTime date; @override String toString() { - return '${runtimeType.toString()}(company=$company, trainNumber=$trainNumber, date=$date)'; + return '${runtimeType.toString()}(evu=$ru, trainNumber=$trainNumber, date=$date)'; } } final class ConnectingState extends BaseTrainJourneyState { - ConnectingState(super.company, super.trainNumber, super.date); + ConnectingState(super.ru, super.trainNumber, super.date); } final class TrainJourneyLoadedState extends BaseTrainJourneyState { - TrainJourneyLoadedState(super.company, super.trainNumber, super.date); + TrainJourneyLoadedState(super.ru, super.trainNumber, super.date); } diff --git a/das_client/lib/app/model/ru.dart b/das_client/lib/app/model/ru.dart new file mode 100644 index 00000000..9647258f --- /dev/null +++ b/das_client/lib/app/model/ru.dart @@ -0,0 +1,33 @@ +import 'package:das_client/app/i18n/i18n.dart'; +import 'package:flutter/material.dart'; + +enum Ru { + sbbP(companyCode: '1085'), + sbbC(companyCode: '2185'), + blsP(companyCode: '1163'), + blsC(companyCode: '3356'), + sob(companyCode: '5458'); + + const Ru({ + required this.companyCode, + }); + + final String companyCode; +} + +extension RuExtension on Ru { + String displayText(BuildContext context) { + switch (this) { + case Ru.sbbP: + return context.l10n.c_ru_sbb_p; + case Ru.sbbC: + return context.l10n.c_ru_sbb_c; + case Ru.blsP: + return context.l10n.c_ru_bls_p; + case Ru.blsC: + return context.l10n.c_ru_bls_c; + case Ru.sob: + return context.l10n.c_ru_sob; + } + } +} diff --git a/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart b/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart index 9017efbf..be32cf53 100644 --- a/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart +++ b/das_client/lib/app/pages/journey/train_journey/widgets/header/main_container.dart @@ -60,8 +60,7 @@ class MainContainer extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.only(left: sbbDefaultSpacing * 0.5), - child: Text('Brugg', - style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0)), + child: Text('Brugg', style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0)), ), ), _buttonArea(), diff --git a/das_client/lib/app/pages/journey/train_selection/train_selection.dart b/das_client/lib/app/pages/journey/train_selection/train_selection.dart index 3fdafabc..f7b3a2e2 100644 --- a/das_client/lib/app/pages/journey/train_selection/train_selection.dart +++ b/das_client/lib/app/pages/journey/train_selection/train_selection.dart @@ -1,8 +1,11 @@ import 'package:das_client/app/bloc/train_journey_cubit.dart'; import 'package:das_client/app/i18n/i18n.dart'; +import 'package:das_client/app/model/ru.dart'; +import 'package:das_client/app/widgets/header.dart'; +import 'package:das_client/util/error_code.dart'; +import 'package:das_client/util/format.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class TrainSelection extends StatefulWidget { @@ -14,101 +17,134 @@ class TrainSelection extends StatefulWidget { class _TrainSelectionState extends State { late TextEditingController _trainNumberController; - late TextEditingController _companyController; + late TextEditingController _dateController; @override void initState() { super.initState(); _trainNumberController = TextEditingController(text: '7839'); - _companyController = TextEditingController(text: '1085'); + _dateController = TextEditingController(); context.trainJourneyCubit.updateTrainNumber(_trainNumberController.text); - context.trainJourneyCubit.updateCompany(_companyController.text); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return Align( - child: SizedBox( - width: 500, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Spacer(), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, sbbDefaultSpacing, 0), - child: SBBTextField( - onChanged: (value) => _onCompanyChanged(context, value), - controller: _companyController, - labelText: context.l10n.p_train_selection_company_description, - icon: SBBIcons.building_tree_small, - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - ), - ), - const SizedBox(height: sbbDefaultSpacing), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, sbbDefaultSpacing, 0), - child: SBBTextField( - onChanged: (value) => _onTrainNumberChanged(context, value), - controller: _trainNumberController, - labelText: context.l10n.p_train_selection_trainnumber_description, - icon: SBBIcons.train_small, - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - ), - ), - const SizedBox(height: sbbDefaultSpacing * 2), - _loadButton(context, state), - const SizedBox(height: sbbDefaultSpacing * 2), - _errorWidget(context, state), - const Spacer(), - ], - ), - ), - ); + if (state is SelectingTrainJourneyState) { + return Column( + children: [ + Header(child: _headerWidgets(context, state)), + Spacer(), + _errorWidget(context, state), + Spacer(), + _loadButton(context, state) + ], + ); + } else { + return Container(); + } }, ); } - Widget _errorWidget(BuildContext context, TrainJourneyState state) { - if (state is SelectingTrainJourneyState && state.errorCode != null) { - return Text('${state.errorCode}', style: SBBTextStyles.mediumBold); + Widget _headerWidgets(BuildContext context, SelectingTrainJourneyState state) { + return Column( + children: [ + _trainNumberWidget(), + _dateDisplayWidget(state), + _ruSelectionWidget(context, state), + ], + ); + } + + Widget _trainNumberWidget() { + return Padding( + padding: const EdgeInsets.fromLTRB(sbbDefaultSpacing, sbbDefaultSpacing, 0, sbbDefaultSpacing / 2), + child: SBBTextField( + onChanged: (value) => context.trainJourneyCubit.updateTrainNumber(value), + controller: _trainNumberController, + labelText: context.l10n.p_train_selection_trainnumber_description, + keyboardType: TextInputType.number, + ), + ); + } + + Widget _dateDisplayWidget(SelectingTrainJourneyState state) { + _dateController.text = Format.date(state.date); + + return Padding( + padding: const EdgeInsets.fromLTRB(sbbDefaultSpacing, 0, 0, sbbDefaultSpacing / 2), + child: GestureDetector( + onTap: () => _showDatePicker(context, state), + child: SBBTextField( + labelText: context.l10n.p_train_selection_date_description, + controller: _dateController, + enabled: false, + ), + ), + ); + } + + Widget _ruSelectionWidget(BuildContext context, SelectingTrainJourneyState state) { + return Padding( + padding: const EdgeInsets.fromLTRB(sbbDefaultSpacing, 0, 0, sbbDefaultSpacing), + child: SBBSelect( + label: context.l10n.p_train_selection_ru_description, + value: state.ru, + items: Ru.values.map((ru) => SelectMenuItem(value: ru, label: ru.displayText(context))).toList(), + onChanged: (selectedRu) => context.trainJourneyCubit.updateCompany(selectedRu), + isLastElement: true, + ), + ); + } + + Widget _errorWidget(BuildContext context, SelectingTrainJourneyState state) { + if (state.errorCode != null) { + return Text(state.errorCode!.displayTextWithErrorCode(context), style: SBBTextStyles.mediumBold); } return Container(); } - Widget _loadButton(BuildContext context, TrainJourneyState state) { - return SBBPrimaryButton( - label: context.l10n.p_train_selection_load, - onPressed: _canContinue(state) - ? () { - final trainJourneyCubit = context.trainJourneyCubit; - if (!trainJourneyCubit.isClosed) { - trainJourneyCubit.loadTrainJourney(); + Widget _loadButton(BuildContext context, SelectingTrainJourneyState state) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: sbbDefaultSpacing, horizontal: sbbDefaultSpacing / 2), + child: SBBPrimaryButton( + label: context.l10n.p_train_selection_load, + onPressed: _canContinue(state) + ? () { + final trainJourneyCubit = context.trainJourneyCubit; + if (!trainJourneyCubit.isClosed) { + trainJourneyCubit.loadTrainJourney(); + } } - } - : null, + : null, + ), ); } - bool _canContinue(TrainJourneyState state) { - if (state is SelectingTrainJourneyState) { - return state.trainNumber != null && - state.trainNumber!.isNotEmpty && - state.company != null && - state.company!.isNotEmpty; - } - return false; + void _showDatePicker(BuildContext context, SelectingTrainJourneyState state) { + showSBBModalSheet( + context: context, title: context.l10n.p_train_selection_choose_date, child: _datePickerWidget(context, state)); } - void _onTrainNumberChanged(BuildContext context, String value) { - context.trainJourneyCubit.updateTrainNumber(value); + Widget _datePickerWidget(BuildContext context, SelectingTrainJourneyState state) { + DateTime now = DateTime.now(); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SBBDatePicker( + initialDate: state.date, + minimumDate: now.add(Duration(days: -1)), + maximumDate: now.add(Duration(hours: 4)), + onDateChanged: (value) => context.trainJourneyCubit.updateDate(value)), + ], + ); } - void _onCompanyChanged(BuildContext context, String value) { - context.trainJourneyCubit.updateCompany(value); + bool _canContinue(SelectingTrainJourneyState state) { + return state.trainNumber != null && state.trainNumber!.isNotEmpty && state.ru != null; } } diff --git a/das_client/lib/app/widgets/header.dart b/das_client/lib/app/widgets/header.dart new file mode 100644 index 00000000..39778dcb --- /dev/null +++ b/das_client/lib/app/widgets/header.dart @@ -0,0 +1,39 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class Header extends StatelessWidget { + const Header({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + _background(context), + _container(context), + ], + ); + } + + Widget _background(BuildContext context) { + final primary = Theme.of(context).colorScheme.secondary; + return Container( + color: primary, + height: sbbDefaultSpacing * 2, + ); + } + + Widget _container(BuildContext context) { + return SBBGroup( + margin: const EdgeInsetsDirectional.fromSTEB( + sbbDefaultSpacing * 0.5, + 0, + sbbDefaultSpacing * 0.5, + sbbDefaultSpacing, + ), + useShadow: false, + child: child, + ); + } +} diff --git a/das_client/lib/sfera/src/model/enums/jp_status.dart b/das_client/lib/sfera/src/model/enums/jp_status.dart new file mode 100644 index 00000000..1e1e4854 --- /dev/null +++ b/das_client/lib/sfera/src/model/enums/jp_status.dart @@ -0,0 +1,16 @@ +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; + +enum JpStatus implements XmlEnum { + valid(xmlValue: 'Valid'), + invalid(xmlValue: 'Invalid'), + unavailable(xmlValue: 'Unavailable'), + update(xmlValue: 'Update'), + overwrite(xmlValue: 'Overwrite'); + + const JpStatus({ + required this.xmlValue, + }); + + @override + final String xmlValue; +} diff --git a/das_client/lib/sfera/src/model/enums/sp_status.dart b/das_client/lib/sfera/src/model/enums/sp_status.dart new file mode 100644 index 00000000..3c837d1b --- /dev/null +++ b/das_client/lib/sfera/src/model/enums/sp_status.dart @@ -0,0 +1,13 @@ +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; + +enum SpStatus implements XmlEnum { + valid(xmlValue: 'Valid'), + invalid(xmlValue: 'Invalid'); + + const SpStatus({ + required this.xmlValue, + }); + + @override + final String xmlValue; +} diff --git a/das_client/lib/sfera/src/model/journey_profile.dart b/das_client/lib/sfera/src/model/journey_profile.dart index 96323e20..0cd8dc85 100644 --- a/das_client/lib/sfera/src/model/journey_profile.dart +++ b/das_client/lib/sfera/src/model/journey_profile.dart @@ -1,3 +1,5 @@ +import 'package:das_client/sfera/src/model/enums/jp_status.dart'; +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; import 'package:das_client/sfera/src/model/segment_profile_list.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/train_identification.dart'; @@ -11,6 +13,8 @@ class JourneyProfile extends SferaXmlElement { Iterable get segmentProfilesLists => children.whereType(); + JpStatus get status => XmlEnum.valueOf(JpStatus.values, attributes['JP_Status']) ?? JpStatus.valid; + @override bool validate() { return validateHasChild('TrainIdentification') && super.validate(); diff --git a/das_client/lib/sfera/src/model/segment_profile.dart b/das_client/lib/sfera/src/model/segment_profile.dart index 0a69d63f..60714fdf 100644 --- a/das_client/lib/sfera/src/model/segment_profile.dart +++ b/das_client/lib/sfera/src/model/segment_profile.dart @@ -1,3 +1,5 @@ +import 'package:das_client/sfera/src/model/enums/sp_status.dart'; +import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/sp_points.dart'; import 'package:das_client/sfera/src/model/sp_zone.dart'; @@ -15,6 +17,8 @@ class SegmentProfile extends SferaXmlElement { String get id => attributes['SP_ID']!; + SpStatus get status => XmlEnum.valueOf(SpStatus.values, attributes['SP_Status']) ?? SpStatus.valid; + SpZone? get zone => children.whereType().firstOrNull; SpPoints? get points => children.whereType().firstOrNull; diff --git a/das_client/lib/sfera/src/service/sfera_service.dart b/das_client/lib/sfera/src/service/sfera_service.dart index dd52b84f..e7f264fd 100644 --- a/das_client/lib/sfera/src/service/sfera_service.dart +++ b/das_client/lib/sfera/src/service/sfera_service.dart @@ -21,9 +21,9 @@ abstract class SferaService { void disconnect(); - static Future messageHeader({TrainIdentification? trainIdentification}) async { + static Future messageHeader({TrainIdentification? trainIdentification, required String sender}) async { return MessageHeader.create(const Uuid().v4(), Format.sferaTimestamp(DateTime.now()), - await DeviceIdInfo.getDeviceId(), 'TMS', '1085', '0085', + await DeviceIdInfo.getDeviceId(), 'TMS', sender, '0085', trainIdentification: trainIdentification); } diff --git a/das_client/lib/sfera/src/service/sfera_service_impl.dart b/das_client/lib/sfera/src/service/sfera_service_impl.dart index 25c46aaa..f4127b6a 100644 --- a/das_client/lib/sfera/src/service/sfera_service_impl.dart +++ b/das_client/lib/sfera/src/service/sfera_service_impl.dart @@ -135,7 +135,7 @@ class SferaServiceImpl implements SferaService { _messageHandlers.remove(task); lastErrorCode = errorCode; Fimber.e('Task $task failed with error code $errorCode'); - if (task is HandshakeTask) { + if (task is HandshakeTask || task is RequestJourneyProfileTask || task is RequestSegmentProfilesTask) { disconnect(); } } diff --git a/das_client/lib/sfera/src/service/task/handshake_task.dart b/das_client/lib/sfera/src/service/task/handshake_task.dart index 29826f02..555ef40e 100644 --- a/das_client/lib/sfera/src/service/task/handshake_task.dart +++ b/das_client/lib/sfera/src/service/task/handshake_task.dart @@ -41,7 +41,7 @@ class HandshakeTask extends SferaTask { ], relatedTrainRequestType: RelatedTrainRequestType.ownTrainAndRelatedTrains, statusReportsEnabled: false); var sferaB2gRequestMessage = - SferaB2gRequestMessage.create(await SferaService.messageHeader(), handshakeRequest: handshakeRequest); + SferaB2gRequestMessage.create(await SferaService.messageHeader(sender: otnId.company), handshakeRequest: handshakeRequest); var success = _mqttService.publishMessage(otnId.company, sferaTrain, sferaB2gRequestMessage.buildDocument().toString()); diff --git a/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart b/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart index 5e71295a..582beea7 100644 --- a/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart +++ b/das_client/lib/sfera/src/service/task/request_journey_profile_task.dart @@ -1,5 +1,6 @@ import 'package:das_client/mqtt/mqtt_component.dart'; import 'package:das_client/sfera/src/model/b2g_request.dart'; +import 'package:das_client/sfera/src/model/enums/jp_status.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/jp_request.dart'; import 'package:das_client/sfera/src/model/otn_id.dart'; @@ -9,6 +10,7 @@ import 'package:das_client/sfera/src/model/train_identification.dart'; import 'package:das_client/sfera/src/repo/sfera_repository.dart'; import 'package:das_client/sfera/src/service/sfera_service.dart'; import 'package:das_client/sfera/src/service/task/sfera_task.dart'; +import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; class RequestJourneyProfileTask extends SferaTask { @@ -38,7 +40,7 @@ class RequestJourneyProfileTask extends SferaTask { var jpRequest = JpRequest.create(trainIdentification); var sferaB2gRequestMessage = SferaB2gRequestMessage.create( - await SferaService.messageHeader(trainIdentification: trainIdentification), + await SferaService.messageHeader(trainIdentification: trainIdentification, sender: otnId.company), b2gRequest: B2gRequest.createJPRequest(jpRequest)); Fimber.i('Sending journey profile request...'); _mqttService.publishMessage(otnId.company, SferaService.sferaTrain(otnId.operationalTrainNumber, otnId.startDate), @@ -49,6 +51,15 @@ class RequestJourneyProfileTask extends SferaTask { Future handleMessage(SferaG2bReplyMessage replyMessage) async { if (replyMessage.payload != null && replyMessage.payload!.journeyProfiles.isNotEmpty) { stopTimeout(); + final journeyProfile = replyMessage.payload!.journeyProfiles.first; + if (journeyProfile.status == JpStatus.invalid || journeyProfile.status == JpStatus.unavailable) { + Fimber.w( + 'Received JourneyProfile with status=${journeyProfile.status}.', + ); + _taskFailedCallback(this, ErrorCode.sferaJpUnavailable); + return true; + } + Fimber.i( 'Received G2bReplyPayload response with ${replyMessage.payload!.journeyProfiles.length} JourneyProfiles and ${replyMessage.payload!.segmentProfiles.length} SegmentProfiles...', ); diff --git a/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart b/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart index 3c66dfca..a0337705 100644 --- a/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart +++ b/das_client/lib/sfera/src/service/task/request_segment_profiles_task.dart @@ -1,5 +1,6 @@ import 'package:das_client/mqtt/mqtt_component.dart'; import 'package:das_client/sfera/src/model/b2g_request.dart'; +import 'package:das_client/sfera/src/model/enums/sp_status.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/otn_id.dart'; import 'package:das_client/sfera/src/model/segment_profile.dart'; @@ -11,6 +12,7 @@ import 'package:das_client/sfera/src/model/train_identification.dart'; import 'package:das_client/sfera/src/repo/sfera_repository.dart'; import 'package:das_client/sfera/src/service/sfera_service.dart'; import 'package:das_client/sfera/src/service/task/sfera_task.dart'; +import 'package:das_client/util/error_code.dart'; import 'package:fimber/fimber.dart'; class RequestSegmentProfilesTask extends SferaTask> { @@ -55,7 +57,7 @@ class RequestSegmentProfilesTask extends SferaTask> { var trainIdentification = TrainIdentification.create(otnId: otnId); var sferaB2gRequestMessage = SferaB2gRequestMessage.create( - await SferaService.messageHeader(trainIdentification: trainIdentification), + await SferaService.messageHeader(trainIdentification: trainIdentification, sender: otnId.company), b2gRequest: B2gRequest.createSPRequest(spRequests)); Fimber.i('Sending segment profiles request...'); @@ -86,11 +88,22 @@ class RequestSegmentProfilesTask extends SferaTask> { 'Received G2bReplyPayload response with ${replyMessage.payload!.segmentProfiles.length} SegmentProfiles...', ); + bool allValid = true; + for (var element in replyMessage.payload!.segmentProfiles) { - await _sferaRepository.saveSegmentProfile(element); + if (element.status == SpStatus.valid) { + await _sferaRepository.saveSegmentProfile(element); + } else { + allValid = false; + } + } + + if (allValid) { + _taskCompletedCallback(this, replyMessage.payload!.segmentProfiles.toList()); + } else { + _taskFailedCallback(this, ErrorCode.sferaSpInvalid); } - _taskCompletedCallback(this, replyMessage.payload!.segmentProfiles.toList()); return true; } return false; diff --git a/das_client/lib/util/error_code.dart b/das_client/lib/util/error_code.dart index 915dcb66..f5b74289 100644 --- a/das_client/lib/util/error_code.dart +++ b/das_client/lib/util/error_code.dart @@ -1,14 +1,43 @@ +import 'package:das_client/app/i18n/i18n.dart'; +import 'package:flutter/material.dart'; + enum ErrorCode { connectionFailed(code: 1), sferaValidationFailed(code: 10000), sferaHandshakeRejected(code: 10001), - sferaRequestTimeout(code: 10002); + sferaRequestTimeout(code: 10002), + sferaJpUnavailable(code: 10003), + sferaSpInvalid(code: 10004); const ErrorCode({ required this.code, }); final int code; +} + +extension ErrorCodeExtension on ErrorCode { + + String displayTextWithErrorCode(BuildContext context) { + return '$code: ${displayText(context)}'; + } + + String displayText(BuildContext context) { + switch (this) { + case ErrorCode.connectionFailed: + return context.l10n.c_error_connection_failed; + case ErrorCode.sferaValidationFailed: + return context.l10n.c_error_sfera_validation_failed; + case ErrorCode.sferaHandshakeRejected: + return context.l10n.c_error_sfera_handshake_rejected; + case ErrorCode.sferaRequestTimeout: + return context.l10n.c_error_sfera_request_timeout; + case ErrorCode.sferaJpUnavailable: + return context.l10n.c_error_sfera_jp_unavailable; + case ErrorCode.sferaSpInvalid: + return context.l10n.c_error_sfera_sp_invalid; + } + } } \ No newline at end of file diff --git a/das_client/lib/util/format.dart b/das_client/lib/util/format.dart index 221f37ee..192caf1c 100644 --- a/das_client/lib/util/format.dart +++ b/das_client/lib/util/format.dart @@ -22,6 +22,12 @@ class Format { return dateFormat.format(localDate); } + static String date(DateTime date) { + final localDate = date.toLocal(); + final dateFormat = DateFormat('dd.MM.yyyy'); + return dateFormat.format(localDate); + } + static String time(DateTime date, {bool showSeconds = true}) { final localDate = date.toLocal(); final format = showSeconds ? DateFormat.HOUR24_MINUTE_SECOND : DateFormat.HOUR24_MINUTE; diff --git a/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart b/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart index 3dcf2779..52ae60a8 100644 --- a/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart +++ b/das_client/test/sfera/service/sfera_request_journey_profile_task_test.dart @@ -71,6 +71,60 @@ void main() { verify(sferaRepository.saveSegmentProfile(any)).called(23); }); + test('Test JP Task fail on JP Invalid', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_jp.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var journeyTask = RequestJourneyProfileTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId); + + await journeyTask.execute((task, data) { + fail('Test should not call success'); + }, (task, errorCode) { + expect(task, journeyTask); + expect(errorCode, ErrorCode.sferaJpUnavailable); + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + expect(result, true); + + verifyNever(sferaRepository.saveJourneyProfile(any)); + verifyNever(sferaRepository.saveSegmentProfile(any)); + }); + + test('Test JP Task fail on JP Unavailable', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232_unavailable_jp.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var journeyTask = RequestJourneyProfileTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId); + + await journeyTask.execute((task, data) { + fail('Test should not call success'); + }, (task, errorCode) { + expect(task, journeyTask); + expect(errorCode, ErrorCode.sferaJpUnavailable); + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + var result = await journeyTask.handleMessage(sferaG2bReplyMessage); + expect(result, true); + + verifyNever(sferaRepository.saveJourneyProfile(any)); + verifyNever(sferaRepository.saveSegmentProfile(any)); + }); + test('Test JP request ignores other messages', () async { when(mqttService.publishMessage(any, any, any)).thenReturn(true); diff --git a/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart b/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart new file mode 100644 index 00000000..7eb8d908 --- /dev/null +++ b/das_client/test/sfera/service/sfera_request_segment_profile_task_test.dart @@ -0,0 +1,163 @@ +import 'dart:io'; + +import 'package:das_client/mqtt/mqtt_component.dart'; +import 'package:das_client/sfera/sfera_component.dart'; +import 'package:das_client/sfera/src/service/task/request_segment_profiles_task.dart'; +import 'package:das_client/util/error_code.dart'; +import 'package:fimber/fimber.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'sfera_request_journey_profile_task_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) +void main() { + late MockMqttService mqttService; + late MockSferaRepository sferaRepository; + late OtnId otnId; + Fimber.plantTree(DebugTree()); + + setUp(() { + mqttService = MockMqttService(); + sferaRepository = MockSferaRepository(); + otnId = OtnId.create('1085', '719', DateTime.now()); + }); + + test('Test SP request successful', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var segmentTask = RequestSegmentProfilesTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId, + journeyProfile: sferaG2bReplyMessage.payload!.journeyProfiles.first); + + await segmentTask.execute((task, data) { + expect(task, segmentTask); + expect(data, sferaG2bReplyMessage.payload!.segmentProfiles); + }, (task, errorCode) { + fail('Task failed with error code $errorCode'); + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + var result = await segmentTask.handleMessage(sferaG2bReplyMessage); + expect(result, true); + }); + + test('Test SP request saves to sfera repository', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var segmentTask = RequestSegmentProfilesTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId, + journeyProfile: sferaG2bReplyMessage.payload!.journeyProfiles.first); + + await segmentTask.execute((task, data) { + expect(task, segmentTask); + }, (task, errorCode) { + fail('Task failed with error code $errorCode'); + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + var result = await segmentTask.handleMessage(sferaG2bReplyMessage); + expect(result, true); + + verifyNever(sferaRepository.saveJourneyProfile(any)); + verify(sferaRepository.saveSegmentProfile(any)).called(23); + }); + + test('Test SP request fail on invalid SP', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_sp.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var segmentTask = RequestSegmentProfilesTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId, + journeyProfile: sferaG2bReplyMessage.payload!.journeyProfiles.first); + + await segmentTask.execute((task, data) { + fail('Test should not call success'); + }, (task, errorCode) { + expect(task, segmentTask); + expect(errorCode, ErrorCode.sferaSpInvalid); + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + var result = await segmentTask.handleMessage(sferaG2bReplyMessage); + expect(result, true); + + verifyNever(sferaRepository.saveJourneyProfile(any)); + verify(sferaRepository.saveSegmentProfile(any)).called(22); + }); + + test('Test SP request ignores other messages', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var segmentTask = RequestSegmentProfilesTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId, + journeyProfile: sferaG2bReplyMessage.payload!.journeyProfiles.first); + + await segmentTask.execute((task, data) { + fail('Test should not call success'); + }, (task, errorCode) { + fail('Test should not call error'); + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + final handShakefile = File('test_resources/SFERA_G2B_ReplyMessage_handshake.xml'); + var handshakeSferaG2bReplyMessage = SferaReplyParser.parse(handShakefile.readAsStringSync()); + var result = await segmentTask.handleMessage(handshakeSferaG2bReplyMessage); + expect(result, false); + }); + + test('Test request segment profile timeout', () async { + when(mqttService.publishMessage(any, any, any)).thenReturn(true); + + final file = File('test_resources/SFERA_G2B_Reply_JP_request_9232.xml'); + var sferaG2bReplyMessage = SferaReplyParser.parse(file.readAsStringSync()); + + var journeyTask = RequestSegmentProfilesTask( + mqttService: mqttService, + sferaRepository: sferaRepository, + otnId: otnId, + journeyProfile: sferaG2bReplyMessage.payload!.journeyProfiles.first, + timeout: const Duration(seconds: 1), + ); + + var timeoutReached = false; + await journeyTask.execute((task, data) { + fail('Test should not call success'); + }, (task, errorCode) { + expect(errorCode, ErrorCode.sferaRequestTimeout); + timeoutReached = true; + }); + + verify(mqttService.publishMessage(any, any, any)).called(1); + + await Future.delayed(const Duration(milliseconds: 1200)); + expect(timeoutReached, true); + }); +} diff --git a/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_jp.xml b/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_jp.xml new file mode 100644 index 00000000..83f0580f --- /dev/null +++ b/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_jp.xml @@ -0,0 +1,446 @@ + + + + 0088 + 1088 + + + + + + 1088 + 9232 + + 2022-05-17 + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_sp.xml b/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_sp.xml new file mode 100644 index 00000000..a6a217e9 --- /dev/null +++ b/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_invalid_sp.xml @@ -0,0 +1,1963 @@ + + + + 0088 + 1088 + + + + + + 1088 + 9232 + + 2022-05-17 + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_unavailable_jp.xml b/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_unavailable_jp.xml new file mode 100644 index 00000000..c1c7f4d5 --- /dev/null +++ b/das_client/test_resources/SFERA_G2B_Reply_JP_request_9232_unavailable_jp.xml @@ -0,0 +1,446 @@ + + + + 0088 + 1088 + + + + + + 1088 + 9232 + + 2022-05-17 + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0088 + + + + + + + + + + + + + + +