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 CHANGED
@@ -1 +1,20 @@
1
- __version__ = "0.1.93"
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
@@ -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("""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))
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("""Unable to check if Pynt CLI version is up-to-date""")
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("""We could not check for updates.""", ui_thread.PrinterText.WARNING))
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] = {sc.get_name(): sc for sc in avail_sub_commands}
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, default="", help=argparse.SUPPRESS)
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, default="", required=False)
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="use to get more detailed execution information",
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(self._connector_type.default_value())
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
- dir = ".pynt"
54
- super().__init__(file_location=os.path.join(os.path.expanduser("~"), dir, "creds.json"),
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":' + str(all_tokens).replace("\'", "\"") + "}"
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyntcli
3
- Version: 0.1.93
3
+ Version: 0.1.94
4
4
  Summary: Command line utility to handle all of Pynt's different integrations
5
5
  Author-email: Pynt-io <support@pynt.io>
6
6
  Project-URL: Homepage, https://pynt.io
@@ -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=9ZJPYemtF-vnUzZtNNDjmi0UnkkMXYvPkfRASPJ5iWc,23
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=X39hiDq6blGh2sGpwVeUXOgnDlhHruGkOoR5aUBzw_k,2982
19
- pyntcli/commands/root.py,sha256=-2aAiOKc_yXAlymxGove6su1mcvYsk4kCNaEawDKwy8,3677
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=xuS9OB21F6B1sUx5XPGxz_6WpG6-KTMbuq50RrZS5OY,29
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=4YqmcHRltbbSWewKQoOZH8QdyMFs6i3L8o2bXY_YvSw,1909
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
- pyntcli-0.1.93.dist-info/METADATA,sha256=VRflEz4VuO9RUwlmI0nWXOqHaTWdkwCvdcPq7cixewY,460
41
- pyntcli-0.1.93.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
42
- pyntcli-0.1.93.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
43
- pyntcli-0.1.93.dist-info/top_level.txt,sha256=64XSgBzSpgwjYjEKHZE7q3JH2a816zEeyZBXfJi3AKI,42
44
- pyntcli-0.1.93.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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()