From 978e77c05b683512c0fad53b5d3aac97dd7f2f63 Mon Sep 17 00:00:00 2001 From: Fabien Letort Date: Thu, 14 Nov 2024 10:12:05 +0100 Subject: [PATCH] gh-041: Add SuperLinter Workflow --- .editorconfig | 10 + .github/linters/.isort.cfg | 3 + .github/linters/.markdown-lint.yml | 23 ++ .github/linters/.yaml-lint.yml | 10 + .github/workflows/linter.yml | 37 +++ .github/workflows/tags.yml | 6 +- .github/workflows/test.yml | 42 ++-- README.md | 60 ++--- action.yml | 36 ++- action/main.py | 94 +++++--- action/parser.py | 108 +++++---- action/test/main_test.py | 222 ++++++++++-------- action/test/parser_test.py | 346 ++++++++++++++++------------- entrypoint.py | 56 +++-- test/data/github.json | 231 ------------------- test/data/job.json | 3 - test/data/matrix.json | 0 test/data/runner.json | 9 - test/data/strategy.json | 6 - test/entrypoint_test.py | 290 ++++++++++++------------ test/install_bats.sh | 6 +- test/test_action.bats | 6 +- test/url_data.yml | 2 +- 23 files changed, 787 insertions(+), 819 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/linters/.isort.cfg create mode 100644 .github/linters/.markdown-lint.yml create mode 100644 .github/linters/.yaml-lint.yml create mode 100644 .github/workflows/linter.yml delete mode 100644 test/data/github.json delete mode 100644 test/data/job.json delete mode 100644 test/data/matrix.json delete mode 100644 test/data/runner.json delete mode 100644 test/data/strategy.json mode change 100644 => 100755 test/test_action.bats diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..32d7772 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# top-most EditorConfig file + + +[*.sh] +indent_style = space +indent_size = 2 + +[*.bats] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/linters/.isort.cfg b/.github/linters/.isort.cfg new file mode 100644 index 0000000..3088e9f --- /dev/null +++ b/.github/linters/.isort.cfg @@ -0,0 +1,3 @@ +[settings] +known_third_party=parameterized +known_first_party=entrypoint,action \ No newline at end of file diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml new file mode 100644 index 0000000..3321ef7 --- /dev/null +++ b/.github/linters/.markdown-lint.yml @@ -0,0 +1,23 @@ +# Unordered list style +MD004: + style: dash + +# Ordered list item prefix +MD029: + style: one + +# Spaces after list markers +MD030: + ul_single: 1 + ol_single: 1 + ul_multi: 1 + ol_multi: 1 + +# Code block style +MD046: + style: fenced + +# MD013/line-length - Line length +MD013: + line_length: 80 + tables: false diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml new file mode 100644 index 0000000..c975a33 --- /dev/null +++ b/.github/linters/.yaml-lint.yml @@ -0,0 +1,10 @@ +rules: + document-end: disable + document-start: + level: warning + present: false + line-length: + level: warning + max: 80 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..f386d6b --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,37 @@ +name: Lint Codebase + +on: + pull_request: + branches: + - main + - develop + push: + branches: + - main + - develop + +permissions: + contents: read + packages: read + statuses: write + +jobs: + lint: + name: Lint Codebase + runs-on: ubuntu-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Lint Codebase + id: super-linter + uses: super-linter/super-linter/slim@v7 + env: + DEFAULT_BRANCH: main + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VALIDATE_ALL_CODEBASE: true + VALIDATE_JSCPD: false diff --git a/.github/workflows/tags.yml b/.github/workflows/tags.yml index b9a2653..29536f2 100644 --- a/.github/workflows/tags.yml +++ b/.github/workflows/tags.yml @@ -1,12 +1,14 @@ -name: 'Tags' +name: "Tags" on: push: tags: - v[0-9]+.[0-9]+.[0-9]+ +permissions: + contents: read jobs: tags: runs-on: ubuntu-latest - name: 'Additional Tags' + name: "Additional Tags" permissions: contents: write actions: write diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8bbc32..28024a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: 'Continuous Testing' +name: "Continuous Testing" on: push: @@ -54,17 +54,19 @@ jobs: hide_complexity: true indicators: true output: both - thresholds: '60 80' - + thresholds: "60 80" + # As irongut/CodeCoverageSummary does not support file by file mode, add it # See https://github.com/irongut/CodeCoverageSummary/issues/236 - name: Write Coverage to Job Summary run: | - cat code-coverage-results.md > my-code-coverage-results.md - echo "\`\`\`" >> my-code-coverage-results.md - coverage report >> my-code-coverage-results.md - echo "\`\`\`" >> my-code-coverage-results.md - cat my-code-coverage-results.md >> $GITHUB_STEP_SUMMARY + { + cat code-coverage-results.md + echo "\`\`\`" + coverage report + echo "\`\`\`" + } > my-code-coverage-results.md + echo my-code-coverage-results.md >> "$GITHUB_STEP_SUMMARY" - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 @@ -72,7 +74,7 @@ jobs: with: recreate: true path: my-code-coverage-results.md - + - name: Testspace client install & config id: testspace_init if: always() && hashFiles('report.xml') != '' @@ -87,14 +89,14 @@ jobs: REGEX="(https://[a-z0-9]+.testspace.com/spaces/[0-9]+/result_sets/[0-9]+)" [[ $output =~ $REGEX ]] result_set_link="${BASH_REMATCH[1]}" - echo "[TestSpace Test Result set]($result_set_link)" >> $GITHUB_STEP_SUMMARY + echo "[TestSpace Test Result set]($result_set_link)" >> "$GITHUB_STEP_SUMMARY" - name: Publish Test Results in GitHub uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: files: report.xml - check_name: 'Unit Test Results' + check_name: "Unit Test Results" test-action-matrix: name: Test With Matrix @@ -105,7 +107,7 @@ jobs: strategy: matrix: test: [1] - env: + env: VAR_TEST1_VALUE: mytest VAR_TEST2_VALUE: isfunny FILE_KEY_1_VALUE: myfile @@ -164,12 +166,12 @@ jobs: - name: Extract branch name shell: bash - run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_OUTPUT" id: extract_branch - name: Test Local Action id: test-action - uses: ./ + uses: ./ with: #keep_template: true data_file: test-data.yml @@ -202,7 +204,7 @@ jobs: if: always() with: files: report.xml - check_name: 'Integ Test Results' + check_name: "Integ Test Results" test-action-no-matrix: name: Test Without Matrix @@ -210,7 +212,7 @@ jobs: permissions: checks: write pull-requests: write - env: + env: VAR_TEST1_VALUE: mytest VAR_TEST2_VALUE: isfunny FILE_KEY_1_VALUE: myfile @@ -263,7 +265,7 @@ jobs: - name: Test Local Action id: test-action - uses: ./ + uses: ./ with: #keep_template: true data_file: test-data.yml @@ -294,8 +296,4 @@ jobs: if: always() with: files: report.xml - check_name: 'Integ Test Results' - - - - + check_name: "Integ Test Results" diff --git a/README.md b/README.md index 221dfac..bc422f0 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,27 @@ # jinja2-template-action -Use Jinja2 template engine on multipe files as a GitHub action. + +Use Jinja2 template engine on multipe files as a GitHub Action. [![Continuous Testing](https://github.com/fletort/jinja2-template-action/actions/workflows/test.yml/badge.svg)](https://github.com/fletort/jinja2-template-action/actions/workflows/test.yml) [![Coverage Status](https://coveralls.io/repos/github/fletort/jinja2-template-action/badge.svg?branch=main)](https://coveralls.io/github/fletort/jinja2-template-action?branch=main) [![Testspace tests count](https://img.shields.io/testspace/total/fletort/fletort%3Ajinja2-template-action/main)](https://fletort.testspace.com/projects/68162/spaces) -This is a very simple version of the action, that is ok for my first need. +This is a very simple version of the action, that is OK for my first need. Futur enhancement will come. -The actual "simple" version transform all local j2 files (recursively) ('*.j2') +The actual "simple" version transform all local j2 files (recursively) ('\*.j2') with the jinja2 library. -The new file name is the same filename without the j2 extension. +The new filename is the same filename without the j2 extension. For exemple, `README.md.j2` becomes `README.md`. -It this version, it can only be used with environment variable, see my [test template file](./test/template.j2). +It this version, it can only be used with environment variable, +see my [test template file](./test/template.j2). -Environement variable as used as with the jinja2 cli, with a _kind_ of `environ` method : -``` +Environement variable as used as with the jinja2 cli, +with a _kind_ of `environ` method : + +```file {{ environ('TEST') }} ``` @@ -32,11 +36,14 @@ Environement variable as used as with the jinja2 cli, with a _kind_ of `environ` ``` Environment variables are used as with jinja2 cli: -``` + +```file {{ environ('TEST') }} ``` -Or as other contextual github information: -``` + +Or as other contextual GitHub information: + +```file {{ env.TEST }} ``` @@ -59,7 +66,7 @@ Or as other contextual github information: data_format: json # can be detected automatically (see below) ``` -Possible data type are: env, yaml, json, ini +Possible data type are: `env`, `yaml`, `json`, `ini` #### 1. env file @@ -76,7 +83,7 @@ SMART=yoyo MY_VAR=True ``` -#### 3. yaml +#### 3. YAML ```file --- @@ -99,34 +106,35 @@ EXEMPLE: #### Related Jinja Template For previous INI, YAML, JSON examples, jinja template will be: -``` + +```file {{ EXEMPLE.SMART }} {{ EXEMPLE.MY_VAR }} ``` For previous ENV example, jinja template will be: -``` + +```file {{ EXEMPLE_SMART }} {{ EXEMPLE_MY_VAR }} ``` - -### Using Workflow Github contextual Information +### Using Workflow GitHub contextual Information Some of the [contextual information about workflow runs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs) are available inside your jinja Template: -- github -- job -- runner -- strategy -- matrix +- `github` +- `job` +- `runner` +- `strategy` +- `matrix` As part of an expression, you can access context information using one of two syntaxes. Index syntax: `github['sha']` Property dereference syntax: `github.sha` -``` +```file {{ github.repository }} {{ job.status }} {{ runner.os }} @@ -134,12 +142,13 @@ Property dereference syntax: `github.sha` {{ matrix.your_matrix_variable_name }} ``` -Note: All strategy information key contains dashes that must me marked as underscore in jinja expression: `${{ strategy.job-index }}` becomes `{{ strategy.job_index }}`. - +Note: All strategy information key contains dashes that must me marked as +underscore in jinja expression: `${{ strategy.job-index }}` becomes +`{{ strategy.job_index }}`. ### Actions inputs - + | Name | Description | Default | | ---- | ----------- | ------- | | `variables` | Variable to substitute in the jinja templates. Must be Key, value pairs in .env file format (key=value). | "" | @@ -148,6 +157,7 @@ Note: All strategy information key contains dashes that must me marked as unders | `data_format` | Format of the `data_file`. Can be `env`, `ini`, `yaml`, `json` or `automatic` (for automatic detection). The automatic detection is based on the extension then on the content. | `automatic` | | `data_url` | URL Link contening inputs variable for the jinja template. | "" | | `data_url_format` | Format of the `data_url`. Can be `env`, `ini`, `yaml`, `json` or `automatic` (for automatic detection). The automatic detection is based on the http header content-type then on the content itself. | `automatic` | + ## Code Quality diff --git a/action.yml b/action.yml index d54bc77..353cd4d 100644 --- a/action.yml +++ b/action.yml @@ -1,37 +1,31 @@ -name: 'Jinja2 Engine Repo' -description: 'Use the Jinja2 templating engine on multiple files' -author: 'fletort' +name: "Jinja2 Engine Repo" +description: "Use the Jinja2 templating engine on multiple files" +author: "fletort" branding: - icon: 'sliders' - color: 'red' + icon: "sliders" + color: "red" inputs: variables: - description: - 'Key, value pairs in .env file format (key=value)' + description: "Key, value pairs in .env file format (key=value)" required: false - default: '' + default: "" keep_template: - description: - 'Put to `true` to keep original template file.' + description: "Put to `true` to keep original template file." default: false data_file: - description: - 'Source file contening inputs variable for the jinja template.' - default: '' + description: "Source file contening inputs variable for the jinja template." + default: "" data_format: - description: - 'Format of the `data_file`. Can be `env`, `ini`, `yaml`, `json` or `automatic` (for automatic detection). The automatic detction is based on the extension then on the content.' + description: "Format of the `data_file`. Can be `env`, `ini`, `yaml`, `json` or `automatic` (for automatic detection). The automatic detction is based on the extension then on the content." default: automatic data_url: - description: - 'Link to a file contening inputs variable for the jinja template.' - default: '' + description: "Link to a file contening inputs variable for the jinja template." + default: "" data_url_format: - description: - 'Format of the `url_file`. Can be `env`, `ini`, `yaml`, `json` or `automatic` (for automatic detection). The automatic detction is based on the content-type http header then on the content.' + description: "Format of the `url_file`. Can be `env`, `ini`, `yaml`, `json` or `automatic` (for automatic detection). The automatic detction is based on the content-type http header then on the content." default: automatic runs: - using: 'composite' + using: "composite" steps: - name: "Manage Dynamic Template" shell: bash diff --git a/action/main.py b/action/main.py index 57252a0..3d93422 100644 --- a/action/main.py +++ b/action/main.py @@ -1,73 +1,95 @@ -import os +"""Main file of the jinja2-template-action action.""" + import json -from jinja2 import Template, Environment, FileSystemLoader +import os + +from jinja2 import Environment, FileSystemLoader + from .parser import FileParser, UrlParser + class Main: - def __init__(self, extensions=('.j2'), basepath='./', keep_template=False): + """Main class of the jinja2-template-action""" + + def __init__(self, extensions=(".j2"), basepath="./", keep_template=False): self.ext = extensions self.basepath = basepath self.keep_template = keep_template - self.env = Environment( - loader=FileSystemLoader(self.basepath) - ) + self.env = Environment(loader=FileSystemLoader(self.basepath)) self.data = {} - # Keep the environ method in template as whe have in the - #jinja2 cli in the first version of this action - self.env.globals["environ"] = lambda key: os.environ.get(key) + # Keep the environ method in template as whe have in the + # jinja2 cli in the first version of this action + self.env.globals["environ"] = os.environ.get # Also add env variable in a classic way env.VAR_NAME self.data["env"] = dict(os.environ) - def addVariables(self, variables): + def add_variables(self, variables): + """ + Add Variables in the jinja2 context + Parameters: + variables (str): Envrionement Variable list, one declaration by line + with a = between the key and the value + """ for variable in variables.split("\n"): clean_variable = bytes(variable.strip(), "utf-8").decode("unicode_escape") if clean_variable != "": name, value = clean_variable.split("=", 1) self.data.update({name: value}) - def addJsonSection(self, sectionName, jsonContent): - ''' + def add_json_section(self, section_name, json_content): + """ Add Json Data in a given section to the Data available to the template engine Parameters: - sectionName (str): Name of the added section. The jsconContent will be encapsuled in this key. + sectionName (str): Name of the added section. The jsconContent will be encapsuled + in this key. jsonContent (str/dict): Json content added in the key defined by sectionName - ''' - if isinstance(jsonContent, str): - data = json.loads(jsonContent) - elif isinstance(jsonContent, dict): - data = jsonContent + """ + if isinstance(json_content, str): + data = json.loads(json_content) + elif isinstance(json_content, dict): + data = json_content else: - raise Exception(f"Unknown type for jsonContent: {type(jsonContent)}") - - # protect again key contening dashes (it is the case in the keys of strategy context for example) - problematic_keys = [key for key in data.keys() if '-' in key] + raise ValueError(f"Unknown type for jsonContent: {type(json_content)}") + + # protect again key contening dashes (it is the case in the keys of strategy + # context for example) + problematic_keys = [key for key in data.keys() if "-" in key] for problematic_key in problematic_keys: new_key = problematic_key.replace("-", "_") data[new_key] = data.pop(problematic_key) - - self.data[sectionName] = data + self.data[section_name] = data - def addDataFile(self, file_path, file_format=None): + def add_data_file(self, file_path, file_format=None): + """ + Add Variable from a file to jinja2 context. + """ parser = FileParser(file_path, file_format) content = parser.parse() self.data.update(content) - def addDataUrl(self, url, data_format=None): + def add_data_url(self, url, data_format=None): + """ + Add Variable from a url to jinja2 context. + """ parser = UrlParser(url, data_format) content = parser.parse() self.data.update(content) - - def renderFile(self, filePath): - with open(f"{filePath}".rsplit(".", 1)[0], 'w') as out: - out.write(self.env.get_template( f"{filePath}").render(self.data)) + + def render_file(self, file_path): + """ + Render One File with saved jinja2 context. + """ + with open(f"{file_path}".rsplit(".", 1)[0], "w", encoding="utf-8") as out: + out.write(self.env.get_template(f"{file_path}").render(self.data)) out.flush() if not self.keep_template: - os.remove(f"{filePath}") + os.remove(f"{file_path}") - def renderAll(self): - for path, dirc, files in os.walk(self.basepath): + def render_all(self): + """ + Render All File with saved jinja2 context. + """ + for path, _, files in os.walk(self.basepath): for name in files: if name.endswith(self.ext): - self.renderFile(f"{path}/{name}") - - + self.render_file(f"{path}/{name}") diff --git a/action/parser.py b/action/parser.py index 1d9ace8..344f27d 100644 --- a/action/parser.py +++ b/action/parser.py @@ -1,49 +1,64 @@ -import os +""" +Parser Module +""" + +import configparser import json -import yaml import urllib.request -import configparser -from pathlib import Path from abc import ABC, abstractmethod +from pathlib import Path + +import yaml class Parser(ABC): + """Abstract Parser Base Class""" - def __init__(self, format=None): - if format and format not in self.FORMATS.keys(): - raise ValueError(f"specified format is unknown. Supported format are: {self.FORMATS.keys()}") - self.format = format + def __init__(self, parser_format=None): + if parser_format and parser_format not in self.FORMATS: + raise ValueError( + "specified format is unknown." + f"Supported format are: {self.FORMATS.keys()}" + ) + self.format = parser_format + self.content = "" @abstractmethod def load(self): - pass + """Load the content to Parse (must be impletend in child class)""" def parse(self): + """Load and Parse the content""" self.content = self.load() - if self.format and self.format not in self.FORMATS.keys(): - raise ValueError(f"specified format is unknown. Supported format are: {self.FORMATS.keys()}") - + if self.format and self.format not in self.FORMATS: + raise ValueError( + "specified format is unknown." + f"Supported format are: {self.FORMATS.keys()}" + ) + if self.format: content = getattr(FileParser, self.FORMATS[self.format])(self.content) else: content = self._parse_generic(self.content) - + return content - + @staticmethod def _parse_ini(content): config_object = configparser.ConfigParser() config_object.optionxform = str config_object.read_string(content) - output_dict={s:dict(config_object.items(s)) for s in config_object.sections()} + output_dict = { + s: dict(config_object.items(s)) for s in config_object.sections() + } return output_dict - + @staticmethod - def _parse_json( content): + def _parse_json(content): return json.loads(content) @staticmethod - def _parse_yaml( content): + def _parse_yaml(content): return yaml.safe_load(content) @staticmethod @@ -59,73 +74,76 @@ def _parse_env(content): @staticmethod def _parse_generic(content): try: - return ('ini',FileParser._parse_ini(content)) + return ("ini", Parser._parse_ini(content)) except configparser.Error: pass try: - return ('json',FileParser._parse_json(content)) + return ("json", Parser._parse_json(content)) except json.JSONDecodeError: pass try: - return ('env',FileParser._parse_env(content)) + return ("env", Parser._parse_env(content)) except ValueError: pass try: - return ('yaml',FileParser._parse_yaml(content)) + return ("yaml", Parser._parse_yaml(content)) except yaml.YAMLError: pass - raise ValueError(f"File format is not automatically recognized") + raise ValueError("File format is not automatically recognized") FORMATS = { - 'ini': '_parse_ini', - 'json': '_parse_json', - 'yml': '_parse_yaml', - 'yaml': '_parse_yaml', - 'env': '_parse_env' + "ini": "_parse_ini", + "json": "_parse_json", + "yml": "_parse_yaml", + "yaml": "_parse_yaml", + "env": "_parse_env", } + class UrlParser(Parser): + """Parser dedicated to Url Content""" + def __init__(self, url, waited_format=None): self.url = url super().__init__(waited_format) - + def load(self): with urllib.request.urlopen(self.url) as remote_content: self.content = remote_content.read() - content_type = remote_content.getheader('content-type') - if (self.format == None) and (content_type in UrlParser.CONTENT_TYPE.keys()): + content_type = remote_content.getheader("content-type") + if (self.format is None) and (content_type in UrlParser.CONTENT_TYPE): self.format = UrlParser.CONTENT_TYPE[content_type] return self.content CONTENT_TYPE = { - 'application/json': 'json', - 'text/json': 'json', - 'application/yaml': 'yaml', - 'application/x-yaml': 'yaml', - 'text/x-yaml': 'yaml', - 'text/yaml': 'yaml' - } - + "application/json": "json", + "text/json": "json", + "application/yaml": "yaml", + "application/x-yaml": "yaml", + "text/x-yaml": "yaml", + "text/yaml": "yaml", + } + class FileParser(Parser): + """Parser dedicated to File""" - def __init__(self, file_path, file_format=None): + def __init__(self, file_path, file_format=None): self.file_path = file_path super().__init__(file_format) def load(self): - with open(self.file_path, "r") as f: + with open(self.file_path, "r", encoding="utf-8") as f: self.content = f.read() if not self.format: - self.format = self._getFormatFromExtension(self.file_path) + self.format = self._get_format_from_extension(self.file_path) return self.content @staticmethod - def _getFormatFromExtension(file_path): + def _get_format_from_extension(file_path): path = Path(file_path) extension = path.suffix.lower().lstrip(".") - if extension in FileParser.FORMATS.keys(): + if extension in FileParser.FORMATS: return extension return None - diff --git a/action/test/main_test.py b/action/test/main_test.py index f65d173..f2176ca 100644 --- a/action/test/main_test.py +++ b/action/test/main_test.py @@ -1,12 +1,16 @@ -import unittest -from unittest.mock import patch, MagicMock -from action.main import Main -import shutil +""" Unit Test of Main Class """ + import os +import shutil +import unittest from pathlib import Path -import subprocess +from unittest.mock import MagicMock, patch + +from action.main import Main + class TestMain(unittest.TestCase): + """Unit Test of Main Class""" def setUp(self): try: @@ -16,155 +20,191 @@ def setUp(self): pass def test_init_env_var_by_key(self): - ''' + """ Main.__init__ unittest: Check if environment variable are added in section env - ''' + """ os.environ["TEST"] = "myfakevalue" m = Main() - self.assertTrue({'TEST': 'myfakevalue'}.items() <= m.data['env'].items()) - del os.environ['TEST'] + self.assertTrue({"TEST": "myfakevalue"}.items() <= m.data["env"].items()) + del os.environ["TEST"] def test_init_env_var_by_method(self): - ''' + """ Main.__init__ unittest: Check if environment variable are availabel with the environ method - ''' + """ os.environ["TEST"] = "myfakevalue" - with open("test.txt.j2", 'w') as out: + with open("test.txt.j2", "w", encoding="utf-8") as out: out.write("{{ environ('TEST') }}") out.flush() - + m = Main() - m.renderFile("test.txt.j2") + m.render_file("test.txt.j2") - self.assertTrue({'TEST': 'myfakevalue'}.items() <= m.data['env'].items()) - del os.environ['TEST'] + self.assertTrue({"TEST": "myfakevalue"}.items() <= m.data["env"].items()) + del os.environ["TEST"] self.assertTrue(os.path.isfile("test.txt"), "Template file is renamed") self.assertFalse(os.path.isfile("test.txt.j2"), "Original File is deleted") - with open("test.txt") as f: + with open("test.txt", encoding="utf-8") as f: result = f.read() self.assertEqual(result, "myfakevalue", "envion method is managed") os.remove("test.txt") - def test_addVariables_oneVariable(self): - ''' + def test_add_variables_one_variable(self): + """ Main.addVariables unittest: Check if one variable can be added - ''' + """ m = Main() - m.addVariables("TEST=toto") - self.assertTrue({'TEST': 'toto'}.items() <= m.data.items()) + m.add_variables("TEST=toto") + self.assertTrue({"TEST": "toto"}.items() <= m.data.items()) - def test_addVariables_multipleVariable(self): - ''' + def test_add_variables_multiple_variable(self): + """ Main.addVariables unittest: Check if multiple variables can be added - ''' + """ m = Main() - m.addVariables(""" + m.add_variables( + """ TEST1=tata TEST2=titi - """) - self.assertTrue({'TEST1': 'tata', 'TEST2': 'titi'}.items() <= m.data.items()) - - def test_addJsonSection_stringValue(self): - ''' - Main.addJsonSection unittest: Check if the section with a string value is correctly added to data. - ''' + """ + ) + self.assertTrue({"TEST1": "tata", "TEST2": "titi"}.items() <= m.data.items()) + + def test_add_json_section_string_value(self): + """ + Main.addJsonSection unittest: Check if the section with a + string value is correctly added to data. + """ m = Main() - m.addJsonSection("my_test_section", "{\"TEST1\": \"tata\", \"TEST2\": \"titi\", \"problematic-key\": \"value\"}") - self.assertTrue({'my_test_section': {'TEST1': 'tata', 'TEST2': 'titi', 'problematic_key': 'value' }}.items() <= m.data.items()) - - def test_addJsonSection_dictValue(self): - ''' - Main.addJsonSection unittest: Check if the section with a dict value is correctly added to data. - ''' + m.add_json_section( + "my_test_section", + '{"TEST1": "tata", "TEST2": "titi", "problematic-key": "value"}', + ) + self.assertTrue( + { + "my_test_section": { + "TEST1": "tata", + "TEST2": "titi", + "problematic_key": "value", + } + }.items() + <= m.data.items() + ) + + def test_add_json_section_dict_value(self): + """ + Main.addJsonSection unittest: Check if the section with a + dict value is correctly added to data. + """ m = Main() - m.addJsonSection("my_test_section", {'TEST1': 'tata', 'TEST2': 'titi', 'problematic-key': 'value'}) - self.assertTrue({'my_test_section': {'TEST1': 'tata', 'TEST2': 'titi', 'problematic_key': 'value'}}.items() <= m.data.items()) - - @patch('action.main.FileParser', spec=True) - def test_addDataFile(self, ParserMock): - ''' - Main.addDataFile unittest: Check that Parser class is inialized, used to parse then returned dict added to global data. - ''' + m.add_json_section( + "my_test_section", + {"TEST1": "tata", "TEST2": "titi", "problematic-key": "value"}, + ) + self.assertTrue( + { + "my_test_section": { + "TEST1": "tata", + "TEST2": "titi", + "problematic_key": "value", + } + }.items() + <= m.data.items() + ) + + @patch("action.main.FileParser", spec=True) + def test_add_data_file(self, parser_mock): + """ + Main.addDataFile unittest: Check that Parser class is inialized, + used to parse then returned dict added to global data. + """ # Get the mock instance for Parser - mock_instance = ParserMock.return_value - mock_instance.parse = MagicMock(return_value={'TEST': 'toto'}) + mock_instance = parser_mock.return_value + mock_instance.parse = MagicMock(return_value={"TEST": "toto"}) m = Main() - m.addDataFile("file_path", "my_format") + m.add_data_file("file_path", "my_format") - ParserMock.assert_called_with("file_path", "my_format") + parser_mock.assert_called_with("file_path", "my_format") self.assertTrue(mock_instance.parse.called, "parse is called") - self.assertTrue({'TEST': 'toto'}.items() <= m.data.items()) - - @patch('action.main.UrlParser', spec=True) - def test_addDataUrl(self, ParserMock): - ''' - Main.test_addDataUrl unittest: Check that Parser class is inialized, used to parse then returned dict added to global data. - ''' + self.assertTrue({"TEST": "toto"}.items() <= m.data.items()) + + @patch("action.main.UrlParser", spec=True) + def test_add_data_url(self, parser_mock): + """ + Main.test_addDataUrl unittest: Check that Parser class is inialized, + used to parse then returned dict added to global data. + """ # Get the mock instance for Parser - mock_instance = ParserMock.return_value - mock_instance.parse = MagicMock(return_value={'TEST': 'toto'}) + mock_instance = parser_mock.return_value + mock_instance.parse = MagicMock(return_value={"TEST": "toto"}) m = Main() - m.addDataUrl("url", "my_format") + m.add_data_url("url", "my_format") - ParserMock.assert_called_with("url", "my_format") + parser_mock.assert_called_with("url", "my_format") self.assertTrue(mock_instance.parse.called, "parse is called") - self.assertTrue({'TEST': 'toto'}.items() <= m.data.items()) + self.assertTrue({"TEST": "toto"}.items() <= m.data.items()) - def test_renderFile_jinja2(self): - ''' + def test_render_file_jinja2(self): + """ Main.renderFile unittest: Check if file is rendered by jinja 2 and orginal file removed. - ''' - with open("test.txt.j2", 'w') as out: + """ + with open("test.txt.j2", "w", encoding="utf-8") as out: out.write("{{ TEST1 }}\n{{ TEST2 }}") out.flush() - + m = Main() - m.data = {'TEST1': 'tata', 'TEST2': 'titi'} - m.renderFile("test.txt.j2") - + m.data = {"TEST1": "tata", "TEST2": "titi"} + m.render_file("test.txt.j2") + self.assertTrue(os.path.isfile("test.txt"), "Template file is renamed") self.assertFalse(os.path.isfile("test.txt.j2"), "Original File is deleted") - with open("test.txt") as f: + with open("test.txt", encoding="utf-8") as f: result = f.read() self.assertEqual(result, "tata\ntiti", "Template file is managed") os.remove("test.txt") - def test_renderFile_keep_file(self): - ''' + def test_render_file_keep_file(self): + """ Main.renderFile unittest: Check if orginal file can be keeped - ''' - open("test.txt.j2", 'a').close() - + """ + open("test.txt.j2", "a", encoding="utf-8").close() # pylint: disable=R1732 + m = Main(keep_template=True) - m.renderFile("test.txt.j2") + m.render_file("test.txt.j2") self.assertTrue(os.path.isfile("test.txt"), "Template file is renamed") self.assertTrue(os.path.isfile("test.txt.j2"), "Original File is NOT deleted") os.remove("test.txt") os.remove("test.txt.j2") - def test_renderAll(self): - ''' + def test_render_all(self): + """ Main.renderAll unittest: Check if multiple file are managed - ''' - open("test1.txt.j2", 'a').close() - open("test2.txt.j2", 'a').close() + """ + open("test1.txt.j2", "a", encoding="utf-8").close() # pylint: disable=R1732 + open("test2.txt.j2", "a", encoding="utf-8").close() # pylint: disable=R1732 Path(".test/directory").mkdir(parents=True, exist_ok=True) - open(".test/directory/test3.txt.j2", 'a').close() - + open( # pylint: disable=R1732 + ".test/directory/test3.txt.j2", "a", encoding="utf-8" + ).close() + m = Main() - m.renderAll() - + m.render_all() + self.assertTrue(os.path.isfile("test1.txt"), "Template file is renamed") self.assertTrue(os.path.isfile("test2.txt"), "Template file is renamed") - self.assertTrue(os.path.isfile(".test/directory/test3.txt"), "Template file is renamed") + self.assertTrue( + os.path.isfile(".test/directory/test3.txt"), "Template file is renamed" + ) self.assertFalse(os.path.isfile("test1.txt.j2"), "Original File is deleted") self.assertFalse(os.path.isfile("test2.txt.j2"), "Original File is deleted") - self.assertFalse(os.path.isfile(".test/directory/test3.txt.j2"), "Original File is deleted") - + self.assertFalse( + os.path.isfile(".test/directory/test3.txt.j2"), "Original File is deleted" + ) + os.remove("test1.txt") os.remove("test2.txt") shutil.rmtree(".test") diff --git a/action/test/parser_test.py b/action/test/parser_test.py index 12c2d21..4c7cdde 100644 --- a/action/test/parser_test.py +++ b/action/test/parser_test.py @@ -1,49 +1,58 @@ +""" +Unit Test of Parser Module +""" + +# pylint: disable=W0212 + import os import unittest + from parameterized import parameterized + from action.parser import FileParser, Parser, UrlParser + class TestParser(unittest.TestCase): - + """Unit test of Parser Class""" + class StubParser(Parser): - def __init__(self, format=None): - super().__init__(format) + """Stub of a Parser Child Class""" + + def __init__(self, parsed_format=None): + super().__init__(parsed_format) def load(self): return "CONTENT_TO_PARSE" - - @parameterized.expand([ - ('ini'), - ('json'), - ('yaml'), - ('yml'), - ('env') - ]) - def test_init_with_managed_format(self, format): - ''' + + @parameterized.expand([("ini"), ("json"), ("yaml"), ("yml"), ("env")]) + def test_init_with_managed_format(self, parsed_format): + """ Parser.__init__ unittest: Init with managed file format is successfull. File format: : ini, json, yaml, yml, env. - ''' - p = TestParser.StubParser(format) - self.assertTrue(p.format == format, "format is stored") + """ + p = TestParser.StubParser(parsed_format) + self.assertTrue(p.format == parsed_format, "format is stored") def test_init_with_unmanaged_format(self): - ''' + """ Parser.__init__ unittest: Parse with defined unmanaged file format raise exception. - ''' + """ with self.assertRaises(ValueError): - p = TestParser.StubParser("strange_format") - - @parameterized.expand([ - ('ini', 'action.parser.Parser._parse_ini'), - ('json', 'action.parser.Parser._parse_json'), - ('yaml', 'action.parser.Parser._parse_yaml'), - ('yml', 'action.parser.Parser._parse_yaml'), - ('env', 'action.parser.Parser._parse_env') - ]) - def test_parse_call_correct_parser(self, format, method): - ''' - Parser.parse unittest: Parse with a preconfigured format must load the content and call directly the dedicated parser. + TestParser.StubParser("strange_format") + + @parameterized.expand( + [ + ("ini", "action.parser.Parser._parse_ini"), + ("json", "action.parser.Parser._parse_json"), + ("yaml", "action.parser.Parser._parse_yaml"), + ("yml", "action.parser.Parser._parse_yaml"), + ("env", "action.parser.Parser._parse_env"), + ] + ) + def test_parse_call_correct_parser(self, parsed_format, method): + """ + Parser.parse unittest: Parse with a preconfigured format must + load the content and call directly the dedicated parser. Test Data: | Format | Waited Parser | | ---- | ------------ | @@ -52,251 +61,270 @@ def test_parse_call_correct_parser(self, format, method): | yaml | action.parser.Parser._parse_yaml | | yml | action.parser.Parser._parse_yaml | | env | action.parser.Parser._parse_env | - ''' - p = TestParser.StubParser(format) - with unittest.mock.patch(method, return_value="fake") as mock : + """ + p = TestParser.StubParser(parsed_format) + with unittest.mock.patch(method, return_value="fake") as mock: ret = p.parse() self.assertTrue(mock.called, f"{method} is called") mock.assert_called_with("CONTENT_TO_PARSE") self.assertTrue(ret == "fake") def test_parse_call_unknow_parser(self): - ''' - Parser.parse unittest: Parse with a format not defined must call directly the generic parser. - ''' + """ + Parser.parse unittest: Parse with a format not defined + must call directly the generic parser. + """ p = TestParser.StubParser() - with unittest.mock.patch('action.parser.Parser._parse_generic', return_value="fake") as mock : + with unittest.mock.patch( + "action.parser.Parser._parse_generic", return_value="fake" + ) as mock: ret = p.parse() - self.assertTrue(mock.called, f"action.parser.Parser._parse_generic is called") + self.assertTrue( + mock.called, "action.parser.Parser._parse_generic is called" + ) mock.assert_called_with("CONTENT_TO_PARSE") self.assertTrue(ret == "fake") def test_parse_ini(self): - ''' + """ Parser._parse_ini unittest: Parse INI content is successfull. - ''' - ini_content=""" + """ + ini_content = """ [exemple] TEST1 = tata TEST2 = titi """ ret = TestParser.StubParser._parse_ini(ini_content) - self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) + self.assertEqual( + {"exemple": {"TEST1": "tata", "TEST2": "titi"}}.items(), ret.items() + ) def test_parse_json(self): - ''' + """ Parser._parse_json unittest: Parse JSON content is successfull. - ''' - json_content="{\"exemple\": {\"TEST1\": \"tata\", \"TEST2\": \"titi\"}}" + """ + json_content = '{"exemple": {"TEST1": "tata", "TEST2": "titi"}}' ret = TestParser.StubParser._parse_json(json_content) - self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) + self.assertEqual( + {"exemple": {"TEST1": "tata", "TEST2": "titi"}}.items(), ret.items() + ) def test_parse_yaml(self): - ''' + """ Parser._parse_yaml unittest: Parse YAML content is successfull. - ''' - yaml_content=""" + """ + yaml_content = """ exemple: TEST1: tata TEST2: titi """ ret = TestParser.StubParser._parse_yaml(yaml_content) - self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) + self.assertEqual( + {"exemple": {"TEST1": "tata", "TEST2": "titi"}}.items(), ret.items() + ) def test_parse_env(self): - ''' + """ Parser._parse_env unittest: Parse ENV content is successfull. - ''' - env_content=""" + """ + env_content = """ TEST1=tata TEST2=titi """ ret = TestParser.StubParser._parse_env(env_content) - self.assertEqual({'TEST1': 'tata', 'TEST2': 'titi'}.items(), ret.items()) + self.assertEqual({"TEST1": "tata", "TEST2": "titi"}.items(), ret.items()) def test_parse_generic_ini(self): - ''' + """ Parser._parse_generic unittest: Generic Parser recognize INI content and parse it. - ''' - ini_content=""" + """ + ini_content = """ [exemple] TEST1 = tata TEST2 = titi """ - format,ret = TestParser.StubParser._parse_generic(ini_content) - self.assertEqual(format, 'ini') - self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) + test_format, ret = TestParser.StubParser._parse_generic(ini_content) + self.assertEqual(test_format, "ini") + self.assertEqual( + {"exemple": {"TEST1": "tata", "TEST2": "titi"}}.items(), ret.items() + ) def test_parse_generic_json(self): - ''' + """ Parser._parse_generic unittest: Generic Parser recognize JSON content and parse it. - ''' - json_content="{\"exemple\": {\"TEST1\": \"tata\", \"TEST2\": \"titi\"}}" - format,ret = TestParser.StubParser._parse_generic(json_content) - self.assertEqual(format, 'json') - self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) + """ + json_content = '{"exemple": {"TEST1": "tata", "TEST2": "titi"}}' + test_format, ret = TestParser.StubParser._parse_generic(json_content) + self.assertEqual(test_format, "json") + self.assertEqual( + {"exemple": {"TEST1": "tata", "TEST2": "titi"}}.items(), ret.items() + ) def test_parse_generic_yaml(self): - ''' + """ Parser._parse_generic unittest: Generic Parser parse recognize YAML content and parse it. - ''' - yaml_content=""" + """ + yaml_content = """ exemple: TEST1: tata TEST2: titi """ - format,ret = TestParser.StubParser._parse_generic(yaml_content) - self.assertEqual(format, 'yaml') - self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) + test_format, ret = TestParser.StubParser._parse_generic(yaml_content) + self.assertEqual(test_format, "yaml") + self.assertEqual( + {"exemple": {"TEST1": "tata", "TEST2": "titi"}}.items(), ret.items() + ) def test_parse_generic_env(self): - ''' + """ Parser._parse_generic unittest: Generic Parser parse recognize ENV content and parse it. - ''' - env_content=""" + """ + env_content = """ TEST1=tata TEST2=titi """ - format,ret = TestParser.StubParser._parse_generic(env_content) - self.assertEqual(format, 'env') - self.assertEqual({'TEST1': 'tata', 'TEST2': 'titi'}.items(), ret.items()) + test_format, ret = TestParser.StubParser._parse_generic(env_content) + self.assertEqual(test_format, "env") + self.assertEqual({"TEST1": "tata", "TEST2": "titi"}.items(), ret.items()) def test_parse_generic_not_managed(self): - ''' - Parser._parse_generic unittest: Generic Parser does'nt recognize/parse unformatted content. - ''' - unknow_content='asd : fgh : ghj' + """ + Parser._parse_generic unittest: Generic Parser does"nt recognize/parse unformatted content. + """ + unknow_content = "asd : fgh : ghj" with self.assertRaises(ValueError): - format,ret = TestParser.StubParser._parse_generic(unknow_content) + TestParser.StubParser._parse_generic(unknow_content) class TestFileParser(unittest.TestCase): + """Unit Test of FileParse Class""" - @parameterized.expand([ - ('ini'), - ('json'), - ('yaml'), - ('yml'), - ('env') - ]) - def test_init_with_managed_format(self, format): - ''' - FileParser.__init__ unittest: Init with managed file format stores the file path and is successfull. + @parameterized.expand([("ini"), ("json"), ("yaml"), ("yml"), ("env")]) + def test_init_with_managed_format(self, test_format): + """ + FileParser.__init__ unittest: Init with managed file + format stores the file path and is successfull. File format: : ini, json, yaml, yml, env. - ''' - p = FileParser("file_path", format) - self.assertEqual(p.file_path, "file_path", "Init store file path without any check") - self.assertTrue(p.format == format, "init stores correct format") + """ + p = FileParser("file_path", test_format) + self.assertEqual( + p.file_path, "file_path", "Init store file path without any check" + ) + self.assertTrue(p.format == test_format, "init stores correct format") def test_init_with_unmanaged_format(self): - ''' + """ FileParser.__init__ unittest: Init with defined unmanaged file format raise exception. - ''' + """ with self.assertRaises(ValueError): - p = FileParser("file_path", "strange_format") + FileParser("file_path", "strange_format") def test_load_with_predefinedformat(self): - ''' - FileParser.load unittest: Load file wih a predefined format, return the file content and keep the format. - ''' - with open("test.txt", 'w') as file: + """ + FileParser.load unittest: Load file wih a predefined format, + return the file content and keep the format. + """ + with open("test.txt", "w", encoding="utf-8") as file: file.write("CONTENT_TO_PARSE") p = FileParser("test.txt", "json") ret = p.load() self.assertEqual(ret, "CONTENT_TO_PARSE", "Load return the file content") - self.assertEqual(p.format, "json", "Load keeps the predefined format whatever the content") + self.assertEqual( + p.format, "json", "Load keeps the predefined format whatever the content" + ) os.remove("test.txt") - @parameterized.expand([ - ('ini'), - ('json'), - ('yaml'), - ('yml'), - ('env') - ]) + @parameterized.expand([("ini"), ("json"), ("yaml"), ("yml"), ("env")]) def test_load_with_format_found_in_extension(self, extension): - ''' - FileParser.load unittest: Load file wihout predefined format but with a managed extension, return the file content and found the format from extension. + """ + FileParser.load unittest: Load file wihout predefined format but with a managed + extension, return the file content and found the format from extension. Possible extension: : ini, json, yaml, yml, env. - ''' - with open(f"file_path.{extension}", 'w') as file: + """ + with open(f"file_path.{extension}", "w", encoding="utf-8") as file: file.write("CONTENT_TO_PARSE") p = FileParser(f"file_path.{extension}") ret = p.load() self.assertEqual(ret, "CONTENT_TO_PARSE", "Load return the file content") - self.assertTrue(p.format == extension, "Load found the format from the extension") + self.assertTrue( + p.format == extension, "Load found the format from the extension" + ) os.remove(f"file_path.{extension}") class TestUrlParser(unittest.TestCase): + """UnitTest of UrlParser Class""" - @parameterized.expand([ - ('ini'), - ('json'), - ('yaml'), - ('yml'), - ('env') - ]) - def test_init_with_managed_format(self, format): - ''' - UrlParser.__init__ unittest: Init with managed file format stores the url and is successfull. + @parameterized.expand([("ini"), ("json"), ("yaml"), ("yml"), ("env")]) + def test_init_with_managed_format(self, test_format): + """ + UrlParser.__init__ unittest: Init with managed file + format stores the url and is successfull. File format: : ini, json, yaml, yml, env. - ''' - p = UrlParser("url", format) + """ + p = UrlParser("url", test_format) self.assertEqual(p.url, "url", "Init store url without any check") - self.assertTrue(p.format == format, "init stores correct format") + self.assertTrue(p.format == test_format, "init stores correct format") def test_init_with_unmanaged_format(self): - ''' + """ UrlParser.__init__ unittest: Init with defined unmanaged format raise exception. - ''' + """ with self.assertRaises(ValueError): - p = UrlParser("url", "strange_format") + UrlParser("url", "strange_format") - @unittest.mock.patch('urllib.request.urlopen') + @unittest.mock.patch("urllib.request.urlopen") def test_load_with_predefinedformat(self, mock_urlopen): - ''' - UrlParser.load unittest: Load file wih a predefined format, return the file content and keep the format. - ''' + """ + UrlParser.load unittest: Load file wih a predefined format, + return the file content and keep the format. + """ cm = unittest.mock.MagicMock() - cm.read.return_value = 'CONTENT_TO_PARSE' + cm.read.return_value = "CONTENT_TO_PARSE" cm.__enter__.return_value = cm mock_urlopen.return_value = cm - p = UrlParser("url", "json") ret = p.load() self.assertEqual(ret, "CONTENT_TO_PARSE", "Load return the file content") - self.assertEqual(p.format, "json", "Load keeps the predefined format whatever the content") - cm.getheader.assert_called_with('content-type') - - @parameterized.expand([ - ('application/json', 'json'), - ('text/json', 'json'), - ('application/yaml', 'yaml'), - ('application/x-yaml', 'yaml'), - ('text/x-yaml', 'yaml'), - ('text/yaml', 'yaml') - ]) - @unittest.mock.patch('urllib.request.urlopen') - def test_load_with_format_found_in_extension(self, content_type, waited_format, mock_urlopen): - ''' - UrlParser.load unittest: Load content wihout predefined format but with a managed http return, return the url content and found the format from http header content-type. + self.assertEqual( + p.format, "json", "Load keeps the predefined format whatever the content" + ) + cm.getheader.assert_called_with("content-type") + + @parameterized.expand( + [ + ("application/json", "json"), + ("text/json", "json"), + ("application/yaml", "yaml"), + ("application/x-yaml", "yaml"), + ("text/x-yaml", "yaml"), + ("text/yaml", "yaml"), + ] + ) + @unittest.mock.patch("urllib.request.urlopen") + def test_load_with_format_found_in_extension( + self, content_type, waited_format, mock_urlopen + ): + """ + UrlParser.load unittest: Load content wihout predefined format but with a managed http + return, return the url content and found the format from http header content-type. Possible content_type: : ini, json, yaml, yml, env. - ''' + """ cm = unittest.mock.MagicMock() cm.getheader.return_value = content_type - cm.read.return_value = 'CONTENT_TO_PARSE' + cm.read.return_value = "CONTENT_TO_PARSE" cm.__enter__.return_value = cm mock_urlopen.return_value = cm p = UrlParser("url") ret = p.load() self.assertEqual(ret, "CONTENT_TO_PARSE", "Load return the file content") - cm.getheader.assert_called_with('content-type') - self.assertEqual(p.format, waited_format, "Load found the format from the http header") - + cm.getheader.assert_called_with("content-type") + self.assertEqual( + p.format, waited_format, "Load found the format from the http header" + ) diff --git a/entrypoint.py b/entrypoint.py index 7bc6e5a..39b1b31 100644 --- a/entrypoint.py +++ b/entrypoint.py @@ -1,39 +1,49 @@ +""" +CLI Entrypoint +""" + import os from pathlib import Path + import click + from action.main import Main + @click.command() -#@click.option('--variables', help='Variables', default=None) -@click.option('--keep_template', is_flag=True) -@click.option('--var_file', default=None) -@click.option('--context', multiple=True, default=[]) -@click.option('--data_file', default=None) -@click.option('--data_format', default=None) -@click.option('--data_url', default=None) -@click.option('--data_url_format', default=None) -def main(keep_template, var_file, context, data_file, data_format, data_url, data_url_format): - main = Main(keep_template=keep_template) - +@click.option("--keep_template", is_flag=True) +@click.option("--var_file", default=None) +@click.option("--context", multiple=True, default=[]) +@click.option("--data_file", default=None) +@click.option("--data_format", default=None) +@click.option("--data_url", default=None) +@click.option("--data_url_format", default=None) +def main( # pylint: disable=R0913 + keep_template, var_file, context, data_file, data_format, data_url, data_url_format +): + """Main CLI Method""" + m = Main(keep_template=keep_template) + if var_file: - with open(var_file) as f: + with open(var_file, encoding="utf-8") as f: variables = f.read() - main.addVariables(variables) + m.add_variables(variables) for context_file in context: - section=Path(os.path.basename(context_file)).stem - with open(context_file) as f: + section = Path(os.path.basename(context_file)).stem + with open(context_file, encoding="utf-8") as f: content = f.read() - if content != "" and content != "null\n": - main.addJsonSection(section, content) + if content not in ("", "null\n"): + m.add_json_section(section, content) if data_file: - main.addDataFile(data_file, data_format) + m.add_data_file(data_file, data_format) if data_url: - main.addDataUrl(data_url, data_url_format) - - main.renderAll() + m.add_data_url(data_url, data_url_format) + + m.render_all() + -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() # pylint: disable=E1120 diff --git a/test/data/github.json b/test/data/github.json deleted file mode 100644 index 71ad955..0000000 --- a/test/data/github.json +++ /dev/null @@ -1,231 +0,0 @@ -{ - "token": "***", - "job": "test-action", - "ref": "refs/heads/5-give-access-to-github-context", - "sha": "d74f16b947d259c5290fd9ac89f107ca00ad2d08", - "repository": "fletort/jinja2-template-action", - "repository_owner": "fletort", - "repository_owner_id": "488105", - "repositoryUrl": "git://github.com/fletort/jinja2-template-action.git", - "run_id": "11408033751", - "run_number": "150", - "retention_days": "90", - "run_attempt": "1", - "artifact_cache_size_limit": "10", - "repository_visibility": "public", - "repo-self-hosted-runners-disabled": false, - "enterprise-managed-business-id": "", - "repository_id": "827837833", - "actor_id": "488105", - "actor": "fletort", - "triggering_actor": "fletort", - "workflow": "Continuous Testing", - "head_ref": "", - "base_ref": "", - "event_name": "push", - "event": { - "after": "d74f16b947d259c5290fd9ac89f107ca00ad2d08", - "base_ref": null, - "before": "0e4a73d7e3af1c6e5439554f371210fb38cdbb0e", - "commits": [ - { - "author": { - "email": "fletort@gmail.com", - "name": "Fabien Letort", - "username": "fletort" - }, - "committer": { - "email": "fletort@gmail.com", - "name": "Fabien Letort", - "username": "fletort" - }, - "distinct": true, - "id": "d74f16b947d259c5290fd9ac89f107ca00ad2d08", - "message": "test", - "timestamp": "2024-10-18T19:14:30+02:00", - "tree_id": "71f8a14d85c6ca5dddae60017607db7e91dd8326", - "url": "https://github.com/fletort/jinja2-template-action/commit/d74f16b947d259c5290fd9ac89f107ca00ad2d08" - } - ], - "compare": "https://github.com/fletort/jinja2-template-action/compare/0e4a73d7e3af...d74f16b947d2", - "created": false, - "deleted": false, - "forced": false, - "head_commit": { - "author": { - "email": "fletort@gmail.com", - "name": "Fabien Letort", - "username": "fletort" - }, - "committer": { - "email": "fletort@gmail.com", - "name": "Fabien Letort", - "username": "fletort" - }, - "distinct": true, - "id": "d74f16b947d259c5290fd9ac89f107ca00ad2d08", - "message": "test", - "timestamp": "2024-10-18T19:14:30+02:00", - "tree_id": "71f8a14d85c6ca5dddae60017607db7e91dd8326", - "url": "https://github.com/fletort/jinja2-template-action/commit/d74f16b947d259c5290fd9ac89f107ca00ad2d08" - }, - "pusher": { - "email": "fletort@gmail.com", - "name": "fletort" - }, - "ref": "refs/heads/5-give-access-to-github-context", - "repository": { - "allow_forking": true, - "archive_url": "https://api.github.com/repos/fletort/jinja2-template-action/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/fletort/jinja2-template-action/assignees{/user}", - "blobs_url": "https://api.github.com/repos/fletort/jinja2-template-action/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/fletort/jinja2-template-action/branches{/branch}", - "clone_url": "https://github.com/fletort/jinja2-template-action.git", - "collaborators_url": "https://api.github.com/repos/fletort/jinja2-template-action/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/fletort/jinja2-template-action/comments{/number}", - "commits_url": "https://api.github.com/repos/fletort/jinja2-template-action/commits{/sha}", - "compare_url": "https://api.github.com/repos/fletort/jinja2-template-action/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/fletort/jinja2-template-action/contents/{+path}", - "contributors_url": "https://api.github.com/repos/fletort/jinja2-template-action/contributors", - "created_at": 1720791496, - "default_branch": "main", - "deployments_url": "https://api.github.com/repos/fletort/jinja2-template-action/deployments", - "description": "Use Jinja2 template engine on multipe files as a GitHub action", - "disabled": false, - "downloads_url": "https://api.github.com/repos/fletort/jinja2-template-action/downloads", - "events_url": "https://api.github.com/repos/fletort/jinja2-template-action/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/fletort/jinja2-template-action/forks", - "full_name": "fletort/jinja2-template-action", - "git_commits_url": "https://api.github.com/repos/fletort/jinja2-template-action/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/fletort/jinja2-template-action/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/fletort/jinja2-template-action/git/tags{/sha}", - "git_url": "git://github.com/fletort/jinja2-template-action.git", - "has_discussions": false, - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/fletort/jinja2-template-action/hooks", - "html_url": "https://github.com/fletort/jinja2-template-action", - "id": 827837833, - "is_template": false, - "issue_comment_url": "https://api.github.com/repos/fletort/jinja2-template-action/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/fletort/jinja2-template-action/issues/events{/number}", - "issues_url": "https://api.github.com/repos/fletort/jinja2-template-action/issues{/number}", - "keys_url": "https://api.github.com/repos/fletort/jinja2-template-action/keys{/key_id}", - "labels_url": "https://api.github.com/repos/fletort/jinja2-template-action/labels{/name}", - "language": "Shell", - "languages_url": "https://api.github.com/repos/fletort/jinja2-template-action/languages", - "license": { - "key": "mit", - "name": "MIT License", - "node_id": "MDc6TGljZW5zZTEz", - "spdx_id": "MIT", - "url": "https://api.github.com/licenses/mit" - }, - "master_branch": "main", - "merges_url": "https://api.github.com/repos/fletort/jinja2-template-action/merges", - "milestones_url": "https://api.github.com/repos/fletort/jinja2-template-action/milestones{/number}", - "mirror_url": null, - "name": "jinja2-template-action", - "node_id": "R_kgDOMVfNiQ", - "notifications_url": "https://api.github.com/repos/fletort/jinja2-template-action/notifications{?since,all,participating}", - "open_issues": 4, - "open_issues_count": 4, - "owner": { - "avatar_url": "https://avatars.githubusercontent.com/u/488105?v=4", - "email": "fletort@gmail.com", - "events_url": "https://api.github.com/users/fletort/events{/privacy}", - "followers_url": "https://api.github.com/users/fletort/followers", - "following_url": "https://api.github.com/users/fletort/following{/other_user}", - "gists_url": "https://api.github.com/users/fletort/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/fletort", - "id": 488105, - "login": "fletort", - "name": "fletort", - "node_id": "MDQ6VXNlcjQ4ODEwNQ==", - "organizations_url": "https://api.github.com/users/fletort/orgs", - "received_events_url": "https://api.github.com/users/fletort/received_events", - "repos_url": "https://api.github.com/users/fletort/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/fletort/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/fletort/subscriptions", - "type": "User", - "url": "https://api.github.com/users/fletort", - "user_view_type": "public" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/fletort/jinja2-template-action/pulls{/number}", - "pushed_at": 1729271673, - "releases_url": "https://api.github.com/repos/fletort/jinja2-template-action/releases{/id}", - "size": 23, - "ssh_url": "git@github.com:fletort/jinja2-template-action.git", - "stargazers": 0, - "stargazers_count": 0, - "stargazers_url": "https://api.github.com/repos/fletort/jinja2-template-action/stargazers", - "statuses_url": "https://api.github.com/repos/fletort/jinja2-template-action/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/fletort/jinja2-template-action/subscribers", - "subscription_url": "https://api.github.com/repos/fletort/jinja2-template-action/subscription", - "svn_url": "https://github.com/fletort/jinja2-template-action", - "tags_url": "https://api.github.com/repos/fletort/jinja2-template-action/tags", - "teams_url": "https://api.github.com/repos/fletort/jinja2-template-action/teams", - "topics": [], - "trees_url": "https://api.github.com/repos/fletort/jinja2-template-action/git/trees{/sha}", - "updated_at": "2024-10-18T13:45:37Z", - "url": "https://github.com/fletort/jinja2-template-action", - "visibility": "public", - "watchers": 0, - "watchers_count": 0, - "web_commit_signoff_required": false - }, - "sender": { - "avatar_url": "https://avatars.githubusercontent.com/u/488105?v=4", - "events_url": "https://api.github.com/users/fletort/events{/privacy}", - "followers_url": "https://api.github.com/users/fletort/followers", - "following_url": "https://api.github.com/users/fletort/following{/other_user}", - "gists_url": "https://api.github.com/users/fletort/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/fletort", - "id": 488105, - "login": "fletort", - "node_id": "MDQ6VXNlcjQ4ODEwNQ==", - "organizations_url": "https://api.github.com/users/fletort/orgs", - "received_events_url": "https://api.github.com/users/fletort/received_events", - "repos_url": "https://api.github.com/users/fletort/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/fletort/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/fletort/subscriptions", - "type": "User", - "url": "https://api.github.com/users/fletort", - "user_view_type": "public" - } - }, - "server_url": "https://github.com", - "api_url": "https://api.github.com", - "graphql_url": "https://api.github.com/graphql", - "ref_name": "5-give-access-to-github-context", - "ref_protected": false, - "ref_type": "branch", - "secret_source": "Actions", - "workflow_ref": "fletort/jinja2-template-action/.github/workflows/test.yml@refs/heads/5-give-access-to-github-context", - "workflow_sha": "d74f16b947d259c5290fd9ac89f107ca00ad2d08", - "workspace": "/home/runner/work/jinja2-template-action/jinja2-template-action", - "action": "test-action", - "event_path": "/home/runner/work/_temp/_github_workflow/event.json", - "action_repository": "", - "action_ref": "", - "path": "/home/runner/work/_temp/_runner_file_commands/add_path_6262300b-2cf9-451b-bd81-cea0401fdc27", - "env": "/home/runner/work/_temp/_runner_file_commands/set_env_6262300b-2cf9-451b-bd81-cea0401fdc27", - "step_summary": "/home/runner/work/_temp/_runner_file_commands/step_summary_6262300b-2cf9-451b-bd81-cea0401fdc27", - "state": "/home/runner/work/_temp/_runner_file_commands/save_state_6262300b-2cf9-451b-bd81-cea0401fdc27", - "output": "/home/runner/work/_temp/_runner_file_commands/set_output_6262300b-2cf9-451b-bd81-cea0401fdc27", - "action_path": "/home/runner/work/jinja2-template-action/jinja2-template-action/./", - "action_status": "success" - } \ No newline at end of file diff --git a/test/data/job.json b/test/data/job.json deleted file mode 100644 index 0460d16..0000000 --- a/test/data/job.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "status": "success" -} \ No newline at end of file diff --git a/test/data/matrix.json b/test/data/matrix.json deleted file mode 100644 index e69de29..0000000 diff --git a/test/data/runner.json b/test/data/runner.json deleted file mode 100644 index eb3d8a8..0000000 --- a/test/data/runner.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "os": "Linux", - "arch": "X64", - "name": "GitHub Actions 19", - "environment": "github-hosted", - "tool_cache": "/opt/hostedtoolcache", - "temp": "/home/runner/work/_temp", - "workspace": "/home/runner/work/jinja2-template-action" - } \ No newline at end of file diff --git a/test/data/strategy.json b/test/data/strategy.json deleted file mode 100644 index 3396c49..0000000 --- a/test/data/strategy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "fail-fast": true, - "job-index": 0, - "job-total": 1, - "max-parallel": 1 - } \ No newline at end of file diff --git a/test/entrypoint_test.py b/test/entrypoint_test.py index a9a8158..dd1040b 100644 --- a/test/entrypoint_test.py +++ b/test/entrypoint_test.py @@ -1,264 +1,276 @@ -import unittest +"""UnitTest entrypoint file""" + import os -from unittest.mock import patch, call -from unittest.mock import MagicMock -from parameterized import parameterized +import unittest +from unittest.mock import call, patch + from click.testing import CliRunner +from parameterized import parameterized + from entrypoint import main -from action.main import Main class TestEntrypoint(unittest.TestCase): - ''' + """ entrypoint.py Unit Test. - - This test the link between cli argument/env. var. and + This test the link between cli argument/env. var. and how Main class is instancied. - ''' - - @patch('entrypoint.Main', spec=True) - def test_main_keepTemplate(self, MainClassMock): - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value - + """ + + @patch("entrypoint.Main", spec=True) + def test_main_keep_template(self, main_class_mock): + """ + entrypoint.main unittest: If keep_template option is used on the cli, + main class must be initialized with the keep_template property to true. + """ # Call the Method with keep_template runner = CliRunner() - result = runner.invoke(main, ['--keep_template']) - - #print(MainClassMock.call_args.kwargs.get('keep_template')) - self.assertTrue(MainClassMock.call_args.kwargs.get('keep_template')) + runner.invoke(main, ["--keep_template"]) + + self.assertTrue(main_class_mock.call_args.kwargs.get("keep_template")) # Call the Method without keep_template - result = runner.invoke(main) - self.assertFalse(MainClassMock.call_args.kwargs.get('keep_template')) - - @patch('entrypoint.Main', spec=True) - def test_main_envVar(self, MainClassMock): - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + runner.invoke(main) + self.assertFalse(main_class_mock.call_args.kwargs.get("keep_template")) + + @patch("entrypoint.Main", spec=True) + def test_main_en_var(self, main_class_mock): + """ + entrypoint.main unittest: If a var file is given, add_variables + method is called with the content of that file. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Add Some Content in a env file - with open("test.txt", 'w') as out: + with open("test.txt", "w", encoding="utf-8") as out: out.write("test_content") out.flush() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--var_file=test.txt"]) + runner.invoke(main, ["--var_file=test.txt"]) - mock_instance.addVariables.assert_called_with("test_content") - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_variables.assert_called_with("test_content") + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the previous call - mock_instance.addVariables.reset_mock() + mock_instance.add_variables.reset_mock() os.remove("test.txt") # Call the Method (click) runner = CliRunner() - result = runner.invoke(main) + runner.invoke(main) self.assertTrue( - call("test_content") not in mock_instance.addVariables.mock_calls, - f"addVariables is not called" + call("test_content") not in mock_instance.add_variables.mock_calls, + "add_variables is not called", ) - @patch('entrypoint.Main', spec=True) - def test_main_context_one(self, MainClassMock): - ''' - entrypoint.main unittest: If one context file is given addJsonSection method is called with its content for the context defiend by the name of the file. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + @patch("entrypoint.Main", spec=True) + def test_main_context_one(self, main_class_mock): + """ + entrypoint.main unittest: If one context file is given add_json_section + method is called with its content for the context defiend by the name of the file. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Add Some Content in a context file - with open("my_context.txt", 'w') as out: + with open("my_context.txt", "w", encoding="utf-8") as out: out.write("test_content") out.flush() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--context=my_context.txt"]) + runner.invoke(main, ["--context=my_context.txt"]) - mock_instance.addJsonSection.assert_called_with("my_context", "test_content") - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_json_section.assert_called_with("my_context", "test_content") + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the previous env var - mock_instance.addJsonSection.reset_mock() + mock_instance.add_json_section.reset_mock() os.remove("my_context.txt") # Call the Method (click) runner = CliRunner() - result = runner.invoke(main) + runner.invoke(main) self.assertTrue( - call("my_context", "test_content") not in mock_instance.addJsonSection.mock_calls, - f"addJsonSection is not called for the previous context" + call("my_context", "test_content") + not in mock_instance.add_json_section.mock_calls, + "add_json_section is not called for the previous context", ) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") - - @parameterized.expand([ - (""), - ("none\n") - ]) - @patch('entrypoint.Main', spec=True) - def test_main_context_null(self, content, MainClassMock): - ''' - entrypoint.main unittest: If context file content is empty or contains 'null\n', addJsonSection method is NOT called. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + self.assertTrue(mock_instance.render_all.called, "render_all is called") + + @parameterized.expand([(""), ("none\n")]) + @patch("entrypoint.Main", spec=True) + def test_main_context_null(self, content, main_class_mock): + """ + entrypoint.main unittest: If context file content is empty or + contains "null\n",add_json_section method is NOT called. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Add Some Content in a context file - with open("my_context.txt", 'w') as out: + with open("my_context.txt", "w", encoding="utf-8") as out: out.write(content) out.flush() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--context=my_context.txt"]) + runner.invoke(main, ["--context=my_context.txt"]) self.assertTrue( - call("my_context", "test_content") not in mock_instance.addJsonSection.mock_calls, - f"addJsonSection is not called for the previous context" + call("my_context", "test_content") + not in mock_instance.add_json_section.mock_calls, + "add_json_section is not called for the previous context", ) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the file os.remove("my_context.txt") - @patch('entrypoint.Main', spec=True) - def test_main_context_multiple(self, MainClassMock): - ''' - entrypoint.main unittest: If multiple context file is given addJsonSection method is called with each file content for the each context defined by the name of each file. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + @patch("entrypoint.Main", spec=True) + def test_main_context_multiple(self, main_class_mock): + """ + entrypoint.main unittest: If multiple context file is given add_json_section method is + called with each file content for the each context defined by the name of each file. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Add Some Content in a context file - with open("my_context1.txt", 'w') as out: + with open("my_context1.txt", "w", encoding="utf-8") as out: out.write("this is") out.flush() - with open("my_context2.txt", 'w') as out: + with open("my_context2.txt", "w", encoding="utf-8") as out: out.write("so funny") out.flush() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, ["--context=my_context1.txt", "--context=my_context2.txt"]) + runner.invoke(main, ["--context=my_context1.txt", "--context=my_context2.txt"]) # Test calls = [call("my_context1", "this is"), call("my_context2", "so funny")] - mock_instance.addJsonSection.assert_has_calls(calls, any_order=True) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_json_section.assert_has_calls(calls, any_order=True) + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Clean os.remove("my_context1.txt") os.remove("my_context2.txt") - @patch('entrypoint.Main', spec=True) - def test_main_data_file_no_format(self, MainClassMock): - ''' - entrypoint.main unittest: If data_file parameter is defined, addDataFile method is called with the file path. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + @patch("entrypoint.Main", spec=True) + def test_main_data_file_no_format(self, main_class_mock): + """ + entrypoint.main unittest: If data_file parameter is defined, + add_data_file method is called with the file path. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--data_file=my_file.json"]) + runner.invoke(main, ["--data_file=my_file.json"]) - mock_instance.addDataFile.assert_called_with("my_file.json", None) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_data_file.assert_called_with("my_file.json", None) + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the previous env var - mock_instance.addDataFile.reset_mock() + mock_instance.add_data_file.reset_mock() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main) + runner.invoke(main) self.assertTrue( - call("my_file.json", None) not in mock_instance.addDataFile.mock_calls, - f"addDataFile is not called for the previous context" + call("my_file.json", None) not in mock_instance.add_data_file.mock_calls, + "add_data_file is not called for the previous context", ) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + self.assertTrue(mock_instance.render_all.called, "render_all is called") - @patch('entrypoint.Main', spec=True) - def test_main_data_file_with_format(self, MainClassMock): - ''' - entrypoint.main unittest: If data_file parameter and data_format are defined, addDataFile method is called with the file path and the format. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + @patch("entrypoint.Main", spec=True) + def test_main_data_file_with_format(self, main_class_mock): + """ + entrypoint.main unittest: If data_file parameter and data_format are defined, + add_data_file method is called with the file path and the format. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--data_file=my_file.json", "--data_format=my_format"]) + runner.invoke(main, ["--data_file=my_file.json", "--data_format=my_format"]) - mock_instance.addDataFile.assert_called_with("my_file.json", "my_format") - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_data_file.assert_called_with("my_file.json", "my_format") + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the previous env var - mock_instance.addDataFile.reset_mock() + mock_instance.add_data_file.reset_mock() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main) + runner.invoke(main) self.assertTrue( - call("my_file.json", "my_format") not in mock_instance.addDataFile.mock_calls, - f"addDataFile is not called for the previous context" + call("my_file.json", "my_format") + not in mock_instance.add_data_file.mock_calls, + "add_data_file is not called for the previous context", ) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + self.assertTrue(mock_instance.render_all.called, "render_all is called") - @patch('entrypoint.Main', spec=True) - def test_main_url_file_no_format(self, MainClassMock): - ''' - entrypoint.main unittest: If data_url parameter is defined, addDataUrl method is called with the url. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + @patch("entrypoint.Main", spec=True) + def test_main_url_file_no_format(self, main_class_mock): + """ + entrypoint.main unittest: If data_url parameter is defined, + add_data_url method is called with the url. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--data_url=my_url"]) + runner.invoke(main, ["--data_url=my_url"]) - mock_instance.addDataUrl.assert_called_with("my_url", None) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_data_url.assert_called_with("my_url", None) + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the previous env var - mock_instance.addDataUrl.reset_mock() + mock_instance.add_data_url.reset_mock() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main) + runner.invoke(main) self.assertTrue( - call("my_url", None) not in mock_instance.addDataUrl.mock_calls, - f"addDataUrl is not called for the previous context" + call("my_url", None) not in mock_instance.add_data_url.mock_calls, + "add_data_url is not called for the previous context", ) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + self.assertTrue(mock_instance.render_all.called, "render_all is called") - @patch('entrypoint.Main', spec=True) - def test_main_urlfile_with_format(self, MainClassMock): - ''' - entrypoint.main unittest: If data_url parameter and data_url_format are defined, addDataUrl method is called with the url and the format. - ''' - # Get the mock instance for MainClassMock - mock_instance = MainClassMock.return_value + @patch("entrypoint.Main", spec=True) + def test_main_urlfile_with_format(self, main_class_mock): + """ + entrypoint.main unittest: If data_url parameter and data_url_format + are defined, add_data_url method is called with the url and the format. + """ + # Get the mock instance for main_class_mock + mock_instance = main_class_mock.return_value # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--data_url=my_url", "--data_url_format=my_format"]) + runner.invoke(main, ["--data_url=my_url", "--data_url_format=my_format"]) - mock_instance.addDataUrl.assert_called_with("my_url", "my_format") - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + mock_instance.add_data_url.assert_called_with("my_url", "my_format") + self.assertTrue(mock_instance.render_all.called, "render_all is called") # Remove the previous env var - mock_instance.addDataUrl.reset_mock() + mock_instance.add_data_url.reset_mock() # Call the Method (click) runner = CliRunner() - result = runner.invoke(main) + runner.invoke(main) self.assertTrue( - call("my_url", "my_format") not in mock_instance.addDataUrl.mock_calls, - f"addDataFile is not called for the previous context" + call("my_url", "my_format") not in mock_instance.add_data_url.mock_calls, + "add_data_file is not called for the previous context", ) - self.assertTrue(mock_instance.renderAll.called, "renderAll is called") \ No newline at end of file + self.assertTrue(mock_instance.render_all.called, "render_all is called") diff --git a/test/install_bats.sh b/test/install_bats.sh index e6715a1..87ccc5d 100755 --- a/test/install_bats.sh +++ b/test/install_bats.sh @@ -1,5 +1,5 @@ #!/bin/bash git -c advice.detachedHead=false clone --depth 1 --branch v1.11.0 https://github.com/bats-core/bats-core.git test/bats -git -c advice.detachedHead=false clone --depth 1 --branch v2.1.0 https://github.com/bats-core/bats-assert.git test/test_helper/bats-assert -git -c advice.detachedHead=false clone --depth 1 --branch v0.3.0 https://github.com/bats-core/bats-support.git test/test_helper/bats-support -git -c advice.detachedHead=false clone --depth 1 --branch v0.4.0 https://github.com/bats-core/bats-file.git test/test_helper/bats-file +git -c advice.detachedHead=false clone --depth 1 --branch v2.1.0 https://github.com/bats-core/bats-assert.git test/test_helper/bats-assert +git -c advice.detachedHead=false clone --depth 1 --branch v0.3.0 https://github.com/bats-core/bats-support.git test/test_helper/bats-support +git -c advice.detachedHead=false clone --depth 1 --branch v0.4.0 https://github.com/bats-core/bats-file.git test/test_helper/bats-file diff --git a/test/test_action.bats b/test/test_action.bats old mode 100644 new mode 100755 index 7ef1b4a..a433ed0 --- a/test/test_action.bats +++ b/test/test_action.bats @@ -1,9 +1,9 @@ #!/usr/bin/env bats setup() { - load 'test_helper/bats-support/load' - load 'test_helper/bats-assert/load' - load 'test_helper/bats-file/load' + load 'test_helper/bats-support/load' + load 'test_helper/bats-assert/load' + load 'test_helper/bats-file/load' } @test "Test Action with all possible entries" { diff --git a/test/url_data.yml b/test/url_data.yml index 8e63445..b63958c 100644 --- a/test/url_data.yml +++ b/test/url_data.yml @@ -1,3 +1,3 @@ url: URL_KEY_1: from_url - URL_KEY_2: it_is_working \ No newline at end of file + URL_KEY_2: it_is_working