From 6b5d876cd5befc7783446b9e5bb8a0610aa20cc0 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Tue, 5 Nov 2024 13:26:53 +0100 Subject: [PATCH 01/12] added -1 day time logic to client --- src/BreCalClient/EditShipcallControl.xaml.cs | 18 ++++---- .../EditTimesAgencyIncomingControl.xaml.cs | 8 ++-- .../EditTimesAgencyOutgoingControl.xaml.cs | 8 ++-- .../EditTimesAgencyShiftingControl.xaml.cs | 12 +++--- src/BreCalClient/EditTimesControl.xaml.cs | 42 +++++++++---------- .../EditTimesTerminalControl.xaml.cs | 8 ++-- src/BreCalClient/Extensions.cs | 8 ++++ 7 files changed, 54 insertions(+), 50 deletions(-) diff --git a/src/BreCalClient/EditShipcallControl.xaml.cs b/src/BreCalClient/EditShipcallControl.xaml.cs index fd42086..8609f0a 100644 --- a/src/BreCalClient/EditShipcallControl.xaml.cs +++ b/src/BreCalClient/EditShipcallControl.xaml.cs @@ -204,26 +204,22 @@ namespace BreCalClient { case ShipcallType.Departure: isEnabled &= this.comboBoxDepartureBerth.SelectedItem != null; - isEnabled &= this.datePickerETD.Value.HasValue; - if(this.datePickerETD.Value.HasValue) - isEnabled &= (this.datePickerETD.Value.Value > DateTime.Now); + isEnabled &= this.datePickerETD.Value.HasValue; + isEnabled &= !(this.datePickerETD.Value.IsTooOld() && this.datePickerETD.Value != this.ShipcallModel.Shipcall?.Etd); isEnabled &= !this.datePickerETD.Value.IsTooFar(); break; case ShipcallType.Arrival: isEnabled &= this.comboBoxArrivalBerth.SelectedItem != null; - isEnabled &= this.datePickerETA.Value.HasValue; - if(this.datePickerETA.Value.HasValue) - isEnabled &= (this.datePickerETA.Value.Value > DateTime.Now); + isEnabled &= this.datePickerETA.Value.HasValue; + isEnabled &= !(this.datePickerETA.Value.IsTooOld() && this.datePickerETA.Value != this.ShipcallModel.Shipcall?.Eta); isEnabled &= !this.datePickerETA.Value.IsTooFar(); break; case ShipcallType.Shifting: isEnabled &= ((this.comboBoxDepartureBerth.SelectedItem != null) && (this.comboBoxArrivalBerth.SelectedItem != null)); isEnabled &= this.datePickerETD.Value.HasValue; - isEnabled &= this.datePickerETA.Value.HasValue; - if (this.datePickerETD.Value.HasValue) - isEnabled &= (this.datePickerETD.Value.Value > DateTime.Now); - if (this.datePickerETA.Value.HasValue) - isEnabled &= (this.datePickerETA.Value.Value > DateTime.Now); + isEnabled &= this.datePickerETA.Value.HasValue; + isEnabled &= !(this.datePickerETD.Value.IsTooOld() && this.datePickerETD.Value != this.ShipcallModel.Shipcall?.Etd); + isEnabled &= !(this.datePickerETA.Value.IsTooOld() && this.datePickerETA.Value != this.ShipcallModel.Shipcall?.Eta); if (this.datePickerETA.Value.HasValue && this.datePickerETD.Value.HasValue) isEnabled &= (this.datePickerETA.Value.Value > this.datePickerETD.Value.Value); isEnabled &= !this.datePickerETD.Value.IsTooFar(); diff --git a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs index 8d85d68..47b5cab 100644 --- a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs @@ -97,13 +97,13 @@ namespace BreCalClient { message = ""; - if (this.datePickerETA.Value.HasValue && (this.datePickerETA.Value.Value < DateTime.Now) && (this.datePickerETA_End.Value == null)) + if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA_End.Value == null) && (this.datePickerETA.Value != this.Times.EtaBerth)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; } - if(this.datePickerETA_End.Value.HasValue && this.datePickerETA_End.Value < DateTime.Now) + if(this.datePickerETA_End.Value.IsTooOld() && (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; @@ -115,13 +115,13 @@ namespace BreCalClient return false; } - if (this.datePickerTidalWindowFrom.Value.HasValue && (this.datePickerTidalWindowFrom.Value.Value < DateTime.Now) && (this.datePickerTidalWindowTo.Value == null)) + if (this.datePickerTidalWindowFrom.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value == null) && (this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom)) { message = BreCalClient.Resources.Resources.textTideTimesInThePast; return false; } - if (this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowTo.Value < DateTime.Now) + if (this.datePickerTidalWindowTo.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) { message = BreCalClient.Resources.Resources.textTideTimesInThePast; return false; diff --git a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs index c850ed8..7721819 100644 --- a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs @@ -107,13 +107,13 @@ namespace BreCalClient { message = ""; - if (this.datePickerETD.Value.HasValue && (this.datePickerETD.Value.Value < DateTime.Now) && (this.datePickerETD_End.Value == null)) + if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD_End.Value == null) && (this.datePickerETD.Value != this.Times.EtdBerth)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; } - if (this.datePickerETD_End.Value.HasValue && this.datePickerETD_End.Value < DateTime.Now) + if (this.datePickerETD_End.Value.IsTooOld() && (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; @@ -125,13 +125,13 @@ namespace BreCalClient return false; } - if (this.datePickerTidalWindowFrom.Value.HasValue && (this.datePickerTidalWindowFrom.Value.Value < DateTime.Now) && (this.datePickerTidalWindowTo.Value == null)) + if (this.datePickerTidalWindowFrom.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value == null) && (this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom)) { message = BreCalClient.Resources.Resources.textTideTimesInThePast; return false; } - if (this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowTo.Value < DateTime.Now) + if (this.datePickerTidalWindowTo.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) { message = BreCalClient.Resources.Resources.textTideTimesInThePast; return false; diff --git a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs index 9307943..08cc2ca 100644 --- a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs @@ -97,13 +97,13 @@ namespace BreCalClient { message = ""; - if (this.datePickerETA.Value.HasValue && (this.datePickerETA.Value.Value < DateTime.Now) && (this.datePickerETA_End.Value == null)) + if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA_End.Value == null) && (this.datePickerETA.Value != this.Times.EtaBerth)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; } - if (this.datePickerETA_End.Value.HasValue && this.datePickerETA_End.Value < DateTime.Now) + if (this.datePickerETA_End.Value.IsTooOld() && (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; @@ -115,13 +115,13 @@ namespace BreCalClient return false; } - if (this.datePickerETD.Value.HasValue && (this.datePickerETD.Value.Value < DateTime.Now) && (this.datePickerETD_End.Value == null)) + if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD_End.Value == null) && (this.datePickerETD.Value != this.Times.EtdBerth)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; } - if (this.datePickerETD_End.Value.HasValue && this.datePickerETD_End.Value < DateTime.Now) + if (this.datePickerETD_End.Value.IsTooOld() && (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; @@ -133,13 +133,13 @@ namespace BreCalClient return false; } - if (this.datePickerTidalWindowFrom.Value.HasValue && (this.datePickerTidalWindowFrom.Value.Value < DateTime.Now) && (this.datePickerTidalWindowTo.Value == null)) + if (this.datePickerTidalWindowFrom.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value == null) && (this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom)) { message = BreCalClient.Resources.Resources.textTideTimesInThePast; return false; } - if (this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowTo.Value < DateTime.Now) + if (this.datePickerTidalWindowTo.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) { message = BreCalClient.Resources.Resources.textTideTimesInThePast; return false; diff --git a/src/BreCalClient/EditTimesControl.xaml.cs b/src/BreCalClient/EditTimesControl.xaml.cs index 079d9ab..89bdd8d 100644 --- a/src/BreCalClient/EditTimesControl.xaml.cs +++ b/src/BreCalClient/EditTimesControl.xaml.cs @@ -1,7 +1,7 @@ // Copyright (c) 2023 schick Informatik // Description: Single dialog to edit times for all participant types // (we might use different controls at a later time) -// +// using BreCalClient.misc.Model; using System; @@ -40,7 +40,7 @@ namespace BreCalClient #region event handler private void Window_Loaded(object sender, RoutedEventArgs e) - { + { this.EnableControls(); this.CopyToControls(); } @@ -49,7 +49,7 @@ namespace BreCalClient { if (!CheckValues(out string message)) { - System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, + System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning); } else @@ -58,7 +58,7 @@ namespace BreCalClient this.DialogResult = true; this.Close(); } - } + } private void buttonCancel_Click(object sender, RoutedEventArgs e) { @@ -99,13 +99,13 @@ namespace BreCalClient message = ""; - if (this.datePickerETABerth.Value.HasValue && (this.datePickerETABerth.Value.Value < DateTime.Now) && (this.datePickerETABerth_End.Value == null)) + if (this.datePickerETABerth.Value.IsTooOld() && (this.datePickerETABerth_End.Value == null) && (this.datePickerETABerth.Value != this.Times.EtaBerth)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; } - if (this.datePickerETABerth_End.Value.HasValue && this.datePickerETABerth_End.Value < DateTime.Now) + if (this.datePickerETABerth_End.Value.IsTooOld() && this.datePickerETABerth_End.Value != this.Times.EtaIntervalEnd) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; @@ -117,13 +117,13 @@ namespace BreCalClient return false; } - if (this.datePickerETDBerth.Value.HasValue && (this.datePickerETDBerth.Value.Value < DateTime.Now) && (this.datePickerETABerth_End.Value == null)) + if (this.datePickerETDBerth.Value.IsTooOld() && (this.datePickerETDBerth_End.Value == null) && (this.datePickerETDBerth.Value != this.Times.EtdBerth)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; } - if (this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth_End.Value < DateTime.Now) + if (this.datePickerETDBerth_End.Value.IsTooOld() && this.datePickerETDBerth_End.Value != this.Times.EtdIntervalEnd) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; @@ -135,13 +135,13 @@ namespace BreCalClient return false; } - if (this.datePickerLockTime.Value.HasValue && (this.datePickerLockTime.Value.Value < DateTime.Now)) + if (this.datePickerLockTime.Value.IsTooOld() && (this.datePickerLockTime.Value != this.Times.LockTime)) { message = BreCalClient.Resources.Resources.textLockTimeInThePast; return false; } - if (this.datePickerZoneEntry.Value.HasValue && this.datePickerZoneEntry.Value < DateTime.Now) + if (this.datePickerZoneEntry.Value.IsTooOld() && (this.datePickerZoneEntry.Value != this.Times.ZoneEntry)) { message = BreCalClient.Resources.Resources.textZoneEntryInThePast; return false; @@ -218,13 +218,13 @@ namespace BreCalClient { this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[displayIndex]); this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[displayIndex]); - } + } else { this.labelETA.Content = BreCalClient.Resources.Resources.textETABerth; this.labelETD.Content = BreCalClient.Resources.Resources.textETDBerth; } - } + } this.SetLockButton(this.Times.EtaBerthFixed ?? false); } @@ -290,11 +290,11 @@ namespace BreCalClient case Extensions.ParticipantType.PORT_ADMINISTRATION: this.datePickerLockTime.IsEnabled = true; break; - case Extensions.ParticipantType.TUG: - case Extensions.ParticipantType.PILOT: - this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival); + case Extensions.ParticipantType.TUG: + case Extensions.ParticipantType.PILOT: + this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival); break; - } + } } private void SetLockButton(bool newValue) @@ -311,7 +311,7 @@ namespace BreCalClient this.imageFixedOrder.Source = new BitmapImage(new Uri(@"pack://application:,,,/Resources/lock_open.png", UriKind.RelativeOrAbsolute)); this.buttonFixedOrder.ToolTip = BreCalClient.Resources.Resources.textTooltipSetFixedOrder; } - } + } #endregion @@ -335,17 +335,17 @@ namespace BreCalClient private void contextMenuItemClearZoneEntry_Click(object sender, RoutedEventArgs e) { this.datePickerZoneEntry.Value = null; - } + } private void contextMenuItemClearATA_Click(object sender, RoutedEventArgs e) { this.datePickerATA.Value = null; - } + } private void contextMenuItemClearATD_Click(object sender, RoutedEventArgs e) { this.datePickerATD.Value = null; - } + } private void contextMenuItemClearETA_End_Click(object sender, RoutedEventArgs e) { @@ -358,6 +358,6 @@ namespace BreCalClient } #endregion - + } } diff --git a/src/BreCalClient/EditTimesTerminalControl.xaml.cs b/src/BreCalClient/EditTimesTerminalControl.xaml.cs index e980c9e..fe9e363 100644 --- a/src/BreCalClient/EditTimesTerminalControl.xaml.cs +++ b/src/BreCalClient/EditTimesTerminalControl.xaml.cs @@ -108,13 +108,13 @@ namespace BreCalClient message = ""; - if (this.datePickerOperationStart.Value.HasValue && (this.datePickerOperationStart.Value.Value < DateTime.Now) && (this.datePickerOperationStart_End.Value == null)) + if (this.datePickerOperationStart.Value.IsTooOld() && (this.datePickerOperationStart_End.Value == null) && (this.datePickerOperationStart.Value != this.Times.OperationsStart)) { message = BreCalClient.Resources.Resources.textOperationStartInThePast; return false; } - if (this.datePickerOperationStart_End.Value.HasValue && this.datePickerOperationStart_End.Value < DateTime.Now) + if (this.datePickerOperationStart_End.Value.IsTooOld() && (this.datePickerOperationStart_End.Value != this.Times.EtaIntervalEnd)) { message = BreCalClient.Resources.Resources.textOperationStartInThePast; return false; @@ -126,13 +126,13 @@ namespace BreCalClient return false; } - if (this.datePickerOperationEnd.Value.HasValue && (this.datePickerOperationEnd.Value.Value < DateTime.Now) && (this.datePickerOperationEnd_End.Value == null)) + if (this.datePickerOperationEnd.Value.IsTooOld() && (this.datePickerOperationEnd_End.Value == null) && (this.datePickerOperationEnd.Value != this.Times.OperationsEnd)) { message = BreCalClient.Resources.Resources.textOperationEndInThePast; return false; } - if (this.datePickerOperationEnd_End.Value.HasValue && this.datePickerOperationEnd_End.Value < DateTime.Now) + if (this.datePickerOperationEnd_End.Value.IsTooOld() && (this.datePickerOperationEnd_End.Value != this.Times.EtdIntervalEnd)) { message = BreCalClient.Resources.Resources.textOperationEndInThePast; return false; diff --git a/src/BreCalClient/Extensions.cs b/src/BreCalClient/Extensions.cs index 54fedb3..f4ba524 100644 --- a/src/BreCalClient/Extensions.cs +++ b/src/BreCalClient/Extensions.cs @@ -69,6 +69,14 @@ namespace BreCalClient return datetime > DateTime.Now.AddYears(1); } + public static bool IsTooOld(this DateTime? datetime) + { + if (datetime == null) return false; + { + return datetime < DateTime.Now.AddDays(-1); + } + } + public static bool IsTypeFlagSet(this Participant participant, ParticipantType flag) { return (participant.Type & (uint)flag) != 0; From 7f51842ccc14cade3e563ad755df4393ece80cfc Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Tue, 5 Nov 2024 14:12:04 +0100 Subject: [PATCH 02/12] Version bump to 1.5.0.9 --- src/BreCalClient/BreCalClient.csproj | 4 ++-- .../Properties/PublishProfiles/ClickOnceTestProfile.pubxml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj index ab26eb8..77cf82b 100644 --- a/src/BreCalClient/BreCalClient.csproj +++ b/src/BreCalClient/BreCalClient.csproj @@ -8,8 +8,8 @@ True BreCalClient.App ..\..\misc\brecal.snk - 1.5.0.7 - 1.5.0.7 + 1.5.0.9 + 1.5.0.9 Bremen calling client A Windows WPF client for the Bremen calling API. containership.ico diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml index 02e1a28..da1678f 100644 --- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml +++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml @@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. 0 - 1.5.0.7 + 1.5.0.9 True Debug True From 83e538a58b8ae97781610b53986a65b6f02634d4 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 6 Nov 2024 16:22:34 +0100 Subject: [PATCH 03/12] Allow new shipcalls up to 1 day in the past for serverside validation --- .../BreCal/validators/input_validation.py | 88 +++++++++---------- .../validators/input_validation_shipcall.py | 32 +++---- .../test_input_validation_shipcall.py | 42 ++++----- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/server/BreCal/validators/input_validation.py b/src/server/BreCal/validators/input_validation.py index cc97ccb..075a51d 100644 --- a/src/server/BreCal/validators/input_validation.py +++ b/src/server/BreCal/validators/input_validation.py @@ -28,7 +28,7 @@ def validation_error_default_asserts(response): def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict): """this function applies more complex validation functions to data, which is sent to a post-request of shipcalls""" # DEPRECATED: this function has been refactored into InputValidationShipcall (see methods for POST and PUT evaluation) - # #TODO_refactor: this function is pretty complex. One may instead build an object, which calls the methods separately. + # #TODO_refactor: this function is pretty complex. One may instead build an object, which calls the methods separately. ##### Section 1: check user_data ##### # check, whether the user belongs to a participant, which is of type ParticipantType.BSMD @@ -36,23 +36,23 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict is_bsmd = check_if_user_is_bsmd_type(user_data) if not is_bsmd: raise ValidationError({"user_participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"}) - + ##### Section 2: check loadedModel ##### valid_ship_id = check_if_ship_id_is_valid(ship_id=loadedModel.get("ship_id", None)) if not valid_ship_id: raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {loadedModel.get('ship_id', None)}"}) - + valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("arrival_berth_id", None)) if not valid_arrival_berth_id: raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {loadedModel.get('arrival_berth_id', None)}"}) - + valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("departure_berth_id", None)) if not valid_departure_berth_id: raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {loadedModel.get('departure_berth_id', None)}"}) - + valid_participant_ids = check_if_participant_ids_are_valid(participants=loadedModel.get("participants",[])) if not valid_participant_ids: - raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"}) + raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"}) ##### Section 3: check content ##### @@ -63,18 +63,18 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict value = content.get(forbidden_key, None) if value is not None: raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"}) - + voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage","")) if voyage_str_is_invalid: - raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"}) - + raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"}) + ##### Section 4: check loadedModel & content ##### # #TODO_refactor: these methods should be placed in separate locations # existance checks in content - # datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future. - time_now = datetime.datetime.now() + # datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future. + time_now = datetime.datetime.now() - datetime.timedelta(days=1) type_ = loadedModel.get("type", int(ShipcallType.undefined)) if int(type_)==int(ShipcallType.undefined): raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"}) @@ -85,7 +85,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict if content.get("arrival_berth_id", None) is None: raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"}) if not eta >= time_now: - raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided."}) + raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided."}) elif int(type_)==int(ShipcallType.departure): etd = loadedModel.get("etd") if (content.get("etd", None) is None): @@ -93,7 +93,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict if content.get("departure_berth_id", None) is None: raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"}) if not etd >= time_now: - raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided."}) + raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided."}) elif int(type_)==int(ShipcallType.shifting): eta = loadedModel.get("eta") etd = loadedModel.get("etd") @@ -103,20 +103,20 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict if (content.get("arrival_berth_id", None) is None) or (content.get("departure_berth_id", None) is None): raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"}) if (not eta >= time_now) or (not etd >= time_now) or (not eta >= etd): - raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided."}) - + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' are too far in the past. Incorrect datetime provided."}) + tidal_window_from = loadedModel.get("tidal_window_from", None) tidal_window_to = loadedModel.get("tidal_window_to", None) if tidal_window_to is not None: if not tidal_window_to >= time_now: - raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."}) + raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."}) if tidal_window_from is not None: if not tidal_window_from >= time_now: - raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."}) - + raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."}) + # #TODO: assert tidal_window_from > tidal_window_to - + # #TODO: len of participants > 0, if agency # * assigned participant for agency return @@ -126,21 +126,21 @@ class InputValidation(): def __init__(self): self.build_supported_models_dictionary() return - + def build_supported_models_dictionary(self): self.supported_models = { Ship:ShipValidation(), Shipcall:ShipcallValidation(), - Berth:BerthValidation(), - User:UserValidation(), + Berth:BerthValidation(), + User:UserValidation(), Participant:ParticipantValidation(), } return - + def assert_if_not_supported(self, dataclass_object): assert type(dataclass_object) in self.supported_models, f"unsupported model. Found: {type(dataclass_object)}" return - + def verify(self, dataclass_object): self.assert_if_not_supported(dataclass_object) @@ -157,17 +157,17 @@ class DataclassValidation(ABC): """parent class of dataclas validators, which determines the outline of every object""" def __init__(self): return - + def check(self, dataclass_object) -> (list, bool): """ - the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations + the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations and a boolean, which determines, whether the check is passed successfully """ all_rules = self.apply_all_rules(dataclass_object) violations = self.filter_violations(all_rules) input_validation_state = self.evaluate(violations) return (violations, input_validation_state) - + @abstractmethod def apply_all_rules(self, dataclass_object) -> list: """ @@ -176,13 +176,13 @@ class DataclassValidation(ABC): """ all_rules = [(True, 'blank_validation_rule')] return all_rules - + def filter_violations(self, all_rules): """input: all_rules, a list of tuples, where each element is (output, validation_name), which are (bool, str). """ # if output is False, a violation is observed violations = [result[1] for result in all_rules if not result[0]] return violations - + def evaluate(self, violations) -> bool: input_validation_state = len(violations)==0 return input_validation_state @@ -194,7 +194,7 @@ class ShipcallValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" raise NotImplementedError() @@ -202,16 +202,16 @@ class ShipcallValidation(DataclassValidation): from BreCal.validators.schema_validation import ship_bollard_pull_is_defined_or_is_not_tug, ship_bollard_pull_is_none_or_in_range, ship_callsign_len_is_seven_at_maximum, ship_eni_len_is_eight, ship_imo_len_is_seven, ship_length_in_range, ship_participant_id_is_defined_or_is_not_tug, ship_participant_id_is_none_or_int, ship_width_in_range -# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, +# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, class ShipValidation(DataclassValidation): """an object that validates a Ship dataclass object""" def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" - # skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, + # skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, """ #TODO_ship_max_draft with pytest.raises(AttributeError, match="'Ship' object has no attribute 'max_draft'"): @@ -225,14 +225,14 @@ class ShipValidation(DataclassValidation): check_rule(dataclass_object) for check_rule in [ - ship_bollard_pull_is_defined_or_is_not_tug, - ship_bollard_pull_is_none_or_in_range, - ship_callsign_len_is_seven_at_maximum, - ship_eni_len_is_eight, - ship_imo_len_is_seven, - ship_length_in_range, - ship_participant_id_is_defined_or_is_not_tug, - ship_participant_id_is_none_or_int, + ship_bollard_pull_is_defined_or_is_not_tug, + ship_bollard_pull_is_none_or_in_range, + ship_callsign_len_is_seven_at_maximum, + ship_eni_len_is_eight, + ship_imo_len_is_seven, + ship_length_in_range, + ship_participant_id_is_defined_or_is_not_tug, + ship_participant_id_is_none_or_int, ship_width_in_range ] ] @@ -243,7 +243,7 @@ class BerthValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" raise NotImplementedError() @@ -254,7 +254,7 @@ class UserValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" raise NotImplementedError() @@ -266,7 +266,7 @@ class ParticipantValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index 40932e0..5eb561f 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -330,8 +330,8 @@ class InputValidationShipcall(): Dates should be in the future. Depending on the ShipcallType, specific values should be checked Perfornms datetime checks in the loadedModel (datetime.datetime objects). """ - # obtain the current datetime to check, whether the provided values are in the future - time_now = datetime.datetime.now() + # obtain the current datetime to check, whether the provided values are after ref time + time_ref = datetime.datetime.now() - datetime.timedelta(days=1) type_ = loadedModel.get("type", ShipcallType.undefined.name) if isinstance(type_, str): # convert the name string to a ShipcallType data model @@ -349,14 +349,14 @@ class InputValidationShipcall(): tidal_window_to = loadedModel.get("tidal_window_to", None) # Estimated arrival or departure times - InputValidationShipcall.check_times_in_future_based_on_type(type_, time_now, eta, etd) + InputValidationShipcall.check_times_in_future_based_on_type(type_, time_ref, eta, etd) # Tidal Window - InputValidationShipcall.check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to) + InputValidationShipcall.check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to) return @staticmethod - def check_times_in_future_based_on_type(type_, time_now, eta, etd): + def check_times_in_future_based_on_type(type_, time_ref, eta, etd): """ checks, whether the ETA & ETD times are in the future. based on the type, this function checks: @@ -380,8 +380,8 @@ class InputValidationShipcall(): if eta is None: # null values -> no violation return - if not eta > time_now: - raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}."}) + if not eta > time_ref: + raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}."}) if etd is not None: raise ValidationError({"etd":f"'etd' should not be set when the shipcall type is 'arrival'."}) @@ -389,8 +389,8 @@ class InputValidationShipcall(): if etd is None: # null values -> no violation return - if not etd > time_now: - raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETD: {etd}."}) + if not etd > time_ref: + raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETD: {etd}."}) if eta is not None: raise ValidationError({"eta":f"'eta' should not be set when the shipcall type is 'departure'."}) @@ -404,8 +404,8 @@ class InputValidationShipcall(): # rules, a user is only allowed to provide *both* values. raise ValidationError({"eta_or_etd":f"For shifting shipcalls one should always provide, both, eta and etd."}) - if (not eta > time_now) or (not etd > time_now): - raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}. ETD: {etd}"}) + if (not eta > time_ref) or (not etd > time_ref): + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}. ETD: {etd}"}) if (not etd < eta): raise ValidationError({"eta_or_etd":f"The estimated time of departure ('etd') must take place *before the estimated time of arrival ('eta'). The ship cannot arrive, before it has departed. Found: ETD: {etd}, ETA: {eta}"}) @@ -414,14 +414,14 @@ class InputValidationShipcall(): return @staticmethod - def check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to): + def check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to): if tidal_window_to is not None: - if not tidal_window_to >= time_now: - raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."}) + if not tidal_window_to >= time_ref: + raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."}) if tidal_window_from is not None: - if not tidal_window_from >= time_now: - raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."}) + if not tidal_window_from >= time_ref: + raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."}) if (tidal_window_to is not None) and (tidal_window_from is not None): if tidal_window_to < tidal_window_from: diff --git a/src/server/tests/validators/test_input_validation_shipcall.py b/src/server/tests/validators/test_input_validation_shipcall.py index 87b3b7f..0e48f55 100644 --- a/src/server/tests/validators/test_input_validation_shipcall.py +++ b/src/server/tests/validators/test_input_validation_shipcall.py @@ -207,7 +207,7 @@ def test_shipcall_post_request_fails_when_voyage_string_is_invalid(get_stub_toke with pytest.raises(ValidationError, match="Longer than maximum length 16"): assert response.status_code==400 raise ValidationError(response.json()) - + # Fail: special characters post_data = original_post_data.copy() post_data["voyage"] = '👽' @@ -226,17 +226,17 @@ def test_shipcall_post_request_fails_when_type_arrival_and_not_in_future(get_stu # accept post_data = original_post_data.copy() post_data["type"] = ShipcallType.arrival.name - post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3)).isoformat() + post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(days=2)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) assert response.status_code == 201 # error post_data = original_post_data.copy() post_data["type"] = ShipcallType.arrival.name - post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat() + post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(days=2)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) - with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"): + with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"): assert response.status_code==400 raise ValidationError(response.json()) return @@ -256,10 +256,10 @@ def test_shipcall_post_request_fails_when_type_departure_and_not_in_future(get_s # error post_data = original_post_data.copy() post_data["type"] = ShipcallType.departure.name - post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat() + post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) - with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"): + with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"): assert response.status_code==400 raise ValidationError(response.json()) return @@ -280,11 +280,11 @@ def test_shipcall_post_request_fails_when_type_shifting_and_not_in_future(get_st # error post_data = original_post_data.copy() post_data["type"] = ShipcallType.shifting.name - post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat() + post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat() post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3,minutes=1)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) - with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"): + with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"): assert response.status_code==400 raise ValidationError(response.json()) return @@ -629,7 +629,7 @@ def test_shipcall_put_request_fails_when_different_participant_id_is_assigned(ge {"id":99115, 'participant_id': 5, 'type': 8}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -652,7 +652,7 @@ def test_shipcall_put_request_success(get_shipcall_id_after_stub_post_request): user_data = {'id':6, 'participant_id':1} loadedModel = post_data content = post_data - + created = datetime.datetime.now()+datetime.timedelta(minutes=1) modified = datetime.datetime.now()+datetime.timedelta(minutes=2) @@ -662,7 +662,7 @@ def test_shipcall_put_request_success(get_shipcall_id_after_stub_post_request): {"id":99115, 'participant_id': 5, 'type': 8}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -687,7 +687,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a user_data = {'id':6, 'participant_id':2} # participant_id 2 is not BSMD and is not authorized. loadedModel = post_data content = post_data - + created = datetime.datetime.now()+datetime.timedelta(minutes=1) modified = datetime.datetime.now()+datetime.timedelta(minutes=2) @@ -697,7 +697,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a {"id":99115, 'participant_id': 5, 'type': 4}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -705,7 +705,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a # no agency assigned ivs = InputValidationShipcall() - + with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")): ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data) return @@ -730,7 +730,7 @@ def test_shipcall_put_request_fails_when_user_is_not_authorized(get_shipcall_id_ {"id":99115, 'participant_id': 5, 'type': 4}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -754,20 +754,20 @@ def test_shipcall_put_request_fails_when_user_tries_self_assignment(get_shipcall created = datetime.datetime.now()+datetime.timedelta(minutes=1) modified = datetime.datetime.now()+datetime.timedelta(minutes=2) - + spm_shipcall_data = [ {"id":99113, 'participant_id': 3, 'type': 1}, {"id":99114, 'participant_id': 4, 'type': 2}, {"id":99115, 'participant_id': 5, 'type': 4}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] - # self-assignment. User is participant 6, and wants to assign participant 6. + # self-assignment. User is participant 6, and wants to assign participant 6. ivs = InputValidationShipcall() with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape("PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")): # previous error message: An agency cannot self-register for a shipcall. The request is issued by an agency-user and tries to assign an AGENCY as the participant of the shipcall."" @@ -813,7 +813,7 @@ def test_shipcall_put_request_works_if_most_values_are_null(): InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content) return - + @@ -860,10 +860,10 @@ def test_shipcall_put_request_fails_input_validation_shipcall_when_shipcall_is_c return def test_post_data_with_valid_data(get_stub_token): - """This unit test uses the input data from + """This unit test uses the input data from # https://trello.com/c/VXVSLTF4/267-shipcall-anlegen-shifting-erh%C3%A4lt-fehler-aufgrund-fr%C3%BCherem-etd-als-eta - to make sure, the failure case no longer appears. + to make sure, the failure case no longer appears. """ url, token = get_stub_token["url"], get_stub_token["token"] From 61707d8fde0a36651a7a60ccee0df557cf10ef91 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 7 Nov 2024 08:33:22 +0100 Subject: [PATCH 04/12] Apply 1 day past rule to both from and to times on eta / etd to avoid loophole --- src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs | 2 +- src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs | 2 +- src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs index 47b5cab..ec38bf0 100644 --- a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs @@ -97,7 +97,7 @@ namespace BreCalClient { message = ""; - if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA_End.Value == null) && (this.datePickerETA.Value != this.Times.EtaBerth)) + if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA.Value != this.Times.EtaBerth)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; diff --git a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs index 7721819..fee20b6 100644 --- a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs @@ -107,7 +107,7 @@ namespace BreCalClient { message = ""; - if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD_End.Value == null) && (this.datePickerETD.Value != this.Times.EtdBerth)) + if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD.Value != this.Times.EtdBerth)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; diff --git a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs index 08cc2ca..4a78eb9 100644 --- a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs @@ -97,7 +97,7 @@ namespace BreCalClient { message = ""; - if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA_End.Value == null) && (this.datePickerETA.Value != this.Times.EtaBerth)) + if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA.Value != this.Times.EtaBerth)) { message = BreCalClient.Resources.Resources.textETAInThePast; return false; @@ -115,7 +115,7 @@ namespace BreCalClient return false; } - if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD_End.Value == null) && (this.datePickerETD.Value != this.Times.EtdBerth)) + if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD.Value != this.Times.EtdBerth)) { message = BreCalClient.Resources.Resources.textETDInThePast; return false; From 904dc629e25b0ca9132f12d32cd93efdfeafe71a Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 7 Nov 2024 09:19:29 +0100 Subject: [PATCH 05/12] Version bump to 1.5.0.10 --- src/BreCalClient/BreCalClient.csproj | 4 ++-- .../Properties/PublishProfiles/ClickOnceTestProfile.pubxml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj index 77cf82b..a5ed4ad 100644 --- a/src/BreCalClient/BreCalClient.csproj +++ b/src/BreCalClient/BreCalClient.csproj @@ -8,8 +8,8 @@ True BreCalClient.App ..\..\misc\brecal.snk - 1.5.0.9 - 1.5.0.9 + 1.5.0.10 + 1.5.0.10 Bremen calling client A Windows WPF client for the Bremen calling API. containership.ico diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml index da1678f..15298b4 100644 --- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml +++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml @@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. 0 - 1.5.0.9 + 1.5.0.10 True Debug True From 529872b5905de5b3935a153315bf66fe25a73c61 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 6 Nov 2024 16:22:34 +0100 Subject: [PATCH 06/12] Allow new shipcalls up to 1 day in the past for serverside validation --- .../BreCal/validators/input_validation.py | 88 +++++++++---------- .../validators/input_validation_shipcall.py | 48 +++++----- .../test_input_validation_shipcall.py | 42 ++++----- 3 files changed, 89 insertions(+), 89 deletions(-) diff --git a/src/server/BreCal/validators/input_validation.py b/src/server/BreCal/validators/input_validation.py index cc97ccb..075a51d 100644 --- a/src/server/BreCal/validators/input_validation.py +++ b/src/server/BreCal/validators/input_validation.py @@ -28,7 +28,7 @@ def validation_error_default_asserts(response): def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict): """this function applies more complex validation functions to data, which is sent to a post-request of shipcalls""" # DEPRECATED: this function has been refactored into InputValidationShipcall (see methods for POST and PUT evaluation) - # #TODO_refactor: this function is pretty complex. One may instead build an object, which calls the methods separately. + # #TODO_refactor: this function is pretty complex. One may instead build an object, which calls the methods separately. ##### Section 1: check user_data ##### # check, whether the user belongs to a participant, which is of type ParticipantType.BSMD @@ -36,23 +36,23 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict is_bsmd = check_if_user_is_bsmd_type(user_data) if not is_bsmd: raise ValidationError({"user_participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"}) - + ##### Section 2: check loadedModel ##### valid_ship_id = check_if_ship_id_is_valid(ship_id=loadedModel.get("ship_id", None)) if not valid_ship_id: raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {loadedModel.get('ship_id', None)}"}) - + valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("arrival_berth_id", None)) if not valid_arrival_berth_id: raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {loadedModel.get('arrival_berth_id', None)}"}) - + valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("departure_berth_id", None)) if not valid_departure_berth_id: raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {loadedModel.get('departure_berth_id', None)}"}) - + valid_participant_ids = check_if_participant_ids_are_valid(participants=loadedModel.get("participants",[])) if not valid_participant_ids: - raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"}) + raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"}) ##### Section 3: check content ##### @@ -63,18 +63,18 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict value = content.get(forbidden_key, None) if value is not None: raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"}) - + voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage","")) if voyage_str_is_invalid: - raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"}) - + raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"}) + ##### Section 4: check loadedModel & content ##### # #TODO_refactor: these methods should be placed in separate locations # existance checks in content - # datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future. - time_now = datetime.datetime.now() + # datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future. + time_now = datetime.datetime.now() - datetime.timedelta(days=1) type_ = loadedModel.get("type", int(ShipcallType.undefined)) if int(type_)==int(ShipcallType.undefined): raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"}) @@ -85,7 +85,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict if content.get("arrival_berth_id", None) is None: raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"}) if not eta >= time_now: - raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided."}) + raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided."}) elif int(type_)==int(ShipcallType.departure): etd = loadedModel.get("etd") if (content.get("etd", None) is None): @@ -93,7 +93,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict if content.get("departure_berth_id", None) is None: raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"}) if not etd >= time_now: - raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided."}) + raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided."}) elif int(type_)==int(ShipcallType.shifting): eta = loadedModel.get("eta") etd = loadedModel.get("etd") @@ -103,20 +103,20 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict if (content.get("arrival_berth_id", None) is None) or (content.get("departure_berth_id", None) is None): raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"}) if (not eta >= time_now) or (not etd >= time_now) or (not eta >= etd): - raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided."}) - + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' are too far in the past. Incorrect datetime provided."}) + tidal_window_from = loadedModel.get("tidal_window_from", None) tidal_window_to = loadedModel.get("tidal_window_to", None) if tidal_window_to is not None: if not tidal_window_to >= time_now: - raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."}) + raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."}) if tidal_window_from is not None: if not tidal_window_from >= time_now: - raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."}) - + raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."}) + # #TODO: assert tidal_window_from > tidal_window_to - + # #TODO: len of participants > 0, if agency # * assigned participant for agency return @@ -126,21 +126,21 @@ class InputValidation(): def __init__(self): self.build_supported_models_dictionary() return - + def build_supported_models_dictionary(self): self.supported_models = { Ship:ShipValidation(), Shipcall:ShipcallValidation(), - Berth:BerthValidation(), - User:UserValidation(), + Berth:BerthValidation(), + User:UserValidation(), Participant:ParticipantValidation(), } return - + def assert_if_not_supported(self, dataclass_object): assert type(dataclass_object) in self.supported_models, f"unsupported model. Found: {type(dataclass_object)}" return - + def verify(self, dataclass_object): self.assert_if_not_supported(dataclass_object) @@ -157,17 +157,17 @@ class DataclassValidation(ABC): """parent class of dataclas validators, which determines the outline of every object""" def __init__(self): return - + def check(self, dataclass_object) -> (list, bool): """ - the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations + the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations and a boolean, which determines, whether the check is passed successfully """ all_rules = self.apply_all_rules(dataclass_object) violations = self.filter_violations(all_rules) input_validation_state = self.evaluate(violations) return (violations, input_validation_state) - + @abstractmethod def apply_all_rules(self, dataclass_object) -> list: """ @@ -176,13 +176,13 @@ class DataclassValidation(ABC): """ all_rules = [(True, 'blank_validation_rule')] return all_rules - + def filter_violations(self, all_rules): """input: all_rules, a list of tuples, where each element is (output, validation_name), which are (bool, str). """ # if output is False, a violation is observed violations = [result[1] for result in all_rules if not result[0]] return violations - + def evaluate(self, violations) -> bool: input_validation_state = len(violations)==0 return input_validation_state @@ -194,7 +194,7 @@ class ShipcallValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" raise NotImplementedError() @@ -202,16 +202,16 @@ class ShipcallValidation(DataclassValidation): from BreCal.validators.schema_validation import ship_bollard_pull_is_defined_or_is_not_tug, ship_bollard_pull_is_none_or_in_range, ship_callsign_len_is_seven_at_maximum, ship_eni_len_is_eight, ship_imo_len_is_seven, ship_length_in_range, ship_participant_id_is_defined_or_is_not_tug, ship_participant_id_is_none_or_int, ship_width_in_range -# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, +# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, class ShipValidation(DataclassValidation): """an object that validates a Ship dataclass object""" def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" - # skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, + # skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range, """ #TODO_ship_max_draft with pytest.raises(AttributeError, match="'Ship' object has no attribute 'max_draft'"): @@ -225,14 +225,14 @@ class ShipValidation(DataclassValidation): check_rule(dataclass_object) for check_rule in [ - ship_bollard_pull_is_defined_or_is_not_tug, - ship_bollard_pull_is_none_or_in_range, - ship_callsign_len_is_seven_at_maximum, - ship_eni_len_is_eight, - ship_imo_len_is_seven, - ship_length_in_range, - ship_participant_id_is_defined_or_is_not_tug, - ship_participant_id_is_none_or_int, + ship_bollard_pull_is_defined_or_is_not_tug, + ship_bollard_pull_is_none_or_in_range, + ship_callsign_len_is_seven_at_maximum, + ship_eni_len_is_eight, + ship_imo_len_is_seven, + ship_length_in_range, + ship_participant_id_is_defined_or_is_not_tug, + ship_participant_id_is_none_or_int, ship_width_in_range ] ] @@ -243,7 +243,7 @@ class BerthValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" raise NotImplementedError() @@ -254,7 +254,7 @@ class UserValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" raise NotImplementedError() @@ -266,7 +266,7 @@ class ParticipantValidation(DataclassValidation): def __init__(self): super().__init__() return - + def apply_all_rules(self, dataclass_object) -> list: """apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)""" diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index 52e380f..0dd8850 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -127,7 +127,7 @@ class InputValidationShipcall(): if is_put_data: # the type of a shipcall may not be changed. It can only be set with the initial POST-request. InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel) - + InputValidationShipcall.check_times_are_in_future(loadedModel, content) # some arguments must not be provided @@ -327,10 +327,10 @@ class InputValidationShipcall(): def check_times_are_in_future(loadedModel:dict, content:dict): """ Dates should be in the future. Depending on the ShipcallType, specific values should be checked - Perfornms datetime checks in the loadedModel (datetime.datetime objects). + Performs datetime checks in the loadedModel (datetime.datetime objects). """ - # obtain the current datetime to check, whether the provided values are in the future - time_now = datetime.datetime.now() + # obtain the current datetime to check, whether the provided values are after ref time + time_ref = datetime.datetime.now() - datetime.timedelta(days=1) type_ = loadedModel.get("type", ShipcallType.undefined.name) if isinstance(type_, str): # convert the name string to a ShipcallType data model @@ -348,14 +348,14 @@ class InputValidationShipcall(): tidal_window_to = loadedModel.get("tidal_window_to", None) # Estimated arrival or departure times - InputValidationShipcall.check_times_in_future_based_on_type(type_, time_now, eta, etd) + InputValidationShipcall.check_times_in_future_based_on_type(type_, time_ref, eta, etd) # Tidal Window - InputValidationShipcall.check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to) + InputValidationShipcall.check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to) return @staticmethod - def check_times_in_future_based_on_type(type_, time_now, eta, etd): + def check_times_in_future_based_on_type(type_, time_ref, eta, etd): """ checks, whether the ETA & ETD times are in the future. based on the type, this function checks: @@ -365,8 +365,8 @@ class InputValidationShipcall(): """ if (eta is None) and (etd is None): return - - time_in_a_year = time_now.replace(time_now.year + 1) + + time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1) if type_ is None: raise ValidationError({"type":f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified."}) @@ -381,8 +381,8 @@ class InputValidationShipcall(): if eta is None: # null values -> no violation return - if not eta > time_now: - raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}."}) + if not eta > time_ref: + raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}."}) if etd is not None: raise ValidationError({"etd":f"'etd' should not be set when the shipcall type is 'arrival'."}) if eta > time_in_a_year: @@ -392,8 +392,9 @@ class InputValidationShipcall(): if etd is None: # null values -> no violation return - if not etd > time_now: - raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETD: {etd}."}) + if not etd > time_ref: + raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETD: {etd}."}) + if eta is not None: raise ValidationError({"eta":f"'eta' should not be set when the shipcall type is 'departure'."}) if etd > time_in_a_year: @@ -408,8 +409,8 @@ class InputValidationShipcall(): # rules, a user is only allowed to provide *both* values. raise ValidationError({"eta_or_etd":f"For shifting shipcalls one should always provide, both, eta and etd."}) - if (not eta > time_now) or (not etd > time_now): - raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}. ETD: {etd}"}) + if (not eta > time_ref) or (not etd > time_ref): + raise ValidationError({"eta_or_etd":f"'eta' and 'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}. ETD: {etd}"}) if (not etd < eta): raise ValidationError({"eta_or_etd":f"The estimated time of departure ('etd') must take place *before the estimated time of arrival ('eta'). The ship cannot arrive, before it has departed. Found: ETD: {etd}, ETA: {eta}"}) @@ -419,30 +420,29 @@ class InputValidationShipcall(): raise ValidationError({"eta":f"'eta' is more than a year in the future. ETA: {eta}."}) if etd > time_in_a_year: raise ValidationError({"etd":f"'etd' is more than a year in the future. ETD: {etd}."}) - + return @staticmethod - def check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to): - - time_in_a_year = time_now.replace(time_now.year + 1) + def check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to): + time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1) if tidal_window_to is not None: - if not tidal_window_to >= time_now: - raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."}) + if not tidal_window_to >= time_ref: + raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."}) if tidal_window_to > time_in_a_year: raise ValidationError({"tidal_window_to":f"'tidal_window_to' is more than a year in the future. Found: {tidal_window_to}."}) if tidal_window_from is not None: - if not tidal_window_from >= time_now: - raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."}) + if not tidal_window_from >= time_ref: + raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."}) if tidal_window_from > time_in_a_year: raise ValidationError({"tidal_window_from":f"'tidal_window_from' is more than a year in the future. Found: {tidal_window_from}."}) if (tidal_window_to is not None) and (tidal_window_from is not None): if tidal_window_to < tidal_window_from: raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}."}) - + if (tidal_window_to is not None and tidal_window_from is None) or (tidal_window_to is None and tidal_window_from is not None): raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' and 'tidal_window_from' must both be provided."}) diff --git a/src/server/tests/validators/test_input_validation_shipcall.py b/src/server/tests/validators/test_input_validation_shipcall.py index 87b3b7f..0e48f55 100644 --- a/src/server/tests/validators/test_input_validation_shipcall.py +++ b/src/server/tests/validators/test_input_validation_shipcall.py @@ -207,7 +207,7 @@ def test_shipcall_post_request_fails_when_voyage_string_is_invalid(get_stub_toke with pytest.raises(ValidationError, match="Longer than maximum length 16"): assert response.status_code==400 raise ValidationError(response.json()) - + # Fail: special characters post_data = original_post_data.copy() post_data["voyage"] = '👽' @@ -226,17 +226,17 @@ def test_shipcall_post_request_fails_when_type_arrival_and_not_in_future(get_stu # accept post_data = original_post_data.copy() post_data["type"] = ShipcallType.arrival.name - post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3)).isoformat() + post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(days=2)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) assert response.status_code == 201 # error post_data = original_post_data.copy() post_data["type"] = ShipcallType.arrival.name - post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat() + post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(days=2)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) - with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"): + with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"): assert response.status_code==400 raise ValidationError(response.json()) return @@ -256,10 +256,10 @@ def test_shipcall_post_request_fails_when_type_departure_and_not_in_future(get_s # error post_data = original_post_data.copy() post_data["type"] = ShipcallType.departure.name - post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat() + post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) - with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"): + with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"): assert response.status_code==400 raise ValidationError(response.json()) return @@ -280,11 +280,11 @@ def test_shipcall_post_request_fails_when_type_shifting_and_not_in_future(get_st # error post_data = original_post_data.copy() post_data["type"] = ShipcallType.shifting.name - post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat() + post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat() post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3,minutes=1)).isoformat() response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data) - with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"): + with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"): assert response.status_code==400 raise ValidationError(response.json()) return @@ -629,7 +629,7 @@ def test_shipcall_put_request_fails_when_different_participant_id_is_assigned(ge {"id":99115, 'participant_id': 5, 'type': 8}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -652,7 +652,7 @@ def test_shipcall_put_request_success(get_shipcall_id_after_stub_post_request): user_data = {'id':6, 'participant_id':1} loadedModel = post_data content = post_data - + created = datetime.datetime.now()+datetime.timedelta(minutes=1) modified = datetime.datetime.now()+datetime.timedelta(minutes=2) @@ -662,7 +662,7 @@ def test_shipcall_put_request_success(get_shipcall_id_after_stub_post_request): {"id":99115, 'participant_id': 5, 'type': 8}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -687,7 +687,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a user_data = {'id':6, 'participant_id':2} # participant_id 2 is not BSMD and is not authorized. loadedModel = post_data content = post_data - + created = datetime.datetime.now()+datetime.timedelta(minutes=1) modified = datetime.datetime.now()+datetime.timedelta(minutes=2) @@ -697,7 +697,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a {"id":99115, 'participant_id': 5, 'type': 4}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -705,7 +705,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a # no agency assigned ivs = InputValidationShipcall() - + with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")): ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data) return @@ -730,7 +730,7 @@ def test_shipcall_put_request_fails_when_user_is_not_authorized(get_shipcall_id_ {"id":99115, 'participant_id': 5, 'type': 4}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] @@ -754,20 +754,20 @@ def test_shipcall_put_request_fails_when_user_tries_self_assignment(get_shipcall created = datetime.datetime.now()+datetime.timedelta(minutes=1) modified = datetime.datetime.now()+datetime.timedelta(minutes=2) - + spm_shipcall_data = [ {"id":99113, 'participant_id': 3, 'type': 1}, {"id":99114, 'participant_id': 4, 'type': 2}, {"id":99115, 'participant_id': 5, 'type': 4}] spm_shipcall_data = [ {**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm} - for spm in + for spm in spm_shipcall_data ] spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data] - # self-assignment. User is participant 6, and wants to assign participant 6. + # self-assignment. User is participant 6, and wants to assign participant 6. ivs = InputValidationShipcall() with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape("PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")): # previous error message: An agency cannot self-register for a shipcall. The request is issued by an agency-user and tries to assign an AGENCY as the participant of the shipcall."" @@ -813,7 +813,7 @@ def test_shipcall_put_request_works_if_most_values_are_null(): InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content) return - + @@ -860,10 +860,10 @@ def test_shipcall_put_request_fails_input_validation_shipcall_when_shipcall_is_c return def test_post_data_with_valid_data(get_stub_token): - """This unit test uses the input data from + """This unit test uses the input data from # https://trello.com/c/VXVSLTF4/267-shipcall-anlegen-shifting-erh%C3%A4lt-fehler-aufgrund-fr%C3%BCherem-etd-als-eta - to make sure, the failure case no longer appears. + to make sure, the failure case no longer appears. """ url, token = get_stub_token["url"], get_stub_token["token"] From 1770c604b29d1c741835741fc3e0833d0924d110 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Sun, 10 Nov 2024 15:08:41 +0100 Subject: [PATCH 07/12] serverside fixes for detecting unchanged time values --- .../validators/input_validation_shipcall.py | 42 ++++++++++++------- .../validators/input_validation_times.py | 2 + 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index 0dd8850..45488f8 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -124,11 +124,14 @@ class InputValidationShipcall(): if check_if_int_is_valid_flag(flags_value, enum_object=ParticipantFlag): raise ValidationError({"flags":f"incorrect value provided for 'flags'. Must be a valid combination of the flags."}) + existing_shipcall = None if is_put_data: - # the type of a shipcall may not be changed. It can only be set with the initial POST-request. - InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel) - InputValidationShipcall.check_times_are_in_future(loadedModel, content) + existing_shipcall = InputValidationShipcall.get_shipcall_by_id(loadedModel.get("id")) + # the type of a shipcall may not be changed. It can only be set with the initial POST-request. + InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel, existing_shipcall) + + InputValidationShipcall.check_times_are_in_future(loadedModel, content, existing_shipcall) # some arguments must not be provided InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys) @@ -255,14 +258,16 @@ class InputValidationShipcall(): raise ValidationError({"participants":f"every participant id and type should be listed only once. Found multiple entries for one of the participants."}) @staticmethod - def check_shipcall_type_is_unchanged(loadedModel:dict): - # the type of a shipcall may only be set on POST requests. Afterwards, shipcall types may not be changed. - query = SQLQuery.get_shipcall_by_id() - shipcall = execute_sql_query_standalone(query=query, model=Shipcall, param={"id":loadedModel.get("id")}, command_type="single") - - if int(loadedModel["type"]) != int(shipcall.type): + def check_shipcall_type_is_unchanged(loadedModel:dict, existing_shipcall:object): + if int(loadedModel["type"]) != int(existing_shipcall.type): raise ValidationError({"type":f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed."}) # @pytest.raises return + + @staticmethod + def get_shipcall_by_id(shipcall_id:int): + query = SQLQuery.get_shipcall_by_id() + shipcall = execute_sql_query_standalone(query=query, model=Shipcall, param={"id":shipcall_id}, command_type="single") + return shipcall @staticmethod def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]): @@ -324,7 +329,7 @@ class InputValidationShipcall(): return @staticmethod - def check_times_are_in_future(loadedModel:dict, content:dict): + def check_times_are_in_future(loadedModel:dict, content:dict, existing_shipcall:object): """ Dates should be in the future. Depending on the ShipcallType, specific values should be checked Performs datetime checks in the loadedModel (datetime.datetime objects). @@ -347,11 +352,20 @@ class InputValidationShipcall(): tidal_window_from = loadedModel.get("tidal_window_from", None) tidal_window_to = loadedModel.get("tidal_window_to", None) - # Estimated arrival or departure times - InputValidationShipcall.check_times_in_future_based_on_type(type_, time_ref, eta, etd) + if existing_shipcall is not None: + existing_eta = existing_shipcall.eta + existing_etd = existing_shipcall.etd + existing_tidal_window_from = existing_shipcall.tidal_window_from + existing_tidal_window_to = existing_shipcall.tidal_window_to + + if eta != existing_eta or etd != existing_etd: + # Estimated arrival or departure times + InputValidationShipcall.check_times_in_future_based_on_type(type_, time_ref, eta, etd) + + if tidal_window_from != existing_tidal_window_from or tidal_window_to != existing_tidal_window_to: + # Tidal Window + InputValidationShipcall.check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to) - # Tidal Window - InputValidationShipcall.check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to) return @staticmethod diff --git a/src/server/BreCal/validators/input_validation_times.py b/src/server/BreCal/validators/input_validation_times.py index 9db0a2c..4b5dad9 100644 --- a/src/server/BreCal/validators/input_validation_times.py +++ b/src/server/BreCal/validators/input_validation_times.py @@ -414,6 +414,8 @@ class InputValidationTimes(): # commonly used in the PUT-request if loadedModel is not None: (shipcall_id, times_assigned_participant) = InputValidationTimes.prepare_authority_check_for_put_request(loadedModel) + else: + loadedModel = get_times_data_for_id(times_id) # commonly used in the DELETE-request if times_id is not None: From 9ad882c2ef3c23a9059ae55d5fcf279686ef3f1e Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Mon, 11 Nov 2024 11:01:31 +0100 Subject: [PATCH 08/12] Fixed situation where end time was reset after from time left 24h window --- .../EditTimesAgencyIncomingControl.xaml.cs | 68 ++++++++----------- .../EditTimesAgencyOutgoingControl.xaml.cs | 44 ++++-------- .../EditTimesAgencyShiftingControl.xaml.cs | 50 ++++++-------- src/BreCalClient/EditTimesControl.xaml.cs | 34 ++++------ .../EditTimesTerminalControl.xaml.cs | 37 ++++------ 5 files changed, 94 insertions(+), 139 deletions(-) diff --git a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs index ec38bf0..fd24e6b 100644 --- a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs @@ -4,7 +4,6 @@ using BreCalClient.misc.Model; using System; -using System.Runtime.Serialization; using System.Windows; using static BreCalClient.Extensions; @@ -97,47 +96,41 @@ namespace BreCalClient { message = ""; - if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA.Value != this.Times.EtaBerth)) - { - message = BreCalClient.Resources.Resources.textETAInThePast; - return false; - } - - if(this.datePickerETA_End.Value.IsTooOld() && (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) - { - message = BreCalClient.Resources.Resources.textETAInThePast; - return false; - } - - if(this.datePickerETA.Value.HasValue && this.datePickerETA_End.Value.HasValue && this.datePickerETA.Value > this.datePickerETA_End.Value) + if ((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaBerth)) // something has changed { - message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue; - return false; + if (datePickerETA.Value.IsTooOld() || datePickerETA_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textETAInThePast; + return false; + } + + if (this.datePickerETA.Value.HasValue && this.datePickerETA_End.Value.HasValue && this.datePickerETA.Value > this.datePickerETA_End.Value) + { + message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue; + return false; + } } - if (this.datePickerTidalWindowFrom.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value == null) && (this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom)) + if((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) // something has changed { - message = BreCalClient.Resources.Resources.textTideTimesInThePast; - return false; - } + if(datePickerTidalWindowTo.Value.IsTooOld() || this.datePickerTidalWindowFrom.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textTideTimesInThePast; + return false; + } - if (this.datePickerTidalWindowTo.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) - { - message = BreCalClient.Resources.Resources.textTideTimesInThePast; - return false; - } + if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value) + { + message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue; + return false; + } - if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value) - { - message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue; - return false; - } - - if((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue)) - { - message = BreCalClient.Resources.Resources.textTidalBothValues; - return false; - } + if ((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue)) + { + message = BreCalClient.Resources.Resources.textTidalBothValues; + return false; + } + } if(this.datePickerETA.Value.IsTooFar() || this.datePickerETA_End.Value.IsTooFar() || this.datePickerTidalWindowFrom.Value.IsTooFar() || this.datePickerTidalWindowTo.Value.IsTooFar()) { @@ -145,8 +138,7 @@ namespace BreCalClient return false; } - if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) || - (this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue)) + if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) || (this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue)) { message = BreCalClient.Resources.Resources.textStartTimeMissing; return false; diff --git a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs index fee20b6..04af41e 100644 --- a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs @@ -4,15 +4,8 @@ using BreCalClient.misc.Model; using System; -using System.Text.RegularExpressions; using System.Windows; -using System.Windows.Controls.Primitives; -using System.Windows.Controls; -using Xceed.Wpf.Toolkit; using static BreCalClient.Extensions; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Media; namespace BreCalClient { @@ -107,17 +100,14 @@ namespace BreCalClient { message = ""; - if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD.Value != this.Times.EtdBerth)) + if((this.datePickerETD.Value != this.Times.EtdBerth) || (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd)) { - message = BreCalClient.Resources.Resources.textETDInThePast; - return false; - } - - if (this.datePickerETD_End.Value.IsTooOld() && (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd)) - { - message = BreCalClient.Resources.Resources.textETDInThePast; - return false; - } + if (datePickerETD.Value.IsTooOld() || datePickerETD_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textETDInThePast; + return false; + } + } if (this.datePickerETD.Value.HasValue && this.datePickerETD_End.Value.HasValue && this.datePickerETD.Value > this.datePickerETD_End.Value) { @@ -125,17 +115,14 @@ namespace BreCalClient return false; } - if (this.datePickerTidalWindowFrom.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value == null) && (this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom)) + if((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) { - message = BreCalClient.Resources.Resources.textTideTimesInThePast; - return false; - } - - if (this.datePickerTidalWindowTo.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) - { - message = BreCalClient.Resources.Resources.textTideTimesInThePast; - return false; - } + if (this.datePickerTidalWindowTo.Value.IsTooOld() || this.datePickerTidalWindowFrom.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textTideTimesInThePast; + return false; + } + } if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value) { @@ -155,8 +142,7 @@ namespace BreCalClient return false; } - if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) || - (this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue)) + if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) || (this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue)) { message = BreCalClient.Resources.Resources.textStartTimeMissing; return false; diff --git a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs index 4a78eb9..5495560 100644 --- a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs @@ -97,17 +97,15 @@ namespace BreCalClient { message = ""; - if (this.datePickerETA.Value.IsTooOld() && (this.datePickerETA.Value != this.Times.EtaBerth)) - { - message = BreCalClient.Resources.Resources.textETAInThePast; - return false; - } - if (this.datePickerETA_End.Value.IsTooOld() && (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) + if((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) { - message = BreCalClient.Resources.Resources.textETAInThePast; - return false; - } + if (this.datePickerETA.Value.IsTooOld() && this.datePickerETA_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textETAInThePast; + return false; + } + } if (this.datePickerETA.Value.HasValue && this.datePickerETA_End.Value.HasValue && this.datePickerETA.Value > this.datePickerETA_End.Value) { @@ -115,17 +113,14 @@ namespace BreCalClient return false; } - if (this.datePickerETD.Value.IsTooOld() && (this.datePickerETD.Value != this.Times.EtdBerth)) + if((this.datePickerETD.Value != this.Times.EtdBerth) || (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd)) { - message = BreCalClient.Resources.Resources.textETDInThePast; - return false; - } - - if (this.datePickerETD_End.Value.IsTooOld() && (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd)) - { - message = BreCalClient.Resources.Resources.textETDInThePast; - return false; - } + if (this.datePickerETD.Value.IsTooOld() || this.datePickerETD_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textETDInThePast; + return false; + } + } if (this.datePickerETD.Value.HasValue && this.datePickerETD_End.Value.HasValue && this.datePickerETD.Value > this.datePickerETD_End.Value) { @@ -133,17 +128,14 @@ namespace BreCalClient return false; } - if (this.datePickerTidalWindowFrom.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value == null) && (this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom)) + if((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) { - message = BreCalClient.Resources.Resources.textTideTimesInThePast; - return false; - } - - if (this.datePickerTidalWindowTo.Value.IsTooOld() && (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) - { - message = BreCalClient.Resources.Resources.textTideTimesInThePast; - return false; - } + if (this.datePickerTidalWindowFrom.Value.IsTooOld() && this.datePickerTidalWindowTo.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textTideTimesInThePast; + return false; + } + } if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value) { diff --git a/src/BreCalClient/EditTimesControl.xaml.cs b/src/BreCalClient/EditTimesControl.xaml.cs index 89bdd8d..1e256e3 100644 --- a/src/BreCalClient/EditTimesControl.xaml.cs +++ b/src/BreCalClient/EditTimesControl.xaml.cs @@ -5,7 +5,6 @@ using BreCalClient.misc.Model; using System; -using System.Runtime.Serialization; using System.Windows; using System.Windows.Media.Imaging; using Xceed.Wpf.Toolkit; @@ -99,16 +98,13 @@ namespace BreCalClient message = ""; - if (this.datePickerETABerth.Value.IsTooOld() && (this.datePickerETABerth_End.Value == null) && (this.datePickerETABerth.Value != this.Times.EtaBerth)) + if ((this.datePickerETABerth.Value != this.Times.EtaBerth) || (this.datePickerETABerth_End.Value != this.Times.EtaIntervalEnd)) { - message = BreCalClient.Resources.Resources.textETAInThePast; - return false; - } - - if (this.datePickerETABerth_End.Value.IsTooOld() && this.datePickerETABerth_End.Value != this.Times.EtaIntervalEnd) - { - message = BreCalClient.Resources.Resources.textETAInThePast; - return false; + if (this.datePickerETABerth.Value.IsTooOld() || this.datePickerETABerth_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textETAInThePast; + return false; + } } if (this.datePickerETABerth.Value.HasValue && this.datePickerETABerth_End.Value.HasValue && this.datePickerETABerth.Value > this.datePickerETABerth_End.Value) @@ -117,16 +113,13 @@ namespace BreCalClient return false; } - if (this.datePickerETDBerth.Value.IsTooOld() && (this.datePickerETDBerth_End.Value == null) && (this.datePickerETDBerth.Value != this.Times.EtdBerth)) + if((this.datePickerETDBerth.Value != this.Times.EtdBerth) || (this.datePickerETDBerth_End.Value != this.Times.EtdIntervalEnd)) { - message = BreCalClient.Resources.Resources.textETDInThePast; - return false; - } - - if (this.datePickerETDBerth_End.Value.IsTooOld() && this.datePickerETDBerth_End.Value != this.Times.EtdIntervalEnd) - { - message = BreCalClient.Resources.Resources.textETDInThePast; - return false; + if(this.datePickerETDBerth.Value.IsTooOld() || this.datePickerETDBerth_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textETDInThePast; + return false; + } } if (this.datePickerETDBerth.Value.HasValue && this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth.Value > this.datePickerETDBerth_End.Value) @@ -154,8 +147,7 @@ namespace BreCalClient return false; } - if((this.datePickerETABerth_End.Value.HasValue && !this.datePickerETABerth.Value.HasValue) || - (this.datePickerETDBerth_End.Value.HasValue && !this.datePickerETDBerth.Value.HasValue)) + if((this.datePickerETABerth_End.Value.HasValue && !this.datePickerETABerth.Value.HasValue) || (this.datePickerETDBerth_End.Value.HasValue && !this.datePickerETDBerth.Value.HasValue)) { message = BreCalClient.Resources.Resources.textStartTimeMissing; return false; diff --git a/src/BreCalClient/EditTimesTerminalControl.xaml.cs b/src/BreCalClient/EditTimesTerminalControl.xaml.cs index fe9e363..368a024 100644 --- a/src/BreCalClient/EditTimesTerminalControl.xaml.cs +++ b/src/BreCalClient/EditTimesTerminalControl.xaml.cs @@ -108,17 +108,14 @@ namespace BreCalClient message = ""; - if (this.datePickerOperationStart.Value.IsTooOld() && (this.datePickerOperationStart_End.Value == null) && (this.datePickerOperationStart.Value != this.Times.OperationsStart)) + if((this.datePickerOperationStart.Value != this.Times.OperationsStart) || (this.datePickerOperationStart_End.Value != this.Times.EtaIntervalEnd)) { - message = BreCalClient.Resources.Resources.textOperationStartInThePast; - return false; - } - - if (this.datePickerOperationStart_End.Value.IsTooOld() && (this.datePickerOperationStart_End.Value != this.Times.EtaIntervalEnd)) - { - message = BreCalClient.Resources.Resources.textOperationStartInThePast; - return false; - } + if(this.datePickerOperationStart.Value.IsTooOld() || this.datePickerOperationStart_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textOperationStartInThePast; + return false; + } + } if (this.datePickerOperationStart.Value.HasValue && this.datePickerOperationStart_End.Value.HasValue && this.datePickerOperationStart.Value > this.datePickerOperationStart_End.Value) { @@ -126,17 +123,14 @@ namespace BreCalClient return false; } - if (this.datePickerOperationEnd.Value.IsTooOld() && (this.datePickerOperationEnd_End.Value == null) && (this.datePickerOperationEnd.Value != this.Times.OperationsEnd)) + if ((this.datePickerOperationEnd.Value != this.Times.OperationsEnd) || (this.datePickerOperationEnd_End.Value != this.Times.EtdIntervalEnd)) { - message = BreCalClient.Resources.Resources.textOperationEndInThePast; - return false; - } - - if (this.datePickerOperationEnd_End.Value.IsTooOld() && (this.datePickerOperationEnd_End.Value != this.Times.EtdIntervalEnd)) - { - message = BreCalClient.Resources.Resources.textOperationEndInThePast; - return false; - } + if(this.datePickerOperationEnd.Value.IsTooOld() || this.datePickerOperationEnd_End.Value.IsTooOld()) + { + message = BreCalClient.Resources.Resources.textOperationEndInThePast; + return false; + } + } if (this.datePickerOperationEnd.Value.HasValue && this.datePickerOperationEnd_End.Value.HasValue && this.datePickerOperationEnd.Value > this.datePickerOperationEnd_End.Value) { @@ -150,8 +144,7 @@ namespace BreCalClient return false; } - if((this.datePickerOperationEnd_End.Value.HasValue && !this.datePickerOperationEnd.Value.HasValue) || - (this.datePickerOperationStart_End.Value.HasValue && !this.datePickerOperationStart.Value.HasValue)) + if((this.datePickerOperationEnd_End.Value.HasValue && !this.datePickerOperationEnd.Value.HasValue) || (this.datePickerOperationStart_End.Value.HasValue && !this.datePickerOperationStart.Value.HasValue)) { message = BreCalClient.Resources.Resources.textStartTimeMissing; return false; From 1150688731c3306c84775cbad46bfddd705dd5d7 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Mon, 11 Nov 2024 11:02:38 +0100 Subject: [PATCH 09/12] Bumped version to 1.5.0.11 --- src/BreCalClient/BreCalClient.csproj | 4 ++-- .../Properties/PublishProfiles/ClickOnceTestProfile.pubxml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj index a5ed4ad..c0fb99d 100644 --- a/src/BreCalClient/BreCalClient.csproj +++ b/src/BreCalClient/BreCalClient.csproj @@ -8,8 +8,8 @@ True BreCalClient.App ..\..\misc\brecal.snk - 1.5.0.10 - 1.5.0.10 + 1.5.0.11 + 1.5.0.11 Bremen calling client A Windows WPF client for the Bremen calling API. containership.ico diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml index 15298b4..9da82a0 100644 --- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml +++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml @@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. 0 - 1.5.0.10 + 1.5.0.11 True Debug True From d4e273279bb240117425a004b99ab4d02ca2d787 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Tue, 12 Nov 2024 08:19:04 +0100 Subject: [PATCH 10/12] adding missing defaults when shipcall is not yet created --- src/server/BreCal/validators/input_validation_shipcall.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index 45488f8..39c200e 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -258,11 +258,11 @@ class InputValidationShipcall(): raise ValidationError({"participants":f"every participant id and type should be listed only once. Found multiple entries for one of the participants."}) @staticmethod - def check_shipcall_type_is_unchanged(loadedModel:dict, existing_shipcall:object): + def check_shipcall_type_is_unchanged(loadedModel:dict, existing_shipcall:object): if int(loadedModel["type"]) != int(existing_shipcall.type): raise ValidationError({"type":f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed."}) # @pytest.raises return - + @staticmethod def get_shipcall_by_id(shipcall_id:int): query = SQLQuery.get_shipcall_by_id() @@ -351,6 +351,10 @@ class InputValidationShipcall(): etd = loadedModel.get("etd") tidal_window_from = loadedModel.get("tidal_window_from", None) tidal_window_to = loadedModel.get("tidal_window_to", None) + existing_eta = None + existing_etd = None + existing_tidal_window_from = None + existing_tidal_window_to = None if existing_shipcall is not None: existing_eta = existing_shipcall.eta From 1e9835cdedc3dbb3a5b4135b9c8855bde92c6513 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 13 Nov 2024 08:07:22 +0100 Subject: [PATCH 11/12] fixed comparison typo --- src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs index fd24e6b..e3ef1c1 100644 --- a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs +++ b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs @@ -96,7 +96,7 @@ namespace BreCalClient { message = ""; - if ((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaBerth)) // something has changed + if ((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) // something has changed { if (datePickerETA.Value.IsTooOld() || datePickerETA_End.Value.IsTooOld()) { From afd5935cf50e33ae8386c040cd0b77f400a0f800 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 13 Nov 2024 08:08:09 +0100 Subject: [PATCH 12/12] Bumbed Version to 1.5.0.12 --- src/BreCalClient/BreCalClient.csproj | 4 ++-- .../Properties/PublishProfiles/ClickOnceTestProfile.pubxml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj index c0fb99d..871b82c 100644 --- a/src/BreCalClient/BreCalClient.csproj +++ b/src/BreCalClient/BreCalClient.csproj @@ -8,8 +8,8 @@ True BreCalClient.App ..\..\misc\brecal.snk - 1.5.0.11 - 1.5.0.11 + 1.5.0.12 + 1.5.0.12 Bremen calling client A Windows WPF client for the Bremen calling API. containership.ico diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml index 9da82a0..97323cc 100644 --- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml +++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml @@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. 0 - 1.5.0.11 + 1.5.0.12 True Debug True