Merge pull request #44 from puls200/hotfix/20240903_maintenance_1.5.0

Hotfix/20240903 maintenance 1.5.0
This commit is contained in:
Daniel Schick 2024-09-05 05:59:44 +02:00 committed by GitHub
commit 3bce9e85b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 262 additions and 45 deletions

View File

@ -147,6 +147,7 @@ The id field is required, missing fields will not be updated.
| Field | Validation | | Field | Validation |
|-------|------------| |-------|------------|
| eta_berth, etd_berth, lock_time, zone_entry, operations_start, operations_end | if set these values must be in the future| | eta_berth, etd_berth, lock_time, zone_entry, operations_start, operations_end | if set these values must be in the future|
| eta_interval_end, etd_interval_end | if set these values must be in the future. They must be larger than their ETA/ETD counterparts. |
| remarks, berth_info | must be <= 512 chars | | remarks, berth_info | must be <= 512 chars |
| participant_type | must not be BSMD | | participant_type | must not be BSMD |
@ -188,6 +189,7 @@ shipcall_id, participant_id, participant_type
1. A dataset may only be changed by a user belonging to the same participant as the times dataset is referring to. 1. A dataset may only be changed by a user belonging to the same participant as the times dataset is referring to.
2. See reference and value checking as specified in /times POST. 2. See reference and value checking as specified in /times POST.
3. The shipcall type may not be changed.
#### Required fields #### Required fields

View File

@ -32,7 +32,7 @@ ___
| 0002 | Zeiten für einen Eintrag weichen voneinander ab | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Agentur, Festmacher usw. - außer BSMD-Spalte)<br/> - Zeiten ungleich (leere Einträge nicht berücksichtigen => 0001) | | | 0002 | Zeiten für einen Eintrag weichen voneinander ab | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Agentur, Festmacher usw. - außer BSMD-Spalte)<br/> - Zeiten ungleich (leere Einträge nicht berücksichtigen => 0001) | |
| 0002 - A | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / einkommend | Schnittmenge aus:<br/>times_agency:<br/> - ETA Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/>____und____<br/>times_portauthority:<br/>- ETA Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth | rot | | 0002 - A | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / einkommend | Schnittmenge aus:<br/>times_agency:<br/> - ETA Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/>____und____<br/>times_portauthority:<br/>- ETA Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth | rot |
| 0002 - B | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / ausgehend | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot | | 0002 - B | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / ausgehend | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot |
| 0002 - C | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / Verholung | Schnittmenge aus:<br />times_agency:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/> - ETA Berth <br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth <br/> - ETD Berth | rot | | 0002 - C | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / Verholung | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot |
| 0003 | Arbeitszeit überschneidet sich mit Fahrtzeit | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Terminal)<br/> - Zeiten passt nicht zu Ankunft / Abfahrt (leere Einträge nicht berücksichtigen => 0001) | | | 0003 | Arbeitszeit überschneidet sich mit Fahrtzeit | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Terminal)<br/> - Zeiten passt nicht zu Ankunft / Abfahrt (leere Einträge nicht berücksichtigen => 0001) | |
| 0003 - A | Terminal / einkommend | times_terminal:<br/> - Operation Start<br/>___vor (kleiner als)____<br/> times_agency:<br/> - ETA Berth | rot, aktuell __deaktiviert__! | | 0003 - A | Terminal / einkommend | times_terminal:<br/> - Operation Start<br/>___vor (kleiner als)____<br/> times_agency:<br/> - ETA Berth | rot, aktuell __deaktiviert__! |
| 0003 - B | Terminal / ausgehend + Verholung | times_terminal:<br/> - Operation Ende<br/>___nach (größer als)____<br/> times_agency:<br/> - ETD Berth | rot, aktuell __deaktiviert__! | | 0003 - B | Terminal / ausgehend + Verholung | times_terminal:<br/> - Operation Ende<br/>___nach (größer als)____<br/> times_agency:<br/> - ETD Berth | rot, aktuell __deaktiviert__! |

View File

@ -266,6 +266,11 @@ class SQLQuery():
query = "SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name" query = "SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name"
return query return query
@staticmethod
def get_ship_by_id()->str:
query = "SELECT * FROM ship where id = ?id?"
return query
@staticmethod @staticmethod
def get_times()->str: def get_times()->str:
query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \ query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \

View File

@ -437,6 +437,21 @@ class TimesSchema(Schema):
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12) valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return return
@validates("eta_interval_end")
def validate_eta_interval_end(self, value):
# violation when time is not in the future, but also does not exceed a threshold for the 'reasonable' future
# when 'value' is 'None', a ValidationError is not issued.
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return
@validates("etd_interval_end")
def validate_etd_interval_end(self, value):
# violation when time is not in the future, but also does not exceed a threshold for the 'reasonable' future
# when 'value' is 'None', a ValidationError is not issued.
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return
# deserialize PUT object target # deserialize PUT object target
class UserSchema(Schema): class UserSchema(Schema):

View File

@ -0,0 +1,12 @@
import datetime
from BreCal.schemas import model
from BreCal.schemas.model import ParticipantType
def get_schema_model_stub_departure():
schemaModel = {'id': 0, 'eta_berth': None, 'eta_berth_fixed': None, 'etd_berth': datetime.datetime(2024, 9, 7, 15, 12, 58), 'etd_berth_fixed': None, 'lock_time': None, 'lock_time_fixed': None, 'zone_entry': None, 'zone_entry_fixed': None, 'operations_start': None, 'operations_end': None, 'remarks': 'test', 'participant_id': 10, 'berth_id': 146, 'berth_info': '', 'pier_side': None, 'shipcall_id': 115, 'participant_type': ParticipantType.AGENCY, 'ata': None, 'atd': None, 'eta_interval_end': None, 'etd_interval_end': None, 'created': None, 'modified': None}
return schemaModel
def get_schema_model_stub_arrival():
schemaModel = {'id': 0, 'eta_berth': datetime.datetime(2024, 9, 7, 15, 12, 58), 'eta_berth_fixed': None, 'etd_berth': None, 'etd_berth_fixed': None, 'lock_time': None, 'lock_time_fixed': None, 'zone_entry': None, 'zone_entry_fixed': None, 'operations_start': None, 'operations_end': None, 'remarks': 'test', 'participant_id': 10, 'berth_id': 146, 'berth_info': '', 'pier_side': None, 'shipcall_id': 115, 'participant_type': ParticipantType.AGENCY, 'ata': None, 'atd': None, 'eta_interval_end': None, 'etd_interval_end': None, 'created': None, 'modified': None}
return schemaModel

View File

@ -6,6 +6,8 @@ from marshmallow import ValidationError
from string import ascii_letters, digits from string import ascii_letters, digits
from BreCal.schemas.model import Ship, Shipcall, Berth, User, Participant, ShipcallType from BreCal.schemas.model import Ship, Shipcall, Berth, User, Participant, ShipcallType
from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.database.sql_queries import SQLQuery
from BreCal.impl.participant import GetParticipant from BreCal.impl.participant import GetParticipant
from BreCal.impl.ships import GetShips from BreCal.impl.ships import GetShips
from BreCal.impl.berths import GetBerths from BreCal.impl.berths import GetBerths
@ -47,14 +49,14 @@ class InputValidationShip():
# 1.) Only users of type BSMD are allowed to PUT # 1.) Only users of type BSMD are allowed to PUT
InputValidationShip.check_user_is_bsmd_type(user_data) InputValidationShip.check_user_is_bsmd_type(user_data)
# 2.) The IMO number field may not be changed # 2.) ID field is mandatory
InputValidationShip.content_contains_ship_id(content)
# 3.) The IMO number field may not be changed
InputValidationShip.put_content_may_not_contain_imo_number(content) InputValidationShip.put_content_may_not_contain_imo_number(content)
# 3.) Check for reasonable Values (see BreCal.schemas.model.ShipSchema) # 4.) Check for reasonable Values (see BreCal.schemas.model.ShipSchema)
InputValidationShip.optionally_evaluate_bollard_pull_value(content) InputValidationShip.optionally_evaluate_bollard_pull_value(content)
# 4.) ID field is mandatory
InputValidationShip.content_contains_ship_id(content)
return return
@staticmethod @staticmethod
@ -101,11 +103,15 @@ class InputValidationShip():
@staticmethod @staticmethod
def put_content_may_not_contain_imo_number(content:dict): def put_content_may_not_contain_imo_number(content:dict):
# IMO is a required field, so it will never be None outside of tests. If so, there is no violation
put_data_ship_imo = content.get("imo",None) put_data_ship_imo = content.get("imo",None)
if put_data_ship_imo is None:
return
# #TODO: Aktuelle IMO abfragen und nach Änderung suchen, bevor eine Fehlermeldung erstellt wird # grab the ship by its ID and compare, whether the IMO is unchanged
ship = execute_sql_query_standalone(SQLQuery.get_ship_by_id(), param={"id":content.get("id")}, command_type="single", model=Ship)
if put_data_ship_imo is not None: if put_data_ship_imo != ship.imo:
raise ValidationError(f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key.") raise ValidationError(f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key.")
return return

View File

@ -123,6 +123,9 @@ class InputValidationShipcall():
# time values must use future-dates # time values must use future-dates
InputValidationShipcall.check_times_are_in_future(loadedModel, content) InputValidationShipcall.check_times_are_in_future(loadedModel, content)
# 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)
# some arguments must not be provided # some arguments must not be provided
InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys) InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys)
return return
@ -247,6 +250,16 @@ class InputValidationShipcall():
if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role
raise ValidationError(f"every participant id and type should be listed only once. Found multiple entries for one of the participants.") raise ValidationError(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):
raise ValidationError(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 @staticmethod
def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]): def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]):
""" """

View File

@ -29,32 +29,32 @@ def build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dic
ShipcallType.arrival:{ ShipcallType.arrival:{
ParticipantType.undefined:[], # should not be set in POST requests ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_start"], ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["eta_berth"], ParticipantType.AGENCY:[],
ParticipantType.MOORING:["eta_berth"], ParticipantType.MOORING:[],
ParticipantType.PILOT:["eta_berth"], ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:["eta_berth"], ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:["eta_berth"], ParticipantType.TUG:[],
}, },
ShipcallType.departure:{ ShipcallType.departure:{
ParticipantType.undefined:[], # should not be set in POST requests ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_end"], ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["etd_berth"], ParticipantType.AGENCY:[],
ParticipantType.MOORING:["etd_berth"], ParticipantType.MOORING:[],
ParticipantType.PILOT:["etd_berth"], ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"], ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:["etd_berth"], ParticipantType.TUG:[],
}, },
ShipcallType.shifting:{ ShipcallType.shifting:{
ParticipantType.undefined:[], # should not be set in POST requests ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_start", "operations_end"], ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["eta_berth", "etd_berth"], ParticipantType.AGENCY:[],
ParticipantType.MOORING:["eta_berth", "etd_berth"], ParticipantType.MOORING:[],
ParticipantType.PILOT:["eta_berth", "etd_berth"], ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:["eta_berth", "etd_berth"], ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:["eta_berth", "etd_berth"], ParticipantType.TUG:[],
}, },
} }
return post_data_type_dependent_required_fields_dict return post_data_type_dependent_required_fields_dict
@ -167,6 +167,17 @@ class InputValidationTimes():
if ParticipantType.BSMD in loadedModel["participant_type"]: if ParticipantType.BSMD in loadedModel["participant_type"]:
raise ValidationError(f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}") raise ValidationError(f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}")
if (loadedModel["etd_interval_end"] is not None) and (loadedModel["etd_berth"] is not None):
time_end_after_time_start = loadedModel["etd_interval_end"] >= loadedModel["etd_berth"]
if not time_end_after_time_start:
raise ValidationError(f"The provided time interval for the estimated departure time is invalid. The interval end takes place before the interval start. Found interval data: {loadedModel['etd_berth']} to {loadedModel['etd_interval_end']}")
if (loadedModel["eta_interval_end"] is not None) and (loadedModel["eta_berth"] is not None):
time_end_after_time_start = loadedModel["eta_interval_end"] >= loadedModel["eta_berth"]
if not time_end_after_time_start:
raise ValidationError(f"The provided time interval for the estimated arrival time is invalid. The interval begin takes place after the interval end. Found interval data: {loadedModel['eta_berth']} to {loadedModel['eta_interval_end']}")
return return
@staticmethod @staticmethod
@ -471,3 +482,44 @@ class InputValidationTimes():
has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))] has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))]
return has_bsmd_flag return has_bsmd_flag
def deprecated_build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dict[ParticipantType,typing.Optional[list[str]]]]:
"""
The required fields of a POST-request depend on ShipcallType and ParticipantType. This function creates
a dictionary, which maps those types to a list of required fields.
The participant types 'undefined' and 'bsmd' should not be used in POST-requests. They return 'None'.
"""
post_data_type_dependent_required_fields_dict = {
ShipcallType.arrival:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["eta_berth"],
ParticipantType.MOORING:["eta_berth"],
ParticipantType.PILOT:["eta_berth"],
ParticipantType.PORT_ADMINISTRATION:["eta_berth"],
ParticipantType.TUG:["eta_berth"],
},
ShipcallType.departure:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["etd_berth"],
ParticipantType.MOORING:["etd_berth"],
ParticipantType.PILOT:["etd_berth"],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"],
ParticipantType.TUG:["etd_berth"],
},
ShipcallType.shifting:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["etd_berth"],
ParticipantType.MOORING:["etd_berth"],
ParticipantType.PILOT:["etd_berth"],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"],
ParticipantType.TUG:["etd_berth"],
},
}
return post_data_type_dependent_required_fields_dict

View File

@ -27,8 +27,15 @@ def create_validation_error_response(ex:ValidationError, status_code:int=400)->t
"errors":errors, "errors":errors,
"valid_data":valid_data "valid_data":valid_data
} }
return (json.dumps(json_response), status_code)
# json.dumps with default=str automatically converts non-serializable values to strings. Hence, datetime objects (which are not)
# natively serializable are properly serialized.
serialized_response = json.dumps(json_response, default=str)
return (serialized_response, status_code)
def create_werkzeug_error_response(ex:Forbidden, status_code:int=403)->typing.Tuple[str,int]: def create_werkzeug_error_response(ex:Forbidden, status_code:int=403)->typing.Tuple[str,int]:
return json.dumps({"message":ex.description}), status_code # json.dumps with default=str automatically converts non-serializable values to strings. Hence, datetime objects (which are not)
# natively serializable are properly serialized.
serialized_response = json.dumps({"message":ex.description}, default=str)
return serialized_response, status_code

View File

@ -27,7 +27,7 @@ error_message_dict = {
# 0002 A+B+C # 0002 A+B+C
"validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}", "validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}",
"validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002B}", "validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002B}",
"validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for ETA and ETD {Rule #0002C}", "validation_rule_fct_shipcall_shifting_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002C}",
# 0003 A+B # 0003 A+B
"validation_rule_fct_eta_time_not_in_operation_window":"The estimated time of arrival will be AFTER the planned start of operations. {Rule #0003A}", "validation_rule_fct_eta_time_not_in_operation_window":"The estimated time of arrival will be AFTER the planned start of operations. {Rule #0003A}",
@ -723,21 +723,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
else: else:
return self.get_no_violation_default_output() return self.get_no_violation_default_output()
def validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(self, shipcall, df_times, *args, **kwargs): def validation_rule_fct_shipcall_shifting_participants_disagree_on_etd(self, shipcall, df_times, *args, **kwargs):
""" """
Code: #0002-C Code: #0002-C
Type: Local Rule Type: Local Rule
Description: this validation checks, whether the participants expect different ETA or ETD times Description: this validation checks, whether the participants expect different ETD times
Filter: only applies to shifting shipcalls Filter: only applies to shifting shipcalls
""" """
violation_state_eta = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query="eta_berth",
df_times=df_times,
applicable_shipcall_type=ShipcallType.SHIFTING
)
violation_state_etd = self.check_participants_agree_on_estimated_time( violation_state_etd = self.check_participants_agree_on_estimated_time(
shipcall = shipcall, shipcall = shipcall,
@ -746,16 +738,14 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
applicable_shipcall_type=ShipcallType.SHIFTING applicable_shipcall_type=ShipcallType.SHIFTING
) )
# apply 'eta_berth' check
# apply 'etd_berth' # apply 'etd_berth'
# violation: if either 'eta_berth' or 'etd_berth' is violated # violation: if either 'etd_berth' is violated
# functionally, this is the same as individually comparing all times for the participants # functionally, this is the same as individually comparing all times for the participants
# times_agency.eta_berth==times_mooring.eta_berth==times_portadministration.eta_berth==times_pilot.eta_berth==times_tug.eta_berth
# times_agency.etd_berth==times_mooring.etd_berth==times_portadministration.etd_berth==times_pilot.etd_berth==times_tug.etd_berth # times_agency.etd_berth==times_mooring.etd_berth==times_portadministration.etd_berth==times_pilot.etd_berth==times_tug.etd_berth
violation_state = (violation_state_eta) or (violation_state_etd) violation_state = (violation_state_etd)
if violation_state: if violation_state:
validation_name = "validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd" validation_name = "validation_rule_fct_shipcall_shifting_participants_disagree_on_etd"
return (StatusFlags.RED, validation_name) return (StatusFlags.RED, validation_name)
else: else:
return self.get_no_violation_default_output() return self.get_no_violation_default_output()

View File

@ -87,6 +87,13 @@ def test_sql_get_ships():
assert all([isinstance(ship, model.Ship) for ship in ships]) assert all([isinstance(ship, model.Ship) for ship in ships])
return return
def test_sql_get_ship_by_id():
query = #"SELECT * FROM ship where id = ?id?" # #TODO_refactor: put into the SQLQuery object
ship = execute_sql_query_standalone(SQLQuery.get_ship_by_id(), param={"id":1}, command_type="single", model=model.Ship)
assert isinstance(ship, model.Ship)
return
def test_sql_get_times(): def test_sql_get_times():
options = {'shipcall_id':153} options = {'shipcall_id':153}
times = execute_sql_query_standalone(query=SQLQuery.get_times(), model=model.Times, param={"scid" : options["shipcall_id"]}) times = execute_sql_query_standalone(query=SQLQuery.get_times(), model=model.Times, param={"scid" : options["shipcall_id"]})

View File

@ -901,3 +901,35 @@ def test_post_data_with_valid_data(get_stub_token):
assert response.status_code==201 assert response.status_code==201
return return
def test_input_validation_shipcall_put_fails_when_type_differs():
from marshmallow import ValidationError
from BreCal.stubs.shipcall import get_stub_valid_shipcall_arrival
from BreCal.schemas import model
from BreCal.database.sql_queries import SQLQuery
from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.validators.input_validation_shipcall import InputValidationShipcall
# stub data
put_data = get_stub_valid_shipcall_arrival()
put_data["id"] = 3
loadedModel = model.ShipcallSchema().load(data=put_data, many=False, partial=True)
# load data from DB
shipcall_id = loadedModel.get("id")
query = SQLQuery.get_shipcall_by_id()
shipcall = execute_sql_query_standalone(query=query, model=model.Shipcall, param={"id":shipcall_id}, command_type="single")
# create failure case. Ensures that the type is not the same as in the loaded shipcall from the DB
failure_case = 1 if shipcall.type==2 else 2
loadedModel["type"] = model.ShipcallType(failure_case)
# should fail: different 'type'
with pytest.raises(ValidationError, match="The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed"):
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
# should pass: same 'type'
loadedModel["type"] = shipcall.type
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
return

View File

@ -409,3 +409,60 @@ def test_input_validation_times_delete_request_fails_when_user_belongs_to_wrong_
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id, pdata=pdata) InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id, pdata=pdata)
return return
def test_input_validation_times_check_dataset_values_for_time_intervals():
import datetime
from BreCal.schemas import model
from BreCal.validators.input_validation_times import InputValidationTimes
from BreCal.stubs.times import get_schema_model_stub_arrival, get_schema_model_stub_departure
from marshmallow import ValidationError
### ETD (departure)
# expected to pass
schemaModel = get_schema_model_stub_departure()
schemaModel["etd_interval_end"] = schemaModel["etd_berth"] + datetime.timedelta(minutes=2)
schemaModel["etd_berth"] = schemaModel["etd_berth"].isoformat()
schemaModel["etd_interval_end"] = schemaModel["etd_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
# expected to fail: the from-to-interval is incorrectly set.
schemaModel = get_schema_model_stub_departure()
schemaModel["etd_interval_end"] = schemaModel["etd_berth"] - datetime.timedelta(minutes=2)
schemaModel["etd_berth"] = schemaModel["etd_berth"].isoformat()
schemaModel["etd_interval_end"] = schemaModel["etd_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
with pytest.raises(ValidationError, match="The provided time interval for the estimated departure time is invalid"):
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
### ETA (arrival)
# expected to pass
schemaModel = get_schema_model_stub_arrival()
schemaModel["eta_interval_end"] = schemaModel["eta_berth"] + datetime.timedelta(minutes=2)
schemaModel["eta_berth"] = schemaModel["eta_berth"].isoformat()
schemaModel["eta_interval_end"] = schemaModel["eta_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
# expected to fail: the from-to-interval is incorrectly set.
schemaModel = get_schema_model_stub_arrival()
schemaModel["eta_interval_end"] = schemaModel["eta_berth"] - datetime.timedelta(minutes=2)
schemaModel["eta_berth"] = schemaModel["eta_berth"].isoformat()
schemaModel["eta_interval_end"] = schemaModel["eta_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
with pytest.raises(ValidationError, match="The provided time interval for the estimated arrival time is invalid"):
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
return

View File

@ -0,0 +1,19 @@
import pytest
def test_create_validation_error_response_is_serializable():
from BreCal.stubs.times_full import get_valid_stub_times
from BreCal.schemas import model
from BreCal.validators.validation_error import create_validation_error_response
content = get_valid_stub_times()
import datetime
content["operations_end"] = (datetime.datetime.now()-datetime.timedelta(minutes=14)).isoformat()
content["id"] = 3
try:
loadedModel = model.TimesSchema().load(data=content, many=False, partial=True)
except Exception as ex:
my_var = ex
create_validation_error_response(ex=ex, status_code=400) # this function initially created errors, as datetime objects were not serializable. Corrected now.
return