Merge branch 'release/1.5.0' of github.com:puls200/brecal into release/1.5.0

This commit is contained in:
Max Metz 2024-09-05 11:55:34 +02:00
commit 7ad8011c52
15 changed files with 253 additions and 48 deletions

View File

@ -112,7 +112,7 @@ Usually the "Z" is missing at the end indicating local time.
#### Required fields
* eta / etd (depending on value of type: 1: eta, 2: etd, 3: both)
* eta / etd (depending on value of type: 1: eta, 2: etd, 3: etd)
* type
* ship_id
* arrival_berth_id / departure_berth_id (depending on type, see above)
@ -147,6 +147,7 @@ The id field is required, missing fields will not be updated.
| Field | Validation |
|-------|------------|
| 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 |
| 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.
2. See reference and value checking as specified in /times POST.
3. The shipcall type may not be changed.
#### 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 - 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 - 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 - 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__! |

View File

@ -8,8 +8,8 @@
<SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>1.5.0.0</AssemblyVersion>
<FileVersion>1.5.0.0</FileVersion>
<AssemblyVersion>1.5.0.1</AssemblyVersion>
<FileVersion>1.5.0.1</FileVersion>
<Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon>

View File

@ -1072,7 +1072,21 @@ namespace BreCalClient
dynamic? msg = JsonConvert.DeserializeObject(m.Value);
if (msg != null)
{
message = msg.message;
if (msg.message != null)
{
caption = $"{caption}: {msg.message}";
}
if((msg.errors != null) && msg.errors.Count > 0)
{
message = "";
foreach(string error in msg.errors)
{
message += error;
message += Environment.NewLine;
}
}
}
}
catch (Exception) { }

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.5.0.0</ApplicationVersion>
<ApplicationVersion>1.5.0.1</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>

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"
return query
@staticmethod
def get_ship_by_id()->str:
query = "SELECT * FROM ship where id = ?id?"
return query
@staticmethod
def get_times()->str:
query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \

View File

@ -436,6 +436,21 @@ class TimesSchema(Schema):
# 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("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

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 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.ships import GetShips
from BreCal.impl.berths import GetBerths
@ -47,14 +49,14 @@ class InputValidationShip():
# 1.) Only users of type BSMD are allowed to PUT
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)
# 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)
# 4.) ID field is mandatory
InputValidationShip.content_contains_ship_id(content)
return
@staticmethod
@ -101,11 +103,15 @@ class InputValidationShip():
@staticmethod
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)
if put_data_ship_imo is None:
return
# 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)
# #TODO: Aktuelle IMO abfragen und nach Änderung suchen, bevor eine Fehlermeldung erstellt wird
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.")
return

View File

@ -123,6 +123,9 @@ class InputValidationShipcall():
# time values must use future-dates
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
InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys)
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
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
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:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_start"],
ParticipantType.AGENCY:["eta_berth"],
ParticipantType.MOORING:["eta_berth"],
ParticipantType.PILOT:["eta_berth"],
ParticipantType.PORT_ADMINISTRATION:["eta_berth"],
ParticipantType.TUG:["eta_berth"],
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:[],
ParticipantType.MOORING:[],
ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:[],
},
ShipcallType.departure:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_end"],
ParticipantType.AGENCY:["etd_berth"],
ParticipantType.MOORING:["etd_berth"],
ParticipantType.PILOT:["etd_berth"],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"],
ParticipantType.TUG:["etd_berth"],
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:[],
ParticipantType.MOORING:[],
ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:[],
},
ShipcallType.shifting:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_start", "operations_end"],
ParticipantType.AGENCY:["eta_berth", "etd_berth"],
ParticipantType.MOORING:["eta_berth", "etd_berth"],
ParticipantType.PILOT:["eta_berth", "etd_berth"],
ParticipantType.PORT_ADMINISTRATION:["eta_berth", "etd_berth"],
ParticipantType.TUG:["eta_berth", "etd_berth"],
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:[],
ParticipantType.MOORING:[],
ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:[],
},
}
return post_data_type_dependent_required_fields_dict
@ -167,6 +167,17 @@ class InputValidationTimes():
if ParticipantType.BSMD in loadedModel["participant_type"]:
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
@staticmethod
@ -471,3 +482,44 @@ class InputValidationTimes():
has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))]
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,7 +27,7 @@ error_message_dict = {
# 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_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
"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:
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
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
"""
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(
shipcall = shipcall,
@ -746,16 +738,14 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
applicable_shipcall_type=ShipcallType.SHIFTING
)
# apply 'eta_berth' check
# 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
# 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
violation_state = (violation_state_eta) or (violation_state_etd)
violation_state = (violation_state_etd)
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)
else:
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])
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():
options = {'shipcall_id':153}
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
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)
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