pyntcli 0.1.93__py3-none-any.whl → 0.1.94__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyntcli/__init__.py +20 -1
- pyntcli/commands/pynt_cmd.py +103 -8
- pyntcli/commands/root.py +13 -3
- pyntcli/store/__init__.py +1 -1
- pyntcli/store/store.py +25 -4
- pyntcli/ui/prompt.py +53 -0
- {pyntcli-0.1.93.dist-info → pyntcli-0.1.94.dist-info}/METADATA +1 -1
- {pyntcli-0.1.93.dist-info → pyntcli-0.1.94.dist-info}/RECORD +12 -10
- {pyntcli-0.1.93.dist-info → pyntcli-0.1.94.dist-info}/WHEEL +1 -1
- tests/commands/test_pynt_cmd.py +178 -0
- {pyntcli-0.1.93.dist-info → pyntcli-0.1.94.dist-info}/entry_points.txt +0 -0
- {pyntcli-0.1.93.dist-info → pyntcli-0.1.94.dist-info}/top_level.txt +0 -0
pyntcli/__init__.py
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.94"
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from logzio import sender
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_silent_logger():
|
|
9
|
+
logger = logging.getLogger("silent_logger")
|
|
10
|
+
logger.addHandler(logging.NullHandler())
|
|
11
|
+
return logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def patched_get_stdout_logger(debug):
|
|
15
|
+
return get_silent_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
sender.get_stdout_logger = patched_get_stdout_logger
|
|
19
|
+
except Exception as e:
|
|
20
|
+
pass
|
pyntcli/commands/pynt_cmd.py
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import requests
|
|
2
3
|
from typing import Dict, List
|
|
4
|
+
from datetime import datetime, timedelta
|
|
3
5
|
from pyntcli import __version__ as cli_version
|
|
4
6
|
from pyntcli.analytics import send as analytics
|
|
5
7
|
from pyntcli.transport import pynt_requests
|
|
6
8
|
from pyntcli.ui import ui_thread
|
|
9
|
+
from pyntcli.ui import prompt
|
|
10
|
+
from pyntcli.store import CredStore, StateStore
|
|
11
|
+
import pyntcli.log.log as log
|
|
7
12
|
|
|
8
13
|
from requests.exceptions import SSLError, HTTPError
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
from . import command, listen, postman, root, sub_command, id_command, newman, har, burp
|
|
12
17
|
|
|
18
|
+
logger = log.get_logger()
|
|
19
|
+
|
|
13
20
|
avail_sub_commands = [
|
|
14
21
|
postman.PostmanSubCommand("postman"),
|
|
15
22
|
id_command.PyntShowIdCommand("pynt-id"),
|
|
@@ -20,11 +27,17 @@ avail_sub_commands = [
|
|
|
20
27
|
burp.BurpCommand("burp"),
|
|
21
28
|
]
|
|
22
29
|
|
|
30
|
+
commands_without_app_id = ["postman", "pynt-id"]
|
|
31
|
+
|
|
23
32
|
|
|
24
33
|
class PyntCommandException(Exception):
|
|
25
34
|
pass
|
|
26
35
|
|
|
27
36
|
|
|
37
|
+
class UserAbortedException(Exception):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
28
41
|
class BadArgumentsException(PyntCommandException):
|
|
29
42
|
pass
|
|
30
43
|
|
|
@@ -48,18 +61,22 @@ def check_is_latest_version(current_version):
|
|
|
48
61
|
ui_thread.print(ui_thread.PrinterText("""Pynt CLI new version is available, upgrade now with:
|
|
49
62
|
python3 -m pip install --upgrade pyntcli""", ui_thread.PrinterText.WARNING))
|
|
50
63
|
except SSLError:
|
|
51
|
-
ui_thread.print(ui_thread.PrinterText(
|
|
64
|
+
ui_thread.print(ui_thread.PrinterText(
|
|
65
|
+
"""Error: Unable to check if Pynt CLI version is up-to-date due to VPN/proxy. Run Pynt with --insecure to fix.""", ui_thread.PrinterText.WARNING))
|
|
52
66
|
except HTTPError:
|
|
53
|
-
ui_thread.print(
|
|
67
|
+
ui_thread.print(
|
|
68
|
+
"""Unable to check if Pynt CLI version is up-to-date""")
|
|
54
69
|
except Exception as e:
|
|
55
|
-
ui_thread.print(ui_thread.PrinterText(
|
|
70
|
+
ui_thread.print(ui_thread.PrinterText(
|
|
71
|
+
"""We could not check for updates.""", ui_thread.PrinterText.WARNING))
|
|
56
72
|
pass
|
|
57
73
|
|
|
58
74
|
|
|
59
75
|
class PyntCommand:
|
|
60
76
|
def __init__(self) -> None:
|
|
61
77
|
self.base: root.BaseCommand = root.BaseCommand()
|
|
62
|
-
self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {
|
|
78
|
+
self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {
|
|
79
|
+
sc.get_name(): sc for sc in avail_sub_commands}
|
|
63
80
|
self._start_command()
|
|
64
81
|
|
|
65
82
|
def _start_command(self):
|
|
@@ -77,15 +94,93 @@ class PyntCommand:
|
|
|
77
94
|
command = getattr(args, "command")
|
|
78
95
|
if not command in self.sub_commands:
|
|
79
96
|
raise NoSuchCommandException()
|
|
80
|
-
|
|
97
|
+
check_is_latest_version(cli_version)
|
|
98
|
+
analytics.emit(analytics.CLI_START)
|
|
81
99
|
if "host_ca" in args and args.host_ca:
|
|
82
100
|
pynt_requests.add_host_ca(args.host_ca)
|
|
83
101
|
|
|
84
102
|
if "insecure" in args and args.insecure:
|
|
85
103
|
pynt_requests.disable_tls_termination()
|
|
86
104
|
|
|
87
|
-
check_is_latest_version(cli_version)
|
|
88
|
-
analytics.emit(analytics.CLI_START)
|
|
89
|
-
|
|
90
105
|
self.base.run_cmd(args)
|
|
106
|
+
|
|
107
|
+
# Some args are recommended/required for business-plan users.
|
|
108
|
+
# These can be verified only after running 'self.base.run_cmd()' where the login occurs
|
|
109
|
+
is_business_plan_user = self._is_business_plan_user()
|
|
110
|
+
try:
|
|
111
|
+
self._post_login_args_validation(
|
|
112
|
+
args, command, is_business_plan_user)
|
|
113
|
+
except UserAbortedException as e:
|
|
114
|
+
ui_thread.print("Aborting...")
|
|
115
|
+
return
|
|
116
|
+
|
|
91
117
|
self.sub_commands[command].run_cmd(args)
|
|
118
|
+
|
|
119
|
+
def _is_business_plan_user(self):
|
|
120
|
+
"""
|
|
121
|
+
A workaround for checking whether it's a free tier user or not
|
|
122
|
+
by try accessing api.pynt.io/v1/config (it's a free tier if the user can't access it)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool: True if it's a business-plan user
|
|
126
|
+
"""
|
|
127
|
+
saas_url = "https://api.pynt.io/v1/config"
|
|
128
|
+
headers = {}
|
|
129
|
+
with CredStore() as store:
|
|
130
|
+
access_keys = store.get_access_token()
|
|
131
|
+
token_type = store.get_token_type()
|
|
132
|
+
headers["Authorization"] = f"{token_type} {access_keys}"
|
|
133
|
+
try:
|
|
134
|
+
response = requests.get(saas_url, headers=headers, timeout=10)
|
|
135
|
+
|
|
136
|
+
if response.status_code == requests.codes.ok:
|
|
137
|
+
return True
|
|
138
|
+
else:
|
|
139
|
+
return False
|
|
140
|
+
except requests.exceptions.RequestException as e:
|
|
141
|
+
# Catch all other request exceptions
|
|
142
|
+
logger.debug(f"An error occurred when accessing '{saas_url}': {e}")
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
def _post_login_args_validation(self, args: argparse.Namespace, command: str, is_business_plan_user: bool):
|
|
146
|
+
# Confirm not using application-id flag if applicable
|
|
147
|
+
if getattr(args, "application_id") or command in commands_without_app_id:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# For a business user, it's recommended to always provide the application-id.
|
|
151
|
+
if is_business_plan_user:
|
|
152
|
+
if getattr(args, "yes") or self._is_missing_app_id_confirmed():
|
|
153
|
+
return
|
|
154
|
+
else:
|
|
155
|
+
raise UserAbortedException()
|
|
156
|
+
|
|
157
|
+
def _is_missing_app_id_confirmed(self) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
Ask for the user's confirmation to continue if he/she wasn't asked in the last week.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
bool: True if the user confirms or he/she has confirmed once in the last week. Otherwise, returns False
|
|
163
|
+
"""
|
|
164
|
+
with StateStore() as state_store:
|
|
165
|
+
current_time = datetime.now()
|
|
166
|
+
prompts_history = state_store.get_prompts_history()
|
|
167
|
+
last_confirmed = prompts_history.get(
|
|
168
|
+
"missing_app_id", {}).get("last_confirmation", "")
|
|
169
|
+
if last_confirmed:
|
|
170
|
+
# Calculate the time delta
|
|
171
|
+
parsed_datetime = datetime.strptime(
|
|
172
|
+
last_confirmed, "%Y-%m-%d %H:%M:%S")
|
|
173
|
+
difference = current_time - parsed_datetime
|
|
174
|
+
if difference < timedelta(days=7):
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
if prompt.confirmation_prompt_with_timeout("Application ID is missing. Use the '--application-id' flag to provide it.\n" +
|
|
178
|
+
"Without an Application ID, the scan will not be associated with your application.\n" +
|
|
179
|
+
"The Application ID can be fetched from https://app.pynt.io/dashboard/applications.\n" +
|
|
180
|
+
"Do you want to continue without associating the scan?", default="yes", timeout=15):
|
|
181
|
+
prompts_history["missing_app_id"] = {"last_confirmation": current_time.strftime("%Y-%m-%d %H:%M:%S")}
|
|
182
|
+
state_store.put_prompts_history(
|
|
183
|
+
prompts_history)
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
return False
|
pyntcli/commands/root.py
CHANGED
|
@@ -63,17 +63,27 @@ class BaseCommand:
|
|
|
63
63
|
action="store_true",
|
|
64
64
|
help="use when target uses self signed certificates",
|
|
65
65
|
)
|
|
66
|
-
parser.add_argument("--dev-flags", type=str,
|
|
66
|
+
parser.add_argument("--dev-flags", type=str,
|
|
67
|
+
default="", help=argparse.SUPPRESS)
|
|
67
68
|
parser.add_argument("--host-ca", type=str, default="")
|
|
68
69
|
parser.add_argument("--transport-config", type=str, default="")
|
|
69
|
-
parser.add_argument("--application-id", type=str,
|
|
70
|
+
parser.add_argument("--application-id", type=str,
|
|
71
|
+
default="", required=False)
|
|
70
72
|
parser.add_argument("--proxy", type=str, default="", required=False)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--yes",
|
|
75
|
+
"-y",
|
|
76
|
+
default=False,
|
|
77
|
+
required=False,
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Automatically answer yes to all prompts and run non-interactively ",
|
|
80
|
+
)
|
|
71
81
|
parser.add_argument(
|
|
72
82
|
"--verbose",
|
|
73
83
|
default=False,
|
|
74
84
|
required=False,
|
|
75
85
|
action="store_true",
|
|
76
|
-
help="
|
|
86
|
+
help="Use to get more detailed execution information",
|
|
77
87
|
)
|
|
78
88
|
parser.add_argument(
|
|
79
89
|
"--use-docker-native",
|
pyntcli/store/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
from .store import CredStore
|
|
1
|
+
from .store import CredStore, StateStore
|
pyntcli/store/store.py
CHANGED
|
@@ -22,7 +22,8 @@ class Store():
|
|
|
22
22
|
|
|
23
23
|
if not os.path.exists(self.file_location):
|
|
24
24
|
with open(self.file_location, "w") as f:
|
|
25
|
-
self.connector = self._connector_type(
|
|
25
|
+
self.connector = self._connector_type(
|
|
26
|
+
self._connector_type.default_value())
|
|
26
27
|
return
|
|
27
28
|
|
|
28
29
|
with open(self.file_location, "r+") as f:
|
|
@@ -39,6 +40,9 @@ class Store():
|
|
|
39
40
|
def get_path(self):
|
|
40
41
|
return self.file_location
|
|
41
42
|
|
|
43
|
+
def _get_default_store_dir(self):
|
|
44
|
+
return os.path.join(os.path.expanduser("~"), ".pynt")
|
|
45
|
+
|
|
42
46
|
def __enter__(self):
|
|
43
47
|
self._get_file_data()
|
|
44
48
|
return self
|
|
@@ -50,14 +54,31 @@ class Store():
|
|
|
50
54
|
|
|
51
55
|
class CredStore(Store):
|
|
52
56
|
def __init__(self) -> None:
|
|
53
|
-
|
|
54
|
-
super().__init__(file_location=os.path.join(
|
|
57
|
+
pynt_dir = super()._get_default_store_dir()
|
|
58
|
+
super().__init__(file_location=os.path.join(pynt_dir, "creds.json"),
|
|
55
59
|
connector_type=JsonStoreConnector)
|
|
56
60
|
|
|
57
61
|
def get_access_token(self):
|
|
58
62
|
return self.get("token")["access_token"]
|
|
59
63
|
|
|
64
|
+
def get_token_type(self):
|
|
65
|
+
return self.get("token")["token_type"]
|
|
66
|
+
|
|
60
67
|
def get_tokens(self):
|
|
61
68
|
all_tokens = self.get("token")
|
|
62
|
-
token_to_json_string = '{"token":' +
|
|
69
|
+
token_to_json_string = '{"token":' + \
|
|
70
|
+
str(all_tokens).replace("\'", "\"") + "}"
|
|
63
71
|
return token_to_json_string
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class StateStore(Store):
|
|
75
|
+
def __init__(self) -> None:
|
|
76
|
+
pynt_dir = super()._get_default_store_dir()
|
|
77
|
+
super().__init__(file_location=os.path.join(pynt_dir, "state.json"),
|
|
78
|
+
connector_type=JsonStoreConnector)
|
|
79
|
+
|
|
80
|
+
def get_prompts_history(self):
|
|
81
|
+
return self.get("prompts_history") or {}
|
|
82
|
+
|
|
83
|
+
def put_prompts_history(self, value):
|
|
84
|
+
return self.put("prompts_history", value)
|
pyntcli/ui/prompt.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import signal
|
|
2
|
+
from pyntcli.ui import ui_thread
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TimeoutExpired(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _timeout_handler(signum, frame):
|
|
10
|
+
raise TimeoutExpired
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def confirmation_prompt_with_timeout(question, default, timeout=10):
|
|
14
|
+
"""Prompt the user with a confirmation question (Yes/No) and return True for 'yes' or False for 'no'.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
question (str): The question to present to the user.
|
|
18
|
+
default (str): The default answer if the user just presses Enter. It should be 'yes' or 'no'.
|
|
19
|
+
timeout (int): The timeout in seconds. If the user doesn't respond within the timeout, the default answer will be selected.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
bool: True if the answer is 'yes', False if the answer is 'no'.
|
|
23
|
+
"""
|
|
24
|
+
valid = {"yes": True, "y": True, "no": False, "n": False}
|
|
25
|
+
|
|
26
|
+
if default is None:
|
|
27
|
+
prompt = " [y/n] "
|
|
28
|
+
elif default == "yes":
|
|
29
|
+
prompt = " [Y/n] "
|
|
30
|
+
elif default == "no":
|
|
31
|
+
prompt = " [y/N] "
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError(f"Invalid default answer: '{default}'")
|
|
34
|
+
|
|
35
|
+
signal.signal(signal.SIGALRM, _timeout_handler)
|
|
36
|
+
signal.alarm(timeout)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
while True:
|
|
40
|
+
choice = input(question + prompt).strip().lower()
|
|
41
|
+
|
|
42
|
+
if choice == "" and default: # Only 'Enter' with default will continue
|
|
43
|
+
return valid[default]
|
|
44
|
+
elif choice in valid:
|
|
45
|
+
return valid[choice]
|
|
46
|
+
else:
|
|
47
|
+
ui_thread.print("Please respond with 'yes' or 'no' (or 'y' or 'n').")
|
|
48
|
+
except TimeoutExpired:
|
|
49
|
+
ui_thread.print(ui_thread.PrinterText(f"\nNo response within {timeout} seconds, defaulting to {'Yes' if default == 'yes' else 'No'}.\n" +
|
|
50
|
+
"You can use '--yes' flag for auto-confirm", ui_thread.PrinterText.WARNING))
|
|
51
|
+
return valid[default]
|
|
52
|
+
finally:
|
|
53
|
+
signal.alarm(0)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
ignoreTests/conftest.py,sha256=gToq5K74GtgeGQXjFvXSzMaE6axBYxAzcFG5XJPOXjI,427
|
|
2
2
|
ignoreTests/auth/login.py,sha256=KFlzWhXBAuwdi7GXf16gCB3ya94LQG2wjcSChE149rQ,3798
|
|
3
3
|
ignoreTests/store/cred_store.py,sha256=_7-917EtNC9eKEumO2_lt-7KuDmCwOZFaowCm7DbA_A,254
|
|
4
|
-
pyntcli/__init__.py,sha256=
|
|
4
|
+
pyntcli/__init__.py,sha256=5NPBuxeQo_sfTOMuw7XfyXg8HYg3BMvDd69Cm_HznpU,402
|
|
5
5
|
pyntcli/main.py,sha256=HORWY5AEK5xmEQybUZBW9ZEDIga8vSnqDfmAR3TY9F0,6502
|
|
6
6
|
pyntcli/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
pyntcli/analytics/send.py,sha256=9TRAEoPbv4rWOZfcNaGanrRJAFvNs39P-uKSl49GcQE,3679
|
|
@@ -15,8 +15,8 @@ pyntcli/commands/id_command.py,sha256=UBEgMIpm4vauTCsKyixltiGUolNg_OfHEJvJ_i5BpJ
|
|
|
15
15
|
pyntcli/commands/listen.py,sha256=b_ES6VRmPFkIkCCQ6MuP6KOeUlf5vT1e7AafnDqJVak,9063
|
|
16
16
|
pyntcli/commands/newman.py,sha256=o3kobgUFoC3jw2XmbYEsQNdYpGO5suFA92lxHw71dzw,5301
|
|
17
17
|
pyntcli/commands/postman.py,sha256=VNARuyCDaDYlrzRMmJk4oM4YTCExOQUDziOS8NpU-KA,5256
|
|
18
|
-
pyntcli/commands/pynt_cmd.py,sha256=
|
|
19
|
-
pyntcli/commands/root.py,sha256
|
|
18
|
+
pyntcli/commands/pynt_cmd.py,sha256=T7jee0yw67Zth3lTkSDInCLnbu_IhpNqb7GqnTiTceQ,7012
|
|
19
|
+
pyntcli/commands/root.py,sha256=4yeGlTeL3Xu2xlSbMYhhaDwUnP29sF2Y2sxq8E1P3lo,3986
|
|
20
20
|
pyntcli/commands/static_file_extensions.py,sha256=PZJb02BI-64tbU-j3rdCNsXzTh7gkIDGxGKbKNw3h5k,1995
|
|
21
21
|
pyntcli/commands/sub_command.py,sha256=GF3-rE_qk2L4jGPFqHLm9SdGINmu3EakhjJTFyWjRms,374
|
|
22
22
|
pyntcli/commands/util.py,sha256=csZHQ2Xbdh-_KX-yIVrnaeNsT0NbuS-ej6kND3CxD_w,4414
|
|
@@ -25,20 +25,22 @@ pyntcli/log/log.py,sha256=cWCdWmUaAwePwdhYDcgNMEG9d9RM34sGahxBCYEdv2Y,1069
|
|
|
25
25
|
pyntcli/pynt_docker/__init__.py,sha256=PQIOVxc7XXtMLfEX7ojgwf_Z3mmTllO3ZvzUZTPOxQY,30
|
|
26
26
|
pyntcli/pynt_docker/container_utils.py,sha256=_Onn7loInzyJAG2-Uk6CGpsuRyelmUFHOvtJj4Uzi9A,175
|
|
27
27
|
pyntcli/pynt_docker/pynt_container.py,sha256=R2iq4Gbbkxg3OEJfJwUp-vroHf9-pJ6HWFqvWucPsGA,18965
|
|
28
|
-
pyntcli/store/__init__.py,sha256=
|
|
28
|
+
pyntcli/store/__init__.py,sha256=1fP8cEAQCF_myja3gnhHH9FEqtBiOJ-2aBmUXSKBdFA,41
|
|
29
29
|
pyntcli/store/json_connector.py,sha256=UGs3uORw3iyn0YJ8kzab-veEZToA6d-ByXYuqEleWsA,560
|
|
30
|
-
pyntcli/store/store.py,sha256=
|
|
30
|
+
pyntcli/store/store.py,sha256=ZLSe0WAjHDp8cSt4BBFDkPGRux4cgOo5UfF7V4naM7U,2559
|
|
31
31
|
pyntcli/store/store_connector.py,sha256=w4LzcpRZesUZL1f63RmLlWEFRtJ6Y6rcS6PkkGtO4MA,357
|
|
32
32
|
pyntcli/transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
pyntcli/transport/pynt_requests.py,sha256=C7OPvcKkRTcxSYuyiWKE59KgA9sRX0d6fm1wnopAmPo,1719
|
|
34
34
|
pyntcli/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
pyntcli/ui/progress.py,sha256=RrnO_jJNunoyupylakmWmHOEPw3lh99OHpKBzL6OBiE,1008
|
|
36
|
+
pyntcli/ui/prompt.py,sha256=6URUWCGpKUPhluWTYVaXvCQuABjiFfJSE3rfRJMvAS0,1838
|
|
36
37
|
pyntcli/ui/pynt_errors.py,sha256=00UprD4tFViREv7kuXGQ99PAKGTpXYixxi3Ndeoeiew,689
|
|
37
38
|
pyntcli/ui/report.py,sha256=W-icPSZrGLOubXgam0LpOvHLl_aZg9Zx9qIkL8Ym5PE,1930
|
|
38
39
|
pyntcli/ui/ui_thread.py,sha256=XUBgLpYQjVhrilU-ofw7VSXgTiwneSdTxm61EvC3x4Q,5091
|
|
39
40
|
tests/test_utils.py,sha256=t5fTQUk1U_Js6iMxcGYGqt4C-crzOJ0CqCKtLkRtUi0,2050
|
|
40
|
-
|
|
41
|
-
pyntcli-0.1.
|
|
42
|
-
pyntcli-0.1.
|
|
43
|
-
pyntcli-0.1.
|
|
44
|
-
pyntcli-0.1.
|
|
41
|
+
tests/commands/test_pynt_cmd.py,sha256=BjGFCFACcSziLrNA6_27t6TjSmvdu54wx9njwLpRSJY,8379
|
|
42
|
+
pyntcli-0.1.94.dist-info/METADATA,sha256=m__wWpEYNkwzQMqrCy14feFm2kT8jxPRamGVZSk1cJ4,460
|
|
43
|
+
pyntcli-0.1.94.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
44
|
+
pyntcli-0.1.94.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
|
|
45
|
+
pyntcli-0.1.94.dist-info/top_level.txt,sha256=64XSgBzSpgwjYjEKHZE7q3JH2a816zEeyZBXfJi3AKI,42
|
|
46
|
+
pyntcli-0.1.94.dist-info/RECORD,,
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import argparse
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from pyntcli.commands.pynt_cmd import PyntCommand, UserAbortedException
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestIsBusinessPlanUser(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
@patch('pyntcli.commands.pynt_cmd.requests.get')
|
|
12
|
+
@patch('pyntcli.commands.pynt_cmd.CredStore')
|
|
13
|
+
def test_is_business_plan_user_success(self, mock_cred_store, mock_requests_get):
|
|
14
|
+
# Mock the CredStore context manager
|
|
15
|
+
mock_store_instance = MagicMock()
|
|
16
|
+
mock_store_instance.get_access_token.return_value = 'fake_token'
|
|
17
|
+
mock_store_instance.get_token_type.return_value = 'Bearer'
|
|
18
|
+
mock_cred_store.return_value.__enter__.return_value = mock_store_instance
|
|
19
|
+
|
|
20
|
+
# Mock the requests.get response
|
|
21
|
+
mock_response = MagicMock()
|
|
22
|
+
mock_response.status_code = 200
|
|
23
|
+
mock_requests_get.return_value = mock_response
|
|
24
|
+
|
|
25
|
+
# Instantiate the class and call the method
|
|
26
|
+
instance = PyntCommand()
|
|
27
|
+
result = instance._is_business_plan_user()
|
|
28
|
+
|
|
29
|
+
# Assert the result
|
|
30
|
+
self.assertTrue(result)
|
|
31
|
+
|
|
32
|
+
@patch('pyntcli.commands.pynt_cmd.requests.get')
|
|
33
|
+
@patch('pyntcli.commands.pynt_cmd.CredStore')
|
|
34
|
+
def test_is_business_plan_user_failure(self, mock_cred_store, mock_requests_get):
|
|
35
|
+
# Mock the CredStore context manager
|
|
36
|
+
mock_store_instance = MagicMock()
|
|
37
|
+
mock_store_instance.get_access_token.return_value = 'fake_token'
|
|
38
|
+
mock_store_instance.get_token_type.return_value = 'Bearer'
|
|
39
|
+
mock_cred_store.return_value.__enter__.return_value = mock_store_instance
|
|
40
|
+
|
|
41
|
+
# Mock the requests.get response
|
|
42
|
+
mock_response = MagicMock()
|
|
43
|
+
mock_response.status_code = 403
|
|
44
|
+
mock_requests_get.return_value = mock_response
|
|
45
|
+
|
|
46
|
+
# Instantiate the class and call the method
|
|
47
|
+
instance = PyntCommand()
|
|
48
|
+
result = instance._is_business_plan_user()
|
|
49
|
+
|
|
50
|
+
# Assert the result
|
|
51
|
+
self.assertFalse(result)
|
|
52
|
+
|
|
53
|
+
@patch('pyntcli.commands.pynt_cmd.requests.get')
|
|
54
|
+
@patch('pyntcli.commands.pynt_cmd.CredStore')
|
|
55
|
+
def test_is_business_plan_user_exception(self, mock_cred_store, mock_requests_get):
|
|
56
|
+
# Mock the CredStore context manager
|
|
57
|
+
mock_store_instance = MagicMock()
|
|
58
|
+
mock_store_instance.get_access_token.return_value = 'fake_token'
|
|
59
|
+
mock_store_instance.get_token_type.return_value = 'Bearer'
|
|
60
|
+
mock_cred_store.return_value.__enter__.return_value = mock_store_instance
|
|
61
|
+
|
|
62
|
+
# Mock the requests.get to raise an exception
|
|
63
|
+
mock_requests_get.side_effect = requests.exceptions.RequestException
|
|
64
|
+
|
|
65
|
+
# Instantiate the class and call the method
|
|
66
|
+
instance = PyntCommand()
|
|
67
|
+
result = instance._is_business_plan_user()
|
|
68
|
+
|
|
69
|
+
# Assert the result
|
|
70
|
+
self.assertFalse(result)
|
|
71
|
+
|
|
72
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=True)
|
|
73
|
+
def test_post_login_args_validation_missing_app_id_user_confirmed(self, _):
|
|
74
|
+
# Instantiate the class and call the method with valid arguments
|
|
75
|
+
instance = PyntCommand()
|
|
76
|
+
try:
|
|
77
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
78
|
+
application_id="", yes=False), "non-postman-command", True)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
81
|
+
|
|
82
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
83
|
+
def test_post_login_args_validation_missing_app_id_yes_flag_used(self, _):
|
|
84
|
+
# Instantiate the class and call the method with valid arguments
|
|
85
|
+
instance = PyntCommand()
|
|
86
|
+
try:
|
|
87
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
88
|
+
application_id="", yes=True), "non-postman-command", True)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
91
|
+
|
|
92
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
93
|
+
def test_post_login_args_validation_missing_app_id_user_aborted(self, _):
|
|
94
|
+
# Instantiate the class and call the method with valid arguments
|
|
95
|
+
instance = PyntCommand()
|
|
96
|
+
with self.assertRaises(UserAbortedException):
|
|
97
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
98
|
+
application_id="", yes=False), "non-postman-command", True)
|
|
99
|
+
|
|
100
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
101
|
+
def test_post_login_args_validation_missing_app_id_free_tier(self, _):
|
|
102
|
+
# Instantiate the class and call the method with valid arguments
|
|
103
|
+
instance = PyntCommand()
|
|
104
|
+
try:
|
|
105
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
106
|
+
application_id="", yes=False), "non-postman-command", False)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
109
|
+
|
|
110
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
111
|
+
def test_post_login_args_validation_missing_app_id_not_required(self, _):
|
|
112
|
+
# Instantiate the class and call the method with valid arguments
|
|
113
|
+
instance = PyntCommand()
|
|
114
|
+
try:
|
|
115
|
+
result = instance._post_login_args_validation(
|
|
116
|
+
argparse.Namespace(application_id="", yes=False), "postman", True)
|
|
117
|
+
result = instance._post_login_args_validation(
|
|
118
|
+
argparse.Namespace(application_id="", yes=False), "pynt-id", True)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
121
|
+
|
|
122
|
+
@patch('pyntcli.commands.pynt_cmd.prompt.confirmation_prompt_with_timeout', return_value=True)
|
|
123
|
+
@patch('pyntcli.commands.pynt_cmd.StateStore')
|
|
124
|
+
def test_missing_app_id_confirmation_confirmation_expired_user_confirms(self, mock_state_store, _):
|
|
125
|
+
long_time_ago = datetime.now() - timedelta(days=100)
|
|
126
|
+
# Mock the CredStore context manager
|
|
127
|
+
mock_store_instance = MagicMock()
|
|
128
|
+
mock_store_instance.get_prompts_history.return_value = {"missing_app_id": {
|
|
129
|
+
"last_confirmation": long_time_ago.strftime("%Y-%m-%d %H:%M:%S")}}
|
|
130
|
+
mock_store_instance.put_prompts_history.return_value = None
|
|
131
|
+
mock_state_store.return_value.__enter__.return_value = mock_store_instance
|
|
132
|
+
|
|
133
|
+
# Instantiate the class and call the method with app_id present
|
|
134
|
+
instance = PyntCommand()
|
|
135
|
+
result = instance._is_missing_app_id_confirmed()
|
|
136
|
+
|
|
137
|
+
# Assert the result
|
|
138
|
+
self.assertTrue(result)
|
|
139
|
+
|
|
140
|
+
@patch('pyntcli.commands.pynt_cmd.prompt.confirmation_prompt_with_timeout', return_value=False)
|
|
141
|
+
@patch('pyntcli.commands.pynt_cmd.StateStore')
|
|
142
|
+
def test_missing_app_id_confirmation_confirmation_expired_user_aborts(self, mock_state_store, _):
|
|
143
|
+
long_time_ago = datetime.now() - timedelta(days=100)
|
|
144
|
+
# Mock the CredStore context manager
|
|
145
|
+
mock_store_instance = MagicMock()
|
|
146
|
+
mock_store_instance.get_prompts_history.return_value = {"missing_app_id": {
|
|
147
|
+
"last_confirmation": long_time_ago.strftime("%Y-%m-%d %H:%M:%S")}}
|
|
148
|
+
mock_store_instance.put_prompts_history.return_value = None
|
|
149
|
+
mock_state_store.return_value.__enter__.return_value = mock_store_instance
|
|
150
|
+
|
|
151
|
+
# Instantiate the class and call the method with app_id present
|
|
152
|
+
instance = PyntCommand()
|
|
153
|
+
result = instance._is_missing_app_id_confirmed()
|
|
154
|
+
|
|
155
|
+
# Assert the result
|
|
156
|
+
self.assertFalse(result)
|
|
157
|
+
|
|
158
|
+
@patch('pyntcli.commands.pynt_cmd.prompt.confirmation_prompt_with_timeout', return_value=False)
|
|
159
|
+
@patch('pyntcli.commands.pynt_cmd.StateStore')
|
|
160
|
+
def test_missing_app_id_confirmation_user_already_confirmed(self, mock_state_store, _):
|
|
161
|
+
recently = datetime.now() - timedelta(hours=1)
|
|
162
|
+
# Mock the CredStore context manager
|
|
163
|
+
mock_store_instance = MagicMock()
|
|
164
|
+
mock_store_instance.get_prompts_history.return_value = {"missing_app_id": {
|
|
165
|
+
"last_confirmation": recently.strftime("%Y-%m-%d %H:%M:%S")}}
|
|
166
|
+
mock_store_instance.put_prompts_history.return_value = None
|
|
167
|
+
mock_state_store.return_value.__enter__.return_value = mock_store_instance
|
|
168
|
+
|
|
169
|
+
# Instantiate the class and call the method with app_id present
|
|
170
|
+
instance = PyntCommand()
|
|
171
|
+
result = instance._is_missing_app_id_confirmed()
|
|
172
|
+
|
|
173
|
+
# Assert the result
|
|
174
|
+
self.assertTrue(result)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == '__main__':
|
|
178
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|