diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index ea1a739c8a..fcd4f9a334 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -188,6 +188,10 @@ class SurveyJobTemplateMixin(models.Model): runtime_extra_vars.pop(variable_key) if default is not None: + # do not add variables that contain an empty string, are not required and are nor present in extra_vars + if default == '' and not survey_element.get('required') and variable_key not in runtime_extra_vars: + continue + decrypted_default = default if survey_element['type'] == "password" and isinstance(decrypted_default, str) and decrypted_default.startswith('$encrypted$'): decrypted_default = decrypt_value(get_encryption_key('value', pk=None), decrypted_default) diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py index 8ac8bcd227..6d9eb5dec9 100644 --- a/awx/main/tests/unit/models/test_survey_models.py +++ b/awx/main/tests/unit/models/test_survey_models.py @@ -176,22 +176,22 @@ def test_display_survey_spec_encrypts_default(survey_spec_factory): @pytest.mark.survey @pytest.mark.parametrize( - "question_type,default,min,max,expect_use,expect_value", + "question_type,default,min,max,expect_valid,expect_use,expect_value", [ - ("text", "", 0, 0, True, ''), # default used - ("text", "", 1, 0, False, 'N/A'), # value less than min length - ("password", "", 1, 0, False, 'N/A'), # passwords behave the same as text - ("multiplechoice", "", 0, 0, False, 'N/A'), # historical bug - ("multiplechoice", "zeb", 0, 0, False, 'N/A'), # zeb not in choices - ("multiplechoice", "coffee", 0, 0, True, 'coffee'), - ("multiselect", None, 0, 0, False, 'N/A'), # NOTE: Behavior is arguable, value of [] may be prefered - ("multiselect", "", 0, 0, False, 'N/A'), - ("multiselect", ["zeb"], 0, 0, False, 'N/A'), - ("multiselect", ["milk"], 0, 0, True, ["milk"]), - ("multiselect", ["orange\nmilk"], 0, 0, False, 'N/A'), # historical bug + ("text", "", 0, 0, True, False, 'N/A'), # valid but empty default not sent for optional question + ("text", "", 1, 0, False, False, 'N/A'), # value less than min length + ("password", "", 1, 0, False, False, 'N/A'), # passwords behave the same as text + ("multiplechoice", "", 0, 0, False, False, 'N/A'), # historical bug + ("multiplechoice", "zeb", 0, 0, False, False, 'N/A'), # zeb not in choices + ("multiplechoice", "coffee", 0, 0, True, True, 'coffee'), + ("multiselect", None, 0, 0, False, False, 'N/A'), # NOTE: Behavior is arguable, value of [] may be prefered + ("multiselect", "", 0, 0, False, False, 'N/A'), + ("multiselect", ["zeb"], 0, 0, False, False, 'N/A'), + ("multiselect", ["milk"], 0, 0, True, True, ["milk"]), + ("multiselect", ["orange\nmilk"], 0, 0, False, False, 'N/A'), # historical bug ], ) -def test_optional_survey_question_defaults(survey_spec_factory, question_type, default, min, max, expect_use, expect_value): +def test_optional_survey_question_defaults(survey_spec_factory, question_type, default, min, max, expect_valid, expect_use, expect_value): spec = survey_spec_factory( [ { @@ -208,7 +208,7 @@ def test_optional_survey_question_defaults(survey_spec_factory, question_type, d jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True) defaulted_extra_vars = jt._update_unified_job_kwargs({}, {}) element = spec['spec'][0] - if expect_use: + if expect_valid: assert jt._survey_element_validation(element, {element['variable']: element['default']}) == [] else: assert jt._survey_element_validation(element, {element['variable']: element['default']}) @@ -218,6 +218,28 @@ def test_optional_survey_question_defaults(survey_spec_factory, question_type, d assert 'c' not in defaulted_extra_vars['extra_vars'] +@pytest.mark.survey +def test_optional_survey_empty_default_with_runtime_extra_var(survey_spec_factory): + """When a user explicitly provides an empty string at runtime for an optional + survey question, the variable should still be included in extra_vars.""" + spec = survey_spec_factory( + [ + { + "required": False, + "default": "", + "choices": "", + "variable": "c", + "min": 0, + "max": 0, + "type": "text", + }, + ] + ) + jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True) + defaulted_extra_vars = jt._update_unified_job_kwargs({}, {'extra_vars': json.dumps({'c': ''})}) + assert json.loads(defaulted_extra_vars['extra_vars'])['c'] == '' + + @pytest.mark.survey @pytest.mark.parametrize( "question_type,default,maxlen,kwargs,expected",