Skip to content

Feature/dan ci integration tests #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 73 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
3440ab9
Update ci.yml
dan-amzulescu Sep 3, 2021
cfb28d8
Update ci.yml
dan-amzulescu Sep 3, 2021
455a702
Update ci.yml
dan-amzulescu Sep 3, 2021
742d311
fixed type list to List
dan-amzulescu Sep 7, 2021
a4288cf
commit before changing the way the attribute values are set during th…
dan-amzulescu Sep 8, 2021
ae8a386
Update integration_context.py
dan-amzulescu Sep 16, 2021
b29c9a4
relocated tests
dan-amzulescu Sep 24, 2021
4825a39
lint
dan-amzulescu Sep 24, 2021
c9f5c13
fix to .env gitignore and naming for files
dan-amzulescu Sep 27, 2021
e570fe9
added secrets
dan-amzulescu Sep 27, 2021
2d47217
Update constants.py
dan-amzulescu Sep 29, 2021
35319c8
flake8
dan-amzulescu Sep 29, 2021
6c8e263
Update ci.yml
dan-amzulescu Sep 29, 2021
310bf0c
Create __init__.py
dan-amzulescu Sep 29, 2021
5a809fa
GITHUB -> GH
dan-amzulescu Sep 30, 2021
896e312
Update ci.yml
dan-amzulescu Sep 30, 2021
e5bb2b0
Update ci.yml
dan-amzulescu Sep 30, 2021
0c8a198
Update ci.yml
dan-amzulescu Sep 30, 2021
2cd8c06
Update ci.yml
dan-amzulescu Sep 30, 2021
4857224
Update ci.yml
dan-amzulescu Sep 30, 2021
c1533b0
Update int_test_real_tf_execute_destroy.py
dan-amzulescu Sep 30, 2021
21321b0
Update int_test_real_tf_execute_destroy.py
dan-amzulescu Sep 30, 2021
20610db
Update int_test_real_tf_execute_destroy.py
dan-amzulescu Sep 30, 2021
1c2429d
test without load_dotenv
dan-amzulescu Sep 30, 2021
35425ce
Update int_test_mock_tf_execute_destroy.py
dan-amzulescu Sep 30, 2021
5ac0226
Delete int_tests.env
qualidan Sep 30, 2021
c22c5dd
Update .gitignore
dan-amzulescu Sep 30, 2021
58cc872
Merge branch 'feature/dan-CI_integration_tests' of https://github.com…
dan-amzulescu Sep 30, 2021
6f9470b
fixes
dan-amzulescu Sep 30, 2021
937e568
env secrets
dan-amzulescu Sep 30, 2021
1202d05
Update ci.yml
dan-amzulescu Sep 30, 2021
7f52d64
fix for MagicMock
dan-amzulescu Sep 30, 2021
3700e42
Update int_test_mock_tf_execute_destroy.py
dan-amzulescu Sep 30, 2021
97b81a9
reservation_id mock in context
dan-amzulescu Sep 30, 2021
653714c
Update integration_context.py
dan-amzulescu Sep 30, 2021
fad8cf4
fix .env in .gitignore
dan-amzulescu Oct 6, 2021
daba747
Merge branch 'master' into feature/dan-CI_integration_tests
qualidan Oct 18, 2021
7ccc1dc
Update clp_envvar_handler.py
dan-amzulescu Oct 18, 2021
1228f5f
Update clp_envvar_handler.py
dan-amzulescu Oct 18, 2021
95a494b
cleanups
alexazarh Oct 21, 2021
28b5e30
moved azure ID`s to secrets
dan-amzulescu Oct 22, 2021
f036afa
A bit of documentation and new KeyVault on Azure
dan-amzulescu Oct 22, 2021
59cacc1
Update int_test_mock_tf_execute_destroy.py
dan-amzulescu Oct 22, 2021
5578ce7
Update int_tests.env
dan-amzulescu Oct 22, 2021
cba93b1
fixed the downloader integration test and also testing how 'package.'…
dan-amzulescu Oct 22, 2021
6a3ea68
Update downloader_integration_context.py
dan-amzulescu Oct 22, 2021
68807f9
removed the "package." from imports of integration tests
dan-amzulescu Oct 22, 2021
8361665
Update int_test_mock_tf_execute_destroy.py
dan-amzulescu Oct 22, 2021
129b442
Update int_test_mock_tf_execute_destroy.py
dan-amzulescu Oct 22, 2021
9bac2d1
Update downloader_integration_context.py
dan-amzulescu Oct 22, 2021
48868b1
multiple changes (Please see description)
dan-amzulescu Oct 27, 2021
fc8705f
Update int_test_real_tf_execute_destroy.py
dan-amzulescu Oct 27, 2021
65ac3a4
Update int_tests.env
dan-amzulescu Oct 27, 2021
1f6709e
no need for mock alias var
dan-amzulescu Oct 27, 2021
ed6a6f2
Update ci.yml
dan-amzulescu Oct 27, 2021
a9368f3
clean ups
alexazarh Nov 5, 2021
37cfed2
fixing lint
alexazarh Nov 5, 2021
368d595
changes to Integration tests
dan-amzulescu Dec 29, 2021
fb13019
Merge branch 'feature/dan-CI_integration_tests' of https://github.com…
dan-amzulescu Dec 29, 2021
eecda88
Update ci.yml
dan-amzulescu Dec 29, 2021
d102887
temp remove azure-validator
dan-amzulescu Dec 29, 2021
99572d1
lint fixes
dan-amzulescu Dec 29, 2021
ccb235d
forgot to import abstractmethod
dan-amzulescu Dec 29, 2021
2597d19
lint
dan-amzulescu Dec 29, 2021
4180343
Update ci.yml
dan-amzulescu Dec 29, 2021
7f7d0fc
testing current branch
dan-amzulescu Dec 29, 2021
282ed02
testing current branch in CI
dan-amzulescu Dec 29, 2021
4937142
Update ci.yml
dan-amzulescu Dec 29, 2021
3cd5739
Update ci.yml
dan-amzulescu Dec 29, 2021
6f19fff
Update ci.yml
dan-amzulescu Dec 29, 2021
02efc04
Update ci.yml
dan-amzulescu Dec 29, 2021
bd5357c
added public repo via current branch
dan-amzulescu Dec 29, 2021
3b912a7
fix lint
alexazarh Feb 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,21 @@ jobs:
python -m pip install --upgrade pip
chmod +x ci_deploy.sh
./ci_deploy.sh
- name: Test with unittest
- name: Test unit tests with unittest
run: |
python -m unittest
- name: Test integration tests for package/tests/integration_tests
env:
GH_TOKEN_DEC: ${{ secrets.GH_TOKEN_DEC }}
CURRENT_BRANCH: ${{ env.BRANCH_NAME }}
AZURE_APPLICATION_KEY_DEC: ${{ secrets.AZURE_APPLICATION_KEY_DEC }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_APPLICATION_ID: ${{ secrets.AZURE_APPLICATION_ID }}


run: |
pwd
echo "1: ${{ env.BRANCH_NAME }}"

python -m unittest discover -p "package/tests/integration_tests/mock_api_based_tests/int_test_mock*.py"
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ __pycache__/
.idea/
venv/
package/tests/.env
package/tests/integration_tests/.env
env/
build/
develop-eggs/
Expand Down Expand Up @@ -64,4 +65,5 @@ cloudshell_config.yml

# env files
*.env
!*.template.env
!*.template.env
!package/tests/integration_tests/int_tests.env
12 changes: 6 additions & 6 deletions package/cloudshell/iac/terraform/services/clp_envvar_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def set_env_vars_based_on_clp(self):

@staticmethod
def does_attribute_match(clp_res_model, clp_attribute, attr_name_to_check) -> bool:
if f"{clp_res_model}.{clp_attribute.Name}" == attr_name_to_check or clp_attribute.Name == attr_name_to_check:
if f"{clp_res_model}.{attr_name_to_check}" == clp_attribute.Name or attr_name_to_check == clp_attribute.Name:
return True
return False

Expand Down Expand Up @@ -57,11 +57,11 @@ def __init__(self, clp_res_model, clp_resource_attributes, shell_helper):

def set_env_vars_based_on_clp(self):
for attr in self._clp_resource_attributes:
if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Subscription ID"):
if self.does_attribute_match(self._clp_res_model, attr, "Azure Subscription ID"):
os.environ["ARM_SUBSCRIPTION_ID"] = attr.Value
if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Tenant ID"):
os.environ["Azure Tenant ID"] = attr.Value
if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Application ID"):
if self.does_attribute_match(self._clp_res_model, attr, "Azure Tenant ID"):
os.environ["ARM_TENANT_ID"] = attr.Value
if self.does_attribute_match(self._clp_res_model, attr, "Azure Application ID"):
os.environ["ARM_CLIENT_ID"] = attr.Value
if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Application Key", True):
if self.does_attribute_match(self._clp_res_model, attr, "Azure Application Key"):
os.environ["ARM_CLIENT_SECRET"] = self._shell_helper.api.DecryptPassword(attr.Value).Value
3 changes: 3 additions & 0 deletions package/cloudshell/iac/terraform/terraform_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ def _validate_remote_backend_or_existing_working_dir(self, sandbox_data_handler,
not LocalDir.does_working_dir_exists(sandbox_data_handler.get_tf_working_dir()):
self._handle_error_output(shell_helper, f"Missing local folder {sandbox_data_handler.get_tf_working_dir()}")

def get_tf_service(self):
return self._tf_service

@staticmethod
def _destroy_passed(sandbox_data_handler):
return sandbox_data_handler.get_status(DESTROY_STATUS) == DESTROY_PASSED
Expand Down
2 changes: 1 addition & 1 deletion package/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cloudshell-shell-core==5.0.5
requests==2.25.1
cloudshell-automation-api==2021.1.0.181140
cloudshell-automation-api==2021.2.0.182230
retry==0.9.2
python-hcl2==2.0.1
4 changes: 4 additions & 0 deletions package/test_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
mock==4.0.3
python-dotenv==0.18.0
pathlib==1.0.1

#azure-common==1.1.27
#azure-mgmt==5.0.0
File renamed without changes.
7 changes: 5 additions & 2 deletions package/tests/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
GITHUB_TF_PUBLIC_HELLO_URL_FILE = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/blob/master/package/tests/tests_helper_files/tf_modules/hello/hello.tf"
GITHUB_TF_PUBLIC_HELLO_URL_FOLDER = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/tree/master/package/tests/tests_helper_files/tf_modules/hello"
GH_TF_PUBLIC_HELLO_URL_FILE = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/blob/master/package/tests/tests_helper_files/tf_modules/hello/hello.tf"
GH_TF_PUBLIC_HELLO_URL_FOLDER = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/tree/master/package/tests/tests_helper_files/tf_modules/hello"


TF_STATE_FILE_STRING = 'tf_state_file_string'
Expand All @@ -8,3 +8,6 @@
TERRAFORM_EXEC_FILE = "terraform.exe"
TF_HELLO_FILE = "hello.tf"
SHELL_NAME = "Generic Terraform Service"

MOCK_ALIAS1 = "MOCK_ALIAS1"
MOCK_ALIAS2 = "MOCK_ALIAS2"
91 changes: 91 additions & 0 deletions package/tests/integration_tests/INT-TEST-README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Integration Tests for CloudShell-Terraform-Shell

The purpose of this documentation section is to provide explanations regarding different aspects of the integrations tests usage and concepts used the making.

## Content

1) Structure
2) Helper Objects/Services
3) How to:
1) Run
2) Extend

### Structure

The Integration tests are devided into two types:
1) Mocked (mock_api_based_tests)
2) Real (real_api_based_tests)

The Mocked (*1) tests will be using the Mock API (multiple patched functions of the API) during tests
for that it shall also use the MockAPIIntegrationData Object that inherits from BaseIntegrationData part of helper_objects\integration_context.py

he Real (*2) tests will be using the Real API (Communicate with an actual live CloudShell server) during tests
* for that one would need to create a int_tests_secrets.env file based on the values mentioned in int_tests_secrets.template.env
in order for the test to work.
* In addition, one must also create a blueprint (and a derived sandbox) with two services (which the tests will use for running the tests)
for that it shall also use the RealAPIIntegrationData Object that inherits from BaseIntegrationData part of helper_objects\integration_context.py

### Helper Objects/Services

1) Helper Object (helper_objects)
1) Environment Variables (helper_objects\env_vars.py)
* Used to hold generatl information regarding the Real reservation testing against
2) Integration Context Data (helper_objects\integration_context.py)
* Used both by "Real" and "Mocked" in order to construct the data for the tests wether real or mocked
2) Helper Services (helper_services)
1) Serice Attributes Factory (helper_services\service_attributes_factory.py)
* Used by "Mocked" tests to create empty attributes

### How To

#### Run
prerequisite: have a a unit tests plugin installed (e.g. unittests)

1) As IDEs have sometimes issues with importing packages, best way is to open the project from it`s "Package" as root dir
2) For running real tests:
1) Instantiate a CloudShell server
2) Create a blueprint with two TF Services
3) create a int_tests_secrets.env file and fill it with the variables (and their values) from int_tests_secrets.template.env
3) For mock tests:
1) either from IDE or CLI : python -m unittest discover -p "int_test_mock*.py"

#### Extend

Test cases part of int_test_mock_tf_execute_destroy.py have several sections:

1) Patching (bypassing the use of real api / server requirements):

For example:

@patch('cloudshell.iac.terraform.services.tf_proc_exec.TfProcExec.can_destroy_run')
@patch('cloudshell.iac.terraform.terraform_shell.SandboxDataHandler')
@patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext')

2) Making sure the patched functions/objects are initialized with the right values:

For example:

def test_execute_and_destroy_azure_vault(self, patch_api, patched_sbdata_handler, can_destroy_run):

can_destroy_run.return_value = True

patch_api.return_value.get_api.return_value = self.mock_api

mock_sbdata_handler = Mock()
mock_sbdata_handler.get_tf_working_dir = self._get_mocked_tf_working_dir
mock_sbdata_handler.set_tf_working_dir = self._set_mocked_tf_working_dir
patched_sbdata_handler.return_value = mock_sbdata_handler

3) the run_execute_and_destroy function that holds 3 parameters
what function needs to run before executing
* create such a function and modify the attributes values there, so it will match the test case requirements here
what function needs to run before destroying
The data used for the test (self.integration_data1)

For example:

self.run_execute_and_destroy(
pre_exec_function=self.pre_exec_azure_vault,
pre_destroy_function=self.pre_destroy,
integration_data=self.integration_data1
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
SHELL_NAME = "Generic Terraform Service"
UUID_ATTRIBUTE = f"{SHELL_NAME}.UUID"

INT_TEST_TF_VER = "0.15.1"
INT_TEST_CLP_RESOURSE = "real_azure"


class ATTRIBUTE_NAMES:
Expand All @@ -17,3 +19,8 @@ class ATTRIBUTE_NAMES:
BRANCH = "Branch"
CLOUD_PROVIDER = "Cloud Provider"
UUID = "UUID"


EXPECTED_VAULT_TF_OUTPUTS = "BLA1=bla1,BLA2=bla2"
EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_DEC = "SECRET_VALUE=test,SECRET_VALUE_2=my_secret"
EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_ENC = 'nwH0Qhxnn7ntDpWxsvol+89EPFFSvYE16gHjACzsQRDzvXXnNFc4WfYpgq5w1kw8'
Original file line number Diff line number Diff line change
@@ -1,51 +1,88 @@
from unittest import mock
from unittest.mock import Mock

from cloudshell.api.cloudshell_api import CloudShellAPISession
from cloudshell.iac.terraform import TerraformShell, TerraformShellConfig
from cloudshell.logging.qs_logger import get_qs_logger
from cloudshell.shell.core.driver_context import ResourceCommandContext


# from tests.integration_tests.helper_objects.env_vars import EnvVars
from package.tests.integration_tests.helper_objects.env_vars import EnvVars
# from tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder
from package.tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder

from abc import ABCMeta, abstractmethod


class BaseIntegrationData(metaclass=ABCMeta):
def __init__(self, service_name: str):
self._sb_service_alias = service_name
self._logger = get_qs_logger(log_group=service_name)

self._build_context()
self._set_general_context_resource_data()
self._set_context()
self.create_tf_shell()

@abstractmethod
def _set_context(self):
raise NotImplementedError()

def _build_context(self):
self.context = mock.create_autospec(ResourceCommandContext)
self.context.connectivity = Mock()

def _set_general_context_resource_data(self):
self.context.resource = Mock()
self.context.resource.attributes = dict()
self.context.resource.name = self._sb_service_alias
self.context.resource.model = 'Generic Terraform Service'

def create_tf_shell(self):
_config = TerraformShellConfig(write_sandbox_messages=True, update_live_status=True)
self.tf_shell = TerraformShell(self.context, self._logger, _config)

class IntegrationData(object):
def __init__(self):

class RealAPIIntegrationData(BaseIntegrationData):
def __init__(self, service_name: str):
self._env_vars = EnvVars()
self.real_api = CloudShellAPISession(
self.api = CloudShellAPISession(
self._env_vars.cs_server,
self._env_vars.cs_user,
self._env_vars.cs_pass,
self._env_vars.cs_domain
)
self._set_context()
self._logger = get_qs_logger(log_group=self.context.resource.name)

# self._create_driver()
BaseIntegrationData.__init__(self, service_name)

def _set_context(self):
self.context = mock.create_autospec(ResourceCommandContext)
self.context.connectivity = mock.MagicMock()
self.context.connectivity.server_address = self._env_vars.cs_server
self.context.connectivity.admin_auth_token = self.real_api.authentication.xmlrpc_token

self.context.resource = mock.MagicMock()
self.context.resource.attributes = dict()
self.context.resource.name = self._env_vars.sb_service_alias
self.context.resource.model = 'Generic Terraform Service'
self.set_context_resource_attributes()

self.context.reservation = mock.MagicMock()
self.context.connectivity.admin_auth_token = self.api.authentication.xmlrpc_token
self.set_context_resource_attributes_from_cs()
self.context.reservation = Mock()
self.context.reservation.reservation_id = self._env_vars.cs_res_id
self.context.reservation.domain = self._env_vars.cs_domain

def set_context_resource_attributes(self):
services = self.real_api.GetReservationDetails(self._env_vars.cs_res_id).ReservationDescription.Services
def set_context_resource_attributes_from_cs(self, the_only_attribute_to_update: str = ""):
services = self.api.GetReservationDetails(self._env_vars.cs_res_id, disableCache=True) \
.ReservationDescription.Services
for service in services:
if service.Alias == self._env_vars.sb_service_alias:
if service.Alias == self._sb_service_alias:
for attribute in service.Attributes:
self.context.resource.attributes[attribute.Name] = attribute.Value
'''
def _create_driver(self) :
self.driver = GenericTerraformServiceDriver()
self.driver.initialize(self.context)
'''
if the_only_attribute_to_update and attribute.Name == the_only_attribute_to_update:
self.context.resource.attributes[attribute.Name] = attribute.Value
return
elif not the_only_attribute_to_update:
self.context.resource.attributes[attribute.Name] = attribute.Value


class MockAPIIntegrationData(BaseIntegrationData):
def __init__(self, service_name: str, mock_api: Mock):
self.api = mock_api
self.api.authentication.xmlrpc_token = Mock()

BaseIntegrationData.__init__(self, service_name)

def _set_context(self):
self.context.resource.attributes = ServiceAttributesMockBuilder.create_empty_attributes()
self.context.reservation = Mock()
self.context.reservation.reservation_id = "mock_reservation_id"
63 changes: 63 additions & 0 deletions package/tests/integration_tests/helper_objects/mock_tests_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os
from unittest.mock import Mock

from package.tests.integration_tests.helper_objects.integration_context import MockAPIIntegrationData
from package.tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder

from cloudshell.api.cloudshell_api import NameValuePair


class MockTestsData(object):
def __init__(self):
self.mock_api = Mock()
self.service1 = Mock()
self.service2 = Mock()
self._mocked_tf_working_dir = ''
self.service1.Alias = ''
self.service1.Attributes = None
self.service2.Alias = ''
self.service2.Attributes = None

self._prepare_mock_services()
self._prepare_mock_api()

def prepare_integration_data(self):
self.integration_data1 = MockAPIIntegrationData(self.service1.Alias, self.mock_api)
self.integration_data2 = MockAPIIntegrationData(self.service2.Alias, self.mock_api)

def _prepare_mock_services(self):
self.service1 = Mock()
self.service1.Alias = "MOCK_ALIAS1"
self.service1.Attributes = ServiceAttributesMockBuilder.create_empty_attributes()
self.service2 = Mock()
self.service2.Alias = "MOCK_ALIAS2"
self.service2.Attributes = ServiceAttributesMockBuilder.create_empty_attributes()

def _prepare_mock_api(self):
self.mock_api = Mock()
self.mock_api.DecryptPassword = _decrypt_password
self.mock_api.GetReservationDetails.return_value.ReservationDescription.Services = [self.service1,
self.service2]

# self.mock_api.SetServiceAttributesValues = self._set_service_attributes_values

self.mock_api.GetResourceDetails.return_value.ResourceFamilyName = 'Cloud Provider'
self.mock_api.GetResourceDetails.return_value.ResourceModelName = 'Microsoft Azure'
self.mock_api.GetResourceDetails.return_value.ResourceAttributes = [
NameValuePair(Name="Azure Subscription ID", Value=os.environ.get("AZURE_SUBSCRIPTION_ID")),
NameValuePair(Name="Azure Tenant ID", Value=os.environ.get("AZURE_TENANT_ID")),
NameValuePair(Name="Azure Application ID", Value=os.environ.get("AZURE_APPLICATION_ID")),
NameValuePair(Name="Azure Application Key", Value=os.environ.get("AZURE_APPLICATION_KEY_DEC"))
]

def _get_mocked_tf_working_dir(self):
return self._mocked_tf_working_dir

def _set_mocked_tf_working_dir(self, tf_working_dir: str):
self._mocked_tf_working_dir = tf_working_dir


def _decrypt_password(x):
result = Mock()
result.Value = x
return result
Loading