Repository: cyberark/cyberark-conjur-cli (language: python)

Issue:
Docker CLI - Policy command
DOD (apply,replace,update)
- [x] Integration tests have been added 
- [x] Implementation has been done (if applicable)
- [ ] Log errors/messages have received approval from Inbal + TW

Helpful resources:
policy epic - https://app.zenhub.com/workspaces/palmtree-5d99d900491c060001c85cba/issues/cyberark/conjur-api-python3/112


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0d6e516..202e5c09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 ## Unreleased
 
 ### Added
+- Add ability to get previous variable/secret versions. This is available in both CLI and API.
 - The `list` flag constraints are now available in both CLI and API to filter resource results
   [cyberark/conjur-api-python3#128](https://github.com/cyberark/conjur-api-python3/issues/91)
 - The `login\logout` methods are now available in CLI to login and out of the CLI
diff --git a/Utils/py_utils.py b/Utils/py_utils.py
index 05a51f77..7c22f4d1 100644
--- a/Utils/py_utils.py
+++ b/Utils/py_utils.py
@@ -1,4 +1,6 @@
 import os
+import tempfile
+import uuid
 from unittest.mock import patch
 
 
@@ -9,12 +11,117 @@ def remove_file(file_path):
 @patch('builtins.input', return_value='yes')
 def init_to_cli(self, mock_input):
     self.invoke_cli(self.cli_auth_params,
-                    ['init', '-u', self.client_params.hostname, '-a', self.client_params.account], exit_code=0)
+                    ['init', '-u', self.client_params.hostname, '-a', self.client_params.account])
 
 def login_to_cli(self):
     self.invoke_cli(self.cli_auth_params,
-            ['login', '-n', self.client_params.login, '-p', self.client_params.env_api_key], exit_code=0)
+            ['login', '-n', self.client_params.login, '-p', self.client_params.env_api_key])
 
 def setup_cli(self):
     init_to_cli(self)
     login_to_cli(self)
+
+ # *************** VARIABLE ***************
+
+def set_variable(self, variable_id, value, exit_code=0):
+    return self.invoke_cli(self.cli_auth_params,
+                       ['variable', 'set', '-i', variable_id, '-v', value], exit_code=exit_code)
+
+def get_variable(self, *variable_ids, exit_code=0):
+    return self.invoke_cli(self.cli_auth_params,
+                           ['variable', 'get', '-i', *variable_ids], exit_code=exit_code)
+
+def assert_set_and_get(self, variable_id):
+    expected_value = uuid.uuid4().hex
+
+    set_variable(self, variable_id, expected_value)
+    output = get_variable(self, variable_id)
+    self.assertEquals(expected_value, output.strip())
+
+def assert_variable_set_fails(self, variable_id, error_class, exit_code=0):
+    with self.assertRaises(error_class):
+        self.set_variable(variable_id, uuid.uuid4().hex, exit_code)
+
+def print_instead_of_raise_error(self, variable_id, error_message_regex):
+    output = self.invoke_cli(self.cli_auth_params,
+                             ['variable', 'set', '-i', variable_id, '-v', uuid.uuid4().hex], exit_code=1)
+
+    self.assertRegex(output, error_message_regex)
+
+ # *************** POLICY ***************
+
+def load_policy(self, policy_path):
+    return self.invoke_cli(self.cli_auth_params,
+                           ['policy', 'load', '-b', 'root', '-f', policy_path])
+
+def generate_policy_string(self):
+        variable_1 = 'simple/basic/{}'.format(uuid.uuid4().hex)
+        variable_2 = 'simple/space filled/{}'.format(uuid.uuid4().hex)
+        variable_3 = 'simple/special @#$%^&*(){{}}[]._+/{id}'.format(id=uuid.uuid4().hex)
+
+# this policy is purposefully not indented because
+# the """ formatter is indentation sensitive and
+# policy should start at the far left of the line
+        policy = \
+"""
+- !variable
+  id: {variable_1}
+- !variable
+  id: {variable_2}
+- !variable
+  id: {variable_3}
+"""
+
+        dynamic_policy = policy.format(variable_1=variable_1,
+                                       variable_2=variable_2,
+                                       variable_3=variable_3)
+
+        return (dynamic_policy, [variable_1, variable_2, variable_3])
+
+def load_policy(self, policy_path, exit_code=0):
+    return self.invoke_cli(self.cli_auth_params,
+                           ['policy', 'load', '-b', 'root', '-f', policy_path], exit_code=exit_code)
+
+def load_policy_from_string(self, policy, exit_code=0):
+    output = None
+    file_name = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
+    with open(file_name, 'w+b') as temp_policy_file:
+        temp_policy_file.write(policy.encode('utf-8'))
+        temp_policy_file.flush()
+        output = load_policy(self, temp_policy_file.name, exit_code)
+
+    os.remove(file_name)
+    return output
+
+def replace_policy(self, policy_path, exit_code=0):
+    return self.invoke_cli(self.cli_auth_params,
+                           ['policy', 'replace', '-b', 'root', '-f', policy_path], exit_code=exit_code)
+
+def replace_policy_from_string(self, policy, exit_code=0):
+    output = None
+    file_name = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
+    with open(file_name, 'w+b') as temp_policy_file:
+        temp_policy_file.write(policy.encode('utf-8'))
+        temp_policy_file.flush()
+
+        # Run the new replace that should not result in newly created roles
+        output = replace_policy(self, temp_policy_file.name, exit_code)
+
+    os.remove(file_name)
+    return output
+
+def update_policy(self, policy_path):
+    return self.invoke_cli(self.cli_auth_params,
+                           ['policy', 'update', '-b', 'root', '-f', policy_path])
+
+def update_policy_from_string(self, policy):
+    output = None
+    file_name=os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
+    with open(file_name, 'w+b') as temp_policy_file:
+        temp_policy_file.write(policy.encode('utf-8'))
+        temp_policy_file.flush()
+
+        # Run the new update that should not result in newly created roles
+        output = update_policy(self, temp_policy_file.name)
+    os.remove(file_name)
+    return output
diff --git a/conjur/api.py b/conjur/api.py
index b879a6f8..4d3b8df7 100644
--- a/conjur/api.py
+++ b/conjur/api.py
@@ -126,7 +126,7 @@ def authenticate(self):
         return invoke_endpoint(HttpVerb.POST, ConjurEndpoint.AUTHENTICATE, params,
                                self.api_key, ssl_verify=self._ssl_verify).text
 
-    def resources_list(self, list_constraints):
+    def resources_list(self, list_constraints=None):
         """
         This method is used to fetch all available resources for the current
         account. Results are returned as an array of identifiers.
@@ -135,16 +135,23 @@ def resources_list(self, list_constraints):
             'account': self._account
         }
         params.update(self._default_params)
-        json_response = invoke_endpoint(HttpVerb.GET, ConjurEndpoint.RESOURCES,
-                                        params,
-                                        query=list_constraints,
-                                        api_token=self.api_token,
-                                        ssl_verify=self._ssl_verify).content
+        if list_constraints is not None:
+            json_response = invoke_endpoint(HttpVerb.GET, ConjurEndpoint.RESOURCES,
+                                            params,
+                                            query=list_constraints,
+                                            api_token=self.api_token,
+                                            ssl_verify=self._ssl_verify).content
+        else:
+            json_response = invoke_endpoint(HttpVerb.GET, ConjurEndpoint.RESOURCES,
+                                params,
+                                api_token=self.api_token,
+                                ssl_verify=self._ssl_verify).content
+
         resources = json.loads(json_response.decode('utf-8'))
 
         # Returns the result as a list of resource ids instead of the raw JSON only
         # when the user does not provide `inspect` as one of their filters
-        if 'inspect' not in list_constraints:
+        if list_constraints is not None and 'inspect' not in list_constraints:
             # For each element (resource) in the resources sequence, we extract the resource id
             resource_list = map(lambda resource: resource['id'], resources)
             return list(resource_list)
@@ -153,20 +160,32 @@ def resources_list(self, list_constraints):
         # https://docs.conjur.org/Latest/en/Content/Developer/Conjur_API_List_Resources.htm?tocpath=Developer%7CREST%C2%A0APIs%7C_____17
         return resources
 
-    def get_variable(self, variable_id):
+    def get_variable(self, variable_id, version=None):
         """
         This method is used to fetch a secret's (aka "variable") value from
         Conjur vault.
         """
-
         params = {
             'kind': self.KIND_VARIABLE,
             'identifier': variable_id,
         }
         params.update(self._default_params)
 
-        return invoke_endpoint(HttpVerb.GET, ConjurEndpoint.SECRETS, params,
-                               api_token=self.api_token, ssl_verify=self._ssl_verify).content
+        query_params = {}
+        if version is not None:
+            query_params = {
+                'version': version
+            }
+
+        # pylint: disable=no-else-return
+        if version is not None:
+            return invoke_endpoint(HttpVerb.GET, ConjurEndpoint.SECRETS, params,
+                                   api_token=self.api_token, query=query_params,
+                                   ssl_verify=self._ssl_verify).content
+        else:
+            return invoke_endpoint(HttpVerb.GET, ConjurEndpoint.SECRETS, params,
+                                   api_token=self.api_token,
+                                   ssl_verify=self._ssl_verify).content
 
     def get_variables(self, *variable_ids):
         """
@@ -222,7 +241,7 @@ def set_variable(self, variable_id, value):
 
     def _load_policy_file(self, policy_id, policy_file, http_verb):
         """
-        This method is used to load, replace or delete a file-based policy into the desired
+        This method is used to load, replace or update a file-based policy into the desired
         name.
         """
 
@@ -242,7 +261,7 @@ def _load_policy_file(self, policy_id, policy_file, http_verb):
         policy_changes = json.loads(json_response)
         return policy_changes
 
-    def apply_policy_file(self, policy_id, policy_file):
+    def load_policy_file(self, policy_id, policy_file):
         """
         This method is used to load a file-based policy into the desired
         name.
@@ -258,9 +277,9 @@ def replace_policy_file(self, policy_id, policy_file):
 
         return self._load_policy_file(policy_id, policy_file, HttpVerb.PUT)
 
-    def delete_policy_file(self, policy_id, policy_file):
+    def update_policy_file(self, policy_id, policy_file):
         """
-        This method is used to delete a file-based policy into the desired
+        This method is used to update a file-based policy into the desired
         policy ID.
         """
 
diff --git a/conjur/argparse_wrapper.py b/conjur/argparse_wrapper.py
index cf6e6581..61f11206 100644
--- a/conjur/argparse_wrapper.py
+++ b/conjur/argparse_wrapper.py
@@ -48,6 +48,7 @@ def _get_resource_namespace(self, resource):
     # pylint: disable=arguments-differ
     def parse_args(self, args=None, namespace=None, resource=None):
         args, arg_flags = self.parse_known_args(args, namespace)
+
         resource = args.resource if args else None
         if arg_flags:
             err_msg = (self.ARG_ERROR_FORMAT % ' '.join(arg_flags))
diff --git a/conjur/cli.py b/conjur/cli.py
index 389ab695..d6b1b560 100644
--- a/conjur/cli.py
+++ b/conjur/cli.py
@@ -28,6 +28,8 @@
 from conjur.list.list_logic import ListLogic
 from conjur.login import LoginLogic, LoginController
 from conjur.logout import LogoutController, LogoutLogic
+from conjur.policy import PolicyData, PolicyLogic, PolicyController
+from conjur.variable import VariableLogic, VariableController, VariableData
 from conjur.version import __version__
 
 # pylint: disable=too-many-statements
@@ -218,25 +220,25 @@ def run(self, *args):
                                                        help='Manage policies')
         policy_subparsers = policy_parser.add_subparsers(dest='action')
 
-        apply_policy_parser = policy_subparsers.add_parser('apply',
-                                                           help='Apply a policy file')
-        apply_policy_parser.add_argument('name',
-                                         help='Name of the policy (usually "root")')
-        apply_policy_parser.add_argument('policy',
-                                         help='File containing the YAML policy')
+        load_policy_parser = policy_subparsers.add_parser('load',
+                                                           help='Load a policy file')
+        load_policy_parser.add_argument('-b', '--branch', required=True,
+                                         help='Provide the policy branch name (usually root)')
+        load_policy_parser.add_argument('-f', '--file', required=True,
+                                         help='Provide policy file name')
 
         replace_policy_parser = policy_subparsers.add_parser('replace',
                                                              help='Replace a policy file')
-        replace_policy_parser.add_argument('name',
+        replace_policy_parser.add_argument('-b', '--branch', required=True,
                                            help='Name of the policy (usually "root")')
-        replace_policy_parser.add_argument('policy',
+        replace_policy_parser.add_argument('-f', '--file', required=True,
                                            help='File containing the YAML policy')
 
-        delete_policy_parser = policy_subparsers.add_parser('delete',
-                                                            help='Delete a policy file')
-        delete_policy_parser.add_argument('name',
+        update_policy_parser = policy_subparsers.add_parser('update',
+                                                            help='Update a policy file')
+        update_policy_parser.add_argument('-b', '--branch', required=True,
                                           help='Name of the policy (usually "root")')
-        delete_policy_parser.add_argument('policy',
+        update_policy_parser.add_argument('-f', '--file', required=True,
                                           help='File containing the YAML policy')
 
         # *************** VARIABLE COMMAND ***************
@@ -244,17 +246,19 @@ def run(self, *args):
         variable_parser = resource_subparsers.add_parser('variable',
                                                          help='Manage variables',
                                                          formatter_class=formatter_class)
-        variable_subparsers = variable_parser.add_subparsers(dest='action')
-        get_variable_parser = variable_subparsers.add_parser('get',
-                                                             help='Get the value of a variable')
-        get_variable_parser.add_argument('variable_id',
-                                 help='ID of a variable', nargs='+')
-        set_variable_parser = variable_subparsers.add_parser('set',
-                                                             help='Set the value of a variable')
-        set_variable_parser.add_argument('variable_id',
-                                         help='ID of the variable')
-        set_variable_parser.add_argument('value',
-                                         help='New value of the variable')
+
+        variable_subparser = variable_parser.add_subparsers(title="Subcommand", dest='action')
+        variable_get_subcommand_parser = variable_subparser.add_parser(name="get")
+        variable_get_subcommand_parser.add_argument('-i', '--id',
+                                                    help='ID of a variable', nargs='+', required=True)
+        variable_get_subcommand_parser.add_argument('-v', '--version',
+                                                    help='Version of a variable')
+
+        variable_set_subcommand_parser = variable_subparser.add_parser(name="set")
+        variable_set_subcommand_parser.add_argument('-i', '--id',
+                                                    help='ID of a variable', required=True)
+        variable_set_subcommand_parser.add_argument('-v', '--value',
+                                                    help='New value of the variable', required=True)
 
         # *************** WHOAMI COMMAND ***************
 
@@ -287,11 +291,11 @@ def run(self, *args):
             sys.exit(0)
         except FileNotFoundError as not_found_error:
             logging.debug(traceback.format_exc())
-            sys.stdout.write(f"Error: No such file or directory: '{not_found_error.filename}'")
+            sys.stdout.write(f"Error: No such file or directory: '{not_found_error.filename}'\n")
             sys.exit(1)
         except requests.exceptions.HTTPError as client_error:
             logging.debug(traceback.format_exc())
-            sys.stdout.write(f"Failed to execute command. Reason: {client_error})")
+            sys.stdout.write(f"Failed to execute command. Reason: {client_error}\n")
             sys.exit(1)
         except Exception as error:
             logging.debug(traceback.format_exc())
@@ -345,6 +349,38 @@ def handle_list_logic(cls, ssl_verify=True, list_data=None, client=None):
                                          list_data=list_data)
         list_controller.load()
 
+    @classmethod
+    def handle_variable_logic(cls, ssl_verify=True, args=None, client=None):
+        """
+        Method that wraps the variable call logic
+        """
+        variable_logic = VariableLogic(client)
+        if args.action == 'get':
+            variable_data = VariableData(action=args.action, id=args.id, value=None,
+                                         variable_version=args.version)
+            variable_controller = VariableController(ssl_verify=ssl_verify,
+                                                     variable_logic=variable_logic,
+                                                     variable_data=variable_data)
+            variable_controller.get_variable()
+        elif args.action == 'set':
+            variable_data = VariableData(action=args.action, id=args.id, value=args.value,
+                                         variable_version=None)
+            variable_controller = VariableController(ssl_verify=ssl_verify,
+                                                     variable_logic=variable_logic,
+                                                     variable_data=variable_data)
+            variable_controller.set_variable()
+
+    @classmethod
+    def handle_policy_logic(cls, ssl_verify=True, policy_data=None, client=None):
+        """
+        Method that wraps the variable call logic
+        """
+        policy_logic = PolicyLogic(client)
+        policy_controller = PolicyController(ssl_verify=ssl_verify,
+                                             policy_logic=policy_logic,
+                                             policy_data=policy_data)
+        policy_controller.load()
+
     @staticmethod
     # pylint: disable=too-many-branches
     def run_action(resource, args):
@@ -367,7 +403,7 @@ def run_action(resource, args):
             return
 
         # Needed for unit tests so that they do not require configuring
-        if os.getenv('TEST_ENV') is None:
+        if os.getenv('TEST_ENV') is None or os.getenv('TEST_ENV') == 'False':
             # If the user runs a command without configuring the CLI or logging in,
             # we request they do so before executing their request
             # pylint: disable=line-too-long
@@ -391,28 +427,13 @@ def run_action(resource, args):
         elif resource == 'whoami':
             result = client.whoami()
             print(json.dumps(result, indent=4))
+
         elif resource == 'variable':
-            variable_id = args.variable_id
-            if args.action == 'get':
-                if len(variable_id) == 1:
-                    variable_value = client.get(variable_id[0])
-                    print(variable_value.decode('utf-8'), end='')
-                else:
-                    variable_values = client.get_many(*variable_id)
-                    print(json.dumps(variable_values, indent=4))
-            else:
-                client.set(variable_id, args.value)
-                print("Value set: '{}'".format(variable_id))
+            Cli.handle_variable_logic(args.ssl_verify, args, client)
+
         elif resource == 'policy':
-            if args.action == 'replace':
-                resources = client.replace_policy_file(args.name, args.policy)
-                print(json.dumps(resources, indent=4))
-            elif args.action == 'delete':
-                resources = client.delete_policy_file(args.name, args.policy)
-                print(json.dumps(resources, indent=4))
-            else:
-                resources = client.apply_policy_file(args.name, args.policy)
-                print(json.dumps(resources, indent=4))
+            policy_data = PolicyData(action=args.action, branch=args.branch, file=args.file)
+            Cli.handle_policy_logic(args.ssl_verify, policy_data, client)
 
     @staticmethod
     def _parse_args(parser):
diff --git a/conjur/client.py b/conjur/client.py
index 559659c5..75b712c1 100644
--- a/conjur/client.py
+++ b/conjur/client.py
@@ -177,11 +177,11 @@ def list(self, list_constraints=None):
         """
         return self._api.resources_list(list_constraints)
 
-    def get(self, variable_id):
+    def get(self, variable_id, version=None):
         """
         Gets a variable value based on its ID
         """
-        return self._api.get_variable(variable_id)
+        return self._api.get_variable(variable_id, version)
 
     def get_many(self, *variable_ids):
         """
@@ -196,11 +196,11 @@ def set(self, variable_id, value):
         """
         self._api.set_variable(variable_id, value)
 
-    def apply_policy_file(self, policy_name, policy_file):
+    def load_policy_file(self, policy_name, policy_file):
         """
         Applies a file-based policy to the Conjur instance
         """
-        return self._api.apply_policy_file(policy_name, policy_file)
+        return self._api.load_policy_file(policy_name, policy_file)
 
     def replace_policy_file(self, policy_name, policy_file):
         """
@@ -208,8 +208,8 @@ def replace_policy_file(self, policy_name, policy_file):
         """
         return self._api.replace_policy_file(policy_name, policy_file)
 
-    def delete_policy_file(self, policy_name, policy_file):
+    def update_policy_file(self, policy_name, policy_file):
         """
         Replaces a file-based policy defined in the Conjur instance
         """
-        return self._api.delete_policy_file(policy_name, policy_file)
+        return self._api.update_policy_file(policy_name, policy_file)
diff --git a/conjur/http.py b/conjur/http.py
index 1675c46d..e3c376c7 100644
--- a/conjur/http.py
+++ b/conjur/http.py
@@ -8,6 +8,7 @@
 """
 
 import base64
+import logging
 from enum import Enum
 from urllib.parse import quote
 
@@ -32,7 +33,6 @@ def invoke_endpoint(http_verb, endpoint, params, *args, check_errors=True,
     """
     This method flexibly invokes HTTP calls from 'requests' module
     """
-
     orig_params = params or {}
 
     # Escape all params
@@ -60,7 +60,18 @@ def invoke_endpoint(http_verb, endpoint, params, *args, check_errors=True,
                               headers=headers)
 
     if check_errors:
-        response.raise_for_status()
+        # takes the "requests" response object and expands the
+        # raise_for_status method to return more helpful errors for debug logs
+        try:
+            response.raise_for_status()
+        except requests.exceptions.HTTPError as http_error:
+            if response.text:
+                logging.debug(requests.exceptions.HTTPError(f'{http_error.response.status_code}' \
+                                                            f' {http_error.response.reason}' \
+                                                            f' {response.text}'))
+            raise http_error
+        except Exception as general_error:
+            raise general_error
 
     return response
 
@@ -83,7 +94,6 @@ def enable_http_logging(): #pragma: no cover
                        "is removed, the PR should not be approved")
 
     # pylint: disable=unreachable,import-outside-toplevel
-    import logging
     #pylint: disable=import-outside-toplevel
     from http.client import HTTPConnection
     HTTPConnection.debuglevel = 1
diff --git a/conjur/policy/__init__.py b/conjur/policy/__init__.py
new file mode 100644
index 00000000..fd96e7d5
--- /dev/null
+++ b/conjur/policy/__init__.py
@@ -0,0 +1,9 @@
+"""
+Policy module
+
+This metafile includes all the functionality that will be exposed
+when you install this module
+"""
+from conjur.policy.policy_controller import PolicyController
+from conjur.policy.policy_logic import PolicyLogic
+from conjur.policy.policy_data import PolicyData
diff --git a/conjur/policy/policy_controller.py b/conjur/policy/policy_controller.py
new file mode 100644
index 00000000..707c90fb
--- /dev/null
+++ b/conjur/policy/policy_controller.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+"""
+PolicyController module
+
+This module is the controller that facilitates all list actions
+required to successfully execute the POLICY command
+"""
+import sys
+
+# pylint: disable=too-few-public-methods
+class PolicyController:
+    """
+    PolicyController
+
+    This class represents the Presentation Layer for the POLICY command
+    """
+    def __init__(self, ssl_verify: bool, policy_logic, policy_data):
+        self.ssl_verify = ssl_verify
+        self.policy_logic = policy_logic
+        self.policy_data = policy_data
+
+    def load(self):
+        """
+        Method that facilitates all method calls in this class
+        """
+        result = self.policy_logic.run_action(self.policy_data)
+        sys.stdout.write(result+'\n')
diff --git a/conjur/policy/policy_data.py b/conjur/policy/policy_data.py
new file mode 100644
index 00000000..a5a7510a
--- /dev/null
+++ b/conjur/policy/policy_data.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+"""
+PolicyData module
+
+This module represents the DTO that holds the params the user passes in.
+We use this DTO to build the policy request
+"""
+
+# pylint: disable=too-few-public-methods
+class PolicyData:
+    """
+    Used for organizing the the params the user passed in to execute the policy command
+    """
+    def __init__(self, **arg_params):
+        self.action = arg_params['action']
+        self.branch = arg_params['branch']
+        self.file = arg_params['file']
+
+    def __repr__(self):
+        result = []
+        # pylint: disable=multiple-statements
+        if self.action=='load': result.append("Loading ")
+        if self.action=='replace': result.append("Replacing ")
+        if self.action=='update': result.append("Updating ")
+        if self.file: result.append(f"'{self.file}' ")
+        if self.branch: result.append(f"under '{self.branch}'...")
+        return ''.join(result)
diff --git a/conjur/policy/policy_logic.py b/conjur/policy/policy_logic.py
new file mode 100644
index 00000000..1ff6e706
--- /dev/null
+++ b/conjur/policy/policy_logic.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+"""
+PolicyLogic module
+
+This module is the business logic for executing the POLICY command
+"""
+# Builtins
+import json
+import logging
+
+# pylint: disable=too-few-public-methods
+class PolicyLogic:
+    """
+    PolicyLogic
+
+    This class holds the business logic for executing and manipulating
+    returned data
+    """
+    def __init__(self, client):
+        self.client = client
+
+    def run_action(self, policy_data):
+        """
+        Method to determine which subcommand action to run {apply, replace, update}
+        """
+        # pylint: disable=logging-fstring-interpolation
+        logging.debug(f"{policy_data}")
+        if policy_data.action == 'replace':
+            resources = self.client.replace_policy_file(policy_data.branch, policy_data.file)
+        elif policy_data.action == 'update':
+            resources = self.client.update_policy_file(policy_data.branch, policy_data.file)
+        else:
+            resources = self.client.load_policy_file(policy_data.branch, policy_data.file)
+
+        return json.dumps(resources, indent=4)
diff --git a/conjur/variable/__init__.py b/conjur/variable/__init__.py
new file mode 100644
index 00000000..acc0364f
--- /dev/null
+++ b/conjur/variable/__init__.py
@@ -0,0 +1,9 @@
+"""
+Variable module
+
+This metafile includes all the functionality that will be exposed
+when you install this module
+"""
+from conjur.variable.variable_controller import VariableController
+from conjur.variable.variable_logic import VariableLogic
+from conjur.variable.variable_data import VariableData
diff --git a/conjur/variable/variable_controller.py b/conjur/variable/variable_controller.py
new file mode 100644
index 00000000..83298585
--- /dev/null
+++ b/conjur/variable/variable_controller.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+"""
+VariableController module
+
+This module is the controller that facilitates all list actions
+required to successfully execute the VARIABLE command
+"""
+import sys
+
+# pylint: disable=too-few-public-methods
+class VariableController:
+    """
+    VariableController
+
+    This class represents the Presentation Layer for the VARIABLE command
+    """
+    def __init__(self, ssl_verify, variable_logic, variable_data):
+        self.ssl_verify = ssl_verify
+        self.variable_logic = variable_logic
+        self.variable_data = variable_data
+
+    def get_variable(self):
+        """
+        Method that facilitates get call to the logic
+        """
+        result = self.variable_logic.get_variable(self.variable_data)
+        sys.stdout.write(result+'\n')
+
+    def set_variable(self):
+        """
+        Method that facilitates set call to the logic
+        """
+        result = self.variable_logic.set_variable(self.variable_data)
+        sys.stdout.write(f"Successfully set value for variable '{result}'\n")
diff --git a/conjur/variable/variable_data.py b/conjur/variable/variable_data.py
new file mode 100644
index 00000000..a8d769d8
--- /dev/null
+++ b/conjur/variable/variable_data.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+"""
+VariableData module
+
+This module represents the DTO that holds the params the user passes in.
+We use this DTO to build the variable request
+"""
+
+# pylint: disable=too-few-public-methods
+class VariableData:
+    """
+    Used for organizing the params the user passed in to execute the variable command
+    """
+    def __init__(self, **arg_params):
+        self.action = arg_params['action']
+        self.variable_id = arg_params['id']
+        # pylint: disable=line-too-long
+        self.variable_version = arg_params['variable_version'] if arg_params['variable_version'] else None
+        self.value = arg_params['value'] if arg_params['value'] else None
+
+    def __repr__(self):
+        result = []
+        # pylint: disable=multiple-statements
+        if self.action == 'get': result.append(f"Getting variable values for: {self.variable_id} ")
+        if self.variable_version: result.append(f"with version {self.variable_version}")
+        if self.action == 'set': result.append(f"Setting variable value for: '{self.variable_id}'")
+        return ''.join(result)
diff --git a/conjur/variable/variable_logic.py b/conjur/variable/variable_logic.py
new file mode 100644
index 00000000..4e9c93ed
--- /dev/null
+++ b/conjur/variable/variable_logic.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+"""
+VariableLogic module
+
+This module is the business logic for executing the VARIABLE command
+"""
+
+# Builtins
+import json
+import logging
+
+# pylint: disable=too-few-public-methods
+class VariableLogic:
+    """
+    VariableLogic
+
+    This class holds the business logic for executing and manipulating
+    returned data
+    """
+    def __init__(self, client):
+        self.client = client
+
+    # pylint: disable=logging-fstring-interpolation
+    def get_variable(self, variable_data):
+        """
+        Method to handle all get action activity
+        """
+        logging.debug(variable_data)
+        # pylint: disable=no-else-return
+        if len(variable_data.variable_id) == 1:
+            variable_value = self.client.get(variable_data.variable_id[0],
+                                             variable_data.variable_version)
+            return variable_value.decode('utf-8')
+        else:
+            variable_values = self.client.get_many(*variable_data.variable_id)
+            return json.dumps(variable_values, indent=4)
+
+    # pylint: disable=logging-fstring-interpolation
+    def set_variable(self, variable_data):
+        """
+        Method to handle all set action activity
+        """
+        logging.debug(variable_data)
+        self.client.set(variable_data.variable_id, variable_data.value)
+
+        logging.debug(f"Successfully set value for variable '{variable_data.variable_id}'")
+        return variable_data.variable_id


Test patch:
diff --git a/test/test_config/variable_policy.yml b/test/test_config/variable_policy.yml
new file mode 100644
index 00000000..127bcfff
--- /dev/null
+++ b/test/test_config/variable_policy.yml
@@ -0,0 +1,2 @@
+- !variable some variable with spaces
+- !variable variablespecialchars
diff --git a/test/test_integration_cli.py b/test/test_integration_cli.py
deleted file mode 100644
index 6680d049..00000000
--- a/test/test_integration_cli.py
+++ /dev/null
@@ -1,380 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-CLI Integration tests
-
-This test file handles the main test flows after initialization and configuration
-"""
-import json
-import tempfile
-import uuid
-
-import Utils
-from conjur.constants import *
-from .util.cli_helpers import integration_test
-from test.util.test_runners.integration_test_case import IntegrationTestCaseBase
-
-
-# Not coverage tested since integration tests doesn't run in
-# the same build step
-class CliIntegrationTest(IntegrationTestCaseBase):  # pragma: no cover
-    DEFINED_VARIABLE_ID = 'one/password'
-
-    def __init__(self, testname, client_params=None, environment_params=None):
-        super(CliIntegrationTest, self).__init__(testname, client_params, environment_params)
-
-    # *************** HELPERS ***************
-
-    def setup_cli_params(self, env_vars, *params):
-        self.cli_auth_params = ['--debug']
-        self.cli_auth_params += params
-
-        return self.cli_auth_params
-
-    def setUp(self):
-        self.setup_cli_params({})
-        Utils.init_to_cli(self)
-        with open(DEFAULT_NETRC_FILE, 'w') as netrc:
-            netrc.write(f"machine {self.client_params.hostname}\n")
-            netrc.write("login admin\n")
-            netrc.write(f"password {self.client_params.env_api_key}\n")
-
-        return self.invoke_cli(self.cli_auth_params,
-                               ['policy', 'replace', 'root', self.environment.path_provider.get_policy_path("initial")])
-
-    def set_variable(self, variable_id, value, exit_code=0):
-        return self.invoke_cli(self.cli_auth_params,
-                               ['variable', 'set', variable_id, value], exit_code=exit_code)
-
-    def apply_policy(self, policy_path):
-        return self.invoke_cli(self.cli_auth_params,
-                               ['policy', 'apply', 'root', policy_path])
-
-    def apply_policy_from_string(self, policy):
-        output = None
-        with tempfile.NamedTemporaryFile() as temp_policy_file:
-            temp_policy_file.write(policy.encode('utf-8'))
-            temp_policy_file.flush()
-
-            # Run the new apply that should not result in newly created roles
-            output = self.apply_policy(temp_policy_file.name)
-
-        return output
-
-    def replace_policy(self, policy_path):
-        return self.invoke_cli(self.cli_auth_params,
-                               ['policy', 'replace', 'root', policy_path])
-
-    def replace_policy_from_string(self, policy):
-        output = None
-        with tempfile.NamedTemporaryFile() as temp_policy_file:
-            temp_policy_file.write(policy.encode('utf-8'))
-            temp_policy_file.flush()
-
-            # Run the new replace that should not result in newly created roles
-            output = self.replace_policy(temp_policy_file.name)
-
-        return output
-
-    def delete_policy(self, policy_path):
-        return self.invoke_cli(self.cli_auth_params,
-                               ['policy', 'delete', 'root', policy_path])
-
-    def delete_policy_from_string(self, policy):
-        output = None
-        with tempfile.NamedTemporaryFile() as temp_policy_file:
-            temp_policy_file.write(policy.encode('utf-8'))
-            temp_policy_file.flush()
-
-            # Run the new delete that should not result in newly created roles
-            output = self.delete_policy(temp_policy_file.name)
-
-        return output
-
-    def get_variable(self, *variable_ids):
-        return self.invoke_cli(self.cli_auth_params,
-                               ['variable', 'get', *variable_ids])
-
-    def assert_set_and_get(self, variable_id):
-        expected_value = uuid.uuid4().hex
-
-        self.set_variable(variable_id, expected_value)
-        output = self.get_variable(variable_id)
-        self.assertEquals(expected_value, output)
-
-    def assert_variable_set_fails(self, variable_id, error_class, exit_code=0):
-        with self.assertRaises(error_class):
-            self.set_variable(variable_id, uuid.uuid4().hex, exit_code)
-
-    def print_instead_of_raise_error(self, variable_id, error_message_regex):
-        output = self.invoke_cli(self.cli_auth_params,
-                                 ['variable', 'set', variable_id, uuid.uuid4().hex], exit_code=1)
-
-        self.assertRegex(output, error_message_regex)
-
-    def generate_policy_string(self):
-        variable_1 = 'simple/basic/{}'.format(uuid.uuid4().hex)
-        variable_2 = 'simple/space filled/{}'.format(uuid.uuid4().hex)
-        variable_3 = 'simple/special @#$%^&*(){{}}[]._+/{id}'.format(id=uuid.uuid4().hex)
-
-        policy = \
-            """
-            - !variable
-              id: {variable_1}
-            - !variable
-              id: {variable_2}
-            - !variable
-              id: {variable_3}
-"""
-
-        dynamic_policy = policy.format(variable_1=variable_1,
-                                       variable_2=variable_2,
-                                       variable_3=variable_3)
-
-        return (dynamic_policy, [variable_1, variable_2, variable_3])
-
-    # *************** TESTS ***************
-    '''
-    A non-existent policy file will return FileNotFound error message
-    '''
-
-    @integration_test
-    def test_apply_policy_raises_file_not_exists_error(self):
-        output = self.invoke_cli(self.cli_auth_params,
-                                 ['policy', 'apply', 'root', 'somepolicy.yml'], exit_code=1)
-        self.assertRegex(output, "Error: No such file or directory:")
-
-    '''
-    A non-existent variable file will return a 404 Not Found error message
-    '''
-
-    @integration_test
-    def test_unknown_secret_raises_not_found_error(self):
-        output = self.invoke_cli(self.cli_auth_params,
-                                 ['variable', 'get', 'unknown'], exit_code=1)
-        self.assertRegex(output, "404 Client Error: Not Found for url:")
-
-    @integration_test
-    def test_https_cli_can_set_and_get_a_defined_variable(self):
-        self.setup_cli_params({})
-
-        self.assert_set_and_get(CliIntegrationTest.DEFINED_VARIABLE_ID)
-
-    @integration_test
-    def test_https_cli_can_set_and_get_a_defined_variable_if_cert_not_provided_and_verification_disabled(self):
-        self.setup_cli_params({}, '--insecure')
-        self.assert_set_and_get(CliIntegrationTest.DEFINED_VARIABLE_ID)
-
-    @integration_test
-    def test_https_cli_can_batch_get_multiple_variables(self):
-        self.setup_cli_params({})
-
-        policy, variables = self.generate_policy_string()
-        with tempfile.NamedTemporaryFile() as temp_policy_file:
-            temp_policy_file.write(policy.encode('utf-8'))
-            temp_policy_file.flush()
-
-            self.apply_policy(temp_policy_file.name)
-
-        value_map = {}
-        for variable in variables:
-            value = uuid.uuid4().hex
-            self.set_variable(variable, value)
-            value_map[variable] = value
-
-        batch_result_string = self.get_variable(*variables)
-        batch_result = json.loads(batch_result_string)
-
-        for variable_name, variable_value in value_map.items():
-            self.assertEquals(variable_value, batch_result[variable_name])
-
-    @integration_test
-    def test_https_can_apply_policy(self):
-        self.setup_cli_params({})
-
-        policy, variables = self.generate_policy_string()
-        self.apply_policy_from_string(policy)
-
-        for variable in variables:
-            self.assert_set_and_get(variable)
-
-    @integration_test
-    def test_https_apply_policy_can_output_returned_data(self):
-        self.setup_cli_params({})
-
-        user_id1 = uuid.uuid4().hex
-        user_id2 = uuid.uuid4().hex
-        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
-                                                                   user_id2=user_id2)
-
-        # Run the new apply that should not result in newly created roles
-        json_result = json.loads(self.apply_policy_from_string(policy))
-
-        account_name = self.client_params.account
-        expected_object = {
-            'version': json_result['version'],
-            'created_roles': {
-                f'{account_name}:user:' + user_id1: {
-                    'id': f'{account_name}:user:' + user_id1,
-                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id1]['api_key'],
-                },
-                f'{account_name}:user:' + user_id2: {
-                    'id': f'{account_name}:user:' + user_id2,
-                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id2]['api_key'],
-                }
-            }
-        }
-
-        self.assertDictEqual(json_result, expected_object)
-
-    @integration_test
-    def test_https_apply_policy_doesnt_break_if_no_created_roles(self):
-        self.setup_cli_params({})
-
-        user_id1 = uuid.uuid4().hex
-        user_id2 = uuid.uuid4().hex
-        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
-                                                                   user_id2=user_id2)
-        # Ensure that the accounts exist
-        self.apply_policy_from_string(policy)
-
-        # Run the new apply that should not result in newly created roles
-        json_result = json.loads(self.apply_policy_from_string(policy))
-
-        expected_object = {
-            'version': json_result['version'],
-            'created_roles': {}
-        }
-
-        self.assertDictEqual(json_result, expected_object)
-
-    @integration_test
-    def test_https_can_replace_policy(self):
-        self.setup_cli_params({})
-
-        orig_policy, old_variables = self.generate_policy_string()
-
-        with tempfile.NamedTemporaryFile() as temp_policy_file:
-            temp_policy_file.write(orig_policy.encode('utf-8'))
-            temp_policy_file.flush()
-
-            self.apply_policy(temp_policy_file.name)
-
-        replacement_policy, new_variables = self.generate_policy_string()
-        self.replace_policy_from_string(replacement_policy)
-
-        for new_variable in new_variables:
-            self.assert_set_and_get(new_variable)
-
-        for old_variable in old_variables:
-            self.print_instead_of_raise_error(old_variable, "404 Client Error: Not Found for url")
-
-    @integration_test
-    def test_https_replace_policy_can_output_returned_data(self):
-        self.setup_cli_params({})
-
-        user_id1 = uuid.uuid4().hex
-        user_id2 = uuid.uuid4().hex
-        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
-                                                                   user_id2=user_id2)
-        json_result = json.loads(self.replace_policy_from_string(policy))
-        account_name = self.client_params.account
-
-        expected_object = {
-            'version': json_result['version'],
-            'created_roles': {
-                f'{account_name}:user:' + user_id1: {
-                    'id': f'{account_name}:user:' + user_id1,
-                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id1]['api_key'],
-                },
-                f'{account_name}:user:' + user_id2: {
-                    f'id': f'{account_name}:user:' + user_id2,
-                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id2]['api_key'],
-                }
-            }
-        }
-
-        self.assertDictEqual(json_result, expected_object)
-
-    @integration_test
-    def test_https_replace_policy_doesnt_break_if_no_created_roles(self):
-        self.setup_cli_params({})
-
-        policy = "- !policy foo\n"
-        json_result = json.loads(self.replace_policy_from_string(policy))
-
-        expected_object = {
-            'version': json_result['version'],
-            'created_roles': {}
-        }
-
-        self.assertDictEqual(json_result, expected_object)
-
-    @integration_test
-    def test_https_can_delete_policy(self):
-        self.setup_cli_params({})
-
-        policy, variables = self.generate_policy_string()
-        self.delete_policy_from_string(policy)
-
-        for variable in variables:
-            self.assert_set_and_get(variable)
-
-    @integration_test
-    def test_https_delete_policy_can_output_returned_data(self):
-        self.setup_cli_params({})
-
-        user_id1 = uuid.uuid4().hex
-        user_id2 = uuid.uuid4().hex
-        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
-                                                                   user_id2=user_id2)
-
-        # Run the new delete that should not result in newly created roles
-        json_result = json.loads(self.delete_policy_from_string(policy))
-
-        account_name = self.client_params.account
-        expected_object = {
-            'version': json_result['version'],
-            'created_roles': {
-                f'{account_name}:user:' + user_id1: {
-                    'id': f'{account_name}:user:' + user_id1,
-                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id1]['api_key'],
-                },
-                f'{account_name}:user:' + user_id2: {
-                    'id': f'{account_name}:user:' + user_id2,
-                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id2]['api_key'],
-                }
-            }
-        }
-
-        self.assertDictEqual(json_result, expected_object)
-
-    @integration_test
-    def test_https_delete_policy_doesnt_break_if_no_created_roles(self):
-        self.setup_cli_params({})
-
-        user_id1 = uuid.uuid4().hex
-        user_id2 = uuid.uuid4().hex
-        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
-                                                                   user_id2=user_id2)
-        # Ensure that the accounts exist
-        self.delete_policy_from_string(policy)
-
-        # Run the new apply that should not result in newly created roles
-        json_result = json.loads(self.delete_policy_from_string(policy))
-
-        expected_object = {
-            'version': json_result['version'],
-            'created_roles': {}
-        }
-
-        self.assertDictEqual(json_result, expected_object)
-
-    @integration_test
-    def test_https_can_get_whoami(self):
-        self.setup_cli_params({})
-
-        output = self.invoke_cli(self.cli_auth_params, ['whoami'])
-        response = json.loads(output)
-        self.assertEquals(response.get('account'), f'{self.client_params.account}')
-        self.assertEquals(response.get('username'), 'admin')
diff --git a/test/test_integration_configurations.py b/test/test_integration_configurations.py
index 00d91fd0..74f41cef 100644
--- a/test/test_integration_configurations.py
+++ b/test/test_integration_configurations.py
@@ -56,7 +56,7 @@ def evoke_list(self, exit_code=0):
     def test_https_conjurrc_is_created_with_all_parameters_given(self, mock_input):
         self.setup_cli_params({})
         self.invoke_cli(self.cli_auth_params,
-                        ['init', '--url', self.client_params.hostname, '--account', 'someaccount'], exit_code=0)
+                        ['init', '--url', self.client_params.hostname, '--account', 'someaccount'])
 
         assert os.path.isfile(DEFAULT_CERTIFICATE_FILE)
 
@@ -88,7 +88,7 @@ def test_https_conjurrc_user_forces_overwrite_does_not_request_confirmation(self
         self.setup_cli_params({})
         output = self.invoke_cli(self.cli_auth_params,
                                  ['init', '--url', self.client_params.hostname, '--account', self.client_params.login,
-                                  '--force'], exit_code=0)
+                                  '--force'])
 
         assert "Not overwriting" not in output
 
diff --git a/test/test_integration_credentials.py b/test/test_integration_credentials.py
index 727d2768..6a2f5df2 100644
--- a/test/test_integration_credentials.py
+++ b/test/test_integration_credentials.py
@@ -68,17 +68,15 @@ def write_to_netrc(self, machine, login, password):
     3. Update their password to a randomly generated one
     4. Login with their password
     '''
-
     @integration_test
     @patch('builtins.input', return_value='yes')
     def test_https_netrc_is_created_with_all_parameters_given(self, mock_input):
         self.invoke_cli(self.cli_auth_params,
-                        ['login', '-n', 'admin', '-p', self.client_params.env_api_key], exit_code=0)
+                        ['login', '-n', 'admin', '-p', self.client_params.env_api_key])
 
         # Load in new user
         self.invoke_cli(self.cli_auth_params,
-                        ['policy', 'replace', 'root', self.environment.path_provider.get_policy_path('conjur')],
-                        exit_code=0)
+                        ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path('conjur')])
 
         # Get admin's access token to be able to use it to change the user's password
         headers = {
@@ -113,7 +111,7 @@ def test_https_netrc_is_created_with_all_parameters_given(self, mock_input):
         # Need to remove the netrc because we are attempting to login as a user with their new password
         os.remove(DEFAULT_NETRC_FILE)
         output = self.invoke_cli(self.cli_auth_params,
-                                 ['login', '-n', 'someuser', '-p', password], exit_code=0)
+                                 ['login', '-n', 'someuser', '-p', password])
 
         self.assertEquals(output.strip(), "Successfully logged in to Conjur")
         self.validate_netrc(f"{self.client_params.hostname}/authn", "someuser", user_api_key)
@@ -121,12 +119,11 @@ def test_https_netrc_is_created_with_all_parameters_given(self, mock_input):
     '''
     Validates interactively provided params create netrc
     '''
-
     @integration_test
     @patch('builtins.input', return_value='admin')
     def test_https_netrc_is_created_with_all_parameters_given_interactively(self, mock_pass):
         with patch('getpass.getpass', return_value=self.client_params.env_api_key):
-            output = self.invoke_cli(self.cli_auth_params, ['login'], exit_code=0)
+            output = self.invoke_cli(self.cli_auth_params, ['login'])
 
             assert os.path.exists(DEFAULT_NETRC_FILE) and os.path.getsize(DEFAULT_NETRC_FILE) != 0
             self.assertEquals(output.strip(), "Successfully logged in to Conjur")
@@ -135,7 +132,6 @@ def test_https_netrc_is_created_with_all_parameters_given_interactively(self, mo
     '''
     Validates a wrong username will raise Unauthorized error
     '''
-
     @integration_test
     @patch('builtins.input', return_value='somebaduser')
     def test_https_netrc_raises_error_with_wrong_user(self, mock_pass):
@@ -149,7 +145,6 @@ def test_https_netrc_raises_error_with_wrong_user(self, mock_pass):
     '''
     Validates a wrong password will raise Unauthorized error
     '''
-
     @integration_test
     @patch('builtins.input', return_value='admin')
     @patch('getpass.getpass', return_value='somewrongpass')
@@ -166,7 +161,7 @@ def test_https_netrc_with_wrong_password(self, mock_pass, mock_input):
     def test_https_netrc_is_created_when_provided_user_api_key(self, mock_pass):
         with patch('getpass.getpass', return_value=self.client_params.env_api_key):
             output = self.invoke_cli(self.cli_auth_params,
-                                     ['login'], exit_code=0)
+                                     ['login'])
 
             assert os.path.exists(DEFAULT_NETRC_FILE) and os.path.getsize(DEFAULT_NETRC_FILE) != 0
             self.assertEquals(output.strip(), "Successfully logged in to Conjur")
@@ -181,8 +176,7 @@ def test_https_netrc_is_created_when_provided_user_api_key(self, mock_pass):
     def test_https_netrc_was_not_overwritten_when_login_failed_but_already_logged_in(self):
 
         successful_run = self.invoke_cli(self.cli_auth_params,
-                                         ['login', '-n', 'admin', '-p', self.client_params.env_api_key],
-                                         exit_code=0)
+                                         ['login', '-n', 'admin', '-p', self.client_params.env_api_key])
         self.assertEquals(successful_run.strip(), "Successfully logged in to Conjur")
 
         unsuccessful_run = self.invoke_cli(self.cli_auth_params,
@@ -197,15 +191,13 @@ def test_https_netrc_was_not_overwritten_when_login_failed_but_already_logged_in
     There is currently no way to fetch a host's API key so this is a work around for the 
     purposes of this test
     '''
-
     @integration_test
     def test_https_netrc_is_created_with_host(self):
         # Setup for fetching the API key of a host. To fetch we need to login
         self.write_to_netrc(f"{self.client_params.hostname}/authn", "admin", self.client_params.env_api_key)
 
         self.invoke_cli(self.cli_auth_params,
-                        ['policy', 'replace', 'root', self.environment.path_provider.get_policy_path('conjur')],
-                        exit_code=0)
+                        ['policy', 'replace', '-b', 'root', '-f',self.environment.path_provider.get_policy_path('conjur')])
 
         url = f"{self.client_params.hostname}/authn/{self.client_params.account}/admin/authenticate"
         headers = {
@@ -227,7 +219,7 @@ def test_https_netrc_is_created_with_host(self):
         os.remove(DEFAULT_NETRC_FILE)
 
         output = self.invoke_cli(self.cli_auth_params,
-                                 ['login', '-n', 'host/somehost', '-p', f'{host_api_key}'], exit_code=0)
+                                 ['login', '-n', 'host/somehost', '-p', f'{host_api_key}'])
 
         self.validate_netrc(f"{self.client_params.hostname}/authn", "host/somehost", host_api_key)
         self.assertEquals(output.strip(), "Successfully logged in to Conjur")
@@ -235,39 +227,40 @@ def test_https_netrc_is_created_with_host(self):
     '''
     Validates when a user can logout successfully
     '''
-
     @integration_test
     def test_https_logout_successful(self):
 
         self.invoke_cli(self.cli_auth_params,
-                        ['login', '-n', 'admin', '-p', self.client_params.env_api_key], exit_code=0)
+                        ['login', '-n', 'admin', '-p', self.client_params.env_api_key])
         assert os.path.exists(DEFAULT_NETRC_FILE) and os.path.getsize(DEFAULT_NETRC_FILE) != 0
 
         output = self.invoke_cli(self.cli_auth_params,
-                                 ['logout'], exit_code=0)
+                                 ['logout'])
 
         self.assertEquals(output.strip(), 'Logged out of Conjur')
-        assert os.path.getsize(DEFAULT_NETRC_FILE) == 1
+        with open(DEFAULT_NETRC_FILE) as netrc_file:
+            assert netrc_file.read().strip() == "", 'netrc file is not empty!'
 
     '''
     Validates when a user attempts to logout after an already 
     successful logout, will fail
     '''
-
     @integration_test
     def test_https_logout_twice_returns_could_not_logout_message(self):
 
         self.invoke_cli(self.cli_auth_params,
-                        ['login', '-n', 'admin', '-p', self.client_params.env_api_key], exit_code=0)
+                        ['login', '-n', 'admin', '-p', self.client_params.env_api_key])
 
         self.invoke_cli(self.cli_auth_params,
-                        ['logout'], exit_code=0)
+                        ['logout'])
 
         unsuccessful_logout = self.invoke_cli(self.cli_auth_params,
                                               ['logout'], exit_code=1)
 
         self.assertEquals(unsuccessful_logout.strip(), "Failed to log out. Please log in.")
-        assert os.path.getsize(DEFAULT_NETRC_FILE) == 1
+        with open(DEFAULT_NETRC_FILE) as netrc_file:
+            assert netrc_file.read().strip() == "", 'netrc file is not empty!'
+
 
     @integration_test
     def test_no_netrc_and_logout_returns_could_not_logout_message(self):
@@ -282,7 +275,6 @@ def test_no_netrc_and_logout_returns_could_not_logout_message(self):
     '''
     Validates logout doesn't remove another entry not associated with Cyberark
     '''
-
     @integration_test
     def test_https_netrc_does_not_remove_irrelevant_entry(self):
 
@@ -296,7 +288,7 @@ def test_https_netrc_does_not_remove_irrelevant_entry(self):
             netrc_test.write("password somepass\n")
 
         self.invoke_cli(self.cli_auth_params,
-                        ['logout'], exit_code=0)
+                        ['logout'])
 
         with open(DEFAULT_NETRC_FILE, 'r') as netrc:
             entries = netrc.readlines()
@@ -312,7 +304,6 @@ def test_https_netrc_does_not_remove_irrelevant_entry(self):
     Validates that when the user does not log in and attempt
     to interface with the CLI, they will be prompted to
     '''
-
     @integration_test
     @patch('builtins.input', return_value='someaccount')
     @patch('getpass.getpass', return_value='somepass')
diff --git a/test/test_integration_list.py b/test/test_integration_list.py
index 4ac84be3..0bcf486c 100644
--- a/test/test_integration_list.py
+++ b/test/test_integration_list.py
@@ -7,7 +7,6 @@
 """
 import io
 from contextlib import redirect_stderr
-from unittest.mock import patch
 
 import Utils
 from .util.cli_helpers import integration_test
@@ -16,9 +15,9 @@
 
 # Not coverage tested since integration tests doesn't run in
 # the same build step
-class CliIntegrationListTest(IntegrationTestCaseBase):  # pragma: no cover
+class CliIntegrationTestList(IntegrationTestCaseBase):  # pragma: no cover
     def __init__(self, testname, client_params=None, environment_params=None):
-        super(CliIntegrationListTest, self).__init__(testname, client_params, environment_params)
+        super(CliIntegrationTestList, self).__init__(testname, client_params, environment_params)
 
     # *************** HELPERS ***************
 
@@ -30,24 +29,25 @@ def setup_cli_params(self, env_vars, *params):
 
     def setUp(self):
         self.setup_cli_params({})
-        # Need to configure the CLI and login to perform further commands
+        # Used to configure the CLI and login to run tests
         Utils.setup_cli(self)
-        return self.invoke_cli(self.cli_auth_params,
-                       ['policy', 'replace', 'root', self.environment.path_provider.get_policy_path("list")])
+        self.invoke_cli(self.cli_auth_params,
+                       ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("list")])
+
+    # *************** TESTS ***************
 
     @integration_test
     def test_list_returns_resources(self):
         output = self.invoke_cli(self.cli_auth_params, ['list'])
-        self.assertEquals(output,
-                          f'[\n    "{self.client_params.account}:policy:root",\n'
-                          f'    "{self.client_params.account}:user:someuser",\n'
-                          f'    "{self.client_params.account}:layer:somelayer",\n'
-                          f'    "{self.client_params.account}:group:somegroup",\n'
-                          f'    "{self.client_params.account}:host:anotherhost",\n'
-                          f'    "{self.client_params.account}:variable:one/password",\n'
-                          f'    "{self.client_params.account}:webservice:somewebservice"\n]\n')
+        self.assertEquals(f'[\n    "{self.client_params.account}:policy:root",\n'
+                      f'    "{self.client_params.account}:user:someuser",\n'
+                      f'    "{self.client_params.account}:layer:somelayer",\n'
+                      f'    "{self.client_params.account}:group:somegroup",\n'
+                      f'    "{self.client_params.account}:host:anotherhost",\n'
+                      f'    "{self.client_params.account}:variable:one/password",\n'
+                      f'    "{self.client_params.account}:webservice:somewebservice"\n]\n', output)
 
-    # TODO This will need to be changed when UX is finalizeds
+    # TODO This will need to be changed when UX is finalized
     @integration_test
     def test_list_help_returns_help_screen(self):
         output = self.invoke_cli(self.cli_auth_params, ['list', '-h'])
@@ -56,7 +56,7 @@ def test_list_help_returns_help_screen(self):
     @integration_test
     def test_list_inspect_user_returns_info_on_user(self):
         self.invoke_cli(self.cli_auth_params,
-               ['policy', 'replace', 'root', self.environment.path_provider.get_policy_path("conjur")])
+               ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("conjur")])
         output = self.invoke_cli(self.cli_auth_params, ['list', '--inspect', '--kind', 'user'])
         self.assertIn(f'        "id": "{self.client_params.account}:user:someuser",\n'
                       f'        "owner": "{self.client_params.account}:user:admin",\n'
@@ -213,25 +213,27 @@ def test_list_offset_negative_raises_error(self):
         output = self.invoke_cli(self.cli_auth_params, ['list', '-o', '-1'], exit_code=1)
         self.assertIn("500 Server Error", output)
 
-    # TODO weird behavior. Instead of [] or failing we get full results
-    @integration_test
-    def test_list_string_offset_raises_error(self):
-        output = self.invoke_cli(self.cli_auth_params, ['list', '-o', 'somestring'])
-        self.assertEquals(output,
-                          f'[\n    "{self.client_params.account}:group:somegroup",\n'
-                          f'    "{self.client_params.account}:host:anotherhost",\n'
-                          f'    "{self.client_params.account}:layer:somelayer",\n'
-                          f'    "{self.client_params.account}:policy:root",\n'
-                          f'    "{self.client_params.account}:user:someuser",\n'
-                          f'    "{self.client_params.account}:variable:one/password",\n'
-                          f'    "{self.client_params.account}:webservice:somewebservice"\n]\n')
+    # This tests is commented out because of a bug in server (https://github.com/cyberark/conjur/issues/1997)
+    # where a string is considered valid input for offset. For example, when offset=somestring a list
+    # of Conjur resources are returned instead of a 500 internal server error
+    # @integration_test
+    # def test_list_string_offset_raises_error(self):
+    #     output = self.invoke_cli(self.cli_auth_params, ['list', '-o', 'somestring'])
+    #     self.assertEquals(output,
+    #                       f'[\n    "{self.client_params.account}:group:somegroup",\n'
+    #                       f'    "{self.client_params.account}:host:anotherhost",\n'
+    #                       f'    "{self.client_params.account}:layer:somelayer",\n'
+    #                       f'    "{self.client_params.account}:policy:root",\n'
+    #                       f'    "{self.client_params.account}:user:someuser",\n'
+    #                       f'    "{self.client_params.account}:variable:one/password",\n'
+    #                       f'    "{self.client_params.account}:webservice:somewebservice"\n]\n')
 
     @integration_test
     def test_list_short_search_returns_list_with_param(self):
         output = self.invoke_cli(self.cli_auth_params, ['list', '-s', 'someuser'])
         self.assertEquals(output,
                   f'[\n    "{self.client_params.account}:user:someuser"\n]\n')
-    test_list_short_search_returns_list_with_param.tester=True
+
     @integration_test
     def test_list_long_search_returns_list_with_param(self):
         output = self.invoke_cli(self.cli_auth_params, ['list', '--search=someuser'])
@@ -264,7 +266,7 @@ def test_list_role_nonexistent_user_returns_forbidden(self):
     @integration_test
     def test_list_combo_limit_and_kind_returns_specified_kind(self):
         self.invoke_cli(self.cli_auth_params,
-               ['policy', 'apply', 'root', self.environment.path_provider.get_policy_path("conjur")])
+               ['policy', 'load', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("conjur")])
         output = self.invoke_cli(self.cli_auth_params, ['list', '-l', '2', '-k', 'host'])
         self.assertEquals(output,
                           f'[\n    "{self.client_params.account}:host:anotherhost",\n'
@@ -273,7 +275,7 @@ def test_list_combo_limit_and_kind_returns_specified_kind(self):
     @integration_test
     def test_list_combo_limit_and_offset_returns_specified_list(self):
         self.invoke_cli(self.cli_auth_params,
-               ['policy', 'apply', 'root', self.environment.path_provider.get_policy_path("conjur")])
+               ['policy', 'load', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("conjur")])
         output = self.invoke_cli(self.cli_auth_params, ['list', '-o', '2', '-l', '3'])
         self.assertEquals(output,
                           f'[\n    "{self.client_params.account}:host:somehost",\n'
diff --git a/test/test_integration_oss.py b/test/test_integration_oss.py
index 203ad538..efb6c924 100644
--- a/test/test_integration_oss.py
+++ b/test/test_integration_oss.py
@@ -62,7 +62,7 @@ def test_https_conjurrc_is_created_with_no_parameters_given(self):
         with patch('builtins.input', side_effect=[self.client_params.hostname, 'yes', 'someotheraccount']):
             self.setup_cli_params({})
             self.invoke_cli(self.cli_auth_params,
-                            ['init'], exit_code=0)
+                            ['init'])
 
             with open(f"{DEFAULT_CONFIG_FILE}", 'r') as conjurrc:
                 lines = conjurrc.readlines()
diff --git a/test/test_integration_policy.py b/test/test_integration_policy.py
new file mode 100644
index 00000000..234a99ab
--- /dev/null
+++ b/test/test_integration_policy.py
@@ -0,0 +1,319 @@
+# -*- coding: utf-8 -*-
+
+"""
+CLI Integration tests
+
+This test file handles the main test flows after initialization and configuration
+"""
+import io
+import json
+import tempfile
+import uuid
+from contextlib import redirect_stderr
+
+import Utils
+from conjur.constants import *
+from .util.cli_helpers import integration_test
+from test.util.test_runners.integration_test_case import IntegrationTestCaseBase
+
+
+# Not coverage tested since integration tests doesn't run in
+# the same build step
+class CliIntegrationPolicy(IntegrationTestCaseBase):  # pragma: no cover
+    capture_stream = io.StringIO()
+    def __init__(self, testname, client_params=None, environment_params=None):
+        super(CliIntegrationPolicy, self).__init__(testname, client_params, environment_params)
+
+    # *************** HELPERS ***************
+
+    def setup_cli_params(self, env_vars, *params):
+        self.cli_auth_params = ['--debug']
+        self.cli_auth_params += params
+
+        return self.cli_auth_params
+
+    def setUp(self):
+        self.setup_cli_params({})
+        Utils.setup_cli(self)
+        return self.invoke_cli(self.cli_auth_params,
+                               ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("initial")])
+
+    # *************** TESTS ***************
+
+    @integration_test
+    def test_https_can_load_policy(self):
+        self.setup_cli_params({})
+
+        policy, variables = Utils.generate_policy_string(self)
+        Utils.load_policy_from_string(self, policy)
+
+        for variable in variables:
+            Utils.assert_set_and_get(self, variable)
+
+    @integration_test
+    def test_load_policy_of_new_resources_returns_new_entry_json_data(self):
+        self.setup_cli_params({})
+
+        user_id1 = uuid.uuid4().hex
+        user_id2 = uuid.uuid4().hex
+        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
+                                                                   user_id2=user_id2)
+
+        # Run the new load that should not result in newly created roles
+        json_result = json.loads(Utils.load_policy_from_string(self, policy))
+
+        account_name = self.client_params.account
+        expected_object = {
+            'version': json_result['version'],
+            'created_roles': {
+                f'{account_name}:user:' + user_id1: {
+                    'id': f'{account_name}:user:' + user_id1,
+                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id1]['api_key'],
+                },
+                f'{account_name}:user:' + user_id2: {
+                    'id': f'{account_name}:user:' + user_id2,
+                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id2]['api_key'],
+                }
+            }
+        }
+
+        self.assertDictEqual(json_result, expected_object)
+
+    '''
+    Validates that a policy command without a subcommand 'load'
+    in this case, will fail and return help screen
+    '''
+    @integration_test
+    def test_policy_load_without_subcommand_returns_help_screen(self):
+        with redirect_stderr(self.capture_stream):
+            self.invoke_cli(self.cli_auth_params,
+                                     ['policy', '-b', 'root', '-f', 'somepolicy.yml'], exit_code=1)
+        self.assertIn("Error argument action: invalid choice: 'root' (choose from", self.capture_stream.getvalue())
+
+    '''
+    Validates that a policy command without the policy path
+    will fail and return help screen
+    '''
+    @integration_test
+    def test_policy_load_without_policy_path_returns_help_screen(self):
+        with redirect_stderr(self.capture_stream):
+            output = self.invoke_cli(self.cli_auth_params,
+                           ['policy', 'load', '-b', 'root'], exit_code=1)
+        self.assertIn("Error the following arguments are required", self.capture_stream.getvalue())
+
+    '''
+    A non-existent policy file will return FileNotFound error message
+    '''
+    @integration_test
+    def test_policy_load_raises_file_not_exists_error(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['policy', 'load', '-b', 'root', '-f', 'somepolicy.yml'], exit_code=1)
+        self.assertRegex(output, "Error: No such file or directory:")
+
+    '''
+    A policy with invalid syntax will return a Unprocessable entity error
+    '''
+    @integration_test
+    def test_policy_bad_syntax_raises_error(self):
+        policy = "- ! user bad syntax"
+        with self.assertLogs('', level='DEBUG') as mock_log:
+            with redirect_stderr(self.capture_stream):
+                output = Utils.load_policy_from_string(self, policy, exit_code=1)
+            self.assertIn("422 Client Error", output)
+        self.assertIn("422 Unprocessable Entity {\"error\":{\"code\":\"validation_failed\",\"message\":",
+                  str(mock_log.output))
+
+    @integration_test
+    def test_policy_replace_load_combo_returns_help_screen(self):
+        with redirect_stderr(self.capture_stream):
+            output = self.invoke_cli(self.cli_auth_params,
+                   ['policy', 'load', 'replace', '-b', 'root', '-f', 'somepolicy.yml'], exit_code=1)
+        self.assertIn('Error unrecognized arguments: replace', self.capture_stream.getvalue())
+
+    @integration_test
+    def test_policy_insecure_prints_warning_in_log(self):
+        with self.assertLogs('', level='DEBUG') as mock_log:
+            self.invoke_cli(self.cli_auth_params,
+                               ['--insecure', 'policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("initial")])
+
+            self.assertIn("Warning: Running the command with '--insecure' makes your system vulnerable to security attacks",
+                          str(mock_log.output))
+
+    @integration_test
+    def test_policy_replace_bad_syntax_raises_error(self):
+        policy = "- ! user bad syntax"
+        output = Utils.replace_policy_from_string(self, policy, exit_code=1)
+
+        self.assertIn("422 Client Error", output)
+
+    @integration_test
+    def test_https_load_policy_doesnt_break_if_no_created_roles(self):
+        self.setup_cli_params({})
+
+        user_id1 = uuid.uuid4().hex
+        user_id2 = uuid.uuid4().hex
+        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
+                                                                   user_id2=user_id2)
+        # Ensure that the accounts exist
+        Utils.load_policy_from_string(self, policy)
+
+        # Run the new load that should not result in newly created roles
+        json_result = json.loads(Utils.load_policy_from_string(self, policy))
+
+        expected_object = {
+            'version': json_result['version'],
+            'created_roles': {}
+        }
+
+        self.assertDictEqual(json_result, expected_object)
+
+    @integration_test
+    def test_https_can_replace_policy(self):
+        self.setup_cli_params({})
+
+        orig_policy, old_variables = Utils.generate_policy_string(self)
+
+        file_name=os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
+        with open(file_name, 'w+b') as temp_policy_file:
+            temp_policy_file.write(orig_policy.encode('utf-8'))
+            temp_policy_file.flush()
+
+            Utils.load_policy(self, temp_policy_file.name)
+
+        replacement_policy, new_variables = Utils.generate_policy_string(self)
+        Utils.replace_policy_from_string(self, replacement_policy)
+
+        for new_variable in new_variables:
+            Utils.assert_set_and_get(self, new_variable)
+
+        for old_variable in old_variables:
+            Utils.print_instead_of_raise_error(self, old_variable, "404 Client Error: Not Found for url")
+        os.remove(file_name)
+
+    @integration_test
+    def test_https_replace_policy_can_output_returned_data(self):
+        self.setup_cli_params({})
+
+        user_id1 = uuid.uuid4().hex
+        user_id2 = uuid.uuid4().hex
+        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
+                                                                   user_id2=user_id2)
+        json_result = json.loads(Utils.replace_policy_from_string(self, policy))
+        account_name = self.client_params.account
+
+        expected_object = {
+            'version': json_result['version'],
+            'created_roles': {
+                f'{account_name}:user:' + user_id1: {
+                    'id': f'{account_name}:user:' + user_id1,
+                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id1]['api_key'],
+                },
+                f'{account_name}:user:' + user_id2: {
+                    f'id': f'{account_name}:user:' + user_id2,
+                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id2]['api_key'],
+                }
+            }
+        }
+
+        self.assertDictEqual(json_result, expected_object)
+
+    @integration_test
+    def test_https_replace_policy_doesnt_break_if_no_created_roles(self):
+        self.setup_cli_params({})
+
+        policy = "- !policy foo\n"
+        json_result = json.loads(Utils.replace_policy_from_string(self, policy))
+
+        expected_object = {
+            'version': json_result['version'],
+            'created_roles': {}
+        }
+
+        self.assertDictEqual(json_result, expected_object)
+
+    @integration_test
+    def test_https_can_update_policy(self):
+        self.setup_cli_params({})
+
+        policy, variables = Utils.generate_policy_string(self)
+        Utils.update_policy_from_string(self, policy)
+
+        for variable in variables:
+            Utils.assert_set_and_get(self, variable)
+
+    @integration_test
+    def test_https_update_policy_can_output_returned_data(self):
+        self.setup_cli_params({})
+
+        user_id1 = uuid.uuid4().hex
+        user_id2 = uuid.uuid4().hex
+        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
+                                                                   user_id2=user_id2)
+
+        # Run the new update that should not result in newly created roles
+        json_result = json.loads(Utils.update_policy_from_string(self, policy))
+
+        account_name = self.client_params.account
+        expected_object = {
+            'version': json_result['version'],
+            'created_roles': {
+                f'{account_name}:user:' + user_id1: {
+                    'id': f'{account_name}:user:' + user_id1,
+                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id1]['api_key'],
+                },
+                f'{account_name}:user:' + user_id2: {
+                    'id': f'{account_name}:user:' + user_id2,
+                    'api_key': json_result['created_roles'][f'{account_name}:user:' + user_id2]['api_key'],
+                }
+            }
+        }
+
+        self.assertDictEqual(json_result, expected_object)
+
+    @integration_test
+    def test_https_update_policy_doesnt_break_if_no_created_roles(self):
+        self.setup_cli_params({})
+
+        user_id1 = uuid.uuid4().hex
+        user_id2 = uuid.uuid4().hex
+        policy = "- !user {user_id1}\n- !user {user_id2}\n".format(user_id1=user_id1,
+                                                                   user_id2=user_id2)
+        # Ensure that the accounts exist
+        Utils.update_policy_from_string(self, policy)
+
+        # Run the new load that should not result in newly created roles
+        json_result = json.loads(Utils.update_policy_from_string(self, policy))
+
+        expected_object = {
+            'version': json_result['version'],
+            'created_roles': {}
+        }
+
+        self.assertDictEqual(json_result, expected_object)
+
+    '''
+    Validates that update deletes record
+    '''
+    @integration_test
+    def test_policy_update_policy_removes_user(self):
+        user_id = uuid.uuid4().hex
+
+        load_policy = f"- !user {user_id}"
+        json.loads(Utils.load_policy_from_string(self, load_policy))
+        update_policy = f"- !delete\n record: !user {user_id}"
+        json.loads(Utils.update_policy_from_string(self, update_policy))
+
+        output = self.invoke_cli(self.cli_auth_params, ['list'])
+
+        # Assert that user_id is not in output
+        self.assertTrue(output.find(user_id) == -1)
+
+    # test_policy_update_policy_removes_user.tester=True
+    @integration_test
+    def test_https_can_get_whoami(self):
+        self.setup_cli_params({})
+
+        output = self.invoke_cli(self.cli_auth_params, ['whoami'])
+        response = json.loads(output)
+        self.assertEquals(response.get('account'), f'{self.client_params.account}')
+        self.assertEquals(response.get('username'), 'admin')
diff --git a/test/test_integration_variable.py b/test/test_integration_variable.py
new file mode 100644
index 00000000..ba5913db
--- /dev/null
+++ b/test/test_integration_variable.py
@@ -0,0 +1,286 @@
+# -*- coding: utf-8 -*-
+
+"""
+CLI Variable Integration tests
+This test file handles the main test flows for the variable command
+"""
+import io
+import json
+import os
+import tempfile
+import uuid
+from contextlib import redirect_stderr
+from unittest.mock import patch
+
+import Utils
+from conjur.constants import DEFAULT_NETRC_FILE
+from test.util.cli_helpers import integration_test
+from test.util.test_runners.integration_test_case import IntegrationTestCaseBase
+from Utils import py_utils as utils
+
+# Not coverage tested since integration tests don't run in
+# the same build step
+class CliIntegrationTestVariable(IntegrationTestCaseBase):  # pragma: no cover
+    DEFINED_VARIABLE_ID = 'one/password'
+    capture_stream = io.StringIO()
+    def __init__(self, testname, client_params=None, environment_params=None):
+        super(CliIntegrationTestVariable, self).__init__(testname, client_params, environment_params)
+
+    # *************** HELPERS ***************
+
+    def setup_cli_params(self, env_vars, *params):
+        self.cli_auth_params = ['--debug']
+        self.cli_auth_params += params
+        return self.cli_auth_params
+
+    def setUp(self):
+        self.setup_cli_params({})
+        # Used to configure the CLI and login to run tests
+        Utils.setup_cli(self)
+        return self.invoke_cli(self.cli_auth_params,
+                               ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("initial")])
+
+    # *************** TESTS ***************
+
+    @integration_test
+    def test_variable_get_insecure_prints_warning_in_log(self):
+        with self.assertLogs('', level='DEBUG') as mock_log:
+            expected_value = uuid.uuid4().hex
+            Utils.set_variable(self, 'one/password', expected_value)
+            self.invoke_cli(self.cli_auth_params,
+                                     ['--insecure', 'variable', 'get', '-i', 'one/password'])
+            self.assertIn("Warning: Running the command with '--insecure' makes your system vulnerable to security attacks",
+                          str(mock_log.output))
+    @integration_test
+    def test_variable_short_version_return_latest_value(self):
+        policy = "- !variable someversionedsecret"
+        Utils.load_policy_from_string(self, policy)
+
+        expected_value = "somesecret"
+        Utils.set_variable(self, 'someversionedsecret', expected_value)
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'someversionedsecret', '-v', '1'])
+        self.assertIn(expected_value, output.strip())
+
+    @integration_test
+    def test_variable_long_version_return_latest_value(self):
+        policy = "- !variable anotherversionedsecret"
+        Utils.load_policy_from_string(self, policy)
+
+        expected_value = "anothersecret"
+        Utils.set_variable(self, 'anotherversionedsecret', expected_value)
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'anotherversionedsecret', '--version', '1'])
+        self.assertEquals(expected_value, output.strip())
+
+    @integration_test
+    def test_variable_different_version_calls_returns_different_versions(self):
+        policy = "- !variable someotherversionedsecret"
+        Utils.load_policy_from_string(self, policy)
+
+        first_version = "first_secret"
+        Utils.set_variable(self, 'someotherversionedsecret', first_version)
+
+        second_version = "second_secret"
+        Utils.set_variable(self, 'someotherversionedsecret', second_version)
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'someotherversionedsecret', '--version', '1'])
+        self.assertEquals(first_version, output.strip())
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'someotherversionedsecret', '--version', '2'])
+        self.assertEquals(second_version, output.strip())
+
+    @integration_test
+    def test_variable_set_insecure_prints_warning_in_log(self):
+        with self.assertLogs('', level='DEBUG') as mock_log:
+            expected_value = uuid.uuid4().hex
+            self.invoke_cli(self.cli_auth_params,
+                       ['--insecure', 'variable', 'set', '-i',  'one/password', '-v', expected_value])
+
+            self.assertIn("Warning: Running the command with '--insecure' makes your system vulnerable to security attacks",
+                          str(mock_log.output))
+
+    @integration_test
+    def test_variable_without_subcommand_returns_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable'])
+        self.assertIn("Usage:\n  conjur [global options] <command> <subcommand> [options] [args]", output)
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_variable_short_with_help_returns_variable_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', '-h'])
+        self.assertIn("usage:  variable", output)
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_variable_long_with_help_returns_variable_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', '--help'])
+        self.assertIn("usage:  variable", output)
+
+    @integration_test
+    def test_variable_get_variable_without_subcommand_raises_error(self):
+        with redirect_stderr(self.capture_stream):
+            output = self.invoke_cli(self.cli_auth_params,
+                                     ['variable', '-i', 'somevariable'], exit_code=1)
+
+        self.assertIn("Error argument action: invalid choice: 'somevariable'", self.capture_stream.getvalue())
+        self.assertIn("usage:  variable", output)
+
+    @integration_test
+    def test_variable_get_long_variable_returns_variable_value(self):
+        expected_value = uuid.uuid4().hex
+        Utils.set_variable(self, 'one/password', expected_value)
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '--id=one/password'])
+        self.assertIn(expected_value, output)
+
+    @integration_test
+    def test_variable_get_short_variable_returns_variable_value(self):
+        expected_value = uuid.uuid4().hex
+        Utils.set_variable(self, 'one/password', expected_value)
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'one/password'])
+        self.assertIn(expected_value, output)
+
+    @integration_test
+    def test_variable_get_with_special_chars_returns_special_chars(self):
+        self.invoke_cli(self.cli_auth_params,
+                       ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("variable")])
+        Utils.set_variable(self, 'variablespecialchars', '"[]{}#@^&<>~\/''\/?\;\';\'"')
+        output = Utils.get_variable(self, 'variablespecialchars')
+        self.assertEquals(output.strip(), '"[]{}#@^&<>~\/''\/?\;\';\'"')
+
+    @integration_test
+    def test_variable_get_variable_has_spaces_returns_variable_value(self):
+        self.invoke_cli(self.cli_auth_params,
+                       ['policy', 'replace', '-b', 'root', '-f', self.environment.path_provider.get_policy_path("variable")])
+        Utils.assert_set_and_get(self, "some variable with spaces")
+
+    @integration_test
+    def test_unknown_variable_raises_not_found_error(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'unknown'], exit_code=1)
+        self.assertRegex(output, "404 Client Error: Not Found for url:")
+
+    @integration_test
+    def test_cli_can_batch_get_multiple_variables(self):
+        policy, variables = Utils.generate_policy_string(self)
+        file_name=os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
+        with open(file_name, 'w+b') as temp_policy_file:
+            temp_policy_file.write(policy.encode('utf-8'))
+            temp_policy_file.flush()
+
+            Utils.load_policy(self, temp_policy_file.name)
+        value_map = {}
+        for variable in variables:
+            value = uuid.uuid4().hex
+            Utils.set_variable(self, variable, value)
+            value_map[variable] = value
+
+        batch_result_string = Utils.get_variable(self, *variables)
+        batch_result = json.loads(batch_result_string)
+
+        for variable_name, variable_value in value_map.items():
+            self.assertEquals(variable_value, batch_result[variable_name])
+
+        os.remove(file_name)
+
+    @integration_test
+    def test_batch_existing_and_nonexistent_variable_raises_error(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'one/password', 'unknown'], exit_code=1)
+        self.assertIn("404 Client Error", output)
+
+    @integration_test
+    def test_batch_existing_and_nonexistent_variable_with_spaces_raises_error(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-i', 'one/password', '"unknown password"'], exit_code=1)
+        self.assertIn("404 Client Error", output)
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_subcommand_get_short_help_returns_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '-h'])
+        self.assertIn("usage: variable", output)
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_subcommand_get_long_help_returns_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'get', '--help'])
+        self.assertIn("usage: variable", output)
+
+    @integration_test
+    def test_variable_get_without_id_returns_help(self):
+        with redirect_stderr(self.capture_stream):
+            self.invoke_cli(self.cli_auth_params,
+                            ['variable', 'get'], exit_code=1)
+        self.assertIn("Error the following arguments are required:", self.capture_stream.getvalue())
+
+    @integration_test
+    def test_variable_set_without_id_returns_help(self):
+        with redirect_stderr(self.capture_stream):
+            self.invoke_cli(self.cli_auth_params,
+                            ['variable', 'set'], exit_code=1)
+        self.assertIn("Error the following arguments are required:", self.capture_stream.getvalue())
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_subcommand_set_long_help_returns_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'set', '--help'])
+        self.assertIn("usage: variable set", output)
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_subcommand_set_short_help_returns_help(self):
+        output = self.invoke_cli(self.cli_auth_params,
+                                 ['variable', 'set', '-h'])
+        self.assertIn("usage: variable set", output)
+
+    # TODO This will need to be changed when UX is finalized
+    @integration_test
+    def test_subcommand_set_variable_without_value_returns_help(self):
+        with redirect_stderr(self.capture_stream):
+            self.invoke_cli(self.cli_auth_params,
+                            ['variable', 'set', '-i', 'one/password'], exit_code=1)
+        self.assertIn("Error the following arguments are required:", self.capture_stream.getvalue())
+
+    @integration_test
+    def test_subcommand_set_variable_with_values_returns_help(self):
+        with redirect_stderr(self.capture_stream):
+            self.invoke_cli(self.cli_auth_params,
+                            ['variable', 'set', '-i', 'one/password', '-v', 'somevalue', 'someothervalue'], exit_code=1)
+        self.assertIn("Error unrecognized arguments: someothervalue", self.capture_stream.getvalue())
+
+    '''
+    Validates that when the user isn't logged in and makes a request,
+    they are prompted to login first and then the command is executed
+    '''
+    @integration_test
+    @patch('builtins.input', return_value='admin')
+    def test_variable_get_without_user_logged_in_prompts_login_and_performs_get(self, mock_input):
+        # TEST_ENV is set to False so we will purposely be prompted to login
+        os.environ['TEST_ENV'] = 'False'
+        try:
+            utils.remove_file(DEFAULT_NETRC_FILE)
+        except OSError:
+            pass
+
+        with patch('getpass.getpass', return_value=self.client_params.env_api_key):
+            output = self.invoke_cli(self.cli_auth_params,
+                                     ['variable', 'set', '-i', 'one/password', '-v', 'somevalue'])
+
+            self.assertIn("Error: You have not logged in", output)
+            self.assertIn("Successfully logged in to Conjur", output)
+            self.assertIn('Successfully set value for variable \'one/password\'', output)
+        os.environ['TEST_ENV'] = 'True'
+
+    @integration_test
+    def test_https_cli_can_set_and_get_a_defined_variable_if_verification_disabled(self):
+        self.setup_cli_params({}, '--insecure')
+        Utils.assert_set_and_get(self, self.DEFINED_VARIABLE_ID)
diff --git a/test/test_unit_api.py b/test/test_unit_api.py
index 85ee7243..9b612757 100644
--- a/test/test_unit_api.py
+++ b/test/test_unit_api.py
@@ -211,6 +211,23 @@ def mock_auth():
         self.verify_http_call(mock_http_client, HttpVerb.GET, ConjurEndpoint.SECRETS,
                               kind='variable',
                               identifier='myvar',
+                              query={},
+                              ssl_verify=True)
+
+    test_get_variable_invokes_http_client_correctly.tester=True
+    @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse())
+    def test_get_variable_with_version_invokes_http_client_correctly(self, mock_http_client):
+        api = Api(url='http://localhost', login_id='mylogin', api_key='apikey')
+        def mock_auth():
+            return 'apitoken'
+        api.authenticate = mock_auth
+
+        api.get_variable('myvar', '1')
+
+        self.verify_http_call(mock_http_client, HttpVerb.GET, ConjurEndpoint.SECRETS,
+                              kind='variable',
+                              identifier='myvar',
+                              query={'version': '1'},
                               ssl_verify=True)
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse())
@@ -226,6 +243,7 @@ def mock_auth():
         self.verify_http_call(mock_http_client, HttpVerb.GET, ConjurEndpoint.SECRETS,
                               kind='variable',
                               identifier='myvar',
+                              query={},
                               ssl_verify='verify')
 
     # Set variable
@@ -261,16 +279,16 @@ def mock_auth():
                               identifier='myvar',
                               ssl_verify='verify')
 
-    # Policy apply
+    # Policy load
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse(text='{}'))
-    def test_apply_policy_invokes_http_client_correctly(self, mock_http_client):
+    def test_load_policy_invokes_http_client_correctly(self, mock_http_client):
         api = Api(url='http://localhost', login_id='mylogin', api_key='apikey')
         def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
-        api.apply_policy_file('mypolicyname', self.POLICY_FILE)
+        api.load_policy_file('mypolicyname', self.POLICY_FILE)
 
         policy_data = None
         with open(self.POLICY_FILE, 'r') as content_file:
@@ -282,23 +300,23 @@ def mock_auth():
                               ssl_verify=True)
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse(text=json.dumps(MOCK_POLICY_CHANGE_OBJECT)))
-    def test_apply_policy_converts_returned_data_to_expected_objects(self, mock_http_client):
+    def test_load_policy_converts_returned_data_to_expected_objects(self, mock_http_client):
         api = Api(url='http://localhost', login_id='mylogin', api_key='apikey')
         def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
-        output = api.apply_policy_file('mypolicyname', self.POLICY_FILE)
+        output = api.load_policy_file('mypolicyname', self.POLICY_FILE)
         self.assertEqual(output, MOCK_POLICY_CHANGE_OBJECT)
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse(text='{}'))
-    def test_apply_policy_passes_down_ssl_verify_parameter(self, mock_http_client):
+    def test_load_policy_passes_down_ssl_verify_parameter(self, mock_http_client):
         api = Api(url='http://localhost', login_id='mylogin', api_key='apikey', ssl_verify='ssl_verify')
         def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
-        api.apply_policy_file('mypolicyname', self.POLICY_FILE)
+        api.load_policy_file('mypolicyname', self.POLICY_FILE)
 
         policy_data = None
         with open(self.POLICY_FILE, 'r') as content_file:
@@ -357,16 +375,16 @@ def mock_auth():
                               identifier='mypolicyname',
                               ssl_verify='ssl_verify')
 
-    # Policy delete
+    # Policy update
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse(text='{}'))
-    def test_delete_policy_invokes_http_client_correctly(self, mock_http_client):
+    def test_update_policy_invokes_http_client_correctly(self, mock_http_client):
         api = Api(url='http://localhost', login_id='mylogin', api_key='apikey')
         def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
-        api.delete_policy_file('mypolicyname', self.POLICY_FILE)
+        api.update_policy_file('mypolicyname', self.POLICY_FILE)
 
         policy_data = None
         with open(self.POLICY_FILE, 'r') as content_file:
@@ -378,23 +396,23 @@ def mock_auth():
                               ssl_verify=True)
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse(text=json.dumps(MOCK_POLICY_CHANGE_OBJECT)))
-    def test_delete_policy_converts_returned_data_to_expected_objects(self, mock_http_client):
+    def test_update_policy_converts_returned_data_to_expected_objects(self, mock_http_client):
         api = Api(url='http://localhost', login_id='mylogin', api_key='apikey')
         def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
-        output = api.delete_policy_file('mypolicyname', self.POLICY_FILE)
+        output = api.update_policy_file('mypolicyname', self.POLICY_FILE)
         self.assertEqual(output, MOCK_POLICY_CHANGE_OBJECT)
 
     @patch('conjur.api.invoke_endpoint', return_value=MockClientResponse(text='{}'))
-    def test_delete_policy_passes_down_ssl_verify_parameter(self, mock_http_client):
+    def test_update_policy_passes_down_ssl_verify_parameter(self, mock_http_client):
         api = Api(url='http://localhost', login_id='mylogin', api_key='apikey', ssl_verify='ssl_verify')
         def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
-        api.delete_policy_file('mypolicyname', self.POLICY_FILE)
+        api.update_policy_file('mypolicyname', self.POLICY_FILE)
 
         policy_data = None
         with open(self.POLICY_FILE, 'r') as content_file:
@@ -461,10 +479,24 @@ def mock_auth():
             return 'apitoken'
         api.authenticate = mock_auth
 
+        api.resources_list()
+
+        self.verify_http_call(mock_http_client, HttpVerb.GET, ConjurEndpoint.RESOURCES,
+                              query={},
+                              ssl_verify=True)
+
+    @patch('conjur.api.invoke_endpoint', \
+           return_value=MockClientResponse(content=json.dumps(MOCK_RESOURCE_LIST)))
+    def test_get_resources_with_constraints_invokes_http_client_correctly(self, mock_http_client):
+        api = Api(url='http://localhost', login_id='mylogin', api_key='apikey')
+        def mock_auth():
+            return 'apitoken'
+        api.authenticate = mock_auth
+
         api.resources_list({'limit':1})
 
         self.verify_http_call(mock_http_client, HttpVerb.GET, ConjurEndpoint.RESOURCES,
-                              query={'limit':1},
+                              query={'limit': 1},
                               ssl_verify=True)
 
     @patch('conjur.api.invoke_endpoint', \
diff --git a/test/test_unit_cli.py b/test/test_unit_cli.py
index 67ab33da..67b2a2f8 100644
--- a/test/test_unit_cli.py
+++ b/test/test_unit_cli.py
@@ -53,7 +53,7 @@ def test_cli_check_copyright_long_version_flag(self, cli_invocation, output, cli
     def test_cli_passes_insecure_flag_to_client(self): pass
 
     # Main method invocations
-    @cli_test(["variable", "set", "foo", "bar"])
+    @cli_test(["variable", "set", "-i", "foo", "-v", "bar"])
     def test_cli_invokes_variable_set_correctly(self, cli_invocation, output, client):
         client.set.assert_called_once_with('foo', 'bar')
 
@@ -61,19 +61,19 @@ def test_cli_invokes_variable_set_correctly(self, cli_invocation, output, client
     def test_cli_variable_parser_doesnt_break_without_action(self, cli_invocation, output, client):
         self.assertIn("Usage", output)
 
-    @cli_test(["variable", "get", "foo"])
+    @cli_test(["variable", "get", "-i", "foo"], get_output=b'A')
     def test_cli_invokes_variable_get_correctly(self, cli_invocation, output, client):
-        client.get.assert_called_once_with('foo')
+        client.get.assert_called_once_with("foo", None)
 
-    @cli_test(["variable", "get", "foo", "bar"], get_many_output={"foo": "A", "bar": "B"})
+    @cli_test(["variable", "get", "-i", "foo", "bar"], get_many_output={"foo": "A", "bar": "B"})
     def test_cli_invokes_variable_get_correctly_with_multiple_vars(self, cli_invocation, output, client):
         client.get_many.assert_called_once_with('foo', 'bar')
 
-    @cli_test(["variable", "get", "foo", "bar"], get_many_output={})
+    @cli_test(["variable", "get", "-i", "foo", "bar"], get_many_output={})
     def test_cli_variable_get_with_multiple_vars_doesnt_break_on_empty_input(self, cli_invocation, output, client):
         self.assertEquals('{}\n', output)
 
-    @cli_test(["variable", "get", "foo", "bar"], get_many_output={"foo": "A", "bar": "B"})
+    @cli_test(["variable", "get", "-i", "foo", "bar"], get_many_output={"foo": "A", "bar": "B"})
     def test_cli_variable_get_with_multiple_vars_outputs_formatted_json(self, cli_invocation, output, client):
         self.assertEquals('{\n    "foo": "A",\n    "bar": "B"\n}\n', output)
 
@@ -81,40 +81,80 @@ def test_cli_variable_get_with_multiple_vars_outputs_formatted_json(self, cli_in
     def test_cli_policy_parser_doesnt_break_without_action(self, cli_invocation, output, client):
         self.assertIn("Usage:", output)
 
-    @cli_test(["policy", "apply", "foo", "foopolicy"])
-    def test_cli_invokes_policy_apply_correctly(self, cli_invocation, output, client):
-        client.apply_policy_file.assert_called_once_with('foo', 'foopolicy')
-
-    @cli_test(["policy", "apply", "foo", "foopolicy"], policy_change_output={})
-    def test_cli_policy_apply_doesnt_break_on_empty_input(self, cli_invocation, output, client):
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "--help"])
+    def test_cli_policy_long_help_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "-h"])
+    def test_cli_policy_short_help_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "load", "--help"])
+    def test_cli_policy_load_long_help_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "load", "-h"])
+    def test_cli_policy_load_short_help_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "replace", "-h"])
+    def test_cli_policy_replace_short_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "replace", "--help"])
+    def test_cli_policy_replace_long_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "update", "-h"])
+    def test_cli_policy_update_short_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    # TODO will change when UX is finalized
+    @cli_test(["policy", "update", "--help"])
+    def test_cli_policy_update_long_returns_help(self, cli_invocation, output, client):
+        self.assertIn("usage:", output)
+
+    @cli_test(["policy", "load", "-b", "foo", "-f", "foopolicy"])
+    def test_cli_invokes_policy_load_correctly(self, cli_invocation, output, client):
+        client.load_policy_file.assert_called_once_with('foo', 'foopolicy')
+
+    @cli_test(["policy", "load", "-b", "foo", "-f", "foopolicy"], policy_change_output={})
+    def test_cli_policy_load_doesnt_break_on_empty_input(self, cli_invocation, output, client):
         self.assertEquals('{}\n', output)
 
-    @cli_test(["policy", "apply", "foo", "foopolicy"], policy_change_output={"foo": "A", "bar": "B"})
-    def test_cli_policy_apply_outputs_formatted_json(self, cli_invocation, output, client):
+    @cli_test(["policy", "load", "-b", "foo", "-f", "foopolicy"], policy_change_output={"foo": "A", "bar": "B"})
+    def test_cli_policy_load_outputs_formatted_json(self, cli_invocation, output, client):
         self.assertEquals('{\n    "foo": "A",\n    "bar": "B"\n}\n', output)
 
-    @cli_test(["policy", "replace", "foo", "foopolicy"])
+    @cli_test(["policy", "replace", "-b", "foo", "-f", "foopolicy"])
     def test_cli_invokes_policy_replace_correctly(self, cli_invocation, output, client):
         client.replace_policy_file.assert_called_once_with('foo', 'foopolicy')
 
-    @cli_test(["policy", "replace", "foo", "foopolicy"], policy_change_output={})
+    @cli_test(["policy", "replace", "-b", "foo", "-f", "foopolicy"], policy_change_output={})
     def test_cli_policy_replace_doesnt_break_on_empty_input(self, cli_invocation, output, client):
         self.assertEquals('{}\n', output)
 
-    @cli_test(["policy", "replace", "foo", "foopolicy"], policy_change_output={"foo": "A", "bar": "B"})
+    @cli_test(["policy", "replace", "-b", "foo", "-f", "foopolicy"], policy_change_output={"foo": "A", "bar": "B"})
     def test_cli_policy_replace_outputs_formatted_json(self, cli_invocation, output, client):
         self.assertEquals('{\n    "foo": "A",\n    "bar": "B"\n}\n', output)
 
-    @cli_test(["policy", "delete", "foo", "foopolicy"])
-    def test_cli_invokes_policy_delete_correctly(self, cli_invocation, output, client):
-        client.delete_policy_file.assert_called_once_with('foo', 'foopolicy')
+    @cli_test(["policy", "update", "-b", "foo", "-f", "foopolicy"])
+    def test_cli_invokes_policy_update_correctly(self, cli_invocation, output, client):
+        client.update_policy_file.assert_called_once_with('foo', 'foopolicy')
 
-    @cli_test(["policy", "delete", "foo", "foopolicy"], policy_change_output={})
-    def test_cli_policy_delete_doesnt_break_on_empty_input(self, cli_invocation, output, client):
+    @cli_test(["policy", "update", "-b", "foo", "-f", "foopolicy"], policy_change_output={})
+    def test_cli_policy_update_doesnt_break_on_empty_input(self, cli_invocation, output, client):
         self.assertEquals('{}\n', output)
 
-    @cli_test(["policy", "delete", "foo", "foopolicy"], policy_change_output={"foo": "A", "bar": "B"})
-    def test_cli_policy_delete_outputs_formatted_json(self, cli_invocation, output, client):
+    @cli_test(["policy", "update", "-b", "foo", "-f", "foopolicy"], policy_change_output={"foo": "A", "bar": "B"})
+    def test_cli_policy_update_outputs_formatted_json(self, cli_invocation, output, client):
         self.assertEquals('{\n    "foo": "A",\n    "bar": "B"\n}\n', output)
 
     @cli_test(["list"], list_output=RESOURCE_LIST)
diff --git a/test/test_unit_client.py b/test/test_unit_client.py
index 70794779..83dae628 100644
--- a/test/test_unit_client.py
+++ b/test/test_unit_client.py
@@ -286,7 +286,7 @@ def test_client_passes_through_api_get_variable_params(self, mock_api_instance,
             mock_api_config):
         Client().get('variable_id')
 
-        mock_api_instance.return_value.get_variable.assert_called_once_with('variable_id')
+        mock_api_instance.return_value.get_variable.assert_called_once_with('variable_id', None)
 
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
@@ -337,11 +337,11 @@ def test_client_passes_through_api_set_variable_params(self, mock_api_instance,
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
     @patch('conjur.client.Api')
-    def test_client_passes_through_api_apply_policy_params(self, mock_api_instance, mock_creds,
+    def test_client_passes_through_api_load_policy_params(self, mock_api_instance, mock_creds,
             mock_api_config):
-        Client().apply_policy_file('name', 'policy')
+        Client().load_policy_file('name', 'policy')
 
-        mock_api_instance.return_value.apply_policy_file.assert_called_once_with(
+        mock_api_instance.return_value.load_policy_file.assert_called_once_with(
             'name',
             'policy',
         )
@@ -349,12 +349,12 @@ def test_client_passes_through_api_apply_policy_params(self, mock_api_instance,
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
     @patch('conjur.client.Api')
-    def test_client_returns_apply_policy_result(self, mock_api_instance, mock_creds, mock_api_config):
-        apply_policy_result = uuid.uuid4().hex
-        mock_api_instance.return_value.apply_policy_file.return_value = apply_policy_result
+    def test_client_returns_load_policy_result(self, mock_api_instance, mock_creds, mock_api_config):
+        load_policy_result = uuid.uuid4().hex
+        mock_api_instance.return_value.load_policy_file.return_value = load_policy_result
 
-        return_value = Client().apply_policy_file('name', 'policy')
-        self.assertEquals(return_value, apply_policy_result)
+        return_value = Client().load_policy_file('name', 'policy')
+        self.assertEquals(return_value, load_policy_result)
 
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
@@ -383,11 +383,11 @@ def test_client_returns_replace_policy_result(self, mock_api_instance, mock_cred
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
     @patch('conjur.client.Api')
-    def test_client_passes_through_api_delete_policy_params(self, mock_api_instance, mock_creds,
+    def test_client_passes_through_api_update_policy_params(self, mock_api_instance, mock_creds,
             mock_api_config):
-        Client().delete_policy_file('name', 'policy')
+        Client().update_policy_file('name', 'policy')
 
-        mock_api_instance.return_value.delete_policy_file.assert_called_once_with(
+        mock_api_instance.return_value.update_policy_file.assert_called_once_with(
             'name',
             'policy'
         )
@@ -395,13 +395,13 @@ def test_client_passes_through_api_delete_policy_params(self, mock_api_instance,
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
     @patch('conjur.client.Api')
-    def test_client_returns_delete_policy_result(self, mock_api_instance, mock_creds,
+    def test_client_returns_update_policy_result(self, mock_api_instance, mock_creds,
             mock_api_config):
-        delete_policy_result = uuid.uuid4().hex
-        mock_api_instance.return_value.delete_policy_file.return_value = delete_policy_result
+        update_policy_result = uuid.uuid4().hex
+        mock_api_instance.return_value.update_policy_file.return_value = update_policy_result
 
-        return_value = Client().delete_policy_file('name', 'policy')
-        self.assertEquals(return_value, delete_policy_result)
+        return_value = Client().update_policy_file('name', 'policy')
+        self.assertEquals(return_value, update_policy_result)
 
     @patch('conjur.client.ApiConfig', return_value=MockApiConfig())
     @patch('conjur.credentials_from_file.CredentialsFromFile.load', return_value=MockCredentials)
diff --git a/test/util/cli_helpers.py b/test/util/cli_helpers.py
index 1570ef8b..d6ef6efe 100644
--- a/test/util/cli_helpers.py
+++ b/test/util/cli_helpers.py
@@ -21,20 +21,20 @@ def test_wrapper_func(self, *inner_args, **inner_kwargs):
     return test_wrapper_func
 
 
-def cli_test(cli_args=[], integration=False, get_many_output=None, list_output=None,
+def cli_test(cli_args=[], integration=False, get_many_output=None, get_output=None, list_output=None,
              policy_change_output={}, whoami_output={}):
     cli_command = 'cli {}'.format(' '.join(cli_args))
-
     def test_cli_decorator(original_function):
         @wraps(original_function)
         def test_wrapper_func(self, *inner_args, **inner_kwargs):
             capture_stream = io.StringIO()
             client_instance_mock = MagicMock()
+            client_instance_mock.get.return_value = get_output
             client_instance_mock.get_many.return_value = get_many_output
             client_instance_mock.list.return_value = list_output
-            client_instance_mock.apply_policy_file.return_value = policy_change_output
+            client_instance_mock.load_policy_file.return_value = policy_change_output
             client_instance_mock.replace_policy_file.return_value = policy_change_output
-            client_instance_mock.delete_policy_file.return_value = policy_change_output
+            client_instance_mock.update_policy_file.return_value = policy_change_output
             client_instance_mock.whoami.return_value = whoami_output
             with self.assertRaises(SystemExit) as sys_exit:
                 with redirect_stdout(capture_stream):
@@ -58,9 +58,8 @@ def test_wrapper_func(self, *inner_args, **inner_kwargs):
 def cli_arg_test(cli_args=None, **kwargs):
     if cli_args is None:
         cli_args = []
-    cli_args += ['variable', 'get', 'foo']
+    cli_args += ['variable', 'get', '-i', 'foo']
     cli_command = 'cli {}'.format(' '.join(cli_args))
-
     default_args = {'debug': False}
     expected_args = {**default_args, **kwargs}
 
@@ -68,13 +67,15 @@ def test_cli_decorator(original_function):
         @wraps(original_function)
         def test_wrapper_func(self, *inner_args, **inner_kwargs):
             capture_stream = io.StringIO()
-            client = None
+            client_instance_mock = MagicMock()
+            client_instance_mock.get.return_value=b'foo'
 
+            client = None
             with self.assertRaises(SystemExit) as sys_exit:
                 with redirect_stdout(capture_stream):
                     with patch.object(sys, 'argv', ["cli"] + cli_args), \
                          patch('conjur.cli.Client') as mock_client:
-                        mock_client.return_value = MagicMock()
+                        mock_client.return_value = client_instance_mock
                         client = mock_client
 
                         Cli().run()
diff --git a/test/util/test_runners/integration_test_case.py b/test/util/test_runners/integration_test_case.py
index 3c657faf..0e17f103 100644
--- a/test/util/test_runners/integration_test_case.py
+++ b/test/util/test_runners/integration_test_case.py
@@ -62,7 +62,7 @@ def invoke_cli_as_code(test_runner, *args, exit_code=0):
             with patch.object(sys, 'argv', ["cli"] + cli_args):
                 Cli().run()
     test_runner.assertEqual(sys_exit.exception.code, exit_code,
-                            "ERROR: CLI returned an unexpected error status code: '{}'".format(cli_args))
+                            f"ERROR: CLI returned an unexpected error status code: '{cli_args}'. Output: {capture_stream.getvalue()}")
     return capture_stream.getvalue()
 
 
diff --git a/test/util/test_runners/integrations_tests_runner.py b/test/util/test_runners/integrations_tests_runner.py
index 3cdb945d..3ab02be2 100644
--- a/test/util/test_runners/integrations_tests_runner.py
+++ b/test/util/test_runners/integrations_tests_runner.py
@@ -25,10 +25,11 @@
 import shutil
 
 # Internals
-from test.test_integration_cli import CliIntegrationTest
+from test.test_integration_policy import CliIntegrationPolicy
+from test.test_integration_variable import CliIntegrationTestVariable
 from test.test_integration_configurations import CliIntegrationTestConfigurations
 from test.test_integration_credentials import CliIntegrationTestCredentials
-from test.test_integration_list import CliIntegrationListTest
+from test.test_integration_list import CliIntegrationTestList
 from test.test_integration_oss import CliIntegrationTestOSS
 from test.util.test_runners.integration_test_case import IntegrationTestCaseBase
 from test.util.test_runners.test_runner_args import TestRunnerArgs
@@ -41,11 +42,11 @@ def main():
     args = TestRunnerArgs.create_from_args()
 
     runner = TestRunner(args)
-
-    test_to_run_list=[CliIntegrationTest,
-                      CliIntegrationTestConfigurations,
+    test_to_run_list=[CliIntegrationTestConfigurations,
+                      CliIntegrationTestVariable,
                       CliIntegrationTestCredentials,
-                      CliIntegrationListTest]
+                      CliIntegrationTestList,
+                      CliIntegrationPolicy]
     if args.run_oss_tests:
         test_to_run_list.append(CliIntegrationTestOSS)
     runner.run_tests(test_modules=test_to_run_list)