diff --git a/tests/utils/models/test_train_model_params.py b/tests/utils/models/test_train_model_params.py new file mode 100644 index 0000000..43a6a51 --- /dev/null +++ b/tests/utils/models/test_train_model_params.py @@ -0,0 +1,371 @@ +"""Unit tests for TrainModelParams with 100% coverage.""" + +import pytest + + +@pytest.fixture +def valid_train_params_dict(): + """Create a valid dictionary for TrainModelParams.""" + return { + 'variable_columns': ['var1', 'var2'], + 'lag_train': 5, + 'lag_val': 3, + 'target_variable': 'target', + 'rem_static_win': True, + 'low_lim': {'var1': 0.0, 'var2': 1.0}, + 'upp_lim': {'var1': 10.0, 'var2': 20.0}, + 'window': 10, + 'use_scaler': True, + 'include_ar': False, + 'bucket_name': 'test-bucket', + 'file_name': 'test-file.csv', + 'line_separator': ',', + 'decimal_separator': '.', + 'train_size': 80, + 'shuffle': True, + 'experiment_run_id': 1, + 'experiment_name': 'test_experiment', + 'removed_intervals': [], + } + + +def test_train_model_params_from_dict_success(valid_train_params_dict): + """Test TrainModelParams.from_dict with valid data.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + params = TrainModelParams.from_dict(valid_train_params_dict) + + assert params.variable_columns == ['var1', 'var2'] + assert params.lag_train == 5 + assert params.lag_val == 3 + assert params.target_variable == 'target' + assert params.rem_static_win is True + assert params.low_lim == {'var1': 0.0, 'var2': 1.0} + assert params.upp_lim == {'var1': 10.0, 'var2': 20.0} + assert params.window == 10 + assert params.use_scaler is True + assert params.include_ar is False + assert params.bucket_name == 'test-bucket' + assert params.file_name == 'test-file.csv' + assert params.line_separator == ',' + assert params.decimal_separator == '.' + assert params.train_size == 80 + assert params.shuffle is True + assert params.experiment_run_id == 1 + assert params.experiment_name == 'test_experiment' + assert params.removed_intervals == [] + + +def test_train_model_params_check_none_raises_value_error(): + """Test _check_none raises ValueError when value is None.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + with pytest.raises(ValueError, match='test_field is required and cannot be None'): + TrainModelParams._check_none(None, str, 'test_field') + + +def test_train_model_params_check_none_raises_type_error(): + """Test _check_none raises TypeError when type is incorrect.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + with pytest.raises(TypeError, match='test_field must be of type str, but got int'): + TrainModelParams._check_none(123, str, 'test_field') + + +def test_train_model_params_check_none_success(): + """Test _check_none returns value when valid.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + result = TrainModelParams._check_none('test_value', str, 'test_field') + assert result == 'test_value' + + +def test_train_model_params_check_type_raises_type_error(): + """Test _check_type raises TypeError when type is incorrect.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + with pytest.raises(TypeError, match='test_field must be of type int, but got str'): + TrainModelParams._check_type('not_an_int', int, 'test_field') + + +def test_train_model_params_check_type_success(): + """Test _check_type returns value when valid.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + result = TrainModelParams._check_type(42, int, 'test_field') + assert result == 42 + + +def test_train_model_params_check_type_with_none(): + """Test _check_type allows None value.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + result = TrainModelParams._check_type(None, str, 'test_field') + assert result is None + + +def test_train_model_params_from_dict_missing_field(valid_train_params_dict): + """Test from_dict raises ValueError when required field is missing.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + del valid_train_params_dict['variable_columns'] + + with pytest.raises(ValueError, match='variable_columns is required and cannot be None'): + TrainModelParams.from_dict(valid_train_params_dict) + + +def test_train_model_params_from_dict_wrong_type(valid_train_params_dict): + """Test from_dict raises TypeError when field has wrong type.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['lag_train'] = 'not_an_int' + + with pytest.raises(TypeError, match='lag_train must be of type int, but got str'): + TrainModelParams.from_dict(valid_train_params_dict) + + +def test_train_model_params_from_dict_with_none_removed_intervals(valid_train_params_dict): + """Test from_dict allows None for removed_intervals.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['removed_intervals'] = None + + params = TrainModelParams.from_dict(valid_train_params_dict) + assert params.removed_intervals is None + + +def test_validate_business_rules_success(valid_train_params_dict): + """Test validate_business_rules with valid parameters.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + params = TrainModelParams.from_dict(valid_train_params_dict) + params.validate_business_rules() # Should not raise + + +def test_validate_business_rules_train_size_too_low(valid_train_params_dict): + """Test validate_business_rules raises error when train_size < 10.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['train_size'] = 5 + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='train_size must be between 10 and 100, got 5'): + params.validate_business_rules() + + +def test_validate_business_rules_train_size_too_high(valid_train_params_dict): + """Test validate_business_rules raises error when train_size > 100.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['train_size'] = 101 + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='train_size must be between 10 and 100, got 101'): + params.validate_business_rules() + + +def test_validate_business_rules_empty_variable_columns(valid_train_params_dict): + """Test validate_business_rules raises error when variable_columns is empty.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['variable_columns'] = [] + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='variable_columns cannot be empty'): + params.validate_business_rules() + + +def test_validate_business_rules_negative_lag_train(valid_train_params_dict): + """Test validate_business_rules raises error when lag_train is negative.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['lag_train'] = -1 + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='lag_train must be positive, got -1'): + params.validate_business_rules() + + +def test_validate_business_rules_negative_lag_val(valid_train_params_dict): + """Test validate_business_rules raises error when lag_val is negative.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['lag_val'] = -2 + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='lag_val must be positive, got -2'): + params.validate_business_rules() + + +def test_validate_business_rules_negative_window(valid_train_params_dict): + """Test validate_business_rules raises error when window is negative.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['window'] = -5 + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='window must be positive, got -5'): + params.validate_business_rules() + + +def test_validate_business_rules_mismatched_limit_keys(valid_train_params_dict): + """Test validate_business_rules raises error when low_lim and upp_lim keys don't match.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['low_lim'] = {'var1': 0.0} + valid_train_params_dict['upp_lim'] = {'var1': 10.0, 'var2': 20.0} + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='low_lim and upp_lim must have the same keys'): + params.validate_business_rules() + + +def test_validate_business_rules_low_lim_greater_than_upp_lim(valid_train_params_dict): + """Test validate_business_rules raises error when low_lim >= upp_lim.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['low_lim'] = {'var1': 15.0, 'var2': 1.0} + valid_train_params_dict['upp_lim'] = {'var1': 10.0, 'var2': 20.0} + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='low_lim must be less than upp_lim for variable "var1"'): + params.validate_business_rules() + + +def test_validate_business_rules_low_lim_equal_to_upp_lim(valid_train_params_dict): + """Test validate_business_rules raises error when low_lim == upp_lim.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['low_lim'] = {'var1': 10.0, 'var2': 1.0} + valid_train_params_dict['upp_lim'] = {'var1': 10.0, 'var2': 20.0} + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='low_lim must be less than upp_lim for variable "var1"'): + params.validate_business_rules() + + +def test_validate_business_rules_empty_bucket_name(valid_train_params_dict): + """Test validate_business_rules raises error when bucket_name is empty.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['bucket_name'] = '' + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='bucket_name cannot be empty or whitespace'): + params.validate_business_rules() + + +def test_validate_business_rules_whitespace_bucket_name(valid_train_params_dict): + """Test validate_business_rules raises error when bucket_name is whitespace.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['bucket_name'] = ' ' + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='bucket_name cannot be empty or whitespace'): + params.validate_business_rules() + + +def test_validate_business_rules_empty_file_name(valid_train_params_dict): + """Test validate_business_rules raises error when file_name is empty.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['file_name'] = '' + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='file_name cannot be empty or whitespace'): + params.validate_business_rules() + + +def test_validate_business_rules_whitespace_file_name(valid_train_params_dict): + """Test validate_business_rules raises error when file_name is whitespace.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['file_name'] = ' \t ' + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='file_name cannot be empty or whitespace'): + params.validate_business_rules() + + +def test_validate_business_rules_empty_experiment_name(valid_train_params_dict): + """Test validate_business_rules raises error when experiment_name is empty.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['experiment_name'] = '' + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='experiment_name cannot be empty or whitespace'): + params.validate_business_rules() + + +def test_validate_business_rules_whitespace_experiment_name(valid_train_params_dict): + """Test validate_business_rules raises error when experiment_name is whitespace.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['experiment_name'] = ' \n ' + params = TrainModelParams.from_dict(valid_train_params_dict) + + with pytest.raises(ValueError, match='experiment_name cannot be empty or whitespace'): + params.validate_business_rules() + + +def test_validate_business_rules_train_size_boundary_10(valid_train_params_dict): + """Test validate_business_rules accepts train_size = 10 (lower boundary).""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['train_size'] = 10 + params = TrainModelParams.from_dict(valid_train_params_dict) + + params.validate_business_rules() # Should not raise + + +def test_validate_business_rules_train_size_boundary_100(valid_train_params_dict): + """Test validate_business_rules accepts train_size = 100 (upper boundary).""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['train_size'] = 100 + params = TrainModelParams.from_dict(valid_train_params_dict) + + params.validate_business_rules() # Should not raise + + +def test_validate_business_rules_zero_lag_train(valid_train_params_dict): + """Test validate_business_rules accepts lag_train = 0.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['lag_train'] = 0 + params = TrainModelParams.from_dict(valid_train_params_dict) + + params.validate_business_rules() # Should not raise + + +def test_validate_business_rules_zero_lag_val(valid_train_params_dict): + """Test validate_business_rules accepts lag_val = 0.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['lag_val'] = 0 + params = TrainModelParams.from_dict(valid_train_params_dict) + + params.validate_business_rules() # Should not raise + + +def test_validate_business_rules_zero_window(valid_train_params_dict): + """Test validate_business_rules accepts window = 0.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['window'] = 0 + params = TrainModelParams.from_dict(valid_train_params_dict) + + params.validate_business_rules() # Should not raise + + +def test_validate_business_rules_empty_limits(valid_train_params_dict): + """Test validate_business_rules accepts empty low_lim and upp_lim.""" + from model_manager.utils.models.train_model_params import TrainModelParams + + valid_train_params_dict['low_lim'] = {} + valid_train_params_dict['upp_lim'] = {} + params = TrainModelParams.from_dict(valid_train_params_dict) + + params.validate_business_rules() # Should not raise