Repository: dowjones/tokendito (language: python)

Issue:
Restructure location of tests
## Description
Move tests into separate files

## Motivation and Context
This change put tests for each module into separate files, so that they are easier to extend.

## How Has This Been Tested?
End-to-end, locally with `tox`, and comparing coveralls coverage reports pre/post #138

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes



Discussion / hints:

Fix patch:
diff --git a/tox.ini b/tox.ini
index d9cdd1bf..e88380fd 100755
--- a/tox.ini
+++ b/tox.ini
@@ -8,7 +8,7 @@ passenv = TERM
 commands =
     coverage erase
     pytest --cov=tokendito --cov-append -v -ra -k 'unit' -s tests/ --
-    pytest --cov=tokendito --cov-append -v -ra -k 'functional and not credentials' -s tests/ --
+    pytest --cov=tokendito --cov-append -v -ra -k 'functional and not auth' -s tests/ --
 
 [testenv:lint]
 skip_install = true
@@ -20,7 +20,7 @@ commands =
 skip_install = true
 passenv = TOKENDITO_*
 commands =
-    pytest --cov=tokendito --cov-append -v -rA -k 'functional and (config or credentials)' -s tests/ {posargs}
+    pytest --cov=tokendito --cov-append -v -rA -k 'functional and auth' -s tests/ {posargs}
 
 [testenv:coverage]
 skip_install = true


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index ac4abc11..ed2f04d8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,6 +1,7 @@
 # vim: set filetype=python ts=4 sw=4
 # -*- coding: utf-8 -*-
 """pytest configuration, hooks, and global fixtures."""
+import pytest
 
 
 def pytest_addoption(parser):
@@ -19,3 +20,52 @@ def pytest_addoption(parser):
         help="Sets an optional config file to read from",
     )
     parser.addoption("--aws-profile", default="pytest", help="Sets the AWS profile name")
+
+
+@pytest.fixture
+def custom_args(request):
+    """Search the custom command-line options and return a list of keys and values."""
+    options = [
+        "--username",
+        "--password",
+        "--okta-tile",
+        "--okta-mfa",
+        "--okta-mfa-response",
+        "--aws-role-arn",
+        "--config-file",
+        "--aws-profile",
+    ]
+    arg_list = []
+    # pytest does not have a method for listing options, so we have look them up.
+    for item in options:
+        if request.config.getoption(item):
+            arg_list.extend([item, request.config.getoption(item)])
+    return arg_list
+
+
+@pytest.fixture(scope="session")
+def config_file(tmp_path_factory):
+    """Generate a path for a temporary ini file that multiple tests can share."""
+    path = tmp_path_factory.mktemp("pytest") / "pytest.ini"
+    return path
+
+
+@pytest.fixture
+def sample_json_response():
+    """Return a response from okta server."""
+    from okta_response_simulation import empty_dict
+    from okta_response_simulation import error_dict
+    from okta_response_simulation import no_auth_methods
+    from okta_response_simulation import no_mfa
+    from okta_response_simulation import no_mfa_no_session_token
+    from okta_response_simulation import with_mfa
+
+    okta_fixture_data = {
+        "okta_response_no_auth_methods": no_auth_methods,
+        "okta_response_empty": empty_dict,
+        "okta_response_error": error_dict,
+        "okta_response_no_mfa": no_mfa,
+        "okta_response_no_mfa_no_session_token": no_mfa_no_session_token,
+        "okta_response_mfa": with_mfa,
+    }
+    return okta_fixture_data
diff --git a/tests/functional/test_auth.py b/tests/functional/test_auth.py
new file mode 100644
index 00000000..65295b68
--- /dev/null
+++ b/tests/functional/test_auth.py
@@ -0,0 +1,112 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Functional, endt-to-end authenticated tests, and local fixtures."""
+import datetime
+from os import environ
+from os import path
+import re
+import sys
+import time
+
+import pytest
+from utils import run_process
+
+sys.path.insert(0, path.dirname(path.dirname(path.dirname(path.abspath(__file__)))))
+
+
+@pytest.mark.run("first")
+def test_generate_credentials(custom_args, config_file):
+    """Run the tool and generate credentials."""
+    import pyotp
+    from tokendito import user
+    from tokendito.config import config
+
+    # Emulate helpers.process_options() bypassing interactive portions.
+    tool_args = user.parse_cli_args(custom_args)
+    config_ini = user.process_ini_file(config_file, "default")
+    config_env = user.process_environment()
+    config_arg = user.process_arguments(tool_args)
+
+    config.update(config_ini)
+    config.update(config_env)
+    config.update(config_arg)
+
+    if (
+        config.aws["role_arn"] is None
+        or config.okta["tile"] is None
+        or config.okta["mfa"] is None
+        or not config.okta["username"]
+        or not config.okta["password"]
+    ):
+        pytest.skip("Not enough arguments collected to execute non-interactively.")
+
+    # If a token response is present and is not in the usual 6-digit format,
+    # assume it is a MFA seed and create a valid response from it.
+    if (
+        config.okta["mfa_response"] is not None
+        and re.match("[0-9]{6}", config.okta["mfa_response"]) is None
+    ):
+        totp = pyotp.TOTP(config.okta["mfa_response"], interval=30)
+        # If there are a few seconds left on the TOTP timer, wait until the next round.
+        time_remaining = totp.interval - datetime.datetime.now().timestamp() % totp.interval
+        if time_remaining < 5:
+            time.sleep(1 + time_remaining)
+        config.okta["mfa_response"] = totp.now()
+        # Update the environment variable that has been modified, if it exists
+        # as this may be passed down to a subprocess.
+        if "TOKENDITO_OKTA_MFA_RESPONSE" in environ:
+            environ["TOKENDITO_OKTA_MFA_RESPONSE"] = config.okta["mfa_response"]
+
+    # Rebuild argument list
+    args = [
+        "--aws-role-arn",
+        f"{config.aws['role_arn']}",
+        "--aws-profile",
+        f"{config.aws['profile']}",
+        "--okta-tile",
+        f"{config.okta['tile']}",
+        "--okta-mfa",
+        f"{config.okta['mfa']}",
+        "--okta-mfa-response",
+        f"{config.okta['mfa_response']}",
+        "--username",
+        f"{config.okta['username']}",
+        "--password",
+        f"{config.okta['password']}",
+        "--loglevel",
+        "DEBUG",
+    ]
+    # run as a local module, as we can't guarantee that the binary is installed.
+    executable = [sys.executable, "-m", "tokendito"]
+    runnable = executable + args
+
+    proc = run_process(runnable)
+    assert "'password': '*****'" in proc["stderr"]
+    assert f"{config.okta['password']}" not in proc["stderr"]
+    assert f"{config.okta['mfa_response']}" not in proc["stderr"]
+    assert '"sessionToken": "*****"' in proc["stderr"]
+    assert proc["exit_status"] == 0
+
+
+@pytest.mark.run("second")
+def test_aws_credentials(custom_args):
+    """Run the AWS cli to verify whether credentials work."""
+    from tokendito import user
+    from tokendito.config import config
+
+    # Emulate helpers.process_options() bypassing interactive portions.
+    tool_args = user.parse_cli_args(custom_args)
+    config_ini = user.process_ini_file(tool_args.user_config_file, "default")
+    config_env = user.process_environment()
+    config_arg = user.process_arguments(tool_args)
+    config.update(config_ini)
+    config.update(config_env)
+    config.update(config_arg)
+
+    if not config.aws["role_arn"]:
+        pytest.skip("No AWS profile defined, test will be skipped.")
+
+    runnable = ["aws", "--profile", config.aws["profile"], "sts", "get-caller-identity"]
+    proc = run_process(runnable)
+    assert not proc["stderr"]
+    assert proc["exit_status"] == 0
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
new file mode 100644
index 00000000..6327bbf9
--- /dev/null
+++ b/tests/functional/test_functional.py
@@ -0,0 +1,155 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Functional non-authenticated tests, and local fixtures."""
+import os
+from os import path
+import re
+import sys
+
+import pytest
+from utils import run_process
+
+sys.path.insert(0, path.dirname(path.dirname(path.dirname(path.abspath(__file__)))))
+
+
+@pytest.fixture
+def package_regex():
+    """Get compiled package regex."""
+    version_regex = re.compile(r"^\S+/(?P<version>\d.\d.\d)\D+")
+    return version_regex
+
+
+@pytest.fixture
+def package_version():
+    """Run test with access to the Tokendito package."""
+    from tokendito import __version__ as tokendito_version
+
+    return tokendito_version
+
+
+@pytest.mark.run("first")
+def test_package_uninstall():
+    """Uninstall tokendito if it is already installed."""
+    proc = run_process([sys.executable, "-m", "pip", "uninstall", "-q", "-q", "-y", "tokendito"])
+    assert not proc["stderr"]
+    assert proc["exit_status"] == 0
+
+
+@pytest.mark.run("second")
+def test_package_install():
+    """Install tokendito as a python package."""
+    repo_root = path.dirname(path.dirname(path.abspath(__file__))) + "/../"
+    proc = run_process([sys.executable, "-m", "pip", "install", "-e", repo_root])
+    assert not proc["stderr"]
+    assert proc["exit_status"] == 0
+
+
+def test_package_exists():
+    """Check whether the package is installed."""
+    proc = run_process([sys.executable, "-m", "pip", "show", "tokendito"])
+    assert not proc["stderr"]
+    assert proc["exit_status"] == 0
+
+
+@pytest.mark.parametrize(
+    "runnable",
+    [
+        [sys.executable, "-m", "tokendito", "--version"],
+        [sys.executable, sys.path[0] + "/tokendito/tokendito.py", "--version"],
+        ["tokendito", "--version"],
+    ],
+)
+def test_version(package_version, package_regex, runnable):
+    """Check if the package version is the same when running in different ways."""
+    local_version = None
+    proc = run_process(runnable)
+    assert not proc["stderr"]
+    assert proc["exit_status"] == 0
+    match = re.match(package_regex, proc["stdout"])
+    if match:
+        local_version = match.group("version")
+    assert package_version == local_version
+
+
+def test_parameter_collection(monkeypatch, tmpdir):
+    """Ensure that the order of arguments has the correct behavior."""
+    from argparse import Namespace
+
+    from tokendito import user
+    from tokendito.config import Config
+
+    config = Config()
+    data = "[default]\n"
+    data += "okta_username = pytest_ini\n"
+    data += "aws_region = pytest_ini\n"
+    data += "okta_org = pytest_ini\n"
+    path = tmpdir.mkdir("pytest").join("pytest.ini")
+    path.write(data)
+    config_ini = user.process_ini_file(path, "default")
+
+    env = {
+        "TOKENDITO_OKTA_ORG": "pytest_env",
+        "okta_tile": "https://acme.okta.org/_env",
+    }
+    monkeypatch.setattr(os, "environ", env)
+    config_env = user.process_environment()
+
+    args = {
+        "okta_username": "pytest_arg",
+        "okta_tile": "https://acme.okta.org/_arg",
+    }
+    config_arg = user.process_arguments(Namespace(**args))
+    config.update(config_ini)
+    config.update(config_env)
+    config.update(config_arg)
+    assert config.aws["region"] == "pytest_ini"
+    assert config.okta["org"] == "pytest_env"
+    assert config.okta["username"] == "pytest_arg"
+    assert config.okta["tile"] == "https://acme.okta.org/_arg"
+
+
+def test_quiet_failure():
+    """Ensure we exit without all the necessary arguments."""
+    args = ["--quiet"]
+    executable = [sys.executable, "-m", "tokendito"]
+    runnable = executable + args
+    proc = run_process(runnable)
+    assert proc["exit_status"] == 1
+    assert "Could not validate configuration to run in quiet mode" in proc["stderr"]
+
+
+def test_generate_config(custom_args, config_file):
+    """Test writing to a config file."""
+    from tokendito import user
+    from tokendito.config import Config
+
+    pytest_cfg = Config()
+    tool_args = user.parse_cli_args(custom_args)
+    config_arg = user.process_arguments(tool_args)
+    pytest_cfg.update(config_arg)
+
+    if (
+        pytest_cfg.okta["tile"] is None
+        or pytest_cfg.okta["mfa"] is None
+        or not pytest_cfg.okta["username"]
+    ):
+        pytest_cfg.okta["tile"] = "https://pytest/home/amazon_aws/0123456789abcdef0123/456"
+        pytest_cfg.okta["mfa"] = "push"
+        pytest_cfg.okta["username"] = "pytest"
+
+    # Rebuild argument list
+    args = [
+        "--configure",
+        "--config-file",
+        f"{config_file}",
+        "--okta-tile",
+        f"{pytest_cfg.okta['tile']}",
+        "--okta-mfa",
+        f"{pytest_cfg.okta['mfa']}",
+        "--username",
+        f"{pytest_cfg.okta['username']}",
+    ]
+    executable = [sys.executable, "-m", "tokendito"]
+    runnable = executable + args
+    proc = run_process(runnable)
+    assert proc["exit_status"] == 0
diff --git a/tests/functional/utils.py b/tests/functional/utils.py
new file mode 100644
index 00000000..b83d6c33
--- /dev/null
+++ b/tests/functional/utils.py
@@ -0,0 +1,41 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Reusable functions."""
+import subprocess
+
+
+def string_decode(bytestring):
+    """Convert a str into a Unicode object.
+
+    The `decode()` method is only available in byte strings. Calling on
+    other string objects generates a `NameError`, and the same string is
+    returned unmodified.
+
+    :param bytestring:
+    :return: decoded string
+    """
+    decoded_string = bytestring
+    try:
+        decoded_string = bytestring.decode("utf-8")
+    except (NameError, TypeError):
+        # If a TypeError is raised, this is a no-op.
+        pass
+
+    return decoded_string
+
+
+def run_process(proc):
+    """Spawn a child process.
+
+    Returns a dict with stdout, sdterr, exit status, and command executed.
+    """
+    process = subprocess.Popen(proc, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    (stdoutdata, stderrdata) = process.communicate()
+
+    proc_status = {
+        "stdout": string_decode(stdoutdata),
+        "stderr": string_decode(stderrdata),
+        "name": " ".join(proc),
+        "exit_status": process.returncode,
+    }
+    return proc_status
diff --git a/tests/functional_test.py b/tests/functional_test.py
deleted file mode 100644
index 756e1632..00000000
--- a/tests/functional_test.py
+++ /dev/null
@@ -1,321 +0,0 @@
-# vim: set filetype=python ts=4 sw=4
-# -*- coding: utf-8 -*-
-"""Functional tests, and local fixtures."""
-import datetime
-import os
-from os import environ
-from os import path
-import re
-import subprocess
-import sys
-import time
-
-import pytest
-
-sys.path.insert(0, path.dirname(path.dirname(path.abspath(__file__))))
-
-
-def string_decode(bytestring):
-    """Convert a str into a Unicode object.
-
-    The `decode()` method is only available in byte strings. Calling on
-    other string objects generates a `NameError`, and the same string is
-    returned unmodified.
-
-    :param bytestring:
-    :return: decoded string
-    """
-    decoded_string = bytestring
-    try:
-        decoded_string = bytestring.decode("utf-8")
-    except (NameError, TypeError):
-        # If a TypeError is raised, this is a no-op.
-        pass
-
-    return decoded_string
-
-
-def run_process(proc):
-    """Spawn a child process.
-
-    Returns a dict with stdout, sdterr, exit status, and command executed.
-    """
-    process = subprocess.Popen(proc, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    (stdoutdata, stderrdata) = process.communicate()
-
-    proc_status = {
-        "stdout": string_decode(stdoutdata),
-        "stderr": string_decode(stderrdata),
-        "name": " ".join(proc),
-        "exit_status": process.returncode,
-    }
-    return proc_status
-
-
-@pytest.fixture
-def package_regex():
-    """Get compiled package regex."""
-    version_regex = re.compile(r"^\S+/(?P<version>\d.\d.\d)\D+")
-    return version_regex
-
-
-@pytest.fixture
-def package_version():
-    """Run test with access to the Tokendito package."""
-    from tokendito import __version__ as tokendito_version
-
-    return tokendito_version
-
-
-@pytest.fixture
-def custom_args(request):
-    """Search the custom command-line options and return a list of keys and values."""
-    options = [
-        "--username",
-        "--password",
-        "--okta-tile",
-        "--okta-mfa",
-        "--okta-mfa-response",
-        "--aws-role-arn",
-        "--config-file",
-        "--aws-profile",
-    ]
-    arg_list = []
-    # pytest does not have a method for listing options, so we have look them up.
-    for item in options:
-        if request.config.getoption(item):
-            arg_list.extend([item, request.config.getoption(item)])
-    return arg_list
-
-
-@pytest.fixture(scope="session")
-def config_file(tmp_path_factory):
-    """Generate a path for a temporary ini file that multiple tests can share."""
-    path = tmp_path_factory.mktemp("pytest") / "pytest.ini"
-    return path
-
-
-@pytest.mark.run("first")
-def test_package_uninstall():
-    """Uninstall tokendito if it is already installed."""
-    proc = run_process([sys.executable, "-m", "pip", "uninstall", "-q", "-q", "-y", "tokendito"])
-    assert not proc["stderr"]
-    assert proc["exit_status"] == 0
-
-
-@pytest.mark.run("second")
-def test_package_install():
-    """Install tokendito as a python package."""
-    repo_root = path.dirname(path.dirname(path.abspath(__file__)))
-    proc = run_process([sys.executable, "-m", "pip", "install", "-e", repo_root])
-    assert not proc["stderr"]
-    assert proc["exit_status"] == 0
-
-
-def test_package_exists():
-    """Check whether the package is installed."""
-    proc = run_process([sys.executable, "-m", "pip", "show", "tokendito"])
-    assert not proc["stderr"]
-    assert proc["exit_status"] == 0
-
-
-@pytest.mark.parametrize(
-    "runnable",
-    [
-        [sys.executable, "-m", "tokendito", "--version"],
-        [sys.executable, sys.path[0] + "/tokendito/tokendito.py", "--version"],
-        ["tokendito", "--version"],
-    ],
-)
-def test_version(package_version, package_regex, runnable):
-    """Check if the package version is the same when running in different ways."""
-    local_version = None
-    proc = run_process(runnable)
-    assert not proc["stderr"]
-    assert proc["exit_status"] == 0
-    match = re.match(package_regex, proc["stdout"])
-    if match:
-        local_version = match.group("version")
-    assert package_version == local_version
-
-
-def test_parameter_collection(monkeypatch, tmpdir):
-    """Ensure that the order of arguments has the correct behavior."""
-    from argparse import Namespace
-
-    from tokendito import user
-    from tokendito.config import Config
-
-    config = Config()
-    data = "[default]\n"
-    data += "okta_username = pytest_ini\n"
-    data += "aws_region = pytest_ini\n"
-    data += "okta_org = pytest_ini\n"
-    path = tmpdir.mkdir("pytest").join("pytest.ini")
-    path.write(data)
-    config_ini = user.process_ini_file(path, "default")
-
-    env = {
-        "TOKENDITO_OKTA_ORG": "pytest_env",
-        "okta_tile": "https://acme.okta.org/_env",
-    }
-    monkeypatch.setattr(os, "environ", env)
-    config_env = user.process_environment()
-
-    args = {
-        "okta_username": "pytest_arg",
-        "okta_tile": "https://acme.okta.org/_arg",
-    }
-    config_arg = user.process_arguments(Namespace(**args))
-    config.update(config_ini)
-    config.update(config_env)
-    config.update(config_arg)
-    assert config.aws["region"] == "pytest_ini"
-    assert config.okta["org"] == "pytest_env"
-    assert config.okta["username"] == "pytest_arg"
-    assert config.okta["tile"] == "https://acme.okta.org/_arg"
-
-
-def test_quiet_failure():
-    """Ensure we exit without all the necessary arguments."""
-    args = ["--quiet"]
-    executable = [sys.executable, "-m", "tokendito"]
-    runnable = executable + args
-    proc = run_process(runnable)
-    assert proc["exit_status"] == 1
-    assert "Could not validate configuration to run in quiet mode" in proc["stderr"]
-
-
-def test_generate_config(custom_args, config_file):
-    """Test writing to a config file."""
-    from tokendito import user
-    from tokendito.config import Config
-
-    pytest_cfg = Config()
-    tool_args = user.parse_cli_args(custom_args)
-    config_arg = user.process_arguments(tool_args)
-    pytest_cfg.update(config_arg)
-
-    if (
-        pytest_cfg.okta["tile"] is None
-        or pytest_cfg.okta["mfa"] is None
-        or not pytest_cfg.okta["username"]
-    ):
-        pytest_cfg.okta["tile"] = "https://pytest/home/amazon_aws/0123456789abcdef0123/456"
-        pytest_cfg.okta["mfa"] = "push"
-        pytest_cfg.okta["username"] = "pytest"
-
-    # Rebuild argument list
-    args = [
-        "--configure",
-        "--config-file",
-        f"{config_file}",
-        "--okta-tile",
-        f"{pytest_cfg.okta['tile']}",
-        "--okta-mfa",
-        f"{pytest_cfg.okta['mfa']}",
-        "--username",
-        f"{pytest_cfg.okta['username']}",
-    ]
-    executable = [sys.executable, "-m", "tokendito"]
-    runnable = executable + args
-    proc = run_process(runnable)
-    assert proc["exit_status"] == 0
-
-
-@pytest.mark.run("second-to-last")
-def test_generate_credentials(custom_args, config_file):
-    """Run the tool and generate credentials."""
-    import pyotp
-    from tokendito import user
-    from tokendito.config import config
-
-    # Emulate helpers.process_options() bypassing interactive portions.
-    tool_args = user.parse_cli_args(custom_args)
-    config_ini = user.process_ini_file(config_file, "default")
-    config_env = user.process_environment()
-    config_arg = user.process_arguments(tool_args)
-
-    config.update(config_ini)
-    config.update(config_env)
-    config.update(config_arg)
-
-    if (
-        config.aws["role_arn"] is None
-        or config.okta["tile"] is None
-        or config.okta["mfa"] is None
-        or not config.okta["username"]
-        or not config.okta["password"]
-    ):
-        pytest.skip("Not enough arguments collected to execute non-interactively.")
-
-    # If a token response is present and is not in the usual 6-digit format,
-    # assume it is a MFA seed and create a valid response from it.
-    if (
-        config.okta["mfa_response"] is not None
-        and re.match("[0-9]{6}", config.okta["mfa_response"]) is None
-    ):
-        totp = pyotp.TOTP(config.okta["mfa_response"], interval=30)
-        # If there are a few seconds left on the TOTP timer, wait until the next round.
-        time_remaining = totp.interval - datetime.datetime.now().timestamp() % totp.interval
-        if time_remaining < 5:
-            time.sleep(1 + time_remaining)
-        config.okta["mfa_response"] = totp.now()
-        # Update the environment variable that has been modified, if it exists
-        # as this may be passed down to a subprocess.
-        if "TOKENDITO_OKTA_MFA_RESPONSE" in environ:
-            environ["TOKENDITO_OKTA_MFA_RESPONSE"] = config.okta["mfa_response"]
-
-    # Rebuild argument list
-    args = [
-        "--aws-role-arn",
-        f"{config.aws['role_arn']}",
-        "--aws-profile",
-        f"{config.aws['profile']}",
-        "--okta-tile",
-        f"{config.okta['tile']}",
-        "--okta-mfa",
-        f"{config.okta['mfa']}",
-        "--okta-mfa-response",
-        f"{config.okta['mfa_response']}",
-        "--username",
-        f"{config.okta['username']}",
-        "--password",
-        f"{config.okta['password']}",
-        "--loglevel",
-        "DEBUG",
-    ]
-    # run as a local module, as we can't guarantee that the binary is installed.
-    executable = [sys.executable, "-m", "tokendito"]
-    runnable = executable + args
-
-    proc = run_process(runnable)
-    assert "'password': '*****'" in proc["stderr"]
-    assert f"{config.okta['password']}" not in proc["stderr"]
-    assert f"{config.okta['mfa_response']}" not in proc["stderr"]
-    assert '"sessionToken": "*****"' in proc["stderr"]
-    assert proc["exit_status"] == 0
-
-
-@pytest.mark.run("last")
-def test_aws_credentials(custom_args):
-    """Run the AWS cli to verify whether credentials work."""
-    from tokendito import user
-    from tokendito.config import config
-
-    # Emulate helpers.process_options() bypassing interactive portions.
-    tool_args = user.parse_cli_args(custom_args)
-    config_ini = user.process_ini_file(tool_args.user_config_file, "default")
-    config_env = user.process_environment()
-    config_arg = user.process_arguments(tool_args)
-    config.update(config_ini)
-    config.update(config_env)
-    config.update(config_arg)
-
-    if not config.aws["role_arn"]:
-        pytest.skip("No AWS profile defined, test will be skipped.")
-
-    runnable = ["aws", "--profile", config.aws["profile"], "sts", "get-caller-identity"]
-    proc = run_process(runnable)
-    assert not proc["stderr"]
-    assert proc["exit_status"] == 0
diff --git a/tests/okta_response_simulation.py b/tests/unit/okta_response_simulation.py
similarity index 98%
rename from tests/okta_response_simulation.py
rename to tests/unit/okta_response_simulation.py
index c204f9db..272a3bb1 100644
--- a/tests/okta_response_simulation.py
+++ b/tests/unit/okta_response_simulation.py
@@ -1,6 +1,6 @@
 # vim: set filetype=python ts=4 sw=4
 # -*- coding: utf-8 -*-
-"""simulation okta reponses."""
+"""Simulatie okta reponses."""
 
 with_mfa = {
     "status": "MFA_REQUIRED",
diff --git a/tests/unit/test_aws.py b/tests/unit/test_aws.py
new file mode 100644
index 00000000..34064fc2
--- /dev/null
+++ b/tests/unit/test_aws.py
@@ -0,0 +1,100 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Unit tests, and local fixtures for AWS module."""
+import pytest
+
+
+def test_assert_credentials():
+    """Test whether getting credentials works as expeted."""
+    from moto import mock_sts
+    from tokendito import aws
+
+    with pytest.raises(SystemExit) as err:
+        aws.assert_credentials({})
+    assert err.value.code == 1
+
+    saml_response = {
+        "Credentials": {
+            "AccessKeyId": "pytest",
+            "SecretAccessKey": "pytest",
+            "SessionToken": "pytest",
+        }
+    }
+    with mock_sts():
+        ret = aws.assert_credentials(role_response=saml_response)
+        assert "Arn" in ret and "UserId" in ret
+
+
+def test_get_regions():
+    """Test retrieval of available AWS regions."""
+    from tokendito import aws
+
+    ret = aws.get_regions(profile="pytest")
+    assert ret == []
+    ret = aws.get_regions()
+    assert "us-east-1" in ret
+
+
+def test_get_output_types():
+    """Test getting AWS output types."""
+    from tokendito import aws
+
+    ret = aws.get_output_types()
+    assert "json" in ret
+
+
+def test_assume_role(mocker):
+    """Test assuming role."""
+    from tokendito import aws
+    from tokendito.config import Config
+
+    pytest_config = Config(
+        okta={
+            "username": "pytest",
+            "password": "pytest",
+            "org": "https://acme.okta.org/",
+        }
+    )
+    role_arn = "0000000000000000000000000:role/testrole"
+    session_name = "pytestsession"
+    assumed_role_object = {
+        "Credentials": {
+            "AccessKeyId": "pytestaccesskey",
+            "SecretAccessKey": "pytestsecretkey",
+            "SessionToken": "pytestsessiontoken",
+        }
+    }
+    mocker.patch("tokendito.aws.handle_assume_role", return_value=assumed_role_object)
+    assert aws.assume_role(pytest_config, role_arn, session_name) == assumed_role_object
+    mocker.patch("tokendito.aws.handle_assume_role", return_value={})
+    with pytest.raises(SystemExit) as error:
+        assert aws.assume_role(pytest_config, role_arn, session_name) == error
+
+
+def test_select_assumeable_role_no_tiles():
+    """Test exiting when there are no assumable roles."""
+    from tokendito import aws
+
+    tiles = [
+        (
+            "https://acme.okta.org/home/amazon_aws/0123456789abcdef0123/456",
+            "saml_response",
+            "arn:aws:iam::000000000000:saml/name,arn:aws:iam::000000000000:role/name",
+            "Tile Label",
+        )
+    ]
+    with pytest.raises(SystemExit) as err:
+        aws.select_assumeable_role(tiles)
+    assert err.value.code == 1
+
+
+@pytest.mark.parametrize("status_code", [(400), (401), (404), (500), (503)])
+def test_authenticate_to_roles(status_code, monkeypatch):
+    """Test if function return correct response."""
+    import requests
+    from tokendito.aws import authenticate_to_roles
+
+    mock_get = {"status_code": status_code, "text": "response"}
+    monkeypatch.setattr(requests, "get", mock_get)
+    with pytest.raises(SystemExit) as error:
+        assert authenticate_to_roles([("http://test.url.com", "")], "secret_session_token") == error
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
index 1db4d60c..aed2908c 100644
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -1,6 +1,6 @@
 # vim: set filetype=python ts=4 sw=4
 # -*- coding: utf-8 -*-
-"""Unit tests, and local fixtures."""
+"""Unit tests, and local fixtures for the Config class."""
 import pytest
 
 
diff --git a/tests/unit/test_duo.py b/tests/unit/test_duo.py
new file mode 100644
index 00000000..6eb9ec9d
--- /dev/null
+++ b/tests/unit/test_duo.py
@@ -0,0 +1,92 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Unit tests, and local fixtures for DUO module."""
+from unittest.mock import Mock
+
+
+def test_set_passcode(mocker):
+    """Check if numerical passcode can handle leading zero values."""
+    from tokendito import duo
+
+    mocker.patch("tokendito.user.tty_assertion", return_value=True)
+    mocker.patch("tokendito.user.input", return_value="0123456")
+    assert duo.set_passcode({"factor": "passcode"}) == "0123456"
+
+
+def test_prepare_duo_info():
+    """Test behaviour empty return duo info."""
+    from tokendito.config import config
+    from tokendito.duo import prepare_duo_info
+
+    selected_okta_factor = {
+        "_embedded": {
+            "factor": {
+                "_embedded": {
+                    "verification": {
+                        "_links": {
+                            "complete": {"href": "http://test.okta.href"},
+                            "script": {"href": "python-v3.7"},
+                        },
+                        "signature": "fdsafdsa:fdsfdfds:fdsfdsfds",
+                        "host": "test_host",
+                    }
+                },
+                "id": 1234,
+            }
+        },
+        "stateToken": 12345,
+    }
+    okta_factor = selected_okta_factor["_embedded"]["factor"]["_embedded"]["verification"]
+    expected_duo_info = {
+        "okta_factor": okta_factor,
+        "factor_id": 1234,
+        "state_token": 12345,
+        "okta_callback_url": "http://test.okta.href",
+        "tx": "fdsafdsa",
+        "tile_sig": "fdsfdfds",
+        "parent": f"{config.okta['org']}/signin/verify/duo/web",
+        "host": "test_host",
+        "sid": "",
+        "version": "3.7",
+    }
+    assert prepare_duo_info(selected_okta_factor) == expected_duo_info
+
+
+def test_get_duo_sid(mocker):
+    """Check if got sid correct."""
+    from tokendito.config import config
+    from tokendito.duo import get_duo_sid
+
+    test_duo_info = {
+        "okta_factor": "okta_factor",
+        "factor_id": 1234,
+        "state_token": 12345,
+        "okta_callback_url": "http://test.okta.href",
+        "tx": "fdsafdsa",
+        "tile_sig": "fdsfdfds",
+        "parent": f"{config.okta['org']}/signin/verify/duo/web",
+        "host": "test_host",
+        "sid": "",
+        "version": "3.7",
+    }
+
+    test_url = "http://test.token.dito?sid=testval"
+    duo_api_response = Mock()
+    duo_api_response.url = test_url
+
+    mocker.patch("tokendito.duo.duo_api_post", return_value=duo_api_response)
+
+    duo_sid_info, duo_auth_response = get_duo_sid(test_duo_info)
+
+    assert duo_sid_info["sid"] == "testval"
+    assert duo_auth_response.url == test_url
+
+
+def test_get_mfa_response():
+    """Test if mfa verify correctly."""
+    from tokendito.duo import get_mfa_response
+
+    mfa_result = Mock()
+    mfa_result.json = Mock(return_value={"response": "test_response"})
+
+    assert get_mfa_response(mfa_result) == "test_response"
diff --git a/tests/unit/test_init.py b/tests/unit/test_init.py
new file mode 100644
index 00000000..9401e3f5
--- /dev/null
+++ b/tests/unit/test_init.py
@@ -0,0 +1,27 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Unit tests, and local fixtures for the Tokendito module."""
+import os
+import sys
+
+import semver
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+def test_import_location():
+    """Ensure module imported is the local one."""
+    import tokendito
+
+    local_path = os.path.realpath(
+        os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/../tokendito/__init__.py"
+    )
+    imported_path = os.path.realpath(tokendito.__file__)
+    assert imported_path.startswith(local_path)
+
+
+def test_semver_version():
+    """Ensure the package version is semver compliant."""
+    from tokendito import __version__ as version
+
+    assert semver.VersionInfo.parse(version)
diff --git a/tests/unit/test_okta.py b/tests/unit/test_okta.py
new file mode 100644
index 00000000..85f69cf4
--- /dev/null
+++ b/tests/unit/test_okta.py
@@ -0,0 +1,596 @@
+# vim: set filetype=python ts=4 sw=4
+# -*- coding: utf-8 -*-
+"""Unit tests, and local fixtures for the Okta module."""
+from unittest.mock import Mock
+
+import pytest
+import requests_mock
+
+
+@pytest.fixture
+def sample_headers():
+    """Return a headers."""
+    headers = {"content-type": "application/json", "accept": "application/json"}
+    return headers
+
+
+@pytest.mark.parametrize(
+    "session_token, expected, mfa_availability",
+    [
+        (345, 345, "okta_response_no_auth_methods"),
+        (345, 345, "okta_response_mfa"),
+        (345, 345, "okta_response_no_auth_methods"),
+        (None, None, "okta_response_no_mfa_no_session_token"),
+    ],
+)
+def test_get_session_token(
+    sample_json_response,
+    session_token,
+    expected,
+    mocker,
+    sample_headers,
+    mfa_availability,
+):
+    """Test whether function return key on specific status."""
+    from tokendito.config import Config
+    from tokendito.okta import get_session_token
+
+    pytest_config = Config()
+    primary_auth = sample_json_response[mfa_availability]
+
+    mocker.patch("tokendito.okta.mfa_challenge", return_value=session_token)
+    assert get_session_token(pytest_config, primary_auth, sample_headers) == expected
+    with pytest.raises(SystemExit) as err:
+        assert get_session_token(pytest_config, None, sample_headers) == err
+
+
+def test_bad_session_token(mocker, sample_json_response, sample_headers):
+    """Test whether function behave accordingly."""
+    from tokendito.config import Config
+    from tokendito.okta import get_session_token
+
+    pytest_config = Config()
+    mocker.patch("tokendito.okta.api_error_code_parser", return_value=None)
+    okta_response_statuses = ["okta_response_error", "okta_response_empty"]
+
+    for response in okta_response_statuses:
+        primary_auth = sample_json_response[response]
+
+        with pytest.raises(SystemExit) as error:
+            assert get_session_token(pytest_config, primary_auth, sample_headers) == error
+
+
+@pytest.mark.parametrize(
+    "mfa_provider, session_token, selected_factor, expected",
+    [
+        ("DUO", 123, {"_embedded": {}}, 123),
+        ("OKTA", 345, {"_embedded": {"factor": {"factorType": "push"}}}, 345),
+        ("GOOGLE", 456, {"_embedded": {"factor": {"factorType": "sms"}}}, 456),
+    ],
+)
+def test_mfa_provider_type(
+    mfa_provider,
+    session_token,
+    selected_factor,
+    expected,
+    mocker,
+    sample_headers,
+):
+    """Test whether function return key on specific MFA provider."""
+    from tokendito.config import Config
+    from tokendito.okta import mfa_provider_type
+
+    payload = {"x": "y", "t": "z"}
+    callback_url = "https://www.acme.org"
+    selected_mfa_option = 1
+    mfa_challenge_url = 1
+    primary_auth = 1
+    pytest_config = Config()
+
+    mfa_verify = {"sessionToken": session_token}
+    mocker.patch(
+        "tokendito.duo.authenticate_duo",
+        return_value=(payload, sample_headers, callback_url),
+    )
+    mocker.patch("tokendito.okta.api_wrapper", return_value=mfa_verify)
+    mocker.patch("tokendito.okta.push_approval", return_value=mfa_verify)
+    mocker.patch("tokendito.okta.totp_approval", return_value=mfa_verify)
+    mocker.patch("tokendito.duo.duo_api_post")
+    assert (
+        mfa_provider_type(
+            pytest_config,
+            mfa_provider,
+            selected_factor,
+            mfa_challenge_url,
+            primary_auth,
+            selected_mfa_option,
+            sample_headers,
+            payload,
+        )
+        == expected
+    )
+
+
+def test_bad_mfa_provider_type(mocker, sample_headers):
+    """Test whether function return key on specific MFA provider."""
+    from tokendito.config import Config
+    from tokendito.okta import mfa_provider_type
+
+    pytest_config = Config()
+    payload = {"x": "y", "t": "z"}
+    callback_url = "https://www.acme.org"
+    selected_mfa_option = 1
+    mfa_challenge_url = 1
+    primary_auth = 1
+    selected_factor = {}
+
+    mfa_verify = {"sessionToken": "pytest_session_token"}
+    mfa_bad_provider = "bad_provider"
+    mocker.patch(
+        "tokendito.duo.authenticate_duo",
+        return_value=(payload, sample_headers, callback_url),
+    )
+    mocker.patch("tokendito.okta.api_wrapper", return_value=mfa_verify)
+    mocker.patch("tokendito.okta.totp_approval", return_value=mfa_verify)
+
+    with pytest.raises(SystemExit) as error:
+        assert (
+            mfa_provider_type(
+                pytest_config,
+                mfa_bad_provider,
+                selected_factor,
+                mfa_challenge_url,
+                primary_auth,
+                selected_mfa_option,
+                sample_headers,
+                payload,
+            )
+            == error
+        )
+
+
+def test_api_wrapper():
+    """Test whether verify_api_method returns the correct data."""
+    from tokendito.okta import api_wrapper
+
+    url = "https://acme.org"
+    with requests_mock.Mocker() as m:
+        data = {"response": "ok"}
+        m.post(url, json=data, status_code=200)
+        assert api_wrapper(url, data) == data
+
+    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
+        data = None
+        m.post(url, json=data, status_code=200)
+        assert api_wrapper(url, data) == error
+
+    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
+        data = {"response": "ok", "errorCode": "0xdeadbeef"}
+        m.post(url, json=data, status_code=200)
+        assert api_wrapper(url, data) == error
+
+    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
+        data = "pytest_bad_datatype"
+        m.post(url, text=data, status_code=403)
+        assert api_wrapper(url, data) == error
+
+    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
+        data = {"response": "incorrect", "errorCode": "0xdeadbeef"}
+        m.post(url, json=data, status_code=403)
+        assert api_wrapper("http://acme.org", data) == error
+
+
+def test_api_error_code_parser():
+    """Test whether message on specific status equal."""
+    from tokendito.okta import _status_dict
+    from tokendito.okta import api_error_code_parser
+
+    okta_status_dict = _status_dict
+
+    for key, value in okta_status_dict.items():
+        assert api_error_code_parser(key) == "Okta auth failed: " + value
+    unexpected_key = "UNEXPECTED_KEY"
+    value = f"Okta auth failed: {unexpected_key}. Please verify your settings and try again."
+    assert api_error_code_parser(unexpected_key) == value
+
+
+@pytest.mark.parametrize(
+    "preset_mfa, output",
+    [("push", 0), (None, 1), ("0xdeadbeef", 1), ("opfrar9yi4bKJNH2WEW", 0), ("pytest_dupe", 1)],
+)
+def test_mfa_index(preset_mfa, output, mocker, sample_json_response):
+    """Test whether the function returns correct mfa index."""
+    from tokendito.okta import mfa_index
+
+    primary_auth = sample_json_response["okta_response_mfa"]
+
+    mfa_options = primary_auth["_embedded"]["factors"]
+    available_mfas = [f"{d['provider']}_{d['factorType']}_{d['id']}" for d in mfa_options]
+    mocker.patch("tokendito.user.select_preferred_mfa_index", return_value=1)
+
+    if preset_mfa == "pytest_dupe":
+        with pytest.raises(SystemExit) as err:
+            mfa_index(preset_mfa, available_mfas, mfa_options)
+        assert err.value.code == output
+    else:
+        assert mfa_index(preset_mfa, available_mfas, mfa_options) == output
+
+
+def test_mfa_options(sample_headers, sample_json_response, mocker):
+    """Test handling of MFA approval."""
+    from tokendito.config import Config
+    from tokendito.okta import totp_approval
+
+    selected_mfa_option = {"factorType": "push"}
+    primary_auth = sample_json_response["okta_response_no_mfa"]
+    payload = {"x": "y", "t": "z"}
+    mfa_challenge_url = "https://pytest"
+    pytest_config = Config(okta={"mfa_response": None})
+
+    # Test that selecting software token returns a session token
+    selected_mfa_option = {"factorType": "token:software:totp"}
+    primary_auth["stateToken"] = "pytest"
+    mfa_verify = {"sessionToken": "pytest"}
+    mocker.patch("tokendito.user.get_input", return_value="012345")
+    mocker.patch("tokendito.okta.api_wrapper", return_value=mfa_verify)
+    ret = totp_approval(
+        pytest_config, selected_mfa_option, sample_headers, mfa_challenge_url, payload, primary_auth
+    )
+    assert ret == mfa_verify
+
+
+def test_mfa_challenge_with_no_mfas(sample_headers, sample_json_response):
+    """Test whether okta response has mfas."""
+    from tokendito.config import Config
+    from tokendito.okta import mfa_challenge
+
+    primary_auth = sample_json_response["okta_response_no_auth_methods"]
+    pytest_config = Config()
+
+    with pytest.raises(SystemExit) as error:
+        assert mfa_challenge(pytest_config, sample_headers, primary_auth) == error
+
+
+@pytest.mark.parametrize(
+    "return_value,side_effect,expected",
+    [
+        ({"status": "SUCCESS", "sessionToken": "pytest"}, None, 0),
+        ({"status": "SUCCESS", "sessionToken": "pytest", "factorResult": "SUCCESS"}, None, 0),
+        ({"status": "MFA_CHALLENGE", "factorResult": "REJECTED"}, None, 2),
+        ({"status": "MFA_CHALLENGE", "factorResult": "TIMEOUT"}, None, 2),
+        ({"status": "UNKNOWN", "factorResult": "UNKNOWN"}, None, 2),
+        (
+            {
+                "status": "MFA_CHALLENGE",
+                "factorResult": "WAITING",
+                "_links": {"next": {"href": None}},
+            },
+            [
+                {
+                    "status": "MFA_CHALLENGE",
+                    "factorResult": "WAITING",
+                    "_links": {"next": {"href": None}},
+                },
+                {"status": "SUCCESS", "sessionToken": "pytest", "factorResult": "SUCCESS"},
+            ],
+            0,
+        ),
+        (
+            {
+                "status": "MFA_CHALLENGE",
+                "factorResult": "WAITING",
+                "_embedded": {"factor": {"_embedded": {"challenge": {"correctAnswer": 100}}}},
+                "_links": {"next": {"href": None}},
+            },
+            [
+                {
+                    "status": "MFA_CHALLENGE",
+                    "factorResult": "WAITING",
+                    "_embedded": {"factor": {"_embedded": {"challenge": {"correctAnswer": 100}}}},
+                    "_links": {"next": {"href": None}},
+                },
+                {"status": "SUCCESS", "sessionToken": "pytest", "factorResult": "SUCCESS"},
+            ],
+            0,
+        ),
+    ],
+)
+def test_push_approval(mocker, sample_headers, return_value, side_effect, expected):
+    """Test push approval."""
+    from tokendito import okta
+
+    challenge_url = "https://pytest/api/v1/authn/factors/factorid/verify"
+
+    mocker.patch("tokendito.okta.api_wrapper", return_value=return_value, side_effect=side_effect)
+    mocker.patch("time.sleep", return_value=0)
+
+    if "status" in return_value and return_value["status"] == "SUCCESS":
+        ret = okta.push_approval(sample_headers, challenge_url, None)
+        assert ret["status"] == "SUCCESS"
+    elif "factorResult" in return_value and return_value["factorResult"] == "WAITING":
+        ret = okta.push_approval(sample_headers, challenge_url, None)
+        assert ret["status"] == "SUCCESS"
+    else:
+        with pytest.raises(SystemExit) as err:
+            okta.push_approval(sample_headers, challenge_url, None)
+        assert err.value.code == expected
+
+
+@pytest.mark.parametrize(
+    "auth_properties,expected",
+    [
+        ({}, False),
+        (None, False),
+        ({"type": "OKTA"}, True),
+        ({"type": "SAML2"}, False),
+    ],
+)
+def test_is_local_auth(auth_properties, expected):
+    """Test local auth method."""
+    from tokendito import okta
+
+    assert okta.is_local_auth(auth_properties) == expected
+
+
+@pytest.mark.parametrize(
+    "auth_properties,expected",
+    [
+        ({}, False),
+        (None, False),
+        ({"type": "OKTA"}, False),
+        ({"type": "SAML2"}, True),
+    ],
+)
+def test_is_saml2_auth(auth_properties, expected):
+    """Test saml2 auth method."""
+    from tokendito import okta
+
+    assert okta.is_saml2_auth(auth_properties) == expected
+
+
+@pytest.mark.parametrize(
+    "html, raw, expected",
+    [
+        (
+            '<html><body><input name="SAMLResponse" type="hidden" '
+            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
+            False,
+            '<?xml version="1.0" encoding="UTF-8"?>',
+        ),
+        (
+            '<html><body><input name="SAMLResponse" type="hidden" '
+            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
+            True,
+            "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
+        ),
+        (
+            "",
+            False,
+            None,
+        ),
+        (
+            "invalid html",
+            False,
+            None,
+        ),
+    ],
+)
+def test_extract_saml_response(html, raw, expected):
+    """Test extracting SAML response."""
+    from tokendito import okta
+
+    assert okta.extract_saml_response(html, raw) == expected
+
+
+@pytest.mark.parametrize(
+    "html, raw, expected",
+    [
+        (
+            '<html><body><input name="SAMLRequest" type="hidden" '
+            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
+            False,
+            '<?xml version="1.0" encoding="UTF-8"?>',
+        ),
+        (
+            '<html><body><input name="SAMLRequest" type="hidden" '
+            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
+            True,
+            "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
+        ),
+        (
+            "",
+            False,
+            None,
+        ),
+        (
+            "invalid html",
+            False,
+            None,
+        ),
+    ],
+)
+def test_extract_saml_request(html, raw, expected):
+    """Test extracting SAML response."""
+    from tokendito import okta
+
+    assert okta.extract_saml_request(html, raw) == expected
+
+
+@pytest.mark.parametrize(
+    "html,expected",
+    [
+        (
+            "<html><body><form action='https://acme.okta.com/app/okta_org2org/"
+            "akjlkjlksjx0xmdd/sso/saml' id='appForm' method='POST'</form></body></html>",
+            "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
+        ),
+        ("<html><body><form action='' id='appForm' method='POST'</form></body></html>", ""),
+        ("invalid html", None),
+    ],
+)
+def test_extract_form_post_url(html, expected):
+    """Test extracting form post URL."""
+    from tokendito import okta
+
+    assert okta.extract_form_post_url(html) == expected
+
+
+@pytest.mark.parametrize(
+    "html,expected",
+    [
+        (
+            "<html><body><input name='RelayState' type='hidden' value='foobar'></body></html>",
+            "foobar",
+        ),
+        ("<html><body><input name='RelayState' type='hidden' value=''></body></html>", ""),
+        ("invalid html", None),
+    ],
+)
+def test_extract_saml_relaystate(html, expected):
+    """Test extracting SAML relay state."""
+    from tokendito import okta
+
+    assert okta.extract_saml_relaystate(html) == expected
+
+
+def test_get_saml_request(mocker):
+    """Test getting SAML request."""
+    from tokendito import okta
+
+    request_wrapper_response = Mock()
+    auth_properties = {"id": "id", "metadata": "metadata"}
+    request_wrapper_response.text = (
+        "<html><body><form action='https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/"
+        "saml' id='appForm' method='POST'</form><input name='SAMLRequest' type='hidden' "
+        "value='PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4='>"
+        "<input name='RelayState' type='hidden' value='foobar'></body></html>"
+    )
+    mocker.patch("tokendito.user.request_wrapper", return_value=request_wrapper_response)
+
+    assert okta.get_saml_request(auth_properties) == {
+        "base_url": "https://acme.okta.com",
+        "post_url": "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
+        "relay_state": "foobar",
+        "request": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
+    }
+
+
+def test_send_saml_request(mocker):
+    """Test sending SAML request."""
+    from tokendito import okta
+
+    request_wrapper_response = Mock()
+    request_wrapper_response.text = (
+        "<html><body><form action='https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/"
+        "saml' id='appForm' method='POST'</form><input name='SAMLResponse' type='hidden' "
+        "value='PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4='>"
+        "<input name='RelayState' type='hidden' value='foobar'></body></html>"
+    )
+
+    saml_request = {"relay_state": "relay_state", "request": "request", "post_url": "post_url"}
+    cookie = {"sid": "pytestcookie"}
+
+    mocker.patch("tokendito.user.request_wrapper", return_value=request_wrapper_response)
+
+    assert okta.send_saml_request(saml_request, cookie) == {
+        "response": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
+        "relay_state": "foobar",
+        "post_url": "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
+    }
+
+
+def test_send_saml_response(mocker):
+    """Test sending SAML response."""
+    from tokendito import okta
+
+    request_wrapper_response = Mock()
+    request_wrapper_response.cookies = {"sid": "pytestcookie"}
+
+    saml_response = {
+        "response": "pytestresponse",
+        "relay_state": "foobar",
+        "post_url": "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
+    }
+
+    mocker.patch("tokendito.user.request_wrapper", return_value=request_wrapper_response)
+    assert okta.send_saml_response(saml_response) == request_wrapper_response.cookies
+
+
+def test_authenticate(mocker):
+    """Test authentication."""
+    from tokendito import okta
+    from tokendito.config import Config
+
+    pytest_config = Config(
+        okta={
+            "username": "pytest",
+            "password": "pytest",
+            "org": "https://acme.okta.org/",
+        }
+    )
+    sid = {"sid": "pytestsid"}
+    mocker.patch("tokendito.user.request_cookies", return_value=sid)
+    mocker.patch("tokendito.okta.local_auth", return_value="foobar")
+    mocker.patch("tokendito.okta.saml2_auth", return_value=sid)
+
+    mocker.patch("tokendito.okta.get_auth_properties", return_value={"type": "OKTA"})
+    assert okta.authenticate(pytest_config) == sid
+
+    mocker.patch("tokendito.okta.get_auth_properties", return_value={"type": "SAML2"})
+    assert okta.authenticate(pytest_config) == sid
+
+    mocker.patch("tokendito.okta.get_auth_properties", return_value={"type": "UNKNOWN"})
+    with pytest.raises(SystemExit) as error:
+        assert okta.authenticate(pytest_config) == error
+
+    mocker.patch("tokendito.okta.get_auth_properties", return_value={})
+    with pytest.raises(SystemExit) as error:
+        assert okta.authenticate(pytest_config) == error
+
+
+def test_local_auth(mocker):
+    """Test local auth method."""
+    from tokendito import okta
+    from tokendito.config import Config
+
+    pytest_config = Config(
+        okta={
+            "username": "pytest",
+            "password": "pytest",
+            "org": "https://acme.okta.org/",
+        }
+    )
+    api_wrapper_response = {"status": "SUCCESS", "sessionToken": "pytesttoken"}
+
+    mocker.patch("tokendito.okta.api_wrapper", return_value=api_wrapper_response)
+    mocker.patch("tokendito.okta.get_session_token", return_value="pytesttoken")
+    assert okta.local_auth(pytest_config) == "pytesttoken"
+
+
+def test_saml2_auth(mocker):
+    """Test saml2 authentication."""
+    from tokendito import okta
+    from tokendito.config import Config
+
+    auth_properties = {"id": "id", "metadata": "metadata"}
+
+    pytest_config = Config(
+        okta={
+            "username": "pytest",
+            "password": "pytest",
+            "org": "https://acme.okta.org/",
+        }
+    )
+    saml_request = {
+        "base_url": "https://acme.okta.com",
+    }
+    mocker.patch("tokendito.okta.get_saml_request", return_value=saml_request)
+    mocker.patch("tokendito.okta.authenticate", return_value="pytestsessioncookie")
+
+    saml_response = {
+        "response": "pytestresponse",
+    }
+
+    mocker.patch("tokendito.okta.send_saml_request", return_value=saml_response)
+    mocker.patch("tokendito.okta.send_saml_response", return_value="pytestsessionid")
+    assert okta.saml2_auth(pytest_config, auth_properties) == "pytestsessionid"
diff --git a/tests/unit_test.py b/tests/unit/test_user.py
similarity index 51%
rename from tests/unit_test.py
rename to tests/unit/test_user.py
index 6a5f3300..295572cd 100644
--- a/tests/unit_test.py
+++ b/tests/unit/test_user.py
@@ -1,68 +1,27 @@
 # vim: set filetype=python ts=4 sw=4
 # -*- coding: utf-8 -*-
-"""Unit tests, and local fixtures."""
+"""Unit tests, and local fixtures for the user module."""
 from datetime import datetime
 import os
 import sys
-from unittest.mock import Mock
 
 import pytest
 import requests_mock
-import semver
-
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-
-
-@pytest.fixture
-def sample_json_response():
-    """Return a response from okta server."""
-    from okta_response_simulation import empty_dict
-    from okta_response_simulation import error_dict
-    from okta_response_simulation import no_auth_methods
-    from okta_response_simulation import no_mfa
-    from okta_response_simulation import no_mfa_no_session_token
-    from okta_response_simulation import with_mfa
-
-    okta_fixture_data = {
-        "okta_response_no_auth_methods": no_auth_methods,
-        "okta_response_empty": empty_dict,
-        "okta_response_error": error_dict,
-        "okta_response_no_mfa": no_mfa,
-        "okta_response_no_mfa_no_session_token": no_mfa_no_session_token,
-        "okta_response_mfa": with_mfa,
-    }
-    return okta_fixture_data
-
-
-@pytest.fixture
-def sample_headers():
-    """Return a headers."""
-    headers = {"content-type": "application/json", "accept": "application/json"}
-    return headers
-
-
-def test_import_location():
-    """Ensure module imported is the local one."""
-    import tokendito
-
-    local_path = os.path.realpath(
-        os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/tokendito/__init__.py"
-    )
-    imported_path = os.path.realpath(tokendito.__file__)
-    assert imported_path.startswith(local_path)
 
 
 @pytest.mark.xfail(
     sys.platform == "win32", reason="Windows does not always handle NULL stdin correctly."
 )
-def test_tty_assertion():
+def test_tty_assertion(monkeypatch):
     """Test the availability of stdin."""
     import os
     import sys
 
     from tokendito.user import tty_assertion
 
-    from io import UnsupportedOperation
+    import io
+
+    monkeypatch.setattr("sys.stdin", io.StringIO(""))
 
     # Save for reuse
     old_stdin = sys.stdin
@@ -85,19 +44,12 @@ def test_tty_assertion():
         # This try/except block is needed for running pytest through en editor
         try:
             os.close(sys.stdin.fileno())
-        except UnsupportedOperation:
+        except io.UnsupportedOperation:
             pass
         tty_assertion()
     assert err.value.code == 1
 
 
-def test_semver_version():
-    """Ensure the package version is semver compliant."""
-    from tokendito import __version__ as version
-
-    assert semver.VersionInfo.parse(version)
-
-
 def test_get_username(mocker):
     """Test whether data sent is the same as data returned."""
     from tokendito import user
@@ -312,27 +264,6 @@ def test_update_ini(tmpdir):
     assert ret.get(profile, "key_pytest_2") == "val_pytest_2"
 
 
-def test_assert_credentials():
-    """Test whether getting credentials works as expeted."""
-    from moto import mock_sts
-    from tokendito import aws
-
-    with pytest.raises(SystemExit) as err:
-        aws.assert_credentials({})
-    assert err.value.code == 1
-
-    saml_response = {
-        "Credentials": {
-            "AccessKeyId": "pytest",
-            "SecretAccessKey": "pytest",
-            "SessionToken": "pytest",
-        }
-    }
-    with mock_sts():
-        ret = aws.assert_credentials(role_response=saml_response)
-        assert "Arn" in ret and "UserId" in ret
-
-
 def test_set_local_credentials(tmpdir):
     """Test setting credentials."""
     from tokendito import user
@@ -359,19 +290,20 @@ def test_set_local_credentials(tmpdir):
 
 
 def test_add_sensitive_value_to_be_masked():
-    """Test adding some values only adds whatas expected."""
+    """Test adding some values only adds what is expected."""
     from tokendito import user
 
+    # Reset global mask_items
+    user.mask_items = []
     user.add_sensitive_value_to_be_masked("should be added")
     user.add_sensitive_value_to_be_masked("should be added2")
     user.add_sensitive_value_to_be_masked("should be added3", "password")
     user.add_sensitive_value_to_be_masked("should not be added", "public")
-    assert (
-        "should be added" in user.mask_items
-        and "should be added2" in user.mask_items
-        and "should be added3" in user.mask_items
-        and len(user.mask_items) == 3
-    )
+
+    assert "should be added" in user.mask_items
+    assert "should be added2" in user.mask_items
+    assert "should be added3" in user.mask_items
+    assert len(user.mask_items) == 3
 
 
 def test_logger_mask(caplog):
@@ -380,18 +312,20 @@ def test_logger_mask(caplog):
 
     from tokendito import user
 
+    secret_dict = {"secret_key": "secret_val"}
     logger = logging.getLogger(__name__)
     logger.addFilter(user.MaskLoggerSecret())
     user.add_sensitive_value_to_be_masked("supersecret")
     user.add_sensitive_value_to_be_masked("another secret", "sessionToken")
+    user.add_sensitive_value_to_be_masked(secret_dict["secret_key"])
     with caplog.at_level(logging.DEBUG):
         logger.debug("This should be displayed, but not: supersecret")
         logger.debug("another secret")
-    assert (
-        "supersecret" not in caplog.text
-        and "another secret" not in caplog.text
-        and "This should be displayed" in caplog.text
-    )
+        logger.debug(secret_dict)
+    assert "supersecret" not in caplog.text
+    assert "another secret" not in caplog.text
+    assert "secret_val" not in caplog.text
+    assert "This should be displayed" in caplog.text
 
 
 def test_display_selected_role():
@@ -469,15 +403,6 @@ def test_utc_to_local():
     assert err.value.code == 1
 
 
-def test_set_passcode(mocker):
-    """Check if numerical passcode can handle leading zero values."""
-    from tokendito import duo
-
-    mocker.patch("tokendito.user.tty_assertion", return_value=True)
-    mocker.patch("tokendito.user.input", return_value="0123456")
-    assert duo.set_passcode({"factor": "passcode"}) == "0123456"
-
-
 def test_process_environment(monkeypatch):
     """Test whether environment variables are interpreted correctly."""
     from tokendito import user
@@ -575,186 +500,6 @@ def test_process_ini_file(tmpdir):
     assert err.value.code == 1
 
 
-@pytest.mark.parametrize(
-    "session_token, expected, mfa_availability",
-    [
-        (345, 345, "okta_response_no_auth_methods"),
-        (345, 345, "okta_response_mfa"),
-        (345, 345, "okta_response_no_auth_methods"),
-        (None, None, "okta_response_no_mfa_no_session_token"),
-    ],
-)
-def test_get_session_token(
-    sample_json_response,
-    session_token,
-    expected,
-    mocker,
-    sample_headers,
-    mfa_availability,
-):
-    """Test whether function return key on specific status."""
-    from tokendito.config import Config
-    from tokendito.okta import get_session_token
-
-    pytest_config = Config()
-    primary_auth = sample_json_response[mfa_availability]
-
-    mocker.patch("tokendito.okta.mfa_challenge", return_value=session_token)
-    assert get_session_token(pytest_config, primary_auth, sample_headers) == expected
-    with pytest.raises(SystemExit) as err:
-        assert get_session_token(pytest_config, None, sample_headers) == err
-
-
-def test_bad_session_token(mocker, sample_json_response, sample_headers):
-    """Test whether function behave accordingly."""
-    from tokendito.config import Config
-    from tokendito.okta import get_session_token
-
-    pytest_config = Config()
-    mocker.patch("tokendito.okta.api_error_code_parser", return_value=None)
-    okta_response_statuses = ["okta_response_error", "okta_response_empty"]
-
-    for response in okta_response_statuses:
-        primary_auth = sample_json_response[response]
-
-        with pytest.raises(SystemExit) as error:
-            assert get_session_token(pytest_config, primary_auth, sample_headers) == error
-
-
-@pytest.mark.parametrize(
-    "mfa_provider, session_token, selected_factor, expected",
-    [
-        ("DUO", 123, {"_embedded": {}}, 123),
-        ("OKTA", 345, {"_embedded": {"factor": {"factorType": "push"}}}, 345),
-        ("GOOGLE", 456, {"_embedded": {"factor": {"factorType": "sms"}}}, 456),
-    ],
-)
-def test_mfa_provider_type(
-    mfa_provider,
-    session_token,
-    selected_factor,
-    expected,
-    mocker,
-    sample_headers,
-):
-    """Test whether function return key on specific MFA provider."""
-    from tokendito.config import Config
-    from tokendito.okta import mfa_provider_type
-
-    payload = {"x": "y", "t": "z"}
-    callback_url = "https://www.acme.org"
-    selected_mfa_option = 1
-    mfa_challenge_url = 1
-    primary_auth = 1
-    pytest_config = Config()
-
-    mfa_verify = {"sessionToken": session_token}
-    mocker.patch(
-        "tokendito.duo.authenticate_duo",
-        return_value=(payload, sample_headers, callback_url),
-    )
-    mocker.patch("tokendito.okta.api_wrapper", return_value=mfa_verify)
-    mocker.patch("tokendito.okta.push_approval", return_value=mfa_verify)
-    mocker.patch("tokendito.okta.totp_approval", return_value=mfa_verify)
-    mocker.patch("tokendito.duo.duo_api_post")
-    assert (
-        mfa_provider_type(
-            pytest_config,
-            mfa_provider,
-            selected_factor,
-            mfa_challenge_url,
-            primary_auth,
-            selected_mfa_option,
-            sample_headers,
-            payload,
-        )
-        == expected
-    )
-
-
-def test_bad_mfa_provider_type(mocker, sample_headers):
-    """Test whether function return key on specific MFA provider."""
-    from tokendito.config import Config
-    from tokendito.okta import mfa_provider_type
-
-    pytest_config = Config()
-    payload = {"x": "y", "t": "z"}
-    callback_url = "https://www.acme.org"
-    selected_mfa_option = 1
-    mfa_challenge_url = 1
-    primary_auth = 1
-    selected_factor = {}
-
-    mfa_verify = {"sessionToken": "pytest_session_token"}
-    mfa_bad_provider = "bad_provider"
-    mocker.patch(
-        "tokendito.duo.authenticate_duo",
-        return_value=(payload, sample_headers, callback_url),
-    )
-    mocker.patch("tokendito.okta.api_wrapper", return_value=mfa_verify)
-    mocker.patch("tokendito.okta.totp_approval", return_value=mfa_verify)
-
-    with pytest.raises(SystemExit) as error:
-        assert (
-            mfa_provider_type(
-                pytest_config,
-                mfa_bad_provider,
-                selected_factor,
-                mfa_challenge_url,
-                primary_auth,
-                selected_mfa_option,
-                sample_headers,
-                payload,
-            )
-            == error
-        )
-
-
-def test_api_wrapper():
-    """Test whether verify_api_method returns the correct data."""
-    from tokendito.okta import api_wrapper
-
-    url = "https://acme.org"
-    with requests_mock.Mocker() as m:
-        data = {"response": "ok"}
-        m.post(url, json=data, status_code=200)
-        assert api_wrapper(url, data) == data
-
-    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
-        data = None
-        m.post(url, json=data, status_code=200)
-        assert api_wrapper(url, data) == error
-
-    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
-        data = {"response": "ok", "errorCode": "0xdeadbeef"}
-        m.post(url, json=data, status_code=200)
-        assert api_wrapper(url, data) == error
-
-    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
-        data = "pytest_bad_datatype"
-        m.post(url, text=data, status_code=403)
-        assert api_wrapper(url, data) == error
-
-    with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
-        data = {"response": "incorrect", "errorCode": "0xdeadbeef"}
-        m.post(url, json=data, status_code=403)
-        assert api_wrapper("http://acme.org", data) == error
-
-
-def test_api_error_code_parser():
-    """Test whether message on specific status equal."""
-    from tokendito.okta import _status_dict
-    from tokendito.okta import api_error_code_parser
-
-    okta_status_dict = _status_dict
-
-    for key, value in okta_status_dict.items():
-        assert api_error_code_parser(key) == "Okta auth failed: " + value
-    unexpected_key = "UNEXPECTED_KEY"
-    value = f"Okta auth failed: {unexpected_key}. Please verify your settings and try again."
-    assert api_error_code_parser(unexpected_key) == value
-
-
 @pytest.mark.parametrize(
     "factor_type, output",
     [
@@ -794,28 +539,6 @@ def test_mfa_option_info(factor_type, output):
     assert mfa_option_info(mfa_option) == output
 
 
-@pytest.mark.parametrize(
-    "preset_mfa, output",
-    [("push", 0), (None, 1), ("0xdeadbeef", 1), ("opfrar9yi4bKJNH2WEW", 0), ("pytest_dupe", 1)],
-)
-def test_mfa_index(preset_mfa, output, mocker, sample_json_response):
-    """Test whether the function returns correct mfa index."""
-    from tokendito.okta import mfa_index
-
-    primary_auth = sample_json_response["okta_response_mfa"]
-
-    mfa_options = primary_auth["_embedded"]["factors"]
-    available_mfas = [f"{d['provider']}_{d['factorType']}_{d['id']}" for d in mfa_options]
-    mocker.patch("tokendito.user.select_preferred_mfa_index", return_value=1)
-
-    if preset_mfa == "pytest_dupe":
-        with pytest.raises(SystemExit) as err:
-            mfa_index(preset_mfa, available_mfas, mfa_options)
-        assert err.value.code == output
-    else:
-        assert mfa_index(preset_mfa, available_mfas, mfa_options) == output
-
-
 def test_select_preferred_mfa_index(mocker, sample_json_response):
     """Test whether the function returns index entered by user."""
     from tokendito.user import select_preferred_mfa_index
@@ -858,106 +581,6 @@ def test_select_preferred_mfa_index_output(email, capsys, mocker, sample_json_re
     assert captured.out == correct_output
 
 
-def test_mfa_options(sample_headers, sample_json_response, mocker):
-    """Test handling of MFA approval."""
-    from tokendito.config import Config
-    from tokendito.okta import totp_approval
-
-    selected_mfa_option = {"factorType": "push"}
-    primary_auth = sample_json_response["okta_response_no_mfa"]
-    payload = {"x": "y", "t": "z"}
-    mfa_challenge_url = "https://pytest"
-    pytest_config = Config(okta={"mfa_response": None})
-
-    # Test that selecting software token returns a session token
-    selected_mfa_option = {"factorType": "token:software:totp"}
-    primary_auth["stateToken"] = "pytest"
-    mfa_verify = {"sessionToken": "pytest"}
-    mocker.patch("tokendito.user.get_input", return_value="012345")
-    mocker.patch("tokendito.okta.api_wrapper", return_value=mfa_verify)
-    ret = totp_approval(
-        pytest_config, selected_mfa_option, sample_headers, mfa_challenge_url, payload, primary_auth
-    )
-    assert ret == mfa_verify
-
-
-def test_mfa_challenge_with_no_mfas(sample_headers, sample_json_response):
-    """Test whether okta response has mfas."""
-    from tokendito.config import Config
-    from tokendito.okta import mfa_challenge
-
-    primary_auth = sample_json_response["okta_response_no_auth_methods"]
-    pytest_config = Config()
-
-    with pytest.raises(SystemExit) as error:
-        assert mfa_challenge(pytest_config, sample_headers, primary_auth) == error
-
-
-@pytest.mark.parametrize(
-    "return_value,side_effect,expected",
-    [
-        ({"status": "SUCCESS", "sessionToken": "pytest"}, None, 0),
-        ({"status": "SUCCESS", "sessionToken": "pytest", "factorResult": "SUCCESS"}, None, 0),
-        ({"status": "MFA_CHALLENGE", "factorResult": "REJECTED"}, None, 2),
-        ({"status": "MFA_CHALLENGE", "factorResult": "TIMEOUT"}, None, 2),
-        ({"status": "UNKNOWN", "factorResult": "UNKNOWN"}, None, 2),
-        (
-            {
-                "status": "MFA_CHALLENGE",
-                "factorResult": "WAITING",
-                "_links": {"next": {"href": None}},
-            },
-            [
-                {
-                    "status": "MFA_CHALLENGE",
-                    "factorResult": "WAITING",
-                    "_links": {"next": {"href": None}},
-                },
-                {"status": "SUCCESS", "sessionToken": "pytest", "factorResult": "SUCCESS"},
-            ],
-            0,
-        ),
-        (
-            {
-                "status": "MFA_CHALLENGE",
-                "factorResult": "WAITING",
-                "_embedded": {"factor": {"_embedded": {"challenge": {"correctAnswer": 100}}}},
-                "_links": {"next": {"href": None}},
-            },
-            [
-                {
-                    "status": "MFA_CHALLENGE",
-                    "factorResult": "WAITING",
-                    "_embedded": {"factor": {"_embedded": {"challenge": {"correctAnswer": 100}}}},
-                    "_links": {"next": {"href": None}},
-                },
-                {"status": "SUCCESS", "sessionToken": "pytest", "factorResult": "SUCCESS"},
-            ],
-            0,
-        ),
-    ],
-)
-def test_push_approval(mocker, sample_headers, return_value, side_effect, expected):
-    """Test push approval."""
-    from tokendito import okta
-
-    challenge_url = "https://pytest/api/v1/authn/factors/factorid/verify"
-
-    mocker.patch("tokendito.okta.api_wrapper", return_value=return_value, side_effect=side_effect)
-    mocker.patch("time.sleep", return_value=0)
-
-    if "status" in return_value and return_value["status"] == "SUCCESS":
-        ret = okta.push_approval(sample_headers, challenge_url, None)
-        assert ret["status"] == "SUCCESS"
-    elif "factorResult" in return_value and return_value["factorResult"] == "WAITING":
-        ret = okta.push_approval(sample_headers, challenge_url, None)
-        assert ret["status"] == "SUCCESS"
-    else:
-        with pytest.raises(SystemExit) as err:
-            okta.push_approval(sample_headers, challenge_url, None)
-        assert err.value.code == expected
-
-
 @pytest.mark.parametrize(
     "selected_role",
     [
@@ -1017,97 +640,6 @@ def test_incorrect_role_arn():
         assert select_role_arn(authenticated_tiles) == error
 
 
-def test_prepare_duo_info():
-    """Test behaviour empty return duo info."""
-    from tokendito.config import config
-    from tokendito.duo import prepare_duo_info
-
-    selected_okta_factor = {
-        "_embedded": {
-            "factor": {
-                "_embedded": {
-                    "verification": {
-                        "_links": {
-                            "complete": {"href": "http://test.okta.href"},
-                            "script": {"href": "python-v3.7"},
-                        },
-                        "signature": "fdsafdsa:fdsfdfds:fdsfdsfds",
-                        "host": "test_host",
-                    }
-                },
-                "id": 1234,
-            }
-        },
-        "stateToken": 12345,
-    }
-    okta_factor = selected_okta_factor["_embedded"]["factor"]["_embedded"]["verification"]
-    expected_duo_info = {
-        "okta_factor": okta_factor,
-        "factor_id": 1234,
-        "state_token": 12345,
-        "okta_callback_url": "http://test.okta.href",
-        "tx": "fdsafdsa",
-        "tile_sig": "fdsfdfds",
-        "parent": f"{config.okta['org']}/signin/verify/duo/web",
-        "host": "test_host",
-        "sid": "",
-        "version": "3.7",
-    }
-    assert prepare_duo_info(selected_okta_factor) == expected_duo_info
-
-
-def test_get_duo_sid(mocker):
-    """Check if got sid correct."""
-    from tokendito.config import config
-    from tokendito.duo import get_duo_sid
-
-    test_duo_info = {
-        "okta_factor": "okta_factor",
-        "factor_id": 1234,
-        "state_token": 12345,
-        "okta_callback_url": "http://test.okta.href",
-        "tx": "fdsafdsa",
-        "tile_sig": "fdsfdfds",
-        "parent": f"{config.okta['org']}/signin/verify/duo/web",
-        "host": "test_host",
-        "sid": "",
-        "version": "3.7",
-    }
-
-    test_url = "http://test.token.dito?sid=testval"
-    duo_api_response = Mock()
-    duo_api_response.url = test_url
-
-    mocker.patch("tokendito.duo.duo_api_post", return_value=duo_api_response)
-
-    duo_sid_info, duo_auth_response = get_duo_sid(test_duo_info)
-
-    assert duo_sid_info["sid"] == "testval"
-    assert duo_auth_response.url == test_url
-
-
-@pytest.mark.parametrize("status_code", [(400), (401), (404), (500), (503)])
-def test_authenticate_to_roles(status_code, monkeypatch):
-    """Test if function return correct response."""
-    import requests
-    from tokendito.aws import authenticate_to_roles
-
-    mock_get = {"status_code": status_code, "text": "response"}
-    monkeypatch.setattr(requests, "get", mock_get)
-    with pytest.raises(SystemExit) as error:
-        assert authenticate_to_roles([("http://test.url.com", "")], "secret_session_token") == error
-
-
-def test_get_mfa_response():
-    """Test if mfa verify correctly."""
-    from tokendito.duo import get_mfa_response
-
-    mfa_result = Mock()
-    mfa_result.json = Mock(return_value={"response": "test_response"})
-
-    assert get_mfa_response(mfa_result) == "test_response"
-
-
 def test_loglevel_collected_from_env(monkeypatch):
     """Ensure that the loglevel collected from env vars."""
     from argparse import Namespace
@@ -1387,54 +919,23 @@ def test_sanitize_config_values():
     assert ret.okta["tile"].startswith(ret.okta["org"])
 
 
-def test_get_regions():
-    """Test retrieval of available AWS regions."""
-    from tokendito import aws
-
-    ret = aws.get_regions(profile="pytest")
-    assert ret == []
-    ret = aws.get_regions()
-    assert "us-east-1" in ret
-
-
-def test_get_output_types():
-    """Test getting AWS output types."""
-    from tokendito import aws
-
-    ret = aws.get_output_types()
-    assert "json" in ret
-
-
-@pytest.mark.parametrize(
-    "auth_properties,expected",
-    [
-        ({}, False),
-        (None, False),
-        ({"type": "OKTA"}, True),
-        ({"type": "SAML2"}, False),
-    ],
-)
-def test_is_local_auth(auth_properties, expected):
-    """Test local auth method."""
-    from tokendito import okta
-
-    assert okta.is_local_auth(auth_properties) == expected
-
-
 @pytest.mark.parametrize(
-    "auth_properties,expected",
+    "saml, expected",
     [
-        ({}, False),
-        (None, False),
-        ({"type": "OKTA"}, False),
-        ({"type": "SAML2"}, True),
+        ("pytest", {}),
+        ("pytest,pytest", {}),
+        (
+            'xsi:type="xs:string">arn:aws:iam::000000000000:saml/name,'
+            "arn:aws:iam::000000000000:role/name</saml2:AttributeValue>",
+            {"arn:aws:iam::000000000000:role/name": "arn:aws:iam::000000000000:saml/name"},
+        ),
     ],
 )
-def test_is_saml2_auth(auth_properties, expected):
-    """Test saml2 auth method."""
-    from tokendito import okta
+def test_extract_arns(saml, expected):
+    """Test extracting Provider/Role ARN pairs from a SAML document."""
+    from tokendito import user
 
-    assert okta.is_saml2_auth(auth_properties) == expected
+    assert user.extract_arns(saml) == expected
 
 
 def test_request_wrapper():
@@ -1454,317 +955,6 @@ def test_request_wrapper():
         assert request_wrapper("POST", url).json() == data
 
     with pytest.raises(SystemExit) as error, requests_mock.Mocker() as m:
-        m.post("invalid_url", json=data, status_code=200)
-        assert request_wrapper("GET", "invalid_url") == error
-
-
-@pytest.mark.parametrize(
-    "html, raw, expected",
-    [
-        (
-            '<html><body><input name="SAMLResponse" type="hidden" '
-            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
-            False,
-            '<?xml version="1.0" encoding="UTF-8"?>',
-        ),
-        (
-            '<html><body><input name="SAMLResponse" type="hidden" '
-            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
-            True,
-            "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
-        ),
-        (
-            "",
-            False,
-            None,
-        ),
-        (
-            "invalid html",
-            False,
-            None,
-        ),
-    ],
-)
-def test_extract_saml_response(html, raw, expected):
-    """Test extracting SAML response."""
-    from tokendito import okta
-
-    assert okta.extract_saml_response(html, raw) == expected
-
-
-@pytest.mark.parametrize(
-    "html, raw, expected",
-    [
-        (
-            '<html><body><input name="SAMLRequest" type="hidden" '
-            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
-            False,
-            '<?xml version="1.0" encoding="UTF-8"?>',
-        ),
-        (
-            '<html><body><input name="SAMLRequest" type="hidden" '
-            'value="PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4="></body></html>',
-            True,
-            "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
-        ),
-        (
-            "",
-            False,
-            None,
-        ),
-        (
-            "invalid html",
-            False,
-            None,
-        ),
-    ],
-)
-def test_extract_saml_request(html, raw, expected):
-    """Test extracting SAML response."""
-    from tokendito import okta
-
-    assert okta.extract_saml_request(html, raw) == expected
-
-
-@pytest.mark.parametrize(
-    "html,expected",
-    [
-        (
-            "<html><body><form action='https://acme.okta.com/app/okta_org2org/"
-            "akjlkjlksjx0xmdd/sso/saml' id='appForm' method='POST'</form></body></html>",
-            "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
-        ),
-        ("<html><body><form action='' id='appForm' method='POST'</form></body></html>", ""),
-        ("invalid html", None),
-    ],
-)
-def test_extract_form_post_url(html, expected):
-    """Test extracting form post URL."""
-    from tokendito import okta
-
-    assert okta.extract_form_post_url(html) == expected
-
-
-@pytest.mark.parametrize(
-    "html,expected",
-    [
-        (
-            "<html><body><input name='RelayState' type='hidden' value='foobar'></body></html>",
-            "foobar",
-        ),
-        ("<html><body><input name='RelayState' type='hidden' value=''></body></html>", ""),
-        ("invalid html", None),
-    ],
-)
-def test_extract_saml_relaystate(html, expected):
-    """Test extracting SAML relay state."""
-    from tokendito import okta
-
-    assert okta.extract_saml_relaystate(html) == expected
-
-
-def test_get_saml_request(mocker):
-    """Test getting SAML request."""
-    from tokendito import okta
-
-    request_wrapper_response = Mock()
-    auth_properties = {"id": "id", "metadata": "metadata"}
-    request_wrapper_response.text = (
-        "<html><body><form action='https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/"
-        "saml' id='appForm' method='POST'</form><input name='SAMLRequest' type='hidden' "
-        "value='PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4='>"
-        "<input name='RelayState' type='hidden' value='foobar'></body></html>"
-    )
-    mocker.patch("tokendito.user.request_wrapper", return_value=request_wrapper_response)
-
-    assert okta.get_saml_request(auth_properties) == {
-        "base_url": "https://acme.okta.com",
-        "post_url": "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
-        "relay_state": "foobar",
-        "request": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
-    }
-
-
-def test_send_saml_request(mocker):
-    """Test sending SAML request."""
-    from tokendito import okta
-
-    request_wrapper_response = Mock()
-    request_wrapper_response.text = (
-        "<html><body><form action='https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/"
-        "saml' id='appForm' method='POST'</form><input name='SAMLResponse' type='hidden' "
-        "value='PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4='>"
-        "<input name='RelayState' type='hidden' value='foobar'></body></html>"
-    )
-
-    saml_request = {"relay_state": "relay_state", "request": "request", "post_url": "post_url"}
-    cookie = {"sid": "pytestcookie"}
-
-    mocker.patch("tokendito.user.request_wrapper", return_value=request_wrapper_response)
-
-    assert okta.send_saml_request(saml_request, cookie) == {
-        "response": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4=",
-        "relay_state": "foobar",
-        "post_url": "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
-    }
-
-
-def test_send_saml_response(mocker):
-    """Test sending SAML response."""
-    from tokendito import okta
-
-    request_wrapper_response = Mock()
-    request_wrapper_response.cookies = {"sid": "pytestcookie"}
-
-    saml_response = {
-        "response": "pytestresponse",
-        "relay_state": "foobar",
-        "post_url": "https://acme.okta.com/app/okta_org2org/akjlkjlksjx0xmdd/sso/saml",
-    }
-
-    mocker.patch("tokendito.user.request_wrapper", return_value=request_wrapper_response)
-    assert okta.send_saml_response(saml_response) == request_wrapper_response.cookies
-
-
-def test_authenticate(mocker):
-    """Test authentication."""
-    from tokendito import okta
-    from tokendito.config import Config
-
-    pytest_config = Config(
-        okta={
-            "username": "pytest",
-            "password": "pytest",
-            "org": "https://acme.okta.org/",
-        }
-    )
-    sid = {"sid": "pytestsid"}
-    mocker.patch("tokendito.user.request_cookies", return_value=sid)
-    mocker.patch("tokendito.okta.local_auth", return_value="foobar")
-    mocker.patch("tokendito.okta.saml2_auth", return_value=sid)
-
-    mocker.patch("tokendito.okta.get_auth_properties", return_value={"type": "OKTA"})
-    assert okta.authenticate(pytest_config) == sid
-
-    mocker.patch("tokendito.okta.get_auth_properties", return_value={"type": "SAML2"})
-    assert okta.authenticate(pytest_config) == sid
-
-    mocker.patch("tokendito.okta.get_auth_properties", return_value={"type": "UNKNOWN"})
-    with pytest.raises(SystemExit) as error:
-        assert okta.authenticate(pytest_config) == error
-
-    mocker.patch("tokendito.okta.get_auth_properties", return_value={})
-    with pytest.raises(SystemExit) as error:
-        assert okta.authenticate(pytest_config) == error
-
-
-def test_local_auth(mocker):
-    """Test local auth method."""
-    from tokendito import okta
-    from tokendito.config import Config
-
-    pytest_config = Config(
-        okta={
-            "username": "pytest",
-            "password": "pytest",
-            "org": "https://acme.okta.org/",
-        }
-    )
-    api_wrapper_response = {"status": "SUCCESS", "sessionToken": "pytesttoken"}
-
-    mocker.patch("tokendito.okta.api_wrapper", return_value=api_wrapper_response)
-    mocker.patch("tokendito.okta.get_session_token", return_value="pytesttoken")
-    assert okta.local_auth(pytest_config) == "pytesttoken"
-
-
-def test_saml2_auth(mocker):
-    """Test saml2 authentication."""
-    from tokendito import okta
-    from tokendito.config import Config
-
-    auth_properties = {"id": "id", "metadata": "metadata"}
-
-    pytest_config = Config(
-        okta={
-            "username": "pytest",
-            "password": "pytest",
-            "org": "https://acme.okta.org/",
-        }
-    )
-    saml_request = {
-        "base_url": "https://acme.okta.com",
-    }
-    mocker.patch("tokendito.okta.get_saml_request", return_value=saml_request)
-    mocker.patch("tokendito.okta.authenticate", return_value="pytestsessioncookie")
-
-    saml_response = {
-        "response": "pytestresponse",
-    }
-
-    mocker.patch("tokendito.okta.send_saml_request", return_value=saml_response)
-    mocker.patch("tokendito.okta.send_saml_response", return_value="pytestsessionid")
-    assert okta.saml2_auth(pytest_config, auth_properties) == "pytestsessionid"
-
-
-def test_assume_role(mocker):
-    """Test assuming role."""
-    from tokendito import aws
-    from tokendito.config import Config
-
-    pytest_config = Config(
-        okta={
-            "username": "pytest",
-            "password": "pytest",
-            "org": "https://acme.okta.org/",
-        }
-    )
-    role_arn = "0000000000000000000000000:role/testrole"
-    session_name = "pytestsession"
-    assumed_role_object = {
-        "Credentials": {
-            "AccessKeyId": "pytestaccesskey",
-            "SecretAccessKey": "pytestsecretkey",
-            "SessionToken": "pytestsessiontoken",
-        }
-    }
-    with mocker.patch("tokendito.aws.handle_assume_role", return_value=assumed_role_object):
-        assert aws.assume_role(pytest_config, role_arn, session_name) == assumed_role_object
-    with mocker.patch("tokendito.aws.handle_assume_role", return_value={}):
-        with pytest.raises(SystemExit) as error:
-            assert aws.assume_role(pytest_config, role_arn, session_name) == error
-
-
-@pytest.mark.parametrize(
-    "saml, expected",
-    [
-        ("pytest", {}),
-        ("pytest,pytest", {}),
-        (
-            'xsi:type="xs:string">arn:aws:iam::000000000000:saml/name,'
-            "arn:aws:iam::000000000000:role/name</saml2:AttributeValue>",
-            {"arn:aws:iam::000000000000:role/name": "arn:aws:iam::000000000000:saml/name"},
-        ),
-    ],
-)
-def test_extract_arns(saml, expected):
-    """Test extracting Provider/Role ARN pairs from a SAML document."""
-    from tokendito import user
-
-    assert user.extract_arns(saml) == expected
-
-
-def test_select_assumeable_role_no_tiles():
-    """Test exiting when there are no assumable roles."""
-    from tokendito import aws
-
-    tiles = [
-        (
-            "https://acme.okta.org/home/amazon_aws/0123456789abcdef0123/456",
-            "saml_response",
-            "arn:aws:iam::000000000000:saml/name,arn:aws:iam::000000000000:role/name",
-            "Tile Label",
-        )
-    ]
-    with pytest.raises(SystemExit) as err:
-        aws.select_assumeable_role(tiles)
-    assert err.value.code == 1
+        m.get(url, json=data, status_code=500)
+        request_wrapper("GET", url)
+    assert error.value.code == 1