Merge pull request #44 from puls200/hotfix/20240903_maintenance_1.5.0
Hotfix/20240903 maintenance 1.5.0
This commit is contained in:
commit
3bce9e85b9
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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__! |
|
||||||
|
|||||||
@ -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, " + \
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
12
src/server/BreCal/stubs/times.py
Normal file
12
src/server/BreCal/stubs/times.py
Normal 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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"]):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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"]})
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
19
src/server/tests/validators/test_validation_error.py
Normal file
19
src/server/tests/validators/test_validation_error.py
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user