diff --git a/.changelog/4373.yml b/.changelog/4373.yml
new file mode 100644
index 0000000000..132d4a26d1
--- /dev/null
+++ b/.changelog/4373.yml
@@ -0,0 +1,4 @@
+changes:
+ - description: Added RM107 to the new validation format. The validation checks if given readme is consist %%FILL HERE%% substring or not.
+ type: internal
+pr_number: 4373
diff --git a/demisto_sdk/commands/validate/sdk_validation_config.toml b/demisto_sdk/commands/validate/sdk_validation_config.toml
index b787e20cd3..ce3b9e6067 100644
--- a/demisto_sdk/commands/validate/sdk_validation_config.toml
+++ b/demisto_sdk/commands/validate/sdk_validation_config.toml
@@ -33,7 +33,7 @@ select = [
"DO100", "DO101", "DO102", "DO103", "DO104", "DO105", "DO106",
"DS100", "DS101", "DS105", "DS106", "DS107",
"SC100", "SC105", "SC106", "SC109",
- "RM101", "RN103", "RM104", "RM105", "RM106", "RM109", "RM113", "RM114",
+ "RM101", "RN103", "RM104", "RM105", "RM106", "RM107", "RM109", "RM113", "RM114",
"CL100",
"GF100", "GF101", "GF102",
"IF100", "IF101", "IF102", "IF103", "IF104", "IF105", "IF106", "IF116",
@@ -59,7 +59,7 @@ select = [
"PA121", "PA123", "PA125", "PA127", "PA130", "PA131", "PA132",
"DO100", "DO101", "DO102", "DO103", "DO104",
"SC100", "SC105", "SC106", "SC109",
- "RM104", "RM105", "RM113", "RM114",
+ "RM104", "RM105", "RM107", "RM113", "RM114",
"CL100",
"GF100", "GF101",
"IF100", "IF101", "IF102", "IF103", "IF104", "IF105", "IF106", "IF116",
diff --git a/demisto_sdk/commands/validate/tests/RM_validators_test.py b/demisto_sdk/commands/validate/tests/RM_validators_test.py
index ff7754452a..d4cc2a0fe3 100644
--- a/demisto_sdk/commands/validate/tests/RM_validators_test.py
+++ b/demisto_sdk/commands/validate/tests/RM_validators_test.py
@@ -1,7 +1,6 @@
from pathlib import Path
import pytest
-
from demisto_sdk.commands.validate.tests.test_tools import (
REPO,
create_integration_object,
@@ -21,6 +20,9 @@
from demisto_sdk.commands.validate.validators.RM_validators.RM106_is_contain_demisto_word import (
IsContainDemistoWordValidator,
)
+from demisto_sdk.commands.validate.validators.RM_validators.RM107_is_template_sentence_in_readme import (
+ IsTemplateNotInReadmeValidator,
+)
from demisto_sdk.commands.validate.validators.RM_validators.RM108_is_integration_image_path_valid import (
IntegrationRelativeImagePathValidator,
)
@@ -43,26 +45,26 @@
"content_items, expected_number_of_failures, expected_msgs",
[
(
- [
- create_pack_object(readme_text="This is a valid readme."),
- create_pack_object(readme_text=""),
- ],
- 0,
- [],
+ [
+ create_pack_object(readme_text="This is a valid readme."),
+ create_pack_object(readme_text=""),
+ ],
+ 0,
+ [],
),
(
- [create_pack_object(readme_text="Invalid readme\nBSD\nCopyright")],
- 1,
- [
- "Invalid keywords related to Copyrights (BSD, MIT, Copyright, proprietary) were found in lines: 2, 3. Copyright section cannot be part of pack readme."
- ],
+ [create_pack_object(readme_text="Invalid readme\nBSD\nCopyright")],
+ 1,
+ [
+ "Invalid keywords related to Copyrights (BSD, MIT, Copyright, proprietary) were found in lines: 2, 3. Copyright section cannot be part of pack readme."
+ ],
),
],
)
def test_IsContainCopyRightSectionValidator_is_valid(
- content_items,
- expected_number_of_failures,
- expected_msgs,
+ content_items,
+ expected_number_of_failures,
+ expected_msgs,
):
"""
Given
@@ -92,48 +94,48 @@ def test_IsContainCopyRightSectionValidator_is_valid(
"content_items, expected_number_of_failures, expected_msgs",
[
(
- [
- create_pack_object(
- paths=["support"],
- values=["partner"],
- readme_text="This is a valid readme.",
- ), # valid readme with partner support
- create_pack_object(
- readme_text=""
- ), # empty readme but with no partner support and no playbooks
- create_pack_object(
- readme_text="This is valid readme", playbooks=1
- ), # should pass as it has a valid readme and playbooks
- ],
- 0,
- [],
+ [
+ create_pack_object(
+ paths=["support"],
+ values=["partner"],
+ readme_text="This is a valid readme.",
+ ), # valid readme with partner support
+ create_pack_object(
+ readme_text=""
+ ), # empty readme but with no partner support and no playbooks
+ create_pack_object(
+ readme_text="This is valid readme", playbooks=1
+ ), # should pass as it has a valid readme and playbooks
+ ],
+ 0,
+ [],
),
(
- [
- create_pack_object(
- paths=["support"], values=["partner"], readme_text=""
- ),
- ], # empty readme with partner support, not valid
- 1,
- [
- "Pack HelloWorld written by a partner or pack containing playbooks must have a full README.md file with pack information. Please refer to https://xsoar.pan.dev/docs/documentation/pack-docs#pack-readme for more information",
- ],
+ [
+ create_pack_object(
+ paths=["support"], values=["partner"], readme_text=""
+ ),
+ ], # empty readme with partner support, not valid
+ 1,
+ [
+ "Pack HelloWorld written by a partner or pack containing playbooks must have a full README.md file with pack information. Please refer to https://xsoar.pan.dev/docs/documentation/pack-docs#pack-readme for more information",
+ ],
),
(
- [
- create_pack_object(readme_text="", playbooks=1)
- ], # empty readme with playbooks, not valid
- 1,
- [
- "Pack HelloWorld written by a partner or pack containing playbooks must have a full README.md file with pack information. Please refer to https://xsoar.pan.dev/docs/documentation/pack-docs#pack-readme for more information"
- ],
+ [
+ create_pack_object(readme_text="", playbooks=1)
+ ], # empty readme with playbooks, not valid
+ 1,
+ [
+ "Pack HelloWorld written by a partner or pack containing playbooks must have a full README.md file with pack information. Please refer to https://xsoar.pan.dev/docs/documentation/pack-docs#pack-readme for more information"
+ ],
),
],
)
def test_empty_readme_validator(
- content_items,
- expected_number_of_failures,
- expected_msgs,
+ content_items,
+ expected_number_of_failures,
+ expected_msgs,
):
"""
Given:
@@ -167,36 +169,36 @@ def test_empty_readme_validator(
[
([create_integration_object()], 0),
(
- [
- create_integration_object(
- readme_content='
'
- )
- ],
- 1,
+ [
+ create_integration_object(
+ readme_content='
'
+ )
+ ],
+ 1,
),
(
- [
- create_script_object(
- readme_content='
'
- )
- ],
- 1,
+ [
+ create_script_object(
+ readme_content='
'
+ )
+ ],
+ 1,
),
(
- [
- create_pack_object(
- readme_text='
'
- )
- ],
- 1,
+ [
+ create_pack_object(
+ readme_text='
'
+ )
+ ],
+ 1,
),
(
- [
- create_playbook_object(
- readme_content='
'
- )
- ],
- 1,
+ [
+ create_playbook_object(
+ readme_content='
'
+ )
+ ],
+ 1,
),
],
)
@@ -232,58 +234,58 @@ def test_is_image_path_validator(content_items, expected_number_of_failures):
"content_items, is_file_exist, expected_number_of_failures, expected_msgs",
[
(
- [
- create_playbook_object(
- readme_content="This is a valid readme without any images.",
- pack_info={"name": "test1"},
- ),
- create_playbook_object(
- readme_content="This is a valid readme if this file exists ![example image](../doc_files/example.png)",
- pack_info={"name": "test1"},
- ),
- create_playbook_object(readme_content="", pack_info={"name": "test1"}),
- create_integration_object(
- readme_content="This is a valid readme without any images.",
- pack_info={"name": "test2"},
- ),
- create_integration_object(
- readme_content="This is a valid readme if this file exists ![example image](../doc_files/example.png)",
- pack_info={"name": "test2"},
- ),
- create_integration_object(
- readme_content="", pack_info={"name": "test2"}
- ),
- ],
- True,
- 0,
- [],
+ [
+ create_playbook_object(
+ readme_content="This is a valid readme without any images.",
+ pack_info={"name": "test1"},
+ ),
+ create_playbook_object(
+ readme_content="This is a valid readme if this file exists ![example image](../doc_files/example.png)",
+ pack_info={"name": "test1"},
+ ),
+ create_playbook_object(readme_content="", pack_info={"name": "test1"}),
+ create_integration_object(
+ readme_content="This is a valid readme without any images.",
+ pack_info={"name": "test2"},
+ ),
+ create_integration_object(
+ readme_content="This is a valid readme if this file exists ![example image](../doc_files/example.png)",
+ pack_info={"name": "test2"},
+ ),
+ create_integration_object(
+ readme_content="", pack_info={"name": "test2"}
+ ),
+ ],
+ True,
+ 0,
+ [],
),
(
- [
- create_playbook_object(
- readme_content="This is not a valid readme if this file doesn't exists ![example image](../doc_files/example.png), ",
- pack_info={"name": "test1"},
- ),
- create_integration_object(
- readme_content="This is not a valid readme if this file doesn't exists ![example image](../doc_files/example.png)",
- pack_info={"name": "test2"},
- ),
- ],
- False,
- 2,
- [
- "The following images do not exist: Packs/test1/doc_files/example.png",
- "The following images do not exist: Packs/test2/doc_files/example.png",
- ],
+ [
+ create_playbook_object(
+ readme_content="This is not a valid readme if this file doesn't exists ![example image](../doc_files/example.png), ",
+ pack_info={"name": "test1"},
+ ),
+ create_integration_object(
+ readme_content="This is not a valid readme if this file doesn't exists ![example image](../doc_files/example.png)",
+ pack_info={"name": "test2"},
+ ),
+ ],
+ False,
+ 2,
+ [
+ "The following images do not exist: Packs/test1/doc_files/example.png",
+ "The following images do not exist: Packs/test2/doc_files/example.png",
+ ],
),
],
)
def test_IsImageExistsInReadmeValidator_is_valid(
- mocker,
- content_items,
- is_file_exist,
- expected_number_of_failures,
- expected_msgs,
+ mocker,
+ content_items,
+ is_file_exist,
+ expected_number_of_failures,
+ expected_msgs,
):
mocker.patch.object(Path, "is_file", return_value=is_file_exist)
@@ -341,39 +343,39 @@ def test_IsPackReadmeNotEqualPackDescriptionValidator_valid():
"content_items, expected_number_of_failures, expected_msgs",
[
(
- [
- create_playbook_object(),
- create_playbook_object(),
- ],
- 1,
- [
- "The Playbook 'Detonate File - JoeSecurity V2' doesn't have a README file. Please add a README.md file in the content item's directory."
- ],
+ [
+ create_playbook_object(),
+ create_playbook_object(),
+ ],
+ 1,
+ [
+ "The Playbook 'Detonate File - JoeSecurity V2' doesn't have a README file. Please add a README.md file in the content item's directory."
+ ],
),
(
- [
- create_script_object(),
- create_script_object(),
- ],
- 1,
- [
- "The Script 'myScript' doesn't have a README file. Please add a README.md file in the content item's directory."
- ],
+ [
+ create_script_object(),
+ create_script_object(),
+ ],
+ 1,
+ [
+ "The Script 'myScript' doesn't have a README file. Please add a README.md file in the content item's directory."
+ ],
),
(
- [
- create_integration_object(),
- create_integration_object(),
- ],
- 1,
- [
- "The Integration 'TestIntegration' doesn't have a README file. Please add a README.md file in the content item's directory."
- ],
+ [
+ create_integration_object(),
+ create_integration_object(),
+ ],
+ 1,
+ [
+ "The Integration 'TestIntegration' doesn't have a README file. Please add a README.md file in the content item's directory."
+ ],
),
],
)
def test_IsReadmeExistsValidator_is_valid(
- content_items, expected_number_of_failures, expected_msgs
+ content_items, expected_number_of_failures, expected_msgs
):
"""
Given:
@@ -478,7 +480,7 @@ def test_ImagePathIntegrationValidator_is_valid_invalid_case():
content_items = [
create_integration_object(
readme_content=" Readme contains absolute path:\n 'Here is an image:\n"
- " ![Example Image](https://www.example.com/images/example_image.jpg)",
+ " ![Example Image](https://www.example.com/images/example_image.jpg)",
description_content="valid description ![Example Image](../../content/image.jpg)",
),
]
@@ -528,8 +530,8 @@ def test_ImagePathOnlyReadMeValidator_is_valid_invalid_case():
content_items = [
create_integration_object(
readme_content=" Readme contains absolute path:\n 'Here is an image:\n"
- " ![Example Image](https://www.example.com/images/example_image.jpg)"
- " ![Example Image](../../content/image.jpg)",
+ " ![Example Image](https://www.example.com/images/example_image.jpg)"
+ " ![Example Image](../../content/image.jpg)",
),
]
expected = (
@@ -542,3 +544,69 @@ def test_ImagePathOnlyReadMeValidator_is_valid_invalid_case():
result = ReadmeRelativeImagePathValidator().is_valid(content_items)
assert result[0].message == expected
+
+
+def test_VerifyTemplateNotInReadmeValidator_valid_case(repo):
+ """
+ Given
+ content_items.
+ - Integration with readme that contains %%FILL HERE%% template substring.
+ - Script with readme that contains %%FILL HERE%% template substring.
+ - Playbook with readme that contains %%FILL HERE%% template substring.
+ - Pack with readme that contains %%FILL HERE%% template substring.
+ When
+ - Calling the IsTemplateNotInReadmeValidator is_valid function.
+
+ Then
+ - Make sure that the pack content_items return False, there is %%FILL HERE%% in the readme file.
+ """
+ content_items = [
+ create_integration_object(
+ readme_content="This checks if we have the sentence %%FILL HERE%% in the README.",
+ ),
+ create_script_object(
+ readme_content="This checks if we have the sentence %%FILL HERE%% in the README.",
+ ),
+ create_playbook_object(
+ readme_content="This checks if we have the sentence %%FILL HERE%% in the README.",
+ ),
+ create_pack_object(
+ readme_text="This checks if we have the sentence %%FILL HERE%% in the README.",
+ )
+ ]
+ assert not IsTemplateNotInReadmeValidator().is_valid(content_items)
+
+
+def test_VerifyTemplateNotInReadmeValidator_invalid_case(repo):
+ """
+ Given
+ content_items.
+ - Integration with readme without %%FILL HERE%% template substring.
+ - Script with readme without %%FILL HERE%% template substring.
+ - Playbook with readme without %%FILL HERE%% template substring.
+ - Pack with readme without %%FILL HERE%% template substring.
+ When
+ - Calling the IsTemplateNotInReadmeValidator is_valid function.
+
+ Then
+ - Make sure that the pack content_items return True, there is not %%FILL HERE%% in the readme file.
+ """
+ content_items = [
+ create_integration_object(
+ readme_content="The specific template substring is not in the README.",
+ ),
+ create_script_object(
+ readme_content="The specific template substring is not in the README.",
+ ),
+ create_playbook_object(
+ readme_content="The specific template substring is not in the README.",
+ ),
+ create_pack_object(
+ readme_text="The specific template substring is not in the README.",
+ )
+ ]
+ expected_error_message = "The template '%%FILL HERE%%' does not exist in the README content."
+ validator_results = IsTemplateNotInReadmeValidator().is_valid(content_items)
+ assert validator_results
+ for validator_result in validator_results:
+ assert validator_result.message == expected_error_message
diff --git a/demisto_sdk/commands/validate/validators/RM_validators/RM107_is_template_sentence_in_readme.py b/demisto_sdk/commands/validate/validators/RM_validators/RM107_is_template_sentence_in_readme.py
new file mode 100644
index 0000000000..1ea63d7a30
--- /dev/null
+++ b/demisto_sdk/commands/validate/validators/RM_validators/RM107_is_template_sentence_in_readme.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+from typing import Iterable, List, Union
+from demisto_sdk.commands.content_graph.objects.integration import Integration
+from demisto_sdk.commands.content_graph.objects.script import Script
+from demisto_sdk.commands.content_graph.objects.playbook import Playbook
+from demisto_sdk.commands.content_graph.objects.pack import Pack
+from demisto_sdk.commands.validate.validators.base_validator import (
+ BaseValidator,
+ ValidationResult,
+)
+
+from demisto_sdk.commands.common.tools import search_substrings_by_line
+
+ContentTypes = Union[Integration, Script, Playbook, Pack]
+
+
+class IsTemplateNotInReadmeValidator(BaseValidator[ContentTypes]):
+ error_code = "RM107"
+ description = "Checks if there are the generic sentence '%%FILL HERE%%' in the README content."
+ rationale = "Checks if there are the generic sentence '%%FILL HERE%%' in the README content."
+ error_message = "The template '%%FILL HERE%%' does not exist in the README content."
+ related_field = "readme.file_content"
+ is_auto_fixable = False
+
+ def is_valid(self, content_items: Iterable[ContentTypes]) -> List[ValidationResult]:
+ """
+ Checks if there are the generic sentence '%%FILL HERE%%' in the README content.
+
+ Return:
+ True if '%%FILL HERE%%' does not exist in the README content, and False if it does.
+ """
+
+ return [
+ ValidationResult(
+ validator=self,
+ message=self.error_message,
+ content_object=content_item,
+ )
+ for content_item in content_items
+ if (
+ invalid_lines := not search_substrings_by_line(
+ phrases_to_search=["%%FILL HERE%%"],
+ text=content_item.readme.file_content
+ )
+ )
+ ]