From cd14bbc6296891fccc8c4ee72f0be8b17e4a7374 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Thu, 12 Sep 2024 23:17:36 +0200 Subject: [PATCH 01/15] feat: add public_cloud_config_info module --- galaxy.yml | 9 +- .../public_cloud_config_info/converge.yml | 14 +++ .../public_cloud_config_info/molecule.yml | 37 ++++++ molecule/public_cloud_config_info/prepare.yml | 9 ++ .../public_cloud_config_info/requirements.yml | 4 + plugins/README.md | 31 +++++ plugins/module_utils/infomaniak_api_client.py | 52 +++++++++ plugins/modules/public_cloud_config_info.py | 110 ++++++++++++++++++ 8 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 molecule/public_cloud_config_info/converge.yml create mode 100644 molecule/public_cloud_config_info/molecule.yml create mode 100644 molecule/public_cloud_config_info/prepare.yml create mode 100644 molecule/public_cloud_config_info/requirements.yml create mode 100644 plugins/README.md create mode 100644 plugins/module_utils/infomaniak_api_client.py create mode 100644 plugins/modules/public_cloud_config_info.py diff --git a/galaxy.yml b/galaxy.yml index 47e2822..0310ea4 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: ednz_cloud -name: hashistack -version: 0.7.0 +name: infomaniak +version: 0.0.0 readme: README.md authors: - Bertrand Lanson @@ -11,9 +11,9 @@ license_file: "LICENSE" # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: ["tools"] +tags: ["infomaniak", "cloud"] dependencies: {} -repository: https://git.ednz.fr/ansible-collections/hashistack +repository: https://git.ednz.fr/ansible-collections/infomaniak documentation: http://docs.example.com homepage: http://example.com issues: http://example.com/issue/tracker @@ -22,7 +22,6 @@ issues: http://example.com/issue/tracker # uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', # and '.git' are always filtered. Mutually exclusive with 'manifest' build_ignore: - - assets** - .gitea** # A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a # list of MANIFEST.in style diff --git a/molecule/public_cloud_config_info/converge.yml b/molecule/public_cloud_config_info/converge.yml new file mode 100644 index 0000000..23ebaae --- /dev/null +++ b/molecule/public_cloud_config_info/converge.yml @@ -0,0 +1,14 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Run module" + ednz_cloud.infomaniak.public_cloud_config_info: + api_token: "{{ lookup('ansible.builtin.env', 'IK_API_TOKEN') }}" + account_id: "{{ lookup('ansible.builtin.env', 'IK_ACCOUNT_ID') }}" + register: _result + + - name: "Print result" + ansible.builtin.debug: + msg: "{{ _result }}" diff --git a/molecule/public_cloud_config_info/molecule.yml b/molecule/public_cloud_config_info/molecule.yml new file mode 100644 index 0000000..23fa83b --- /dev/null +++ b/molecule/public_cloud_config_info/molecule.yml @@ -0,0 +1,37 @@ +--- +dependency: + name: galaxy + options: + requirements-file: ./requirements.yml +driver: + name: docker +platforms: + - name: instance + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true +provisioner: + name: ansible + config_options: + defaults: + remote_tmp: /tmp/.ansible +verifier: + name: ansible +scenario: + name: public_cloud_config_info + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/public_cloud_config_info/prepare.yml b/molecule/public_cloud_config_info/prepare.yml new file mode 100644 index 0000000..f917aaa --- /dev/null +++ b/molecule/public_cloud_config_info/prepare.yml @@ -0,0 +1,9 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Install python requirements" + ansible.builtin.pip: + name: requests + executable: pip3 diff --git a/molecule/public_cloud_config_info/requirements.yml b/molecule/public_cloud_config_info/requirements.yml new file mode 100644 index 0000000..9180507 --- /dev/null +++ b/molecule/public_cloud_config_info/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +collections: + - name: ednz_cloud.infomaniak diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..6260634 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.15/plugins/plugins.html). diff --git a/plugins/module_utils/infomaniak_api_client.py b/plugins/module_utils/infomaniak_api_client.py new file mode 100644 index 0000000..d7e442a --- /dev/null +++ b/plugins/module_utils/infomaniak_api_client.py @@ -0,0 +1,52 @@ +import requests +import json + +INFOMANIAK_API_URL = "https://api.infomaniak.com" + + +class InfomaniakAPIClient: + def __init__(self, api_version, api_token): + self.base_url = f"{INFOMANIAK_API_URL}/{api_version}" + self.headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + "Accept": "application/json", + } + + def _request(self, method, endpoint, data=None, params=None): + url = f"{self.base_url}{endpoint}" + try: + response = requests.request( + method=method, + url=url, + headers=self.headers, + json=data, + params=params, + timeout=10, + ) + response.raise_for_status() + try: + return response.json() + except json.JSONDecodeError: + raise Exception(f"Invalid JSON response: {response.text}") + except requests.exceptions.HTTPError as http_err: + error_content = response.content if response else "No response" + raise Exception( + f"HTTP error occurred: {http_err}, Response content: {error_content}" + ) + except requests.exceptions.RequestException as req_err: + raise Exception(f"Request error occurred: {req_err}") + except Exception as err: + raise Exception(f"An error occurred: {err}") + + def get(self, endpoint, params=None): + return self._request("GET", endpoint, params=params) + + def post(self, endpoint, data=None): + return self._request("POST", endpoint, data=data) + + def put(self, endpoint, data=None): + return self._request("PUT", endpoint, data=data) + + def delete(self, endpoint): + return self._request("DELETE", endpoint) diff --git a/plugins/modules/public_cloud_config_info.py b/plugins/modules/public_cloud_config_info.py new file mode 100644 index 0000000..2fd77f4 --- /dev/null +++ b/plugins/modules/public_cloud_config_info.py @@ -0,0 +1,110 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: ednz_cloud.infomaniak.public_cloud_config_info + +short_description: Retrieves the public cloud configuration from the Infomaniak API. + +version_added: "0.1.0" + +description: + - This module queries the Infomaniak API to retrieve the public cloud configuration for a given account. + - It uses the provided API token for authentication and the account ID as a query parameter. + +requirements: + - C(requests) + +options: + api_token: + description: The API token used to authenticate with the Infomaniak API. + required: true + type: str + account_id: + description: The account ID for which to query the public cloud configuration. + required: true + type: str + +author: + - Bertrand Lanson (@ednxzu) +""" + +EXAMPLES = r""" +# Example: Retrieve public cloud configuration for account ID 965060 +- name: Get public cloud configuration + public_cloud_config_info: + api_token: "your_api_token" + account_id: "123456" +""" + +RETURN = r""" +config: + description: + - The public cloud configuration for the specified account. + type: dict + returned: always + sample: + free_tier: 300 + free_tier_used: 24.34 + account_resource_level: 2 + valid_from: 1707584356 + valid_to: 1717192799 + project_count: 2 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ednz_cloud.infomaniak.plugins.module_utils.infomaniak_api_client import ( + InfomaniakAPIClient, +) + + +def get_public_cloud_config(client: InfomaniakAPIClient, account_id: str): + endpoint = "/public_clouds/config" + params = {"account_id": account_id} + response = client.get(endpoint, params=params) + + if response.get("result") != "success": + raise Exception(f"API request failed with result: {response.get('result')}") + + return response.get("data", {}) + + +def run_module(): + module_args = dict( + api_token=dict(type="str", required=True, no_log=True), + account_id=dict(type="str", required=True), + ) + + result = dict(changed=False, public_cloud_config={}) + + module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) + + api_token = module.params["api_token"] + account_id = module.params["account_id"] + + if module.check_mode: + module.exit_json( + changed=False, + msg="Check mode: No changes made, would retrieve public cloud config.", + ) + + client = InfomaniakAPIClient(api_version="1", api_token=api_token) + + try: + config_data = get_public_cloud_config(client, account_id) + result["config"] = config_data + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() -- 2.45.2 From f5fc5bb8456b371e947fd35d4233527682815f56 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 13:13:43 +0200 Subject: [PATCH 02/15] feat: add unit tests for public_cloud_config_info module --- .gitea/workflows/.gitkeep | 0 .gitea/workflows/pull-request-open.yml | 53 ++++ .gitea/workflows/release.yml | 54 ++++ .gitignore | 19 +- molecule/.gitkeep | 0 plugins/.gitkeep | 0 plugins/modules/public_cloud_config_info.py | 2 +- tests/requirements.txt | 5 + .../modules/test_public_cloud_config_info.py | 292 ++++++++++++++++++ 9 files changed, 416 insertions(+), 9 deletions(-) delete mode 100644 .gitea/workflows/.gitkeep create mode 100644 .gitea/workflows/pull-request-open.yml create mode 100644 .gitea/workflows/release.yml delete mode 100644 molecule/.gitkeep delete mode 100644 plugins/.gitkeep create mode 100644 tests/requirements.txt create mode 100644 tests/unit/plugins/modules/test_public_cloud_config_info.py diff --git a/.gitea/workflows/.gitkeep b/.gitea/workflows/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml new file mode 100644 index 0000000..5037893 --- /dev/null +++ b/.gitea/workflows/pull-request-open.yml @@ -0,0 +1,53 @@ +--- +name: pull-requests-open +on: + pull_request: + types: + - opened + - edited + - synchronize + branches: + - main + - develop + +jobs: + commit-history-check: + name: Check commit compliance + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Install commitizen + run: pip3 install commitizen + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Verify commit message compliance + run: | + echo "cz check --rev-range origin/${{ gitea.event.pull_request.base.ref }}.." + cz check --rev-range origin/${{ gitea.event.pull_request.base.ref }}.. + shell: bash + working-directory: ${{ gitea.workspace }} + + run-tests: + name: Run tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install requirements + run: pip3 install -r tests/requirements.txt + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Run ansible unit tests + uses: ansible-community/ansible-test-gh-action@release/v1 + with: + ansible-core-version: v2.17.4 + testing-type: units + collection-src-directory: ${{ gitea.workspace }} diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..e05f041 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,54 @@ +--- +name: release +on: + push: + branches: + - main + +jobs: + do-release: + if: "!startsWith(github.event.head_commit.message, 'bump:')" + runs-on: ubuntu-latest + name: Bump version and create changelog with commitizen + steps: + - name: Get secrets from vault + id: import-secrets + uses: hashicorp/vault-action@v3 + with: + url: "https://vault.ednz.fr" + method: approle + roleId: ${{ secrets.VAULT_APPROLE_ID }} + secretId: ${{ secrets.VAULT_APPROLE_SECRET_ID }} + secrets: | + kv/data/applications/gitea/users/actions username | GITEA_ACTIONS_USERNAME ; + kv/data/applications/gitea/users/actions token_write | GITEA_ACTIONS_TOKEN ; + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_TOKEN }} + + - name: Install commitizen + run: pip3 install commitizen + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Configure git credentials + uses: oleksiyrudenko/gha-git-credentials@v2 + with: + global: true + name: "Gitea-Actions Bot" + email: "gitea-actions@ednz.fr" + actor: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_USERNAME }} + token: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_TOKEN }} + + - name: Do release + run: cz -nr 21 bump --yes + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Push release + run: git push && git push --tags + shell: bash + working-directory: ${{ gitea.workspace }} diff --git a/.gitignore b/.gitignore index cb534dd..2ae2ad8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ # ignore molecule/testinfra pycache **/__pycache__ + +# ignore vscode files .vscode -roles/ednz_cloud.* -vault_config.yml -consul_config.yml -**/certificates/** -**/secrets/credentials.yml -**/secrets/credentials.decrypt.yml -**/secrets/vault.yml -**/.ansible-vault + +# ignore test output (auto-generated) +tests/output/** + +# ignore virtual environments +.env +.venv +env +venv diff --git a/molecule/.gitkeep b/molecule/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/.gitkeep b/plugins/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/modules/public_cloud_config_info.py b/plugins/modules/public_cloud_config_info.py index 2fd77f4..030ffc5 100644 --- a/plugins/modules/public_cloud_config_info.py +++ b/plugins/modules/public_cloud_config_info.py @@ -79,7 +79,7 @@ def run_module(): account_id=dict(type="str", required=True), ) - result = dict(changed=False, public_cloud_config={}) + result = dict(changed=False, config={}) module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..01e96bc --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,5 @@ +ansible==10.3.0 +requests==2.32.3 +pytest==8.3.3 +pytest-xdist==3.6.1 +coverage==7.3.2 diff --git a/tests/unit/plugins/modules/test_public_cloud_config_info.py b/tests/unit/plugins/modules/test_public_cloud_config_info.py new file mode 100644 index 0000000..1e401c8 --- /dev/null +++ b/tests/unit/plugins/modules/test_public_cloud_config_info.py @@ -0,0 +1,292 @@ +import unittest +from unittest.mock import patch, MagicMock +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ednz_cloud.infomaniak.plugins.modules import ( + public_cloud_config_info, +) + + +class TestPublicCloudConfigInfoModule(unittest.TestCase): + """ + Unit tests for the public_cloud_config_info Ansible module. + """ + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" + ) + def test_successful_retrieval(self, mock_client_class, mock_ansible_module): + """ + Test a successful retrieval of public cloud configuration. + """ + # Mock API response data + api_response_data = { + "result": "success", + "data": { + "free_tier": 300, + "free_tier_used": 24.34, + "account_resource_level": 2, + "valid_from": 1707584356, + "valid_to": 1717192799, + "project_count": 2, + }, + } + + # Mock the InfomaniakAPIClient instance + mock_client_instance = MagicMock() + mock_client_instance.get.return_value = api_response_data + mock_client_class.return_value = mock_client_instance + + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + mock_module_instance.check_mode = False # Simulate non-check mode + mock_ansible_module.return_value = mock_module_instance + + # Mock exit_json to prevent actual exit + mock_module_instance.exit_json = MagicMock() + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert that the InfomaniakAPIClient was initialized with the correct token + mock_client_class.assert_called_with( + api_version="1", api_token="mock_api_token" + ) + + # Assert that the correct API call was made + mock_client_instance.get.assert_called_once_with( + "/public_clouds/config", params={"account_id": "mock_account_id"} + ) + + # Assert exit_json is called once with the expected data + mock_module_instance.exit_json.assert_called_once_with( + changed=False, config=api_response_data["data"] + ) + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" + ) + def test_api_failure(self, mock_client_class, mock_ansible_module): + """ + Test failure scenario where API returns an error result. + """ + + # Mock API failure response + mock_client_instance = MagicMock() + mock_client_instance.get.side_effect = Exception( + "API request failed with result: error" + ) + mock_client_class.return_value = mock_client_instance + + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "invalid_account_id", + } + mock_module_instance.check_mode = False # Simulate non-check mode + mock_ansible_module.return_value = mock_module_instance + + # Mock fail_json to prevent actual exit + mock_module_instance.fail_json = MagicMock() + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert fail_json was called with the expected error message + mock_module_instance.fail_json.assert_called_once_with( + msg="API request failed with result: error" + ) + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + def test_missing_required_params(self, mock_ansible_module): + """ + Test behavior when required parameters are missing. + """ + # Mock the AnsibleModule instance + mock_module_instance = MagicMock() + + # Simulate missing 'api_token' in the parameters + mock_module_instance.params = { + "account_id": "mock_account_id", # Missing api_token + } + + # Simulate Ansible behavior: fail_json should be called when 'api_token' is missing + mock_ansible_module.return_value = mock_module_instance + + # Execute the run_module function and expect KeyError + with self.assertRaises(KeyError): + public_cloud_config_info.run_module() + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" + ) + def test_empty_api_response(self, mock_client_class, mock_ansible_module): + """ + Test behavior when the API returns an empty response. + """ + # Mock API response data + api_response_data = {"result": "success", "data": {}} # Empty data + + # Mock the InfomaniakAPIClient instance + mock_client_instance = MagicMock() + mock_client_instance.get.return_value = api_response_data + mock_client_class.return_value = mock_client_instance + + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + mock_module_instance.check_mode = False # Simulate non-check mode + mock_ansible_module.return_value = mock_module_instance + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert exit_json is called once with empty config data + mock_module_instance.exit_json.assert_called_once_with(changed=False, config={}) + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" + ) + def test_unexpected_api_response_format( + self, mock_client_class, mock_ansible_module + ): + """ + Test behavior when the API response format is unexpected. + """ + # Mock an unexpected API response (missing 'result') + api_response_data = { + "data": { + "free_tier": 300, + } + } + + # Mock the InfomaniakAPIClient instance + mock_client_instance = MagicMock() + mock_client_instance.get.return_value = api_response_data + mock_client_class.return_value = mock_client_instance + + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + mock_ansible_module.return_value = mock_module_instance + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert fail_json is called with an appropriate error + mock_module_instance.fail_json.assert_called_once_with( + msg="API request failed with result: None" + ) + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + def test_check_mode(self, mock_ansible_module): + """ + Test behavior when check_mode is enabled. + """ + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + mock_module_instance.check_mode = True # Simulate check mode + mock_ansible_module.return_value = mock_module_instance + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert exit_json is called with the check_mode message + mock_module_instance.exit_json.assert_called_once_with( + changed=False, + msg="Check mode: No changes made, would retrieve public cloud config.", + ) + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" + ) + def test_network_timeout(self, mock_client_class, mock_ansible_module): + """ + Test behavior when a network timeout occurs. + """ + # Simulate a network timeout error + mock_client_instance = MagicMock() + mock_client_instance.get.side_effect = Exception("Request timeout") + mock_client_class.return_value = mock_client_instance + + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + mock_ansible_module.return_value = mock_module_instance + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert fail_json is called with the timeout error message + mock_module_instance.fail_json.assert_called_once_with(msg="Request timeout") + + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + ) + @patch( + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" + ) + def test_invalid_json_response(self, mock_client_class, mock_ansible_module): + """ + Test behavior when the API returns an invalid JSON response. + """ + # Mock the InfomaniakAPIClient instance to raise a JSONDecodeError + mock_client_instance = MagicMock() + mock_client_instance.get.side_effect = Exception("Invalid JSON response") + mock_client_class.return_value = mock_client_instance + + # Mock AnsibleModule parameters + mock_module_instance = MagicMock() + mock_module_instance.params = { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + mock_ansible_module.return_value = mock_module_instance + + # Execute the run_module function + public_cloud_config_info.run_module() + + # Assert fail_json is called with the appropriate error message + mock_module_instance.fail_json.assert_called_once_with( + msg="Invalid JSON response" + ) + + +if __name__ == "__main__": + unittest.main() -- 2.45.2 From 452a1cd25256d6288f7b2895e3238283f329929a Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 13:15:07 +0200 Subject: [PATCH 03/15] ci: add dependencies between PR jobs --- .gitea/workflows/pull-request-open.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 5037893..264c8d0 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -36,6 +36,7 @@ jobs: run-tests: name: Run tests runs-on: ubuntu-latest + needs: commit-history-check steps: - name: Checkout uses: actions/checkout@v4 -- 2.45.2 From 84989cfbee629d48eb5ae4343dc68cc0bc1f8479 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 13:18:01 +0200 Subject: [PATCH 04/15] ci: make a simple test setup for now --- .gitea/workflows/pull-request-open.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 264c8d0..4ab33eb 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -47,8 +47,11 @@ jobs: working-directory: ${{ gitea.workspace }} - name: Run ansible unit tests - uses: ansible-community/ansible-test-gh-action@release/v1 - with: - ansible-core-version: v2.17.4 - testing-type: units - collection-src-directory: ${{ gitea.workspace }} + run: ansible-test units --coverage + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Print coverage informations + run: ansible-test coverage report + shell: bash + working-directory: ${{ gitea.workspace }} -- 2.45.2 From 194d5ed1f04f32642dbfaee137fc356cbb86ab03 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 20:47:50 +0200 Subject: [PATCH 05/15] ci: fix tests --- .gitea/workflows/pull-request-open.yml | 7 + .../modules/test_public_cloud_config_info.py | 347 +++++++++--------- 2 files changed, 172 insertions(+), 182 deletions(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 4ab33eb..fcc53ca 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -46,6 +46,13 @@ jobs: shell: bash working-directory: ${{ gitea.workspace }} + - name: Setup testing environment + run: | + mkdir -p /tmp/ansible_collections/ednz_cloud + ln -s ${{ gitea.workspace }} /tmp/ansible_collections/ednz_cloud/infomaniak + shell: bash + working-directory: ${{ gitea.workspace }} + - name: Run ansible unit tests run: ansible-test units --coverage shell: bash diff --git a/tests/unit/plugins/modules/test_public_cloud_config_info.py b/tests/unit/plugins/modules/test_public_cloud_config_info.py index 1e401c8..89db23a 100644 --- a/tests/unit/plugins/modules/test_public_cloud_config_info.py +++ b/tests/unit/plugins/modules/test_public_cloud_config_info.py @@ -1,11 +1,40 @@ import unittest +import json +import pytest from unittest.mock import patch, MagicMock from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import basic +from ansible.module_utils.common.text.converters import to_bytes from ansible_collections.ednz_cloud.infomaniak.plugins.modules import ( public_cloud_config_info, ) +def set_module_args(args): + if "_ansible_remote_tmp" not in args: + args["_ansible_remote_tmp"] = "/tmp" + if "_ansible_keep_remote_files" not in args: + args["_ansible_keep_remote_files"] = False + + args = json.dumps({"ANSIBLE_MODULE_ARGS": args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + def __init__(self, kwargs): + self.kwargs = kwargs + + +def exit_json(*args, **kwargs): + if "changed" not in kwargs: + kwargs["changed"] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + raise SystemExit(json.dumps(kwargs)) + + class TestPublicCloudConfigInfoModule(unittest.TestCase): """ Unit tests for the public_cloud_config_info Ansible module. @@ -21,7 +50,6 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): """ Test a successful retrieval of public cloud configuration. """ - # Mock API response data api_response_data = { "result": "success", "data": { @@ -34,209 +62,139 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): }, } - # Mock the InfomaniakAPIClient instance mock_client_instance = MagicMock() mock_client_instance.get.return_value = api_response_data mock_client_class.return_value = mock_client_instance - # Mock AnsibleModule parameters mock_module_instance = MagicMock() mock_module_instance.params = { "api_token": "mock_api_token", "account_id": "mock_account_id", } - mock_module_instance.check_mode = False # Simulate non-check mode + mock_module_instance.check_mode = False mock_ansible_module.return_value = mock_module_instance - # Mock exit_json to prevent actual exit mock_module_instance.exit_json = MagicMock() - # Execute the run_module function public_cloud_config_info.run_module() - # Assert that the InfomaniakAPIClient was initialized with the correct token mock_client_class.assert_called_with( api_version="1", api_token="mock_api_token" ) - # Assert that the correct API call was made mock_client_instance.get.assert_called_once_with( "/public_clouds/config", params={"account_id": "mock_account_id"} ) - # Assert exit_json is called once with the expected data mock_module_instance.exit_json.assert_called_once_with( changed=False, config=api_response_data["data"] ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" - ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" - ) - def test_api_failure(self, mock_client_class, mock_ansible_module): - """ - Test failure scenario where API returns an error result. - """ + # def test_successful_retrieval(self): + # """ + # Test a successful retrieval of public cloud configuration. + # """ + # set_module_args( + # { + # "api_token": "mock_api_token", + # "account_id": "mock_account_id", + # } + # ) + # + # api_response_data = { + # "result": "success", + # "data": { + # "free_tier": 300, + # "free_tier_used": 24.34, + # "account_resource_level": 2, + # "valid_from": 1707584356, + # "valid_to": 1717192799, + # "project_count": 2, + # }, + # } + # + # self.mock_client_instance.get.return_value = api_response_data + # + # with patch.object( + # AnsibleModule, "exit_json", side_effect=exit_json + # ) as mock_exit_json: + # with patch.object( + # AnsibleModule, "fail_json", side_effect=fail_json + # ) as mock_fail_json: + # with pytest.raises(AnsibleExitJson) as e: + # public_cloud_config_info.main() + # mock_fail_json.assert_not_called() + # result = e.value.kwargs + # assert result["changed"] is False + # assert "config" in result + # assert result["config"] == api_response_data["data"] + # mock_exit_json.assert_called_once() + # self.mock_client_class.assert_called_with( + # api_version="1", api_token="mock_api_token" + # ) + # self.mock_client_instance.get.assert_called_once_with( + # "/public_clouds/config", + # params={"account_id": "mock_account_id"}, + # ) - # Mock API failure response - mock_client_instance = MagicMock() - mock_client_instance.get.side_effect = Exception( - "API request failed with result: error" - ) - mock_client_class.return_value = mock_client_instance - - # Mock AnsibleModule parameters - mock_module_instance = MagicMock() - mock_module_instance.params = { - "api_token": "mock_api_token", - "account_id": "invalid_account_id", - } - mock_module_instance.check_mode = False # Simulate non-check mode - mock_ansible_module.return_value = mock_module_instance - - # Mock fail_json to prevent actual exit - mock_module_instance.fail_json = MagicMock() - - # Execute the run_module function - public_cloud_config_info.run_module() - - # Assert fail_json was called with the expected error message - mock_module_instance.fail_json.assert_called_once_with( - msg="API request failed with result: error" - ) - - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" - ) - def test_missing_required_params(self, mock_ansible_module): + def test_missing_required_params(self): """ Test behavior when required parameters are missing. """ - # Mock the AnsibleModule instance - mock_module_instance = MagicMock() - - # Simulate missing 'api_token' in the parameters - mock_module_instance.params = { - "account_id": "mock_account_id", # Missing api_token - } - - # Simulate Ansible behavior: fail_json should be called when 'api_token' is missing - mock_ansible_module.return_value = mock_module_instance - - # Execute the run_module function and expect KeyError - with self.assertRaises(KeyError): - public_cloud_config_info.run_module() - - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" - ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" - ) - def test_empty_api_response(self, mock_client_class, mock_ansible_module): - """ - Test behavior when the API returns an empty response. - """ - # Mock API response data - api_response_data = {"result": "success", "data": {}} # Empty data - - # Mock the InfomaniakAPIClient instance - mock_client_instance = MagicMock() - mock_client_instance.get.return_value = api_response_data - mock_client_class.return_value = mock_client_instance - - # Mock AnsibleModule parameters - mock_module_instance = MagicMock() - mock_module_instance.params = { - "api_token": "mock_api_token", - "account_id": "mock_account_id", - } - mock_module_instance.check_mode = False # Simulate non-check mode - mock_ansible_module.return_value = mock_module_instance - - # Execute the run_module function - public_cloud_config_info.run_module() - - # Assert exit_json is called once with empty config data - mock_module_instance.exit_json.assert_called_once_with(changed=False, config={}) - - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" - ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" - ) - def test_unexpected_api_response_format( - self, mock_client_class, mock_ansible_module - ): - """ - Test behavior when the API response format is unexpected. - """ - # Mock an unexpected API response (missing 'result') - api_response_data = { - "data": { - "free_tier": 300, + set_module_args( + { + "api_token": "mock_api_token", + # 'account_id' is missing } - } - - # Mock the InfomaniakAPIClient instance - mock_client_instance = MagicMock() - mock_client_instance.get.return_value = api_response_data - mock_client_class.return_value = mock_client_instance - - # Mock AnsibleModule parameters - mock_module_instance = MagicMock() - mock_module_instance.params = { - "api_token": "mock_api_token", - "account_id": "mock_account_id", - } - mock_ansible_module.return_value = mock_module_instance - - # Execute the run_module function - public_cloud_config_info.run_module() - - # Assert fail_json is called with an appropriate error - mock_module_instance.fail_json.assert_called_once_with( - msg="API request failed with result: None" ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" - ) - def test_check_mode(self, mock_ansible_module): - """ - Test behavior when check_mode is enabled. - """ - # Mock AnsibleModule parameters - mock_module_instance = MagicMock() - mock_module_instance.params = { - "api_token": "mock_api_token", - "account_id": "mock_account_id", - } - mock_module_instance.check_mode = True # Simulate check mode - mock_ansible_module.return_value = mock_module_instance + with patch.object(AnsibleModule, "fail_json", side_effect=fail_json): + with pytest.raises(SystemExit) as e: + public_cloud_config_info.main() - # Execute the run_module function - public_cloud_config_info.run_module() + captured_output = json.loads(e.value.args[0]) - # Assert exit_json is called with the check_mode message - mock_module_instance.exit_json.assert_called_once_with( - changed=False, - msg="Check mode: No changes made, would retrieve public cloud config.", + assert "msg" in captured_output + assert "missing required arguments: account_id" in captured_output["msg"] + + def test_check_mode(self): + """ + Test the behavior in check mode, where no changes should be made. + """ + set_module_args( + { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + "_ansible_check_mode": True, + } ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" - ) - @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" - ) - def test_network_timeout(self, mock_client_class, mock_ansible_module): + with patch.object( + AnsibleModule, "exit_json", side_effect=exit_json + ) as mock_exit_json: + with pytest.raises(AnsibleExitJson) as e: + public_cloud_config_info.main() + + mock_exit_json.assert_called_once_with( + changed=False, + msg="Check mode: No changes made, would retrieve public cloud config.", + ) + + def test_network_timeout(self): """ Test behavior when a network timeout occurs. """ + + set_module_args( + { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + ) + + with patch.object(AnsibleModule, "fail_json", side_effect=fail_json): + with pytest.raises(SystemExit) as e: + public_cloud_config_info.main() # Simulate a network timeout error mock_client_instance = MagicMock() mock_client_instance.get.side_effect = Exception("Request timeout") @@ -257,36 +215,61 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): mock_module_instance.fail_json.assert_called_once_with(msg="Request timeout") @patch( - "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule" + "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" ) + def test_network_timeout(self, mock_client_class): + """ + Test behavior when a network timeout occurs. + """ + # Simulate a network timeout error + mock_client_instance = MagicMock() + mock_client_instance.get.side_effect = Exception("Request timeout") + mock_client_class.return_value = mock_client_instance + + set_module_args( + { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } + ) + + with patch.object( + AnsibleModule, "fail_json", side_effect=fail_json + ) as mock_fail_json: + with pytest.raises(SystemExit) as e: + public_cloud_config_info.main() + + captured_output = json.loads(e.value.args[0]) + assert "msg" in captured_output + assert captured_output["msg"] == "Request timeout" + mock_fail_json.assert_called_once() + @patch( "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" ) - def test_invalid_json_response(self, mock_client_class, mock_ansible_module): + def test_invalid_json_response(self, mock_client_class): """ Test behavior when the API returns an invalid JSON response. """ - # Mock the InfomaniakAPIClient instance to raise a JSONDecodeError mock_client_instance = MagicMock() mock_client_instance.get.side_effect = Exception("Invalid JSON response") mock_client_class.return_value = mock_client_instance - # Mock AnsibleModule parameters - mock_module_instance = MagicMock() - mock_module_instance.params = { - "api_token": "mock_api_token", - "account_id": "mock_account_id", - } - mock_ansible_module.return_value = mock_module_instance - - # Execute the run_module function - public_cloud_config_info.run_module() - - # Assert fail_json is called with the appropriate error message - mock_module_instance.fail_json.assert_called_once_with( - msg="Invalid JSON response" + # Set module args with the required parameters + set_module_args( + { + "api_token": "mock_api_token", + "account_id": "mock_account_id", + } ) + with patch.object( + AnsibleModule, "fail_json", side_effect=fail_json + ) as mock_fail_json: + with pytest.raises(SystemExit) as e: + public_cloud_config_info.main() -if __name__ == "__main__": - unittest.main() + captured_output = json.loads(e.value.args[0]) + assert "msg" in captured_output + assert captured_output["msg"] == "Invalid JSON response" + mock_fail_json.assert_called_once() -- 2.45.2 From 883140deea2cd9407a26a07999b769e1b0542e84 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 20:48:21 +0200 Subject: [PATCH 06/15] ci: fix working dir for running tests --- .gitea/workflows/pull-request-open.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index fcc53ca..9b1563a 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -61,4 +61,4 @@ jobs: - name: Print coverage informations run: ansible-test coverage report shell: bash - working-directory: ${{ gitea.workspace }} + working-directory: /tmp/ansible_collections/ednz_cloud/infomaniak -- 2.45.2 From 74c9f8b2f8ca84e80fdf26eebd346f72d9e50301 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 20:49:33 +0200 Subject: [PATCH 07/15] ci: fix working dir for running tests --- .gitea/workflows/pull-request-open.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 9b1563a..5852f9d 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -56,7 +56,7 @@ jobs: - name: Run ansible unit tests run: ansible-test units --coverage shell: bash - working-directory: ${{ gitea.workspace }} + working-directory: /tmp/ansible_collections/ednz_cloud/infomaniak - name: Print coverage informations run: ansible-test coverage report -- 2.45.2 From 29675007db5d5ddc62e29ffd9af2b3fc7d18343a Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 20:53:19 +0200 Subject: [PATCH 08/15] ci: install collection locally to run tests --- .gitea/workflows/pull-request-open.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 5852f9d..fd9d3ff 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -47,18 +47,16 @@ jobs: working-directory: ${{ gitea.workspace }} - name: Setup testing environment - run: | - mkdir -p /tmp/ansible_collections/ednz_cloud - ln -s ${{ gitea.workspace }} /tmp/ansible_collections/ednz_cloud/infomaniak + run: ansible-galaxy collection install ${{ gitea.workspace }} shell: bash working-directory: ${{ gitea.workspace }} - name: Run ansible unit tests run: ansible-test units --coverage shell: bash - working-directory: /tmp/ansible_collections/ednz_cloud/infomaniak + working-directory: ${{ gitea.workspace }} - name: Print coverage informations run: ansible-test coverage report shell: bash - working-directory: /tmp/ansible_collections/ednz_cloud/infomaniak + working-directory: ${{ gitea.workspace }} -- 2.45.2 From a0e45be6919af985aa1b7f33c8f4ae89edc3eca6 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 21:09:31 +0200 Subject: [PATCH 09/15] ci: test ansible-test action --- .gitea/workflows/pull-request-open.yml | 28 +++++++++----------------- tests/{ => unit}/requirements.txt | 0 2 files changed, 10 insertions(+), 18 deletions(-) rename tests/{ => unit}/requirements.txt (100%) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index fd9d3ff..a203c7f 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -41,22 +41,14 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Install requirements - run: pip3 install -r tests/requirements.txt - shell: bash - working-directory: ${{ gitea.workspace }} + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: "3.10" - - name: Setup testing environment - run: ansible-galaxy collection install ${{ gitea.workspace }} - shell: bash - working-directory: ${{ gitea.workspace }} - - - name: Run ansible unit tests - run: ansible-test units --coverage - shell: bash - working-directory: ${{ gitea.workspace }} - - - name: Print coverage informations - run: ansible-test coverage report - shell: bash - working-directory: ${{ gitea.workspace }} + - name: Run unit tests + uses: ansible-community/ansible-test-gh-action@release/v1 + with: + ansible-core-version: stable-2.14 + target-python-version: 3.10 + testing-type: units diff --git a/tests/requirements.txt b/tests/unit/requirements.txt similarity index 100% rename from tests/requirements.txt rename to tests/unit/requirements.txt -- 2.45.2 From 8bcc020346a4cbf4f153f06a5b645099c6ce6894 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 21:16:00 +0200 Subject: [PATCH 10/15] ci: test with failing tests --- .../modules/test_public_cloud_config_info.py | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/tests/unit/plugins/modules/test_public_cloud_config_info.py b/tests/unit/plugins/modules/test_public_cloud_config_info.py index 89db23a..abd6109 100644 --- a/tests/unit/plugins/modules/test_public_cloud_config_info.py +++ b/tests/unit/plugins/modules/test_public_cloud_config_info.py @@ -180,40 +180,6 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): msg="Check mode: No changes made, would retrieve public cloud config.", ) - def test_network_timeout(self): - """ - Test behavior when a network timeout occurs. - """ - - set_module_args( - { - "api_token": "mock_api_token", - "account_id": "mock_account_id", - } - ) - - with patch.object(AnsibleModule, "fail_json", side_effect=fail_json): - with pytest.raises(SystemExit) as e: - public_cloud_config_info.main() - # Simulate a network timeout error - mock_client_instance = MagicMock() - mock_client_instance.get.side_effect = Exception("Request timeout") - mock_client_class.return_value = mock_client_instance - - # Mock AnsibleModule parameters - mock_module_instance = MagicMock() - mock_module_instance.params = { - "api_token": "mock_api_token", - "account_id": "mock_account_id", - } - mock_ansible_module.return_value = mock_module_instance - - # Execute the run_module function - public_cloud_config_info.run_module() - - # Assert fail_json is called with the timeout error message - mock_module_instance.fail_json.assert_called_once_with(msg="Request timeout") - @patch( "ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient" ) @@ -241,7 +207,7 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): captured_output = json.loads(e.value.args[0]) assert "msg" in captured_output - assert captured_output["msg"] == "Request timeout" + assert not captured_output["msg"] == "Request timeout" mock_fail_json.assert_called_once() @patch( -- 2.45.2 From 9ed53d92a4db1519ad873939322f036116d22b4f Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 21:36:28 +0200 Subject: [PATCH 11/15] ci: more testing CI --- .gitea/workflows/pull-request-open.yml | 25 ++++++++++++++----- .../modules/test_public_cloud_config_info.py | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index a203c7f..794707e 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -46,9 +46,22 @@ jobs: with: python-version: "3.10" - - name: Run unit tests - uses: ansible-community/ansible-test-gh-action@release/v1 - with: - ansible-core-version: stable-2.14 - target-python-version: 3.10 - testing-type: units + - name: Install requirements + run: pip3 install -r tests/requirements.txt + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Setup testing environment + run: ansible-galaxy collection install ${{ gitea.workspace }} + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Run ansible unit tests + run: ansible-test units --coverage + shell: bash + working-directory: ${{ gitea.workspace }} + + - name: Print coverage informations + run: ansible-test coverage report + shell: bash + working-directory: ${{ gitea.workspace }} diff --git a/tests/unit/plugins/modules/test_public_cloud_config_info.py b/tests/unit/plugins/modules/test_public_cloud_config_info.py index abd6109..032d6da 100644 --- a/tests/unit/plugins/modules/test_public_cloud_config_info.py +++ b/tests/unit/plugins/modules/test_public_cloud_config_info.py @@ -207,7 +207,7 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): captured_output = json.loads(e.value.args[0]) assert "msg" in captured_output - assert not captured_output["msg"] == "Request timeout" + assert captured_output["msg"] == "Request timeout" mock_fail_json.assert_called_once() @patch( -- 2.45.2 From b7e65abd3d8ea65d34cbb142b9b74525a7adb9b4 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 21:37:40 +0200 Subject: [PATCH 12/15] ci: replace requirement file for unit tests --- tests/{unit => }/requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{unit => }/requirements.txt (100%) diff --git a/tests/unit/requirements.txt b/tests/requirements.txt similarity index 100% rename from tests/unit/requirements.txt rename to tests/requirements.txt -- 2.45.2 From 83d275f313451f832103b8a507c42a3f10995cc5 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 21:40:36 +0200 Subject: [PATCH 13/15] ci: some test path --- .gitea/workflows/pull-request-open.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 794707e..9c90494 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -59,9 +59,9 @@ jobs: - name: Run ansible unit tests run: ansible-test units --coverage shell: bash - working-directory: ${{ gitea.workspace }} + working-directory: /root/.ansible/collections/ednz_cloud/infomaniak - name: Print coverage informations run: ansible-test coverage report shell: bash - working-directory: ${{ gitea.workspace }} + working-directory: /root/.ansible/collections/ednz_cloud/infomaniak -- 2.45.2 From 2df6309bf172c17921ee5513c7dd87ed887386f4 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sat, 14 Sep 2024 21:41:43 +0200 Subject: [PATCH 14/15] ci: some test path --- .gitea/workflows/pull-request-open.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml index 9c90494..575895a 100644 --- a/.gitea/workflows/pull-request-open.yml +++ b/.gitea/workflows/pull-request-open.yml @@ -59,9 +59,9 @@ jobs: - name: Run ansible unit tests run: ansible-test units --coverage shell: bash - working-directory: /root/.ansible/collections/ednz_cloud/infomaniak + working-directory: /root/.ansible/collections/ansible_collections/ednz_cloud/infomaniak - name: Print coverage informations run: ansible-test coverage report shell: bash - working-directory: /root/.ansible/collections/ednz_cloud/infomaniak + working-directory: /root/.ansible/collections/ansible_collections/ednz_cloud/infomaniak -- 2.45.2 From ab996acb36293938005bb65d2af1ba667f5b6e6b Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Sun, 15 Sep 2024 15:38:00 +0200 Subject: [PATCH 15/15] test: remove unused tests --- .../modules/test_public_cloud_config_info.py | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/tests/unit/plugins/modules/test_public_cloud_config_info.py b/tests/unit/plugins/modules/test_public_cloud_config_info.py index 032d6da..23c88ce 100644 --- a/tests/unit/plugins/modules/test_public_cloud_config_info.py +++ b/tests/unit/plugins/modules/test_public_cloud_config_info.py @@ -1,7 +1,7 @@ import unittest -import json import pytest -from unittest.mock import patch, MagicMock +import json +from unittest.mock import patch, MagicMock, Mock from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import basic from ansible.module_utils.common.text.converters import to_bytes @@ -90,53 +90,6 @@ class TestPublicCloudConfigInfoModule(unittest.TestCase): changed=False, config=api_response_data["data"] ) - # def test_successful_retrieval(self): - # """ - # Test a successful retrieval of public cloud configuration. - # """ - # set_module_args( - # { - # "api_token": "mock_api_token", - # "account_id": "mock_account_id", - # } - # ) - # - # api_response_data = { - # "result": "success", - # "data": { - # "free_tier": 300, - # "free_tier_used": 24.34, - # "account_resource_level": 2, - # "valid_from": 1707584356, - # "valid_to": 1717192799, - # "project_count": 2, - # }, - # } - # - # self.mock_client_instance.get.return_value = api_response_data - # - # with patch.object( - # AnsibleModule, "exit_json", side_effect=exit_json - # ) as mock_exit_json: - # with patch.object( - # AnsibleModule, "fail_json", side_effect=fail_json - # ) as mock_fail_json: - # with pytest.raises(AnsibleExitJson) as e: - # public_cloud_config_info.main() - # mock_fail_json.assert_not_called() - # result = e.value.kwargs - # assert result["changed"] is False - # assert "config" in result - # assert result["config"] == api_response_data["data"] - # mock_exit_json.assert_called_once() - # self.mock_client_class.assert_called_with( - # api_version="1", api_token="mock_api_token" - # ) - # self.mock_client_instance.get.assert_called_once_with( - # "/public_clouds/config", - # params={"account_id": "mock_account_id"}, - # ) - def test_missing_required_params(self): """ Test behavior when required parameters are missing. -- 2.45.2