Skip to content
This repository was archived by the owner on May 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions shared/validation/user_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@

custom_status_common_config = {
"name_prefix": {"type": "string", "regex": r"^[\w\-\.]+$"},
# Note that "type" is a reserved word in Cerberus parser so use with caution as a
# key in the schema. See workaround at places that call this schema.
"type": {"type": "string", "allowed": ("project", "patch", "changes")},
"target": percent_type_or_auto,
"threshold": percent_type,
Expand Down Expand Up @@ -119,8 +121,17 @@

flags_rule_basic_properties = {
"statuses": {
"type": "list",
"schema": {"type": "dict", "schema": flag_status_attributes},
# Use "anyof" to avoid error in Cerberus when child has a key named "type". More background at https://github.com/codecov/shared/pull/588
"anyof": [
{
"type": "list",
"schema": {
"type": "dict",
"schema": flag_status_attributes,
"allow_unknown": False,
},
},
]
},
"carryforward_mode": {
"type": "string",
Expand All @@ -134,8 +145,17 @@

component_rule_basic_properties = {
"statuses": {
"type": "list",
"schema": {"type": "dict", "schema": component_status_attributes},
# Use "anyof" to avoid error in Cerberus when child has a key named "type". More background at https://github.com/codecov/shared/pull/588
"anyof": [
{
"type": "list",
"schema": {
"type": "dict",
"schema": component_status_attributes,
"allow_unknown": False,
},
}
],
},
"flag_regexes": {"type": "list", "schema": {"type": "string"}},
"paths": path_list_structure,
Expand Down
136 changes: 129 additions & 7 deletions tests/unit/validation/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,61 @@ def test_many_flags_validation(self):
}
assert validate_yaml(user_input) == expected_result

def test_flags_schema_error_for_key_named_type(self):
user_input = {
"flag_management": {
"default_rules": {
"carryforward": False,
"statuses": [{"name_prefix": "aaa", "type": "patch"}],
},
"individual_flags": [
{
"name": "cawcaw",
"paths": ["banana"],
"after_n_builds": 3,
# expected "statuses" to be a list but is an object instead & that
# object contains a key named "type" (reserved word)
"statuses": {"type": "patch"},
}
],
},
}

with pytest.raises(Exception) as exp:
validate_yaml(user_input)

err = exp.value
assert err is not None, "validate_yaml didn't raise anything"
assert err.error_location == [
"flag_management",
"individual_flags",
0,
"statuses",
]
assert err.error_message == "no definitions validate"
assert err.error_dict == {
"flag_management": [
{
"individual_flags": [
{
0: [
{
"statuses": [
"no definitions validate",
{
"anyof definition 0": [
"must be of list type"
]
},
]
}
],
}
]
}
]
}

def test_validate_bot_none(self):
user_input = {"codecov": {"bot": None}}
expected_result = {"codecov": {"bot": None}}
Expand Down Expand Up @@ -797,14 +852,30 @@ def test_yaml_with_flag_management_statuses_with_flags(self):
}
with pytest.raises(InvalidYamlException) as exc:
validate_yaml(user_input)
assert exc.value.error_location == [
"flag_management",
"default_rules",
"statuses",
0,
"flags",
assert exc.value.error_location == [
"flag_management",
"default_rules",
"statuses",
]
assert exc.value.error_message == "no definitions validate"
assert exc.value.error_dict == {
"flag_management": [
{
"default_rules": [
{
"statuses": [
"no definitions validate",
{
"anyof definition 0": [
{0: [{"flags": ["unknown field"]}]}
]
},
]
}
]
}
]
assert exc.value.error_message == "extra keys not allowed"
}

def test_github_checks(self):
user_input = {"github_checks": True}
Expand Down Expand Up @@ -1331,6 +1402,57 @@ def test_components_schema_error():
}


def test_components_schema_error_for_key_named_type():
user_input = {
"component_management": {
"default_rules": {
"flag_regexes": ["global_flag"],
},
"individual_components": [
{
"name": "fruits",
"component_id": "app_0",
"flag_regexes": ["fruit_.*", "^specific_flag$"],
"paths": ["src/.*"],
# expected "statuses" to be a list but is an object instead & that
# object contains a key named "type" (reserved word)
"statuses": {"type": "patch", "name_prefix": "co", "target": 90},
}
],
}
}

with pytest.raises(Exception) as exp:
validate_yaml(user_input)
err = exp.value
assert err is not None, "validate_yaml didn't raise anything"
assert err.error_location == [
"component_management",
"individual_components",
0,
"statuses",
]
assert err.error_message == "no definitions validate"
assert err.error_dict == {
"component_management": [
{
"individual_components": [
{
0: [
{
"statuses": [
"no definitions validate",
{"anyof definition 0": ["must be of list type"]},
]
}
],
}
]
}
]
}


def test_removed_code_behavior_config_valid():
user_input = {
"coverage": {
Expand Down