This repository has been archived on 2025-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
BreCal/src/server/tests/validators/test_input_validation_times.py

399 lines
22 KiB
Python

import pytest
import os
import random
import datetime
from marshmallow import ValidationError
from BreCal import local_db
from BreCal.schemas import model
from BreCal.schemas.model import ParticipantType
from BreCal.validators.input_validation_times import InputValidationTimes
from BreCal.stubs.times_full import get_valid_stub_times, get_valid_stub_for_pytests
instance_path = os.path.join(os.path.expanduser('~'), "brecal", "src", "server", "instance", "instance")
local_db.initPool(os.path.dirname(instance_path), connection_filename="connection_data_local.json")
def test_input_validation_times_fails_when_berth_info_exceeds_length_limit():
# success
post_data = get_valid_stub_times()
post_data["berth_info"] = "a"*512 # 512 characters
model.TimesSchema().load(data=post_data, many=False, partial=True)
post_data["berth_info"] = "" # 0 characters
model.TimesSchema().load(data=post_data, many=False, partial=True)
# failure
with pytest.raises(ValidationError, match="Longer than maximum length 512."):
post_data["berth_info"] = "a"*513 # 513 characters
model.TimesSchema().load(data=post_data, many=False, partial=True)
return
def test_input_validation_times_fails_when_remarks_exceeds_length_limit():
# success
post_data = get_valid_stub_times()
post_data["remarks"] = "a"*512 # 512 characters
model.TimesSchema().load(data=post_data, many=False, partial=True)
post_data["remarks"] = "" # 0 characters
model.TimesSchema().load(data=post_data, many=False, partial=True)
# failure
with pytest.raises(ValidationError, match="Longer than maximum length 512."):
post_data["remarks"] = "a"*513 # 513 characters
model.TimesSchema().load(data=post_data, many=False, partial=True)
return
def test_input_validation_times_fails_when_participant_type_is_bsmd():
# BSMD -> Failure
post_data = get_valid_stub_times()
post_data["participant_type"] = int(ParticipantType.BSMD)
with pytest.raises(ValidationError, match="the participant_type must not be .BSMD"):
model.TimesSchema().load(data=post_data, many=False, partial=True)
# IntFlag property: BSMD & AGENCY -> Failure
post_data = get_valid_stub_times()
post_data["participant_type"] = int(ParticipantType(ParticipantType.BSMD+ParticipantType.AGENCY))
with pytest.raises(ValidationError, match="the participant_type must not be .BSMD"):
model.TimesSchema().load(data=post_data, many=False, partial=True)
return
def test_input_validation_times_fails_when_time_key_is_not_reasonable():
"""
every time key (e.g., 'eta_berth' or 'zone_entry') must be reasonable. The validation expects
these values to be 'in the future' (larger than datetime.datetime.now()) and not 'in the too distant future'
(e.g., more than one year from now.)
"""
for time_key in ["eta_berth", "etd_berth", "lock_time", "zone_entry", "operations_start", "operations_end"]:
post_data = get_valid_stub_times()
# success
post_data[time_key] = (datetime.datetime.now() + datetime.timedelta(minutes=11)).isoformat()
model.TimesSchema().load(data=post_data, many=False, partial=True)
# fails
with pytest.raises(ValidationError, match="The provided value must be in the future."):
post_data[time_key] = (datetime.datetime.now() - datetime.timedelta(minutes=11)).isoformat()
model.TimesSchema().load(data=post_data, many=False, partial=True)
# fails
with pytest.raises(ValidationError, match="The provided value is in the too distant future and exceeds a threshold for 'reasonable' entries."):
post_data[time_key] = (datetime.datetime.now() + datetime.timedelta(days=367)).isoformat()
model.TimesSchema().load(data=post_data, many=False, partial=True)
return
def test_input_validation_times_fails_when_user_is_bsmd_user():
# create stub-data for a POST request
from BreCal.services.jwt_handler import decode_jwt
from BreCal.database.sql_utils import get_user_data_for_id
import re
# user 4 is a BSMD user -> fails
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=4)
with pytest.raises(ValidationError, match=re.escape("current user belongs to BSMD. Cannot post 'times' datasets.")):
InputValidationTimes.check_user_is_not_bsmd_type(user_data)
# user 13 is not a BSMD user -> passes
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=13)
# success
InputValidationTimes.check_user_is_not_bsmd_type(user_data)
return
def test_input_validation_times_fails_when_participant_type_entry_already_exists():
# the participant type already has an entry -> fails
with pytest.raises(ValidationError, match="A dataset for the participant type is already present. Participant Type:"):
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["participant_type"] = int(ParticipantType.AGENCY)
# 2.) datasets may only be created, if the respective participant type did not already create one.
InputValidationTimes.check_if_entry_already_exists_for_participant_type(user_data, loadedModel, content)
return
def test_input_validation_times_fails_when_participant_type_deviates_from_shipcall_participant_map():
# success
# user id 3 is assigned as participant_type=4, but the stub assigns participant_type=4
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
InputValidationTimes.check_if_user_fits_shipcall_participant_map(user_data, loadedModel, content)
# fails
# user id 4 is assigned as participant_type=1, but the stub assigns participant_type=4
with pytest.raises(ValidationError, match="is assigned to the shipcall in a different role."):
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=4)
InputValidationTimes.check_if_user_fits_shipcall_participant_map(user_data, loadedModel, content)
return
def test_input_validation_times_fails_when_id_references_do_not_exist():
# success: all IDs exist
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
InputValidationTimes.check_dataset_references(content)
# fails: IDs do not exist
# iterates once for each, berth_id, shipcall_id, participant_id and generates an artificial, non-existing ID
for key in ["berth_id", "shipcall_id", "participant_id"]:
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
content[key] = loadedModel[key] = 9912737
with pytest.raises(ValidationError, match=f"The referenced {key} '{content[key]}' does not exist in the database."):
InputValidationTimes.check_dataset_references(content)
return
from BreCal.schemas.model import ParticipantType
def test_input_validation_times_fails_when_missing_required_fields_arrival():
"""
evaluates every individual combination of arriving shipcalls, where one of the required values is arbitrarily missing
randomly selects one of the non-terminal ParticipantTypes, which are reasonable (not .BSMD), and validates. This makes sure,
that over time, every possible combination has been tested.
"""
# arrival + not-terminal
non_terminal_list = [ParticipantType.AGENCY, ParticipantType.MOORING, ParticipantType.PILOT, ParticipantType.PORT_ADMINISTRATION, ParticipantType.TUG]
for key in ["eta_berth"]+InputValidationTimes.get_post_data_type_independent_fields():
random_participant_type_for_unit_test = random.sample(non_terminal_list,k=1)[0]
# pass: all required fields exist for the current shipcall type (arrival/incoming)
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
loadedModel["participant_type"] = random_participant_type_for_unit_test
content["participant_type"] = int(random_participant_type_for_unit_test)
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# fails: iteratively creates stubs, where one of the required keys is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
loadedModel["participant_type"] = random_participant_type_for_unit_test
content["participant_type"] = int(random_participant_type_for_unit_test)
with pytest.raises(ValidationError, match="At least one of the required fields is missing. Missing:"):
loadedModel[key] = content[key] = None
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# arrival + terminal
for key in ["operations_start"]+InputValidationTimes.get_post_data_type_independent_fields():
# pass: all required fields exist for the current shipcall type (arrival/incoming)
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
loadedModel["participant_type"] = ParticipantType.TERMINAL
content["participant_type"] = int(ParticipantType.TERMINAL)
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# fails: iteratively creates stubs, where one of the required keys is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
loadedModel["participant_type"] = ParticipantType.TERMINAL
content["participant_type"] = int(ParticipantType.TERMINAL)
with pytest.raises(ValidationError, match="At least one of the required fields is missing. Missing:"):
loadedModel[key] = content[key] = None
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
return
def test_input_validation_times_fails_when_missing_required_fields_departure():
"""
evaluates every individual combination of departing shipcalls, where one of the required values is arbitrarily missing
randomly selects one of the non-terminal ParticipantTypes, which are reasonable (not .BSMD), and validates. This makes sure,
that over time, every possible combination has been tested.
"""
# departure + not-terminal
non_terminal_list = [ParticipantType.AGENCY, ParticipantType.MOORING, ParticipantType.PILOT, ParticipantType.PORT_ADMINISTRATION, ParticipantType.TUG]
for key in ["etd_berth"]+InputValidationTimes.get_post_data_type_independent_fields():
# select a *random* particiipant type, which is reasonable and *not* TERMINAL, and validate the function.
random_participant_type_for_unit_test = random.sample(non_terminal_list,k=1)[0]
# pass
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 241
loadedModel["participant_type"] = random_participant_type_for_unit_test
content["participant_type"] = int(random_participant_type_for_unit_test)
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# fails: iteratively creates stubs, where one of the required keys is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 241
loadedModel["participant_type"] = random_participant_type_for_unit_test
content["participant_type"] = int(random_participant_type_for_unit_test)
with pytest.raises(ValidationError, match="At least one of the required fields is missing. Missing:"):
loadedModel[key] = content[key] = None
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# departure + terminal
for key in ["operations_end"]+InputValidationTimes.get_post_data_type_independent_fields():
# pass
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 241
loadedModel["participant_type"] = ParticipantType.TERMINAL
content["participant_type"] = int(ParticipantType.TERMINAL)
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# fails: iteratively creates stubs, where one of the required keys is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 241
loadedModel["participant_type"] = ParticipantType.TERMINAL
content["participant_type"] = int(ParticipantType.TERMINAL)
with pytest.raises(ValidationError, match="At least one of the required fields is missing. Missing:"):
loadedModel[key] = content[key] = None
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
return
def test_input_validation_times_fails_when_missing_required_fields_shifting():
"""
evaluates every individual combination of shifting shipcalls, where one of the required values is arbitrarily missing
randomly selects one of the non-terminal ParticipantTypes, which are reasonable (not .BSMD), and validates. This makes sure,
that over time, every possible combination has been tested.
"""
# shifting + not-terminal
non_terminal_list = [ParticipantType.AGENCY, ParticipantType.MOORING, ParticipantType.PILOT, ParticipantType.PORT_ADMINISTRATION, ParticipantType.TUG]
for key in ["eta_berth", "etd_berth"]+InputValidationTimes.get_post_data_type_independent_fields():
# select a *random* particiipant type, which is reasonable and *not* TERMINAL, and validate the function.
random_participant_type_for_unit_test = random.sample(non_terminal_list,k=1)[0]
# pass: all required fields exist for the current shipcall type (arrival/incoming)
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 189
loadedModel["participant_type"] =random_participant_type_for_unit_test
content["participant_type"] = int(random_participant_type_for_unit_test)
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# fails: iteratively creates stubs, where one of the required keys is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 189
loadedModel["participant_type"] = random_participant_type_for_unit_test
content["participant_type"] = int(random_participant_type_for_unit_test)
with pytest.raises(ValidationError, match="At least one of the required fields is missing. Missing:"):
loadedModel[key] = content[key] = None
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# shifting + terminal
for key in ["operations_start", "operations_end"]+InputValidationTimes.get_post_data_type_independent_fields():
# pass: all required fields exist for the current shipcall type (arrival/incoming)
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 189
loadedModel["participant_type"] = ParticipantType.TERMINAL
content["participant_type"] = int(ParticipantType.TERMINAL)
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
# fails: iteratively creates stubs, where one of the required keys is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
loadedModel["shipcall_id"] = content["shipcall_id"] = 189
loadedModel["participant_type"] = ParticipantType.TERMINAL
content["participant_type"] = int(ParticipantType.TERMINAL)
with pytest.raises(ValidationError, match="At least one of the required fields is missing. Missing:"):
loadedModel[key] = content[key] = None
InputValidationTimes.check_times_required_fields_post_data(loadedModel, content)
return
def test_input_validation_times_fails_when_participant_type_is_not_assigned__or__user_does_not_belong_to_the_same_participant_id():
"""
There are two failure cases in InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines
1.) when the participant type is simply not assigned
2.) when the participant type matches to the user, but the participant_id is not assigned
Test case:
shipcall_id 222 is assigned to the participants {"participant_id": 136, "type":2} and {"participant_id": 136, "type":8}
Case 1:
When user_id 3 should be set as participant_type 4, the call fails, because type 4 is not assigned
Case 2:
When user_id 2 (participant_id 2) should be set as participant_type 2, the call fails even though type 2 exists,
because participant_id 136 is assigned
Case 3:
When user_id 28 (participant_id 136) is set as participant_type 2, the call passes.
"""
# fails: participant type 4 does not exist
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
participant_type = 4
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
loadedModel["participant_id"] = content["participant_id"] = 2
loadedModel["participant_type"] = content["participant_type"] = participant_type
with pytest.raises(ValidationError, match=f"Could not find a matching time dataset for the provided participant_type: {participant_type}. Found Time Datasets:"):
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=loadedModel, times_id=None)
# fails: participant type 2 exists, but user_id 2 is part of the wrong participant_id group (user_id 28 or 29 would be)
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=2)
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
participant_type = 2
loadedModel["participant_type"] = content["participant_type"] = participant_type
with pytest.raises(ValidationError, match="The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id:"):
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=loadedModel, times_id=None)
# pass: participant type 2 exists & user_id is part of participant_id group 136, which is correct
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=28)
loadedModel["shipcall_id"] = content["shipcall_id"] = 222
participant_type = 2
loadedModel["participant_type"] = content["participant_type"] = participant_type
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=loadedModel, times_id=None)
return
def test_input_validation_times_put_request_fails_when_id_field_is_missing():
"""used within PUT-requests. When 'id' is missing, a ValidationError is issued"""
# passes: as an 'id' is provided
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
content["id"] = 379
InputValidationTimes.check_times_required_fields_put_data(content)
# fails: 'id' field is missing
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
content.pop("id",None)
with pytest.raises(ValidationError, match="A PUT-request requires an 'id' reference, which was not found."):
InputValidationTimes.check_times_required_fields_put_data(content)
return
def test_input_validation_times_delete_request_fails_when_times_id_is_deleted_already():
# passes: id exists
times_id = 379
InputValidationTimes.check_if_entry_is_already_deleted(times_id)
# passes: id exists
times_id = 391
InputValidationTimes.check_if_entry_is_already_deleted(times_id)
# fails
times_id = 11
with pytest.raises(ValidationError, match=f"The selected time entry is already deleted. ID: {times_id}"):
InputValidationTimes.check_if_entry_is_already_deleted(times_id)
# fails
times_id = 4
with pytest.raises(ValidationError, match=f"The selected time entry is already deleted. ID: {times_id}"):
InputValidationTimes.check_if_entry_is_already_deleted(times_id)
return
def test_input_validation_times_delete_request_fails_when_times_id_does_not_exist_():
# passes: times_id exists
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=28)
times_id = 392
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
# fails: times_id does not exist
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=28)
times_id = 4
with pytest.raises(ValidationError, match=f"Unknown times_id. Could not find a matching entry for ID: {times_id}"):
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
return
def test_input_validation_times_delete_request_fails_when_user_belongs_to_wrong_participant_id():
# fails: participant_id should be 136, but user_id=3 belongs to participant_id=2
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=3)
times_id = 392
with pytest.raises(ValidationError, match=f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id:"):
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
# passes: participant_id should be 136, and user_id=28 belongs to participant_id=2
user_data, loadedModel, content = get_valid_stub_for_pytests(user_id=28)
times_id = 392
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
return