mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
docs(dev-guide): improve quality redrive (#7718)
Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com> Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
# Unit Tests
|
||||
# Unit Tests for Prowler Checks
|
||||
|
||||
The unit tests for the Prowler checks varies between each provider supported.
|
||||
Unit tests for Prowler checks vary based on the provider being evaluated.
|
||||
|
||||
Here we left some good reads about unit testing and things we've learnt through all the process.
|
||||
Below are key resources and insights gained throughout the testing process.
|
||||
|
||||
**Python Testing**
|
||||
|
||||
- https://docs.python-guide.org/writing/tests/
|
||||
|
||||
**Where to patch**
|
||||
**Where to Patch**
|
||||
|
||||
- https://docs.python.org/3/library/unittest.mock.html#where-to-patch
|
||||
- https://stackoverflow.com/questions/893333/multiple-variables-in-a-with-statement
|
||||
- https://docs.python.org/3/reference/compound_stmts.html#the-with-statement
|
||||
|
||||
**Utils to trace mocking and test execution**
|
||||
**Utilities for Tracing Mocking and Test Execution**
|
||||
|
||||
- https://news.ycombinator.com/item?id=36054868
|
||||
- https://docs.python.org/3/library/sys.html#sys.settrace
|
||||
@@ -22,175 +22,251 @@ Here we left some good reads about unit testing and things we've learnt through
|
||||
|
||||
## General Recommendations
|
||||
|
||||
When creating tests for some provider's checks we follow these guidelines trying to cover as much test scenarios as possible:
|
||||
When writing tests for Prowler provider checks, follow these guidelines to maximize coverage across test scenarios:
|
||||
|
||||
1. Create a test without resource to generate 0 findings, because Prowler will generate 0 findings if a service does not contain the resources the check is looking for audit.
|
||||
2. Create test to generate both a `PASS` and a `FAIL` result.
|
||||
3. Create tests with more than 1 resource to evaluate how the check behaves and if the number of findings is right.
|
||||
1. Zero Findings Scenario:
|
||||
Develop tests where no resources exist. Prowler returns zero findings if the audited service lacks the required resources.
|
||||
|
||||
## How to run Prowler tests
|
||||
2. Positive and Negative Outcomes:
|
||||
Create tests that generate both a passing (`PASS`) and a failing (`FAIL`) result.
|
||||
|
||||
To run the Prowler test suite you need to install the testing dependencies already included in the `pyproject.toml` file. If you didn't install it yet please read the developer guide introduction [here](./introduction.md#get-the-code-and-install-all-dependencies).
|
||||
3. Multi-Resource Evaluations:
|
||||
Design tests with multiple resources to verify check behavior and ensure the correct number of findings.
|
||||
|
||||
Then in the project's root path execute `pytest -n auto -vvv -s -x` or use the `Makefile` with `make test`.
|
||||
## Running Prowler Tests
|
||||
|
||||
Other commands to run tests:
|
||||
To execute the Prowler test suite, install the necessary dependencies listed in the `pyproject.toml` file.
|
||||
|
||||
- Run tests for a provider: `pytest -n auto -vvv -s -x tests/providers/<provider>/services`
|
||||
- Run tests for a provider service: `pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>`
|
||||
- Run tests for a provider check: `pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>/<check>`
|
||||
### Prerequisites
|
||||
|
||||
If you have not installed Prowler yet, refer to the [developer guide introduction](./introduction.md#get-the-code-and-install-all-dependencies).
|
||||
|
||||
### Executing Tests
|
||||
|
||||
Navigate to the project's root directory and execute: `pytest -n auto -vvv -s -x`
|
||||
|
||||
Alternatively, use:
|
||||
`Makefile` with `make test`.
|
||||
|
||||
Other Commands for Running Tests
|
||||
|
||||
- Running tests for a provider:
|
||||
`pytest -n auto -vvv -s -x tests/providers/<provider>/services`
|
||||
- Running tests for a provider service:
|
||||
`pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>`
|
||||
- Running tests for a provider check:
|
||||
`pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>/<check>`
|
||||
|
||||
???+ note
|
||||
Refer to the [pytest documentation](https://docs.pytest.org/en/7.1.x/getting-started.html) documentation for more information.
|
||||
Refer to the [pytest documentation](https://docs.pytest.org/en/7.1.x/getting-started.html) for more details.
|
||||
|
||||
## AWS
|
||||
## AWS Testing Approaches
|
||||
|
||||
For the AWS provider we have ways to test a Prowler check based on the following criteria:
|
||||
For AWS provider, different testing approaches apply based on API coverage based on several criteria.
|
||||
|
||||
???+ note
|
||||
We use and contribute to the [Moto](https://github.com/getmoto/moto) library which allows us to easily mock out tests based on AWS infrastructure. **It's awesome!**
|
||||
Prowler leverages and contributes to the[Moto](https://github.com/getmoto/moto) library for mocking AWS infrastructure in tests.
|
||||
|
||||
- AWS API calls covered by [Moto](https://github.com/getmoto/moto):
|
||||
- Service tests with `@mock_aws`
|
||||
- Checks tests with `@mock_aws`
|
||||
- AWS API calls not covered by Moto:
|
||||
- Service test with `mock_make_api_call`
|
||||
- Checks tests with [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock)
|
||||
- AWS API calls partially covered by Moto:
|
||||
- Service test with `@mock_aws` and `mock_make_api_call`
|
||||
- Checks tests with `@mock_aws` and `mock_make_api_call`
|
||||
- AWS API Calls Covered by [Moto](https://github.com/getmoto/moto):
|
||||
- Service Tests: `@mock_aws`
|
||||
- Checks Tests: `@mock_aws`
|
||||
|
||||
In the following section we are going to explain all of the above scenarios with examples. The main difference between those scenarios comes from if the [Moto](https://github.com/getmoto/moto) library covers the AWS API calls made by the service. You can check the covered API calls [here](https://github.com/getmoto/moto/blob/master/IMPLEMENTATION_COVERAGE.md).
|
||||
- AWS API Calls Not Covered by Moto:
|
||||
- Service Tests: `mock_make_api_call`
|
||||
- Checks Tests: [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock)
|
||||
|
||||
### Checks
|
||||
- AWS API Calls Partially Covered by Moto:
|
||||
- Service Tests: `@mock_aws` and `mock_make_api_call`
|
||||
- Check Tests: `@mock_aws` and `mock_make_api_call`
|
||||
|
||||
For the AWS tests examples we are going to use the tests for the `iam_password_policy_uppercase` check.
|
||||
#### AWS Check Testing Scenarios
|
||||
|
||||
This section is going to be divided based on the API coverage of the [Moto](https://github.com/getmoto/moto) library.
|
||||
The following section provides examples for each testing scenario. The primary distinction between these scenarios depends on whether the [Moto](https://github.com/getmoto/moto) library covers the AWS API calls made by the service. You can review the supported API calls [here](https://github.com/getmoto/moto/blob/master/IMPLEMENTATION_COVERAGE.md).
|
||||
|
||||
#### API calls covered
|
||||
### AWS Check Testing Approach
|
||||
|
||||
If the [Moto](https://github.com/getmoto/moto) library covers the API calls we want to test, we can use the `@mock_aws` decorator. This will mocked out all the API calls made to AWS keeping the state within the code decorated, in this case the test function.
|
||||
For AWS test examples, we reference tests for the `iam_password_policy_uppercase` check.
|
||||
|
||||
This section is categorized based on [Moto](https://github.com/getmoto/moto) API coverage.
|
||||
|
||||
#### API Calls Covered by Moto
|
||||
|
||||
When the [Moto](https://github.com/getmoto/moto) library supports the API calls required for testing, use the `@mock_aws` decorator. This ensures that all AWS API calls within the decorated function are properly mocked while maintaining state within the test.
|
||||
|
||||
```python
|
||||
# We need to import the unittest.mock to allow us to patch some objects
|
||||
# not to use shared ones between test, hence to isolate the test
|
||||
# Import unittest.mock to enable object patching
|
||||
# This prevents shared objects between tests, ensuring test isolation
|
||||
from unittest import mock
|
||||
|
||||
# Boto3 client and session to call the AWS APIs
|
||||
# Import Boto3 client and session for AWS API calls
|
||||
from boto3 import client, session
|
||||
|
||||
# Moto decorator
|
||||
# Import Moto decorator for mocking AWS services
|
||||
from moto import mock_aws
|
||||
|
||||
# Constants used
|
||||
# Define constants for test execution
|
||||
AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
# We always name the test classes like Test_<check_name>
|
||||
# Test class naming convention: Test_<check_name>
|
||||
class Test_iam_password_policy_uppercase:
|
||||
|
||||
# We include the Moto decorator
|
||||
# Apply the Moto decorator for AWS service mocking
|
||||
@mock_aws
|
||||
# We name the tests with test_<service>_<check_name>_<test_action>
|
||||
# Test naming convention: test_<service>_<check_name>_<test_action>
|
||||
def test_iam_password_policy_no_uppercase_flag(self):
|
||||
# First, we have to create an IAM client
|
||||
Steps
|
||||
|
||||
# Step 1: Create an IAM client for API calls in the specified region
|
||||
iam_client = client("iam", region_name=AWS_REGION)
|
||||
|
||||
# Then, since all the AWS accounts have a password
|
||||
# policy we want to set to False the RequireUppercaseCharacters
|
||||
# Step 2: Modify the account password policy to disable uppercase character enforcement
|
||||
|
||||
# Action: Setting RequireUppercaseCharacters to False
|
||||
|
||||
iam_client.update_account_password_policy(RequireUppercaseCharacters=False)
|
||||
|
||||
# The aws_provider is mocked using set_mocked_aws_provider to use it as the return of the get_global_provider method.
|
||||
# this mocked provider is defined in fixtures
|
||||
# Step 3: Mock the AWS provider to ensure isolated testing
|
||||
|
||||
# Using 'set_mocked_aws_provider' allows overriding the provider response
|
||||
# This mocked provider is defined in test fixtures
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
# The Prowler service import MUST be made within the decorated
|
||||
# code not to make real API calls to the AWS service.
|
||||
# Step 4: Ensure Prowler service imports occur within the decorated function
|
||||
# This prevents accidental real API calls to AWS during test execution
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
# Prowler for AWS uses a shared object called aws_provider where it stores
|
||||
# the info related with the provider
|
||||
# Mocking AWS Provider and IAM Client for Prowler Tests
|
||||
|
||||
#Prowler for AWS relies on a shared object, aws_provider, which stores provider-related information.
|
||||
|
||||
# To ensure proper test isolation and prevent shared objects between tests, we apply mocking techniques.
|
||||
|
||||
# Mocking Global AWS Provider
|
||||
|
||||
#To mock the global provider, we use mock.patch() to override the get_global_provider() method, ensuring aws_provider is the return value.
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
# We have to mock also the iam_client from the check to enforce that the iam_client used is the one
|
||||
# created within this check because patch != import, and if you execute tests in parallel some objects
|
||||
# can be already initialised hence the check won't be isolated
|
||||
mock.patch(
|
||||
|
||||
# Mocking IAM Client for Test Isolation
|
||||
|
||||
#In addition to mocking the provider, we must also mock the iam_client from the check. This ensures that the IAM client used in the test is the one explicitly created within the test.
|
||||
|
||||
# ⚠️ Important:
|
||||
|
||||
# patch != import—simply importing does not ensure proper isolation.
|
||||
|
||||
# Running tests in parallel may cause unintended object initialization, impacting test integrity.
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_password_policy_uppercase.iam_password_policy_uppercase.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
# We import the check within the two mocks not to initialise the iam_client with some shared information from
|
||||
# the aws_provider or the IAM service.
|
||||
# Importing the IAM Check
|
||||
|
||||
# To prevent initialization issues, import the check inside the two-mock context.
|
||||
|
||||
# This ensures the IAM client does not retain shared data from aws_provider or the IAM service.
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_password_policy_uppercase.iam_password_policy_uppercase import (
|
||||
iam_password_policy_uppercase,
|
||||
)
|
||||
|
||||
# Once imported, we only need to instantiate the check's class
|
||||
# Executing the IAM Check
|
||||
|
||||
# Once imported, instantiate the check’s class.
|
||||
|
||||
check = iam_password_policy_uppercase()
|
||||
|
||||
# And then, call the execute() function to run the check
|
||||
# against the IAM client we've set up.
|
||||
# Then run the execute function()
|
||||
# against the set up IAM client.
|
||||
|
||||
result = check.execute()
|
||||
|
||||
# Last but not least, we need to assert all the fields
|
||||
# from the check's results
|
||||
# Validating the Check Results
|
||||
# Finally, assert all fields to verify expected results.
|
||||
|
||||
assert len(results) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "IAM password policy does not require at least one uppercase letter."
|
||||
assert result[0].status_extended == "IAM password policy does not srequire at least one uppercase letter."
|
||||
assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
|
||||
assert result[0].resource_tags == []
|
||||
assert result[0].region == AWS_REGION
|
||||
```
|
||||
|
||||
#### API calls not covered
|
||||
#### Handling API Calls Not Covered by Moto
|
||||
|
||||
If the IAM service for the check's we want to test is not covered by Moto, we have to inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock). As we have pointed above, we cannot instantiate the service since it will make real calls to the AWS APIs.
|
||||
If the IAM service required for testing is not supported by the Moto library, use [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock) to inject objects into the service client.
|
||||
|
||||
???+ warning
|
||||
As stated above, direct service instantiation must be avoided to prevent actual AWS API calls.
|
||||
|
||||
???+ note
|
||||
The following example uses the IAM GetAccountPasswordPolicy which is covered by Moto but this is only for demonstration purposes.
|
||||
The example below demonstrates the IAM GetAccountPasswordPolicy API, which is covered by Moto, but is used for instructional purposes only.
|
||||
|
||||
The following code shows how to use MagicMock to create the service objects.
|
||||
#### Mocking Service Objects Using MagicMock
|
||||
|
||||
The following code demonstrates how to use MagicMock to create service objects.
|
||||
|
||||
```python
|
||||
# We need to import the unittest.mock to allow us to patch some objects
|
||||
# not to use shared ones between test, hence to isolate the test
|
||||
# Import unittest.mock to enable object patching
|
||||
# This prevents shared objects between tests, ensuring test isolation
|
||||
|
||||
from unittest import mock
|
||||
|
||||
# Constants used
|
||||
# Define constants for test execution
|
||||
|
||||
AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
# We always name the test classes like Test_<check_name>
|
||||
# Test class naming convention: Test_<check_name>
|
||||
|
||||
class Test_iam_password_policy_uppercase:
|
||||
|
||||
# We name the tests with test_<service>_<check_name>_<test_action>
|
||||
# Test naming convention: test_<service>_<check_name>_<test_action>
|
||||
|
||||
def test_iam_password_policy_no_uppercase_flag(self):
|
||||
# Mocked client with MagicMock
|
||||
|
||||
# Mock IAM client with MagicMock
|
||||
|
||||
mocked_iam_client = mock.MagicMock
|
||||
|
||||
# Since the IAM Password Policy has their own model we have to import it
|
||||
# Import IAM PasswordPolicy model, as it has its own model
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import PasswordPolicy
|
||||
|
||||
# Create the mock PasswordPolicy object
|
||||
# Create a mock PasswordPolicy object with predefined attributes
|
||||
|
||||
mocked_iam_client.password_policy = PasswordPolicy(
|
||||
length=5,
|
||||
symbols=True,
|
||||
numbers=True,
|
||||
# We set the value to False to test the check
|
||||
# The value must be set to False to trigger a failure scenario
|
||||
uppercase=False,
|
||||
lowercase=True,
|
||||
allow_change=False,
|
||||
expiration=True,
|
||||
)
|
||||
|
||||
# In this scenario we have to mock also the IAM service and the iam_client from the check to enforce # that the iam_client used is the one created within this check because patch != import, and if you # execute tests in parallel some objects can be already initialised hence the check won't be isolated.
|
||||
# In this case we don't use the Moto decorator, we use the mocked IAM client for both objects
|
||||
# In this scenario, both the IAM service and the iam_client from the check must be mocked to ensure test isolation. This guarantees that the iam_client used in the test is the one explicitly instantiated within the test itself.
|
||||
|
||||
# Note: Simply applying a patch does not modify imports (patch != import).
|
||||
|
||||
# If tests are executed in parallel, objects may already be initialized,
|
||||
# leading to unintended shared state and breaking test isolation.
|
||||
|
||||
# Unlike other cases, we do not use the Moto decorator here.
|
||||
|
||||
# Instead, we mock the IAM client for both objects to prevent real AWS API interactions.
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_service.IAM",
|
||||
new=mocked_iam_client,
|
||||
@@ -198,21 +274,31 @@ class Test_iam_password_policy_uppercase:
|
||||
"prowler.providers.aws.services.iam.iam_client.iam_client",
|
||||
new=mocked_iam_client,
|
||||
):
|
||||
# We import the check within the two mocks not to initialise the iam_client with some shared information from
|
||||
# the aws_provider or the IAM service.
|
||||
# Importing the IAM Check
|
||||
|
||||
# To prevent initialization issues, import the check inside the two-mock context.
|
||||
|
||||
# This ensures the IAM client does not retain shared data from aws_provider or the IAM service.
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_password_policy_uppercase.iam_password_policy_uppercase import (
|
||||
iam_password_policy_uppercase,
|
||||
)
|
||||
|
||||
# Once imported, we only need to instantiate the check's class
|
||||
# Executing the IAM Check
|
||||
|
||||
# Once imported, instantiate the check’s class.
|
||||
|
||||
check = iam_password_policy_uppercase()
|
||||
|
||||
# And then, call the execute() function to run the check
|
||||
# against the IAM client we've set up.
|
||||
# Then run the execute function()
|
||||
# against the set up IAM client.
|
||||
|
||||
result = check.execute()
|
||||
|
||||
# Last but not least, we need to assert all the fields
|
||||
# from the check's results
|
||||
# Validating the Check Results
|
||||
|
||||
# Finally, assert all fields to verify expected results.
|
||||
|
||||
assert len(results) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "IAM password policy does not require at least one uppercase letter."
|
||||
@@ -222,14 +308,15 @@ class Test_iam_password_policy_uppercase:
|
||||
assert result[0].region == AWS_REGION
|
||||
```
|
||||
|
||||
As it can be seen in the above scenarios, the check execution should always be into the context of mocked/patched objects. This way we ensure it reviews only the objects created under the scope the test.
|
||||
#### Ensuring Test Isolation with Mocked/Patched Objects
|
||||
|
||||
#### API calls partially covered
|
||||
In all above scenarios, check execution must occur within the context of mocked or patched objects. This guarantees that the test only evaluates objects explicitly created within its scope, preventing interference from shared state or external dependencies.
|
||||
|
||||
If the API calls we want to use in the service are partially covered by the Moto decorator we have to create our own mocked API calls to use it in combination.
|
||||
#### Handling Partially Covered API Calls
|
||||
|
||||
To do so, you need to mock the `botocore.client.BaseClient._make_api_call` function, which is the Boto3 function in charge of making the real API call to the AWS APIs, using `mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`:
|
||||
When a service requires API calls that are partially covered by the Moto decorator, additional mocking is necessary. In such cases, custom mocked API calls must be implemented alongside Moto to ensure full coverage.
|
||||
|
||||
To achieve this, mock the `botocore.client.BaseClient._make_api_call` function—the method responsible for making actual API requests to AWS—using `mock.patch <https://docs.python.org/3/library/unittest.mock.html#patch>`:
|
||||
|
||||
```python
|
||||
|
||||
@@ -239,13 +326,18 @@ from unittest.mock import patch
|
||||
from moto import mock_aws
|
||||
|
||||
# Original botocore _make_api_call function
|
||||
|
||||
orig = botocore.client.BaseClient._make_api_call
|
||||
|
||||
# Mocked botocore _make_api_call function
|
||||
|
||||
def mock_make_api_call(self, operation_name, kwarg):
|
||||
# As you can see the operation_name has the get_account_password_policy snake_case form but
|
||||
# we are using the GetAccountPasswordPolicy form.
|
||||
# Rationale -> https://github.com/boto/botocore/blob/develop/botocore/client.py#L810:L816
|
||||
|
||||
# The 'operation_name' follows the snake_case format (get_account_password_policy),
|
||||
# but we use the PascalCase form (GetAccountPasswordPolicy) for consistency with Boto3 conventions.
|
||||
|
||||
# Reference: https://github.com/boto/botocore/blob/develop/botocore/client.py#L810:L816
|
||||
|
||||
if operation_name == 'GetAccountPasswordPolicy':
|
||||
return {
|
||||
'PasswordPolicy': {
|
||||
@@ -261,29 +353,41 @@ def mock_make_api_call(self, operation_name, kwarg):
|
||||
'HardExpiry': True|False
|
||||
}
|
||||
}
|
||||
# If we don't want to patch the API call
|
||||
|
||||
# If API call patching is not required, return the original method execution.
|
||||
|
||||
return orig(self, operation_name, kwarg)
|
||||
|
||||
# We always name the test classes like Test_<check_name>
|
||||
# Test class naming convention: Test_<check_name>
|
||||
|
||||
class Test_iam_password_policy_uppercase:
|
||||
|
||||
# We include the custom API call mock decorator for the service we want to use
|
||||
# Apply custom API call mock decorator for the required service
|
||||
|
||||
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
# We include also the IAM Moto decorator for the API calls supported
|
||||
|
||||
# Also include IAM Moto decorator for supported API calls
|
||||
|
||||
@mock_iam
|
||||
# We name the tests with test_<service>_<check_name>_<test_action>
|
||||
|
||||
# Test naming convention: test_<service>_<check_name>_<test_action>
|
||||
|
||||
def test_iam_password_policy_no_uppercase_flag(self):
|
||||
# Check the previous section to see the check test since is the same
|
||||
|
||||
# Refer to the previous section for the check test, as the implementation remains unchanged.
|
||||
```
|
||||
|
||||
Note that this does not use Moto, to keep it simple, but if you use any `moto`-decorators in addition to the patch, the call to `orig(self, operation_name, kwarg)` will be intercepted by Moto.
|
||||
???+ note
|
||||
This example does not use Moto to simplify the setup.
|
||||
However, if additional `moto` decorators are applied alongside the patch, Moto will automatically intercept the call to `orig(self, operation_name, kwarg)`.
|
||||
|
||||
???+ note
|
||||
The above code comes from here https://docs.getmoto.org/en/latest/docs/services/patching_other_services.html
|
||||
The source of the above implementation can be found here:[Patch Other Services with Moto](https://docs.getmoto.org/en/latest/docs/services/patching\_other\_services.html)
|
||||
|
||||
#### Mocking more than one service
|
||||
#### Mocking Several Services
|
||||
|
||||
Since the provider is being mocked, multiple attributes can be configured to customize its behavior:
|
||||
|
||||
Since we are mocking the provider, it can be customized setting multiple attributes to the provider:
|
||||
```python
|
||||
def set_mocked_aws_provider(
|
||||
audited_regions: list[str] = [],
|
||||
@@ -306,8 +410,7 @@ def set_mocked_aws_provider(
|
||||
) -> AwsProvider:
|
||||
```
|
||||
|
||||
If the test your are creating belongs to a check that uses more than one provider service, you should mock each of the services used. For example, the check `cloudtrail_logs_s3_bucket_access_logging_enabled` requires the CloudTrail and the S3 client, hence the service's mock part of the test will be as follows:
|
||||
|
||||
If a test is designed for a check that interacts with multiple provider services, each service used must be individually mocked. For instance, if the check `cloudtrail_logs_s3_bucket_access_logging_enabled` relies on both the CloudTrail and S3 clients, the test's service mocking section should be structured as follows:
|
||||
|
||||
```python
|
||||
with mock.patch(
|
||||
@@ -328,42 +431,45 @@ with mock.patch(
|
||||
):
|
||||
```
|
||||
|
||||
|
||||
As you can see in the above code, it is required to mock the AWS audit info and both services used.
|
||||
|
||||
As demonstrated in the code above, mocking both the AWS audit information and all utilized services is mandatory for proper test execution.
|
||||
|
||||
#### Patching vs. Importing
|
||||
|
||||
This is an important topic within the Prowler check's unit testing. Due to the dynamic nature of the check's load, the process of importing the service client from a check is the following:
|
||||
Properly understanding patching versus importing is critical for unit testing with Prowler checks. Given the dynamic nature of the check-loading mechanism, the process for importing a service client within a check follows this structured approach:
|
||||
|
||||
1. `<check>.py`:
|
||||
```python
|
||||
from prowler.providers.<provider>.services.<service>.<service>_client import <service>_client
|
||||
```
|
||||
|
||||
```python
|
||||
from prowler.providers.<provider>.services.<service>.<service>_client import <service>_client
|
||||
```
|
||||
|
||||
2. `<service>_client.py`:
|
||||
```python
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.<provider>.services.<service>.<service>_service import <SERVICE>
|
||||
|
||||
<service>_client = <SERVICE>(Provider.get_global_provider())
|
||||
```
|
||||
```python
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.<provider>.services.<service>.<service>_service import <SERVICE>
|
||||
|
||||
Due to the above import path it's not the same to patch the following objects because if you run a bunch of tests, either in parallel or not, some clients can be already instantiated by another check, hence your test execution will be using another test's service instance:
|
||||
<service>_client = <SERVICE>(Provider.get_global_provider())
|
||||
```
|
||||
|
||||
Due to the import path structure, patching certain objects does not always ensure full isolation. If multiple tests—executed sequentially or in parallel—reuse service clients, some instances may already be initialized by another check. This can lead to unintended shared state, affecting test accuracy:
|
||||
|
||||
- `<service>_client` imported at `<check>.py`
|
||||
- `<service>_client` initialised at `<service>_client.py`
|
||||
- `<SERVICE>` imported at `<service>_client.py`
|
||||
|
||||
A useful read about this topic can be found in the following article: https://stackoverflow.com/questions/8658043/how-to-mock-an-import
|
||||
#### Additional Resources on Mocking Imports
|
||||
|
||||
For a deeper understanding of mocking imports in Python, refer to the following article: https://stackoverflow.com/questions/8658043/how-to-mock-an-import
|
||||
|
||||
#### Different ways to mock the service client
|
||||
#### Approaches to Mocking a Service Client
|
||||
|
||||
##### Mocking the service client at the service client level
|
||||
1\. Mocking the Service Client at the Service Client Level
|
||||
|
||||
Mocking a service client using the following code ...
|
||||
2\. Mocking a Service Client via Below Code Implementation
|
||||
|
||||
Once all required attributes are configured for the mocked provider, it can be used as the service client for test execution:
|
||||
|
||||
Once the needed attributes are set for the mocked provider, you can use the mocked provider:
|
||||
```python title="Mocking the service_client"
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
@@ -373,18 +479,20 @@ with mock.patch(
|
||||
new=<SERVICE>(set_mocked_aws_provider([<region>])),
|
||||
):
|
||||
```
|
||||
|
||||
will cause that the service will be initialised twice:
|
||||
|
||||
1. When the `<SERVICE>(set_mocked_aws_provider([<region>]))` is mocked out using `mock.patch` to have the object ready for the patching.
|
||||
2. At the `<service>_client.py` when we are patching it since the `mock.patch` needs to go to that object an initialise it, hence the `<SERVICE>(set_mocked_aws_provider([<region>]))` will be called again.
|
||||
1. When `<SERVICE>(set_mocked_aws_provider([<region>]))` is mocked out using `mock.patch`, it must be properly prepared before patching to ensure test consistency.
|
||||
|
||||
Then, when we import the `<service>_client.py` at `<check>.py`, since we are mocking where the object is used, Python will use the mocked one.
|
||||
2. At the point of patching, in `<service>_client.py`, and since `mock.patch` needs to access said object and initialise it, `<SERVICE>(set_mocked_aws_provider([<region>]))` will be called again.
|
||||
|
||||
In the [next section](./unit-testing.md#mocking-the-service-and-the-service-client-at-the-service-client-level) you will see an improved version to mock objects.
|
||||
Later, when importing `<service>_client.py` at `<check>.py`, Python uses the mocked instance since the patch was applied at the correct reference point.
|
||||
|
||||
In the [next section](./unit-testing.md#mocking-the-service-and-the-service-client-at-the-service-client-level) we will explore an improved approach to mock objects.
|
||||
|
||||
##### Mocking the service and the service client at the service client level
|
||||
Mocking a service client using the following code ...
|
||||
##### Mocking the Service and the Service Client at the Service Client Level
|
||||
|
||||
##### Mocking a Service Client via Below Code Implementation
|
||||
|
||||
```python title="Mocking the service and the service_client"
|
||||
with mock.patch(
|
||||
@@ -398,35 +506,42 @@ with mock.patch(
|
||||
new=service_client,
|
||||
):
|
||||
```
|
||||
will cause that the service will be initialised once, just when the `set_mocked_aws_provider([<region>])` is mocked out using `mock.patch`.
|
||||
|
||||
Then, at the check_level when Python tries to import the client with `from prowler.providers.<provider>.services.<service>.<service>_client`, since it is already mocked out, the execution will continue using the `service_client` without getting into the `<service>_client.py`.
|
||||
will cause that the service is initialized only once—at the moment of mocking out `set_mocked_aws_provider([<region>])` using `mock.patch`.
|
||||
|
||||
Later, when Python attempts to import the client at the check level, the execution continues using`from prowler.providers.<provider>.services.<service>.<service>_client`. As a result of it being already mocked out, the execution will continue using `service_client` without getting into `<service>_client.py`.
|
||||
|
||||
### Services
|
||||
### Testing AWS Services
|
||||
|
||||
For testing the AWS services we have to follow the same logic as with the AWS checks, we have to check if the AWS API calls made by the service are covered by Moto and we have to test the service `__init__` to verify that the information is being correctly retrieved.
|
||||
AWS service testing follows the same methodology as AWS checks:
|
||||
Verify whether the AWS API calls made by the service are covered by Moto.
|
||||
|
||||
The service tests could act as *Integration Tests* since we test how the service retrieves the information from the provider, but since Moto or the custom mock objects mocks that calls this test will fall into *Unit Tests*.
|
||||
Execute tests on the service `__init__` to ensure correct information retrieval.
|
||||
|
||||
Please refer to the [AWS checks tests](./unit-testing.md#checks) for more information on how to create tests and check the existing services tests [here](https://github.com/prowler-cloud/prowler/tree/master/tests/providers/aws/services).
|
||||
While service tests resemble *Integration Tests*, as they assess how the service interacts with the provider, they ultimately fall under *Unit Tests*, due to the use of Moto or custom mock objects.
|
||||
|
||||
For detailed guidance on test creation and existing service tests, refer to the [AWS checks test](./unit-testing.md#checks) [documentation](https://github.com/prowler-cloud/prowler/tree/master/tests/providers/aws/services).
|
||||
|
||||
## GCP
|
||||
|
||||
### Checks
|
||||
### GCP Check Testing Approach
|
||||
|
||||
For the GCP Provider we don't have any library to mock out the API calls we use. So in this scenario we inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
|
||||
Currently the GCP Provider does not have a dedicated library for mocking API calls. To ensure proper test isolation, objects must be manually injected into the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
|
||||
|
||||
The following code shows how to use MagicMock to create the service objects for a GCP check test. It is a real example adapted for informative purposes.
|
||||
Mocking Service Objects Using MagicMock
|
||||
|
||||
The following code demonstrates how to use MagicMock to create service objects for a GCP check test. This is a real-world implementation, adapted for instructional clarity.
|
||||
|
||||
```python
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
# Import some constant values needed in every check
|
||||
# Import constant values needed in every check
|
||||
|
||||
from tests.providers.gcp.gcp_fixtures import GCP_PROJECT_ID, set_mocked_gcp_provider
|
||||
|
||||
# We are going to create a test for the compute_project_os_login_enabled check
|
||||
# Create a test for the compute_project_os_login_enabled check
|
||||
|
||||
class Test_compute_project_os_login_enabled:
|
||||
|
||||
def test_one_compliant_project(self):
|
||||
@@ -437,13 +552,15 @@ class Test_compute_project_os_login_enabled:
|
||||
id=GCP_PROJECT_ID,
|
||||
enable_oslogin=True,
|
||||
)
|
||||
# Mocked client with MagicMock
|
||||
# Mock IAM client with MagicMock
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.projects = [project]
|
||||
|
||||
# In this scenario we have to mock the app_client from the check to enforce that the compute_client used is the one created above
|
||||
# And also is mocked the return value of get_global_provider function to return our GCP mocked provider defined in fixtures
|
||||
# In this scenario, the app_client from the check must be mocked to ensure that the compute_client used in the test is the explicitly created instance.
|
||||
|
||||
# Additionally, the return value of the get_global_provider function is mocked to return the predefined GCP mocked provider from the test fixtures.
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
@@ -451,16 +568,24 @@ class Test_compute_project_os_login_enabled:
|
||||
"prowler.providers.gcp.services.compute.compute_project_os_login_enabled.compute_project_os_login_enabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
# We import the check within the two mocks
|
||||
# Import the check within the two mocks
|
||||
|
||||
from prowler.providers.gcp.services.compute.compute_project_os_login_enabled.compute_project_os_login_enabled import (
|
||||
compute_project_os_login_enabled,
|
||||
)
|
||||
# Once imported, we only need to instantiate the check's class
|
||||
|
||||
# Executing the IAM Check
|
||||
# Once imported, instantiate the check’s class.
|
||||
|
||||
check = compute_project_os_login_enabled()
|
||||
# And then, call the execute() function to run the check
|
||||
# against the Compute client we've set up.
|
||||
|
||||
# Then run the execute function()
|
||||
# against the set up Compute client.
|
||||
|
||||
result = check.execute()
|
||||
|
||||
# Assert the expected results
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
@@ -471,7 +596,10 @@ class Test_compute_project_os_login_enabled:
|
||||
assert result[0].location == "global"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
|
||||
# Complementary test to make more coverage for different scenarios
|
||||
# Complementary Test
|
||||
|
||||
# The following is an additional test for a wider scenario coverage
|
||||
|
||||
def test_one_non_compliant_project(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Project
|
||||
|
||||
@@ -510,19 +638,28 @@ class Test_compute_project_os_login_enabled:
|
||||
|
||||
```
|
||||
|
||||
### Services
|
||||
### Testing GCP Services
|
||||
|
||||
For testing Google Cloud Services, we have to follow the same logic as with the Google Cloud checks. We still mocking all API calls, but in this case, every API call to set up an attribute is defined in [fixtures file](https://github.com/prowler-cloud/prowler/blob/master/tests/providers/gcp/gcp_fixtures.py) in `mock_api_client` function. Remember that EVERY method of a service must be tested.
|
||||
The testing of Google Cloud Services follows the same principles as the one of Google Cloud checks. While all API calls must be mocked, attribute setup for API calls in this scenario is defined in the fixtures file, specifically within the [fixtures file](https://github.com/prowler-cloud/prowler/blob/master/tests/providers/gcp/gcp_fixtures.py) in the `mock_api_client` function.
|
||||
|
||||
The following code shows a real example of a testing class, but it has more comments than usual for educational purposes.
|
||||
???+ important
|
||||
Every method within a service must be tested to ensure full coverage and accurate validation.
|
||||
|
||||
The following example presents a real testing class, but includes additional comments for educational purposes, explaining key concepts and implementation details.
|
||||
|
||||
```python title="BigQuery Service Test"
|
||||
# We need to import the unittest.mock.patch to allow us to patch some objects
|
||||
# not to use shared ones between test, hence to isolate the test
|
||||
|
||||
# Import unittest.mock.patch to enable object patching
|
||||
# This prevents shared objects between tests, ensuring test isolation
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
# Import the class needed from the service file
|
||||
|
||||
from prowler.providers.gcp.services.bigquery.bigquery_service import BigQuery
|
||||
# Necessary constans and functions from fixtures file
|
||||
|
||||
# Use necessary constants and functions from fixtures file
|
||||
|
||||
from tests.providers.gcp.gcp_fixtures import (
|
||||
GCP_PROJECT_ID,
|
||||
mock_api_client,
|
||||
@@ -532,10 +669,10 @@ from tests.providers.gcp.gcp_fixtures import (
|
||||
|
||||
|
||||
class TestBigQueryService:
|
||||
# Only method needed to test full service
|
||||
# The only method needed to test full service
|
||||
def test_service(self):
|
||||
# In this case we are mocking the __is_api_active__ to ensure our mocked project is used
|
||||
# And all the client to use our mocked API calls
|
||||
# Mocking '__is_api_active__' ensures that the test utilizes the predefined mocked project instead of a real instance.
|
||||
# Additionally, all client interactions are patched to use the mocked API calls.
|
||||
with patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
@@ -547,7 +684,7 @@ class TestBigQueryService:
|
||||
bigquery_client = BigQuery(
|
||||
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
|
||||
)
|
||||
# Check all attributes of the tested class is well set up according API calls mocked from GCP fixture file
|
||||
# Verify that all attributes of the tested class are correctly initialized based on the API calls mocked from the GCP fixture file.
|
||||
assert bigquery_client.service == "bigquery"
|
||||
assert bigquery_client.project_ids == [GCP_PROJECT_ID]
|
||||
|
||||
@@ -581,16 +718,28 @@ class TestBigQueryService:
|
||||
assert not bigquery_client.tables[1].cmk_encryption
|
||||
assert bigquery_client.tables[1].project_id == GCP_PROJECT_ID
|
||||
```
|
||||
As it can be confusing where all these values come from, I'll give an example to make this clearer. First we need to check
|
||||
what is the API call used to obtain the datasets. In this case if we check the service the call is
|
||||
`self.client.datasets().list(projectId=project_id)`.
|
||||
|
||||
Now in the fixture file we have to mock this call in our `MagicMock` client in the function `mock_api_client`. The best way to mock
|
||||
is following the actual format, add one function where the client is passed to be changed, the format of this function name must be
|
||||
`mock_api_<endpoint>_calls` (*endpoint* refers to the first attribute pointed after *client*).
|
||||
Clarifying Value Origins with an Example
|
||||
|
||||
In the example of BigQuery the function is called `mock_api_dataset_calls`. And inside of this function we found an assignation to
|
||||
be used in the `_get_datasets` method in BigQuery class:
|
||||
Understanding where specific values originate can be challenging, so the following example provides clarity.
|
||||
|
||||
- Step 1: Identify the API Call for Dataset Retrieval
|
||||
|
||||
To determine how datasets are obtained, examine the API call used by the service. In this case, the relevant service call is: `self.client.datasets().list(projectId=project_id)`.
|
||||
|
||||
- Step 2: Mocking the API Call in the Fixture File
|
||||
|
||||
In the fixture file, mock this call in the `MagicMock` client, in the function `mock_api_client`.
|
||||
|
||||
- Step 3: Structuring the Mock Function
|
||||
|
||||
The best approach for mocking is to adhere to the service’s existing format:
|
||||
|
||||
Define a dedicated function that modifies the client.
|
||||
|
||||
Follow the naming convention: `mock_api_<endpoint>_calls` (*endpoint* refers to the first attribute pointed after *client*).
|
||||
|
||||
For BigQuery, the mock function is called `mock_api_dataset_calls`. Within this function, an assignment is made for use in the `_get_datasets` method of the BigQuery class:
|
||||
|
||||
```python
|
||||
# Mocking datasets
|
||||
@@ -619,39 +768,46 @@ client.datasets().list().execute.return_value = {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Azure
|
||||
|
||||
### Checks
|
||||
### Azure Check Testing Approach
|
||||
|
||||
For the Azure Provider we don't have any library to mock out the API calls we use. So in this scenario we inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
|
||||
Currently the Azure Provider does not have a dedicated library for mocking API calls. To ensure proper test isolation, objects must be manually injected into the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
|
||||
|
||||
The following code shows how to use MagicMock to create the service objects for a Azure check test. It is a real example adapted for informative purposes.
|
||||
Mocking Service Objects Using MagicMock
|
||||
|
||||
The following code demonstrates how to use MagicMock to create service objects for an Azure check test. This is a real-world implementation, adapted for instructional clarity.
|
||||
|
||||
```python title="app_ensure_http_is_redirected_to_https_test.py"
|
||||
# We need to import the unittest.mock to allow us to patch some objects
|
||||
# not to use shared ones between test, hence to isolate the test
|
||||
|
||||
# Import unittest.mock to enable object patching
|
||||
# This prevents shared objects between tests, ensuring test isolation
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
# Import some constans values needed in almost every check
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
# We are going to create a test for the app_ensure_http_is_redirected_to_https check
|
||||
# Create a test for the app_ensure_http_is_redirected_to_https check
|
||||
|
||||
class Test_app_ensure_http_is_redirected_to_https:
|
||||
|
||||
# We name the tests with test_<service>_<check_name>_<test_action>
|
||||
# Test naming convention: test_<service>_<check_name>_<test_action>
|
||||
def test_app_http_to_https_disabled(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
# Mocked client with MagicMock
|
||||
# Mock IAM client with MagicMock
|
||||
app_client = mock.MagicMock
|
||||
|
||||
# In this scenario we have to mock the app_client from the check to enforce that the app_client used is the one created above
|
||||
# And also is mocked the return value of get_global_provider function to return our Azure mocked provider defined in fixtures
|
||||
# In this scenario, the app_client from the check must be mocked to ensure that the app_client used in the test is the explicitly created instance.
|
||||
|
||||
# Additionally, the return value of the get_global_provider function is mocked to return the predefined Azure mocked provider from the test fixtures.
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
@@ -659,7 +815,7 @@ class Test_app_ensure_http_is_redirected_to_https:
|
||||
"prowler.providers.azure.services.app.app_ensure_http_is_redirected_to_https.app_ensure_http_is_redirected_to_https.app_client",
|
||||
new=app_client,
|
||||
):
|
||||
# We import the check within the two mocks
|
||||
# Import the check within the two mocks
|
||||
from prowler.providers.azure.services.app.app_ensure_http_is_redirected_to_https.app_ensure_http_is_redirected_to_https import (
|
||||
app_ensure_http_is_redirected_to_https,
|
||||
)
|
||||
@@ -681,10 +837,11 @@ class Test_app_ensure_http_is_redirected_to_https:
|
||||
)
|
||||
}
|
||||
}
|
||||
# Once imported, we only need to instantiate the check's class
|
||||
# Executing the IAM Check
|
||||
# Once imported, instantiate the check’s class.
|
||||
check = app_ensure_http_is_redirected_to_https()
|
||||
# And then, call the execute() function to run the check
|
||||
# against the App client we've set up.
|
||||
# Then run the execute function()
|
||||
# against the set up App client.
|
||||
result = check.execute()
|
||||
# Assert the expected results
|
||||
assert len(result) == 1
|
||||
@@ -698,7 +855,9 @@ class Test_app_ensure_http_is_redirected_to_https:
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].location == "West Europe"
|
||||
|
||||
# Complementary test to make more coverage for different scenarios
|
||||
# Complementary Test
|
||||
# The following is an additional test for a wider scenario coverage
|
||||
|
||||
def test_app_http_to_https_enabled(self):
|
||||
resource_id = f"/subscriptions/{uuid4()}"
|
||||
app_client = mock.MagicMock
|
||||
@@ -744,28 +903,38 @@ class Test_app_ensure_http_is_redirected_to_https:
|
||||
|
||||
```
|
||||
|
||||
### Services
|
||||
### Testing Azure Services
|
||||
|
||||
For testing Azure services, we have to follow the same logic as with the Azure checks. We still mock all the API calls, but in this case, every method that uses an API call to set up an attribute is mocked with the [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch) decorator at the beginning of the class. Remember that every method of a service MUST be tested.
|
||||
The testing of Azure Services follows the same principles as the one of Google Cloud checks. All API calls are still mocked, but for methods that initialize attributes via an API call, use the [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch) decorator at the beginning of the class to ensure proper mocking.
|
||||
|
||||
The following code shows a real example of a testing class, but it has more comments than usual for educational purposes.
|
||||
???+ important "Remember"
|
||||
Every method within a service must be tested to ensure full coverage and accurate validation.
|
||||
|
||||
The following example presents a real testing class, but includes additional comments for educational purposes, explaining key concepts and implementation details.
|
||||
|
||||
```python title="AppInsights Service Test"
|
||||
# We need to import the unittest.mock.patch to allow us to patch some objects
|
||||
# not to use shared ones between test, hence to isolate the test
|
||||
|
||||
# Import unittest.mock.patch to enable object patching
|
||||
# This prevents shared objects between tests, ensuring test isolation
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
# Import the models needed from the service file
|
||||
|
||||
from prowler.providers.azure.services.appinsights.appinsights_service import (
|
||||
AppInsights,
|
||||
Component,
|
||||
)
|
||||
|
||||
# Import some constans values needed in almost every check
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
# Function to mock the service function _get_components, this function task is to return a possible value that real function could returns
|
||||
# Function to mock the service function _get_components; the aim of this function is to return a possible value that a real function could return.
|
||||
|
||||
def mock_appinsights_get_components(_):
|
||||
return {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
@@ -777,24 +946,25 @@ def mock_appinsights_get_components(_):
|
||||
}
|
||||
}
|
||||
|
||||
# Patch decorator to use the mocked function instead the function with the real API call
|
||||
# Patch decorator to use the mocked function instead of the function with the real API call
|
||||
|
||||
@patch(
|
||||
"prowler.providers.azure.services.appinsights.appinsights_service.AppInsights._get_components",
|
||||
new=mock_appinsights_get_components,
|
||||
)
|
||||
class Test_AppInsights_Service:
|
||||
# Mandatory test for every service, this method test the instance of the client is correct
|
||||
# Mandatory test for every service; this method tests if the instance of the client is correct.
|
||||
def test_get_client(self):
|
||||
app_insights = AppInsights(set_mocked_azure_provider())
|
||||
assert (
|
||||
app_insights.clients[AZURE_SUBSCRIPTION_ID].__class__.__name__
|
||||
== "ApplicationInsightsManagementClient"
|
||||
)
|
||||
# Second typical method that test if subscriptions is defined inside the client object
|
||||
# Second typical method that tests if subscriptions are defined inside the client object.
|
||||
def test__get_subscriptions__(self):
|
||||
app_insights = AppInsights(set_mocked_azure_provider())
|
||||
assert app_insights.subscriptions.__class__.__name__ == "dict"
|
||||
# Test for the function _get_components, inside this client is used the mocked function
|
||||
# Test for the function _get_components; the mocked function is used within this client.
|
||||
def test_get_components(self):
|
||||
appinsights = AppInsights(set_mocked_azure_provider())
|
||||
assert len(appinsights.components) == 1
|
||||
|
||||
Reference in New Issue
Block a user