From e911da20ef8af57a91f8c3df8a37cd5bc6662d62 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Fri, 18 Oct 2024 09:51:29 +0200 Subject: [PATCH 1/3] Fixed tidal window validation and description output --- .../BreCal/validators/input_validation_shipcall.py | 9 ++++++--- src/server/BreCal/validators/validation_error.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index 40932e0..bbf8625 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -127,9 +127,8 @@ 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) - else: - # time values must use future-dates - InputValidationShipcall.check_times_are_in_future(loadedModel, content) + + InputValidationShipcall.check_times_are_in_future(loadedModel, content) # some arguments must not be provided InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys) @@ -426,6 +425,10 @@ class InputValidationShipcall(): 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."}) + return @staticmethod diff --git a/src/server/BreCal/validators/validation_error.py b/src/server/BreCal/validators/validation_error.py index 823a924..b10673d 100644 --- a/src/server/BreCal/validators/validation_error.py +++ b/src/server/BreCal/validators/validation_error.py @@ -41,7 +41,7 @@ def unbundle_validation_error_message(message): unbundle_(message, unbundled=unbundled) if len(unbundled)>0: error_field = "ValidationError in the following field(s): " + " & ".join([unb["error_field"] for unb in unbundled]) - error_description = "Error Description(s): " + " & ".join([unb["error_description"] for unb in unbundled]) + error_description = " " . join([unb["error_description"] for unb in unbundled]) else: error_field = "ValidationError" error_description = "unknown validation error" From f1e13559861b3e5424c694e20a17fb6d0d52edd3 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Fri, 18 Oct 2024 11:49:18 +0200 Subject: [PATCH 2/3] more validation input fixes --- .../validators/input_validation_shipcall.py | 19 ++++++++++++++++++- .../validators/input_validation_times.py | 3 ++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/server/BreCal/validators/input_validation_shipcall.py b/src/server/BreCal/validators/input_validation_shipcall.py index bbf8625..52e380f 100644 --- a/src/server/BreCal/validators/input_validation_shipcall.py +++ b/src/server/BreCal/validators/input_validation_shipcall.py @@ -365,6 +365,8 @@ class InputValidationShipcall(): """ if (eta is None) and (etd is None): return + + time_in_a_year = time_now.replace(time_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."}) @@ -383,6 +385,8 @@ class InputValidationShipcall(): raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. 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: + raise ValidationError({"eta":f"'eta' is more than a year in the future. ETA: {eta}."}) elif int(type_)==int(ShipcallType.departure): if etd is None: # null values -> no violation @@ -390,9 +394,10 @@ class InputValidationShipcall(): 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 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: + raise ValidationError({"etd":f"'etd' is more than a year in the future. ETD: {etd}."}) elif int(type_)==int(ShipcallType.shifting): if (eta is None) and (etd is None): # null values -> no violation @@ -410,17 +415,29 @@ class InputValidationShipcall(): if (eta is not None and etd is None) or (eta is None and etd is not None): raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'."}) + if eta > time_in_a_year: + 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) + 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 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 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: diff --git a/src/server/BreCal/validators/input_validation_times.py b/src/server/BreCal/validators/input_validation_times.py index 25c58df..9db0a2c 100644 --- a/src/server/BreCal/validators/input_validation_times.py +++ b/src/server/BreCal/validators/input_validation_times.py @@ -124,7 +124,8 @@ class InputValidationTimes(): InputValidationTimes.check_if_entry_is_already_deleted(times_id) # 2.) Only users of the same participant_id, which the times dataset refers to, can delete the entry - InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id) + if not check_if_user_is_bsmd_type(user_data): + InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id) return @staticmethod From 529872b5905de5b3935a153315bf66fe25a73c61 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 6 Nov 2024 16:22:34 +0100 Subject: [PATCH 3/3] 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"]