From 29c1fc721803d48d8d3071ed71740e2a4848b2a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:55:31 +0000 Subject: [PATCH 1/3] build(deps): bump pytest-cov from 5.0.0 to 6.0.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 5.0.0 to 6.0.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v5.0.0...v6.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- test/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements.txt b/test/requirements.txt index 635b7c8..8068f7c 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,4 +1,4 @@ coverage==7.6.4 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 parameterized==0.9.0 \ No newline at end of file From 31a98f2fd74124b59103b833d0dbe201d003a1e8 Mon Sep 17 00:00:00 2001 From: Fabien Letort Date: Fri, 8 Nov 2024 10:01:36 +0100 Subject: [PATCH 2/3] Rework with a base class --- action/main.py | 7 +- action/parser.py | 68 ++++++++++++------- action/test/parser_test.py | 136 ++++++++++++++++++++++++------------- entrypoint.py | 7 +- 4 files changed, 144 insertions(+), 74 deletions(-) diff --git a/action/main.py b/action/main.py index e826a56..fac45c2 100644 --- a/action/main.py +++ b/action/main.py @@ -1,7 +1,7 @@ import os import json from jinja2 import Template, Environment, FileSystemLoader -from .parser import Parser +from .parser import FileParser class Main: def __init__(self, extensions=('.j2'), basepath='./', keep_template=False): @@ -48,9 +48,12 @@ def addJsonSection(self, sectionName, jsonContent): self.data[sectionName] = data def addDataFile(self, file_path, file_format=None): - parser = Parser(file_path, file_format) + parser = FileParser(file_path, file_format) content = parser.parse() self.data.update(content) + + def addDataUrl(self, url, data_format=None): + pass def renderFile(self, filePath): with open(f"{filePath}".rsplit(".", 1)[0], 'w') as out: diff --git a/action/parser.py b/action/parser.py index e62a0da..a0644b5 100644 --- a/action/parser.py +++ b/action/parser.py @@ -1,37 +1,34 @@ import os import json import yaml +import urllib.request import configparser from pathlib import Path +from abc import ABC, abstractmethod -class Parser: +class Parser(ABC): - def __init__(self, file_path, file_format=None): - if file_format and file_format not in self.FORMATS.keys(): + 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()}") - if not file_format: - file_format = self._getFormatFromExtension(file_path) - self.file_format = file_format - self.file_path = file_path + self.format = format + + @abstractmethod + def load(self): + pass def parse(self): - with open(self.file_path, "r") as f: - file_content = f.read() - if self.file_format: - content = getattr(Parser, self.FORMATS[self.file_format])(file_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: + content = getattr(FileParser, self.FORMATS[self.format])(self.content) else: - content = self._parse_generic(file_content) + content = self._parse_generic(self.content) return content - - @staticmethod - def _getFormatFromExtension(file_path): - path = Path(file_path) - extension = path.suffix.lower().lstrip(".") - if extension in Parser.FORMATS.keys(): - return extension - return None @staticmethod def _parse_ini(content): @@ -62,19 +59,19 @@ def _parse_env(content): @staticmethod def _parse_generic(content): try: - return ('ini',Parser._parse_ini(content)) + return ('ini',FileParser._parse_ini(content)) except configparser.Error: pass try: - return ('json',Parser._parse_json(content)) + return ('json',FileParser._parse_json(content)) except json.JSONDecodeError: pass try: - return ('env',Parser._parse_env(content)) + return ('env',FileParser._parse_env(content)) except ValueError: pass try: - return ('yaml',Parser._parse_yaml(content)) + return ('yaml',FileParser._parse_yaml(content)) except yaml.YAMLError: pass @@ -86,4 +83,25 @@ def _parse_generic(content): 'yml': '_parse_yaml', 'yaml': '_parse_yaml', 'env': '_parse_env' - } \ No newline at end of file + } + +class FileParser(Parser): + + 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: + self.content = f.read() + if not self.format: + self.format = self._getFormatFromExtension(self.file_path) + return self.content + + @staticmethod + def _getFormatFromExtension(file_path): + path = Path(file_path) + extension = path.suffix.lower().lstrip(".") + if extension in FileParser.FORMATS.keys(): + return extension + return None diff --git a/action/test/parser_test.py b/action/test/parser_test.py index e3b7caa..be78836 100644 --- a/action/test/parser_test.py +++ b/action/test/parser_test.py @@ -1,10 +1,17 @@ import os import unittest from parameterized import parameterized -from action.parser import Parser +from action.parser import FileParser, Parser class TestParser(unittest.TestCase): - + + class StubParser(Parser): + def __init__(self, format=None): + super().__init__(format) + + def load(self): + return "CONTENT_TO_PARSE" + @parameterized.expand([ ('ini'), ('json'), @@ -17,33 +24,15 @@ def test_init_with_managed_format(self, format): Parser.__init__ unittest: Init with managed file format is successfull. File format: : ini, json, yaml, yml, env. ''' - p = Parser("file_path", format) - self.assertTrue(p.file_format == format, "format is stored") - self.assertTrue(p.file_path == "file_path", "file_path is stored") + p = TestParser.StubParser(format) + self.assertTrue(p.format == format, "format is stored") def test_init_with_unmanaged_format(self): ''' - Parser.__init__ unittest: Init with unmanaged file format raise exception. - File format: : ini, json, yaml, yml, env. + Parser.__init__ unittest: Parse with defined unmanaged file format raise exception. ''' with self.assertRaises(ValueError): - p = Parser("file_path", "strange_format") - - @parameterized.expand([ - ('ini'), - ('json'), - ('yaml'), - ('yml'), - ('env') - ]) - def test_init_with_format_found_in_extension(self, extension): - ''' - Parser.__init__ unittest: Init without format specified but with a official extension defines the format. - Possible extension: : ini, json, yaml, yml, env. - ''' - p = Parser(f"file_path.{extension}") - self.assertTrue(p.file_format == extension, "format is stored") - self.assertTrue(p.file_path == f"file_path.{extension}", "file_path is stored") + p = TestParser.StubParser("strange_format") @parameterized.expand([ ('ini', 'action.parser.Parser._parse_ini'), @@ -54,7 +43,7 @@ def test_init_with_format_found_in_extension(self, extension): ]) def test_parse_call_correct_parser(self, format, method): ''' - Parser.parse unittest: Parse with a preconfigured format must call directly the dedicated parser. + Parser.parse unittest: Parse with a preconfigured format must load the content and call directly the dedicated parser. Test Data: | Format | Waited Parser | | ---- | ------------ | @@ -64,26 +53,18 @@ def test_parse_call_correct_parser(self, format, method): | yml | action.parser.Parser._parse_yaml | | env | action.parser.Parser._parse_env | ''' - with open("test.txt", 'w') as file: - file.write("CONTENT_TO_PARSE") - - p = Parser("test.txt", format) + p = TestParser.StubParser(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") - os.remove("test.txt") - def test_parse_call_unknow_parser(self): ''' - Parser.parse unittest: Parse with a unknow format must call directly the generic parser. - ''' - with open("test.txt", 'w') as file: - file.write("CONTENT_TO_PARSE") - - p = Parser("test.txt") + 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 : ret = p.parse() self.assertTrue(mock.called, f"action.parser.Parser._parse_generic is called") @@ -99,7 +80,7 @@ def test_parse_ini(self): TEST1 = tata TEST2 = titi """ - ret = Parser._parse_ini(ini_content) + ret = TestParser.StubParser._parse_ini(ini_content) self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) def test_parse_json(self): @@ -107,7 +88,7 @@ def test_parse_json(self): Parser._parse_json unittest: Parse JSON content is successfull. ''' json_content="{\"exemple\": {\"TEST1\": \"tata\", \"TEST2\": \"titi\"}}" - ret = Parser._parse_json(json_content) + ret = TestParser.StubParser._parse_json(json_content) self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) def test_parse_yaml(self): @@ -119,7 +100,7 @@ def test_parse_yaml(self): TEST1: tata TEST2: titi """ - ret = Parser._parse_yaml(yaml_content) + ret = TestParser.StubParser._parse_yaml(yaml_content) self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) def test_parse_env(self): @@ -130,7 +111,7 @@ def test_parse_env(self): TEST1=tata TEST2=titi """ - ret = Parser._parse_env(env_content) + ret = TestParser.StubParser._parse_env(env_content) self.assertEqual({'TEST1': 'tata', 'TEST2': 'titi'}.items(), ret.items()) def test_parse_generic_ini(self): @@ -142,7 +123,7 @@ def test_parse_generic_ini(self): TEST1 = tata TEST2 = titi """ - format,ret = Parser._parse_generic(ini_content) + format,ret = TestParser.StubParser._parse_generic(ini_content) self.assertEqual(format, 'ini') self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) @@ -151,7 +132,7 @@ 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 = Parser._parse_generic(json_content) + format,ret = TestParser.StubParser._parse_generic(json_content) self.assertEqual(format, 'json') self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) @@ -164,7 +145,7 @@ def test_parse_generic_yaml(self): TEST1: tata TEST2: titi """ - format,ret = Parser._parse_generic(yaml_content) + format,ret = TestParser.StubParser._parse_generic(yaml_content) self.assertEqual(format, 'yaml') self.assertEqual({'exemple': {'TEST1': 'tata', 'TEST2': 'titi'}}.items(), ret.items()) @@ -176,7 +157,7 @@ def test_parse_generic_env(self): TEST1=tata TEST2=titi """ - format,ret = Parser._parse_generic(env_content) + format,ret = TestParser.StubParser._parse_generic(env_content) self.assertEqual(format, 'env') self.assertEqual({'TEST1': 'tata', 'TEST2': 'titi'}.items(), ret.items()) @@ -186,5 +167,68 @@ def test_parse_generic_not_managed(self): ''' unknow_content='asd : fgh : ghj' with self.assertRaises(ValueError): - format,ret = Parser._parse_generic(unknow_content) + format,ret = TestParser.StubParser._parse_generic(unknow_content) + + +class TestFileParser(unittest.TestCase): + + @parameterized.expand([ + ('ini'), + ('json'), + ('yaml'), + ('yml'), + ('env') + ]) + def test_init_with_managed_format(self, format): + ''' + Parser.__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") + + def test_init_with_unmanaged_format(self): + ''' + Parser.__init__ unittest: Parse with defined unmanaged file format raise exception. + ''' + with self.assertRaises(ValueError): + p = 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: + 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") + + os.remove("test.txt") + + @parameterized.expand([ + ('ini'), + ('json'), + ('yaml'), + ('yml'), + ('env') + ]) + def test_load_with_format_found_in_extension(self, extension): + ''' + Parser.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: + 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") + + os.remove(f"file_path.{extension}") + diff --git a/entrypoint.py b/entrypoint.py index 78a677a..7bc6e5a 100644 --- a/entrypoint.py +++ b/entrypoint.py @@ -10,7 +10,9 @@ @click.option('--context', multiple=True, default=[]) @click.option('--data_file', default=None) @click.option('--data_format', default=None) -def main(keep_template, var_file, context, data_file, data_format): +@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) if var_file: @@ -27,6 +29,9 @@ def main(keep_template, var_file, context, data_file, data_format): if data_file: main.addDataFile(data_file, data_format) + + if data_url: + main.addDataUrl(data_url, data_url_format) main.renderAll() From b59db11738291c9fbabb7e121075481703c890fc Mon Sep 17 00:00:00 2001 From: Fabien Letort Date: Fri, 8 Nov 2024 12:08:45 +0100 Subject: [PATCH 3/3] gh-028: Add Data Source on URL link --- .github/workflows/test.yml | 11 ++++++ README.md | 6 +-- action.yml | 20 +++++++++- action/main.py | 6 ++- action/parser.py | 24 ++++++++++++ action/test/main_test.py | 19 +++++++++- action/test/parser_test.py | 76 ++++++++++++++++++++++++++++++++++++-- test/entrypoint_test.py | 70 +++++++++++++++++++++++++++++++---- test/url_data.yml | 3 ++ 9 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 test/url_data.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 02813e7..a8bbc32 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -153,11 +153,20 @@ jobs: echo "${{ env.FILE_KEY_1_VALUE }}" >> expected echo "{{ exemple.FILE_KEY_2 }}" >> all_test.j2 echo "${{ env.FILE_KEY_2_VALUE }}" >> expected + echo "{{ url.URL_KEY_1 }}" >> all_test.j2 + echo "from_url" >> expected + echo "{{ url.URL_KEY_2 }}" >> all_test.j2 + echo "it_is_working" >> expected echo "" >> all_test.j2 cat all_test.j2 cat expected + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + - name: Test Local Action id: test-action uses: ./ @@ -165,6 +174,8 @@ jobs: #keep_template: true data_file: test-data.yml data_format: yaml + data_url: https://raw.githubusercontent.com/${{ github.repository }}/refs/heads/${{ steps.extract_branch.outputs.branch }}/test/url_data.yml + data_url_format: yaml variables: | TEST1=${{ env.VAR_TEST1_VALUE }} TEST2=${{ env.VAR_TEST2_VALUE }} diff --git a/README.md b/README.md index 3f5331a..221dfac 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,9 @@ Note: All strategy information key contains dashes that must me marked as unders | `variables` | Variable to substitute in the jinja templates. Must be Key, value pairs in .env file format (key=value). | "" | | `keep_template` | Put to `true` to keep original template file. | `false` | | `data_file` | Source file contening inputs variable for the jinja template. | "" | -| `data_format` | 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. | `automatic` | - - +| `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 710fc14..d54bc77 100644 --- a/action.yml +++ b/action.yml @@ -22,6 +22,14 @@ inputs: 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: '' + 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.' + default: automatic runs: using: 'composite' steps: @@ -53,7 +61,17 @@ runs: data_format="--data_format=${{inputs.data_format}}" fi fi - python3 ${{github.action_path}}/entrypoint.py ${keep_template} ${data_file} ${data_format} \ + data_url="" + data_url_format="" + if [[ ! -z "${{inputs.data_url}}" ]];then + data_url="--data_url=${{inputs.data_url}}" + if [[ "${{inputs.data_url_format}}" != "automatic" ]]; then + data_url_format="--data_url_format=${{inputs.data_url_format}}" + fi + fi + python3 ${{github.action_path}}/entrypoint.py ${keep_template} \ + ${data_file} ${data_format} \ + ${data_url} ${data_url_format} \ --var_file ${{ runner.temp }}/build_logs/inputs_variables.env \ --context ${{ runner.temp }}/build_logs/github.json \ --context ${{ runner.temp }}/build_logs/job.json \ diff --git a/action/main.py b/action/main.py index fac45c2..57252a0 100644 --- a/action/main.py +++ b/action/main.py @@ -1,7 +1,7 @@ import os import json from jinja2 import Template, Environment, FileSystemLoader -from .parser import FileParser +from .parser import FileParser, UrlParser class Main: def __init__(self, extensions=('.j2'), basepath='./', keep_template=False): @@ -53,7 +53,9 @@ def addDataFile(self, file_path, file_format=None): self.data.update(content) def addDataUrl(self, url, data_format=None): - pass + 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: diff --git a/action/parser.py b/action/parser.py index a0644b5..1d9ace8 100644 --- a/action/parser.py +++ b/action/parser.py @@ -85,6 +85,29 @@ def _parse_generic(content): 'env': '_parse_env' } +class UrlParser(Parser): + 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()): + 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' + } + + class FileParser(Parser): def __init__(self, file_path, file_format=None): @@ -105,3 +128,4 @@ def _getFormatFromExtension(file_path): if extension in FileParser.FORMATS.keys(): return extension return None + diff --git a/action/test/main_test.py b/action/test/main_test.py index 93bc1d9..f65d173 100644 --- a/action/test/main_test.py +++ b/action/test/main_test.py @@ -80,7 +80,7 @@ def test_addJsonSection_dictValue(self): 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.Parser', spec=True) + @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. @@ -91,12 +91,27 @@ def test_addDataFile(self, ParserMock): m = Main() m.addDataFile("file_path", "my_format") - print(ParserMock.mock_calls) ParserMock.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. + ''' + # Get the mock instance for Parser + mock_instance = ParserMock.return_value + mock_instance.parse = MagicMock(return_value={'TEST': 'toto'}) + + m = Main() + m.addDataUrl("url", "my_format") + + ParserMock.assert_called_with("url", "my_format") + self.assertTrue(mock_instance.parse.called, "parse is called") + self.assertTrue({'TEST': 'toto'}.items() <= m.data.items()) + def test_renderFile_jinja2(self): ''' Main.renderFile unittest: Check if file is rendered by jinja 2 and orginal file removed. diff --git a/action/test/parser_test.py b/action/test/parser_test.py index be78836..12c2d21 100644 --- a/action/test/parser_test.py +++ b/action/test/parser_test.py @@ -1,7 +1,7 @@ import os import unittest from parameterized import parameterized -from action.parser import FileParser, Parser +from action.parser import FileParser, Parser, UrlParser class TestParser(unittest.TestCase): @@ -181,7 +181,7 @@ class TestFileParser(unittest.TestCase): ]) def test_init_with_managed_format(self, format): ''' - Parser.__init__ unittest: Init with managed file format stores the file path and is successfull. + 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) @@ -190,7 +190,7 @@ def test_init_with_managed_format(self, format): def test_init_with_unmanaged_format(self): ''' - Parser.__init__ unittest: Parse with defined unmanaged file format raise exception. + FileParser.__init__ unittest: Init with defined unmanaged file format raise exception. ''' with self.assertRaises(ValueError): p = FileParser("file_path", "strange_format") @@ -218,7 +218,7 @@ def test_load_with_predefinedformat(self): ]) def test_load_with_format_found_in_extension(self, extension): ''' - Parser.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: @@ -232,3 +232,71 @@ def test_load_with_format_found_in_extension(self, extension): os.remove(f"file_path.{extension}") +class TestUrlParser(unittest.TestCase): + + @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. + File format: : ini, json, yaml, yml, env. + ''' + p = UrlParser("url", format) + self.assertEqual(p.url, "url", "Init store url without any check") + self.assertTrue(p.format == 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") + + @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. + ''' + cm = unittest.mock.MagicMock() + 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. + 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.__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") + diff --git a/test/entrypoint_test.py b/test/entrypoint_test.py index 6ba0649..a9a8158 100644 --- a/test/entrypoint_test.py +++ b/test/entrypoint_test.py @@ -163,18 +163,18 @@ def test_main_data_file_no_format(self, MainClassMock): runner = CliRunner() result = runner.invoke(main, [f"--data_file=my_file.json"]) - mock_instance.addJsonSection.addDataFile("my_file.json", None) + mock_instance.addDataFile.assert_called_with("my_file.json", None) self.assertTrue(mock_instance.renderAll.called, "renderAll is called") # Remove the previous env var - mock_instance.addJsonSection.reset_mock() + mock_instance.addDataFile.reset_mock() # Call the Method (click) runner = CliRunner() result = runner.invoke(main) self.assertTrue( - call("my_file.json", None) not in mock_instance.addJsonSection.mock_calls, + call("my_file.json", None) not in mock_instance.addDataFile.mock_calls, f"addDataFile is not called for the previous context" ) self.assertTrue(mock_instance.renderAll.called, "renderAll is called") @@ -189,20 +189,76 @@ def test_main_data_file_with_format(self, MainClassMock): # Call the Method (click) runner = CliRunner() - result = runner.invoke(main, [f"--data_file=my_file.json --data_format=my_format"]) + result = runner.invoke(main, [f"--data_file=my_file.json", "--data_format=my_format"]) - mock_instance.addJsonSection.addDataFile("my_file.json", "my_format") + mock_instance.addDataFile.assert_called_with("my_file.json", "my_format") self.assertTrue(mock_instance.renderAll.called, "renderAll is called") # Remove the previous env var - mock_instance.addJsonSection.reset_mock() + mock_instance.addDataFile.reset_mock() + + # Call the Method (click) + runner = CliRunner() + result = 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" + ) + self.assertTrue(mock_instance.renderAll.called, "renderAll 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 + + # Call the Method (click) + runner = CliRunner() + result = runner.invoke(main, [f"--data_url=my_url"]) + + mock_instance.addDataUrl.assert_called_with("my_url", None) + self.assertTrue(mock_instance.renderAll.called, "renderAll is called") + + # Remove the previous env var + mock_instance.addDataUrl.reset_mock() + + # Call the Method (click) + runner = CliRunner() + result = 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" + ) + self.assertTrue(mock_instance.renderAll.called, "renderAll 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 + + # Call the Method (click) + runner = CliRunner() + result = runner.invoke(main, [f"--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") + + # Remove the previous env var + mock_instance.addDataUrl.reset_mock() # Call the Method (click) runner = CliRunner() result = runner.invoke(main) self.assertTrue( - call("my_file.json", "my_format") not in mock_instance.addJsonSection.mock_calls, + call("my_url", "my_format") not in mock_instance.addDataUrl.mock_calls, f"addDataFile is not called for the previous context" ) self.assertTrue(mock_instance.renderAll.called, "renderAll is called") \ No newline at end of file diff --git a/test/url_data.yml b/test/url_data.yml new file mode 100644 index 0000000..8e63445 --- /dev/null +++ b/test/url_data.yml @@ -0,0 +1,3 @@ +url: + URL_KEY_1: from_url + URL_KEY_2: it_is_working \ No newline at end of file