strideutils 0.7.8__tar.gz → 0.7.10__tar.gz

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.
Files changed (44) hide show
  1. {strideutils-0.7.8 → strideutils-0.7.10}/.bumpversion.cfg +1 -1
  2. {strideutils-0.7.8 → strideutils-0.7.10}/.github/workflows/publish.yml +4 -2
  3. {strideutils-0.7.8 → strideutils-0.7.10}/.github/workflows/test.yml +3 -1
  4. strideutils-0.7.10/MANIFEST.in +1 -0
  5. {strideutils-0.7.8 → strideutils-0.7.10}/Makefile +0 -3
  6. {strideutils-0.7.8 → strideutils-0.7.10}/PKG-INFO +1 -1
  7. {strideutils-0.7.8 → strideutils-0.7.10}/pyproject.toml +1 -1
  8. strideutils-0.7.10/strideutils/exec_tx.py +62 -0
  9. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/redis_connector.py +12 -18
  10. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/stride_config.py +0 -12
  11. strideutils-0.7.10/strideutils/strideutils/__init__.pyi +0 -0
  12. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils.egg-info/PKG-INFO +1 -1
  13. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils.egg-info/SOURCES.txt +2 -0
  14. {strideutils-0.7.8 → strideutils-0.7.10}/tests/test_redis_connector.py +12 -6
  15. {strideutils-0.7.8 → strideutils-0.7.10}/tests/test_sheets_connector.py +1 -1
  16. strideutils-0.7.10/tests/test_twilio_connector.py +92 -0
  17. strideutils-0.7.8/strideutils/exec_tx.py +0 -62
  18. strideutils-0.7.8/tests/test_twilio_connector.py +0 -80
  19. {strideutils-0.7.8 → strideutils-0.7.10}/.github/workflows/lint.yml +0 -0
  20. {strideutils-0.7.8 → strideutils-0.7.10}/.github/workflows/version.yaml +0 -0
  21. {strideutils-0.7.8 → strideutils-0.7.10}/.gitignore +0 -0
  22. {strideutils-0.7.8 → strideutils-0.7.10}/LICENSE +0 -0
  23. {strideutils-0.7.8 → strideutils-0.7.10}/README.md +0 -0
  24. {strideutils-0.7.8 → strideutils-0.7.10}/setup.cfg +0 -0
  25. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/Makefile +0 -0
  26. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/__init__.py +0 -0
  27. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/coingecko.py +0 -0
  28. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/config_examples/env.local.example +0 -0
  29. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/config_examples/strideutils_config.yaml.example +0 -0
  30. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/cryptography.py +0 -0
  31. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/invariant_helpers.py +0 -0
  32. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/run_safely.py +0 -0
  33. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/sheets_connector.py +0 -0
  34. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/slack_connector.py +0 -0
  35. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/stride_alerts.py +0 -0
  36. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/stride_requests.py +0 -0
  37. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/twilio_connector.py +0 -0
  38. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils/types.py +0 -0
  39. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils.egg-info/dependency_links.txt +0 -0
  40. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils.egg-info/requires.txt +0 -0
  41. {strideutils-0.7.8 → strideutils-0.7.10}/strideutils.egg-info/top_level.txt +0 -0
  42. {strideutils-0.7.8 → strideutils-0.7.10}/tests/test_config.py +0 -0
  43. {strideutils-0.7.8 → strideutils-0.7.10}/tests/test_encryption.py +0 -0
  44. {strideutils-0.7.8 → strideutils-0.7.10}/tests/test_slack_connector.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.7.8
2
+ current_version = 0.7.10
3
3
  commit = False
4
4
  tag = False
5
5
 
@@ -23,7 +23,7 @@ jobs:
23
23
  - name: setup
24
24
  run: |
25
25
  python -m pip install -U pip
26
- pip install --upgrade build setuptools twine wheel bump2version toml-cli
26
+ pip install --upgrade build setuptools twine wheel bump2version toml-cli mypy
27
27
  git config user.name "GitHub Actions"
28
28
  git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
29
29
 
@@ -55,5 +55,7 @@ jobs:
55
55
  TWINE_USERNAME: "__token__"
56
56
  TWINE_PASSWORD: "${{ secrets.PYPI_TOKEN }}"
57
57
  run: |
58
+ stubgen -m strideutils -o ./strideutils
59
+ echo "include strideutils/*.pyi" >> MANIFEST.in
58
60
  python -m build
59
- twine upload dist/*
61
+ twine upload dist/*
@@ -12,6 +12,8 @@ on:
12
12
  jobs:
13
13
  test:
14
14
  runs-on: ubuntu-latest
15
+ env:
16
+ ENV: DEV
15
17
  steps:
16
18
  - uses: actions/checkout@v3
17
19
 
@@ -26,4 +28,4 @@ jobs:
26
28
  pip install -e .[dev]
27
29
 
28
30
  - name: Run tests
29
- run: pytest tests -v
31
+ run: pytest tests -v
@@ -0,0 +1 @@
1
+ include strideutils/*.pyi
@@ -21,9 +21,6 @@ format:
21
21
 
22
22
  tidy: lint type-check format
23
23
 
24
- stub-gen:
25
- $(PYTHON) run stubgen -p strideutils -o pyi
26
-
27
24
  test:
28
25
  @echo "Running unit tests..."
29
26
  @$(VENV_BIN)/pytest . -v
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strideutils
3
- Version: 0.7.8
3
+ Version: 0.7.10
4
4
  Summary: Utility functions for monitoring the Stride blockchain
5
5
  Author-email: Stride Labs <hello@stridelabs.co>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "strideutils"
7
- version = "0.7.8"
7
+ version = "0.7.10"
8
8
  authors = [
9
9
  {name = "Stride Labs", email = "hello@stridelabs.co"},
10
10
  ]
@@ -0,0 +1,62 @@
1
+ import json
2
+ import re
3
+ import subprocess
4
+ import time
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from strideutils import stride_requests
9
+ from strideutils.stride_config import config
10
+
11
+
12
+ @dataclass
13
+ class TxResponse:
14
+ stdout: dict
15
+ stderr: str
16
+ tx_hash: str = ""
17
+ success: bool = False
18
+ code: Optional[int] = None
19
+ tx_response: Optional[dict] = None
20
+ raw_log: Optional[str] = None
21
+
22
+
23
+ def execute_tx(command: str, api_endpoint: str = config.stride.api_endpoint) -> TxResponse:
24
+ """
25
+ Execute a tx and query the tx hash
26
+ """
27
+ # Tack on the auto-confirm if it's not already in the command and capture the output as JSON
28
+ if "-y" not in command:
29
+ command += " -y"
30
+ command += " --output json"
31
+
32
+ # Run the command in the shell and store the response
33
+ output = subprocess.run(command, shell=True, capture_output=True, text=True)
34
+
35
+ # If there was stderr, return the response as failed
36
+ if not output.stdout or output.stderr:
37
+ return TxResponse(stdout={}, stderr=output.stderr, raw_log=output.stderr)
38
+
39
+ # Cast the stdout and build the response
40
+ response = TxResponse(stdout=json.loads(output.stdout), stderr=output.stderr)
41
+
42
+ # Get the tx hash or return an error if one was not generated
43
+ response.tx_hash = response.stdout.get("txhash", "")
44
+ if response.tx_hash == "":
45
+ response.raw_log = "Tx hash not found after running tx"
46
+ return response
47
+
48
+ # Poll the tx hash for the codeID and raw log
49
+ for _ in range(30):
50
+ response_data = stride_requests.get_tx(response.tx_hash, api_endpoint=api_endpoint)
51
+ if not response_data.get("tx_response"):
52
+ time.sleep(2)
53
+ continue
54
+
55
+ response.code = int(response_data["tx_response"]["code"])
56
+ response.raw_log = response_data["tx_response"]["raw_log"]
57
+ response.success = response.code == 0
58
+ return response
59
+
60
+ # If the tx was never found on chain, return an error
61
+ response.raw_log = "Transaction not found on chain after one minute"
62
+ return response
@@ -63,37 +63,31 @@ ALL_DATABASE_NAMES = list(DATABASE_ENVIRONMENT_CONFIGS.keys())
63
63
 
64
64
 
65
65
  class RedisClient:
66
- """Singleton client used to interact with Redis databases."""
66
+ """Redis client to connect to multiple databases at once"""
67
67
 
68
- _instance = None
68
+ # Mapping of database name to redis client
69
69
  _dbs: Dict[str, redis.Redis] = {}
70
70
 
71
- def __new__(cls, db_names: Optional[List[str] | str] = None):
72
- if cls._instance is None:
73
- cls._instance = super(RedisClient, cls).__new__(cls)
74
- cls._instance.__init__(db_names)
75
- return cls._instance
76
-
77
- def __init__(self, db_names: Optional[List[str] | str] = None):
78
- if len(self._dbs) == 0:
79
- self._init_dbs(db_names)
80
-
81
- def _init_dbs(self, db_name_or_names: Optional[List[str] | str] = None):
71
+ def __init__(self, db_name_or_names: Optional[List[str] | str] = None):
82
72
  """
83
73
  Initializes Redis database connections.
84
74
 
85
75
  Args:
86
- db_names: Optional database name or list of database names to initialize.
87
- If None, initializes all databases.
76
+ db_name_or_names: Optional database name or list of database names to initialize.
77
+ If None, initializes all databases.
88
78
 
89
79
  Raises:
90
- ValueError: If db_names is an empty list or contains invalid database names.
80
+ ValueError: If db_name_or_names is an empty list or contains invalid database names.
91
81
  """
92
- db_names = [db_name_or_names] if type(db_name_or_names) is str else db_name_or_names
82
+ self._dbs: Dict[str, redis.Redis] = {}
83
+
84
+ db_names = [db_name_or_names] if isinstance(db_name_or_names, str) else db_name_or_names
85
+
93
86
  if db_names is not None and len(db_names) == 0:
94
- raise ValueError("db_names cannot be an empty list")
87
+ raise ValueError("db_name_or_names cannot be an empty list")
95
88
 
96
89
  db_names = db_names or ALL_DATABASE_NAMES
90
+
97
91
  invalid_db_names = [name for name in db_names if name not in ALL_DATABASE_NAMES]
98
92
  if invalid_db_names:
99
93
  raise ValueError(f"Invalid Redis database names: {invalid_db_names}")
@@ -218,9 +218,6 @@ class Config(ConfigObj):
218
218
  ENV: str
219
219
  timezone: str = "US/Eastern"
220
220
 
221
- # Gsheets - loads from str env var in PROD
222
- PUBLICSHEETS_AUTH: Union[str, ConfigDict] = field(default_factory=ConfigDict)
223
-
224
221
  # Coingecko
225
222
  COINGECKO_API_TOKEN: str = ""
226
223
 
@@ -231,15 +228,6 @@ class Config(ConfigObj):
231
228
  alerts_playbook: str = ""
232
229
  slack_channels: ConfigDict = field(default_factory=ConfigDict)
233
230
 
234
- # Upstash
235
- UPSTASH_PUBLIC_PASSWORD: str = ""
236
- UPSTASH_STRIDE_FRONTEND_PASSWORD: str = ""
237
- UPSTASH_STRIDE_BACKEND_PASSWORD: str = ""
238
- UPSTASH_STRIDE_DYDX_PUBLIC_PASSWORD: str = ""
239
- UPSTASH_STRIDE_DYDX_AIRDROP_PASSWORD: str = ""
240
- UPSTASH_STRIDE_SAGA_AIRDROP_PASSWORD: str = ""
241
- UPSTASH_STRIDE_MILESTONES_PASSWORD: str = ""
242
-
243
231
  # Stride internal secrets
244
232
  SLACK_BEARER_TOKEN: str = ""
245
233
  SLACK_SUCCESS_CHANNEL_ID: str = ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strideutils
3
- Version: 0.7.8
3
+ Version: 0.7.10
4
4
  Summary: Utility functions for monitoring the Stride blockchain
5
5
  Author-email: Stride Labs <hello@stridelabs.co>
6
6
  License: MIT License
@@ -1,6 +1,7 @@
1
1
  .bumpversion.cfg
2
2
  .gitignore
3
3
  LICENSE
4
+ MANIFEST.in
4
5
  Makefile
5
6
  README.md
6
7
  pyproject.toml
@@ -30,6 +31,7 @@ strideutils.egg-info/requires.txt
30
31
  strideutils.egg-info/top_level.txt
31
32
  strideutils/config_examples/env.local.example
32
33
  strideutils/config_examples/strideutils_config.yaml.example
34
+ strideutils/strideutils/__init__.pyi
33
35
  tests/test_config.py
34
36
  tests/test_encryption.py
35
37
  tests/test_redis_connector.py
@@ -4,9 +4,7 @@ from strideutils.redis_connector import RedisClient
4
4
 
5
5
  @pytest.fixture(autouse=True)
6
6
  def reset_redis_client():
7
- RedisClient._reset()
8
7
  yield
9
- RedisClient._reset()
10
8
 
11
9
  @pytest.fixture
12
10
  def mock_redis():
@@ -36,10 +34,18 @@ def redis_client(mock_redis, mock_env_vars):
36
34
  client = RedisClient()
37
35
  return client
38
36
 
39
- def test_redis_client_singleton():
40
- client1 = RedisClient()
41
- client2 = RedisClient()
42
- assert client1 is client2
37
+
38
+ def test_redis_client_non_singleton():
39
+ """Test that each RedisClient instance is independent"""
40
+ client1 = RedisClient(['public'])
41
+ client2 = RedisClient(['frontend'])
42
+
43
+ # Different instances
44
+ assert client1 is not client2
45
+
46
+ # Different database connections
47
+ assert set(client1._dbs.keys()) == {'public'}
48
+ assert set(client2._dbs.keys()) == {'frontend'}
43
49
 
44
50
  def test_init_with_specific_dbs():
45
51
  client = RedisClient(['public', 'frontend'])
@@ -58,7 +58,7 @@ def test_sheets_write_sheet_new_worksheet(sheets_client):
58
58
  df = pd.DataFrame({'A': [1, 3], 'B': [2, 4]})
59
59
  sheets_client.write_sheet(df, 'sheet_id', 'new_sheet')
60
60
 
61
- mock_spreadsheet.add_worksheet.assert_called_once_with(title='new_sheet', rows="1", cols="1")
61
+ mock_spreadsheet.add_worksheet.assert_called_once_with(title='new_sheet', rows=1, cols=1)
62
62
 
63
63
  def test_sheets_reorder_sheet(sheets_client):
64
64
  mock_spreadsheet = MagicMock()
@@ -0,0 +1,92 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock
3
+ from twilio.rest import Client as TwilioRestClient
4
+ from strideutils.twilio_connector import TwilioClient
5
+ from strideutils.stride_config import Environment as e
6
+
7
+ @pytest.fixture(autouse=True)
8
+ def reset_twilio_client():
9
+ TwilioClient._instance = None
10
+ yield
11
+ TwilioClient._instance = None
12
+
13
+ @pytest.fixture
14
+ def mock_twilio_rest_client():
15
+ with patch('strideutils.twilio_connector.Client') as mock:
16
+ mock_instance = MagicMock()
17
+ mock.return_value = mock_instance
18
+ yield mock_instance
19
+
20
+ @pytest.fixture(autouse=True)
21
+ def mock_env_vars():
22
+ with patch('strideutils.twilio_connector.get_env_or_raise') as mock_get_env:
23
+ def side_effect(arg):
24
+ if arg == e.TWILIO_ACCOUNT_ID:
25
+ return 'fake_account_id'
26
+ elif arg == e.TWILIO_API_TOKEN:
27
+ return 'fake_api_token'
28
+ elif arg == e.TWILIO_ALERTS_NUMBER:
29
+ return 'fake_alert_numbers'
30
+ return 'dummy_value'
31
+ mock_get_env.side_effect = side_effect
32
+ yield mock_get_env
33
+
34
+ @pytest.fixture
35
+ def twilio_client(mock_twilio_rest_client, mock_env_vars):
36
+ client = TwilioClient()
37
+ return client
38
+
39
+ def test_twilio_client_singleton():
40
+ client1 = TwilioClient()
41
+ client2 = TwilioClient()
42
+ assert client1 is client2
43
+
44
+ def test_twilio_client_initialization(twilio_client, mock_env_vars):
45
+ assert twilio_client.account_id == 'fake_account_id'
46
+ assert twilio_client.api_token == 'fake_api_token'
47
+ assert twilio_client.alert_numbers == 'fake_alert_numbers'
48
+ assert isinstance(twilio_client.client, MagicMock)
49
+
50
+ def test_call_single_recipient(twilio_client, mock_twilio_rest_client):
51
+ with patch('strideutils.twilio_connector.config') as mock_config:
52
+ mock_config.PHONE_NUMBERS = {'recipient': '+12223334444'}
53
+ mock_config.TWILIO_ALERTS_NUMBER = '+15556667777'
54
+
55
+ twilio_client.call("Test message", "recipient")
56
+
57
+ expected_twiml = "<Response><Say>Test message</Say></Response>"
58
+ mock_twilio_rest_client.calls.create.assert_called_once_with(
59
+ to='+12223334444',
60
+ from_='+15556667777',
61
+ twiml=expected_twiml
62
+ )
63
+
64
+ def test_call_multiple_recipients(twilio_client, mock_twilio_rest_client):
65
+ with patch('strideutils.twilio_connector.config') as mock_config:
66
+ mock_config.PHONE_NUMBERS = {
67
+ 'recipient1': '+12223334444',
68
+ 'recipient2': '+13334445555'
69
+ }
70
+ mock_config.TWILIO_ALERTS_NUMBER = '+15556667777'
71
+
72
+ twilio_client.call("Test message", ["recipient1", "recipient2"])
73
+
74
+ expected_twiml = "<Response><Say>Test message</Say></Response>"
75
+ assert mock_twilio_rest_client.calls.create.call_count == 2
76
+ mock_twilio_rest_client.calls.create.assert_any_call(
77
+ to='+12223334444',
78
+ from_='+15556667777',
79
+ twiml=expected_twiml
80
+ )
81
+ mock_twilio_rest_client.calls.create.assert_any_call(
82
+ to='+13334445555',
83
+ from_='+15556667777',
84
+ twiml=expected_twiml
85
+ )
86
+
87
+ def test_call_invalid_recipient(twilio_client, mock_twilio_rest_client):
88
+ with patch('strideutils.twilio_connector.config') as mock_config:
89
+ mock_config.PHONE_NUMBERS = {}
90
+
91
+ with pytest.raises(KeyError):
92
+ twilio_client.call("Test message", "invalid_recipient")
@@ -1,62 +0,0 @@
1
- import re
2
- import subprocess
3
- import time
4
- from dataclasses import dataclass
5
- from typing import Optional
6
-
7
- from strideutils import stride_requests
8
- from strideutils.stride_config import config
9
-
10
-
11
- @dataclass
12
- class TxResponse:
13
- stdout: str
14
- stderr: str
15
- tx_response: Optional[dict] = None
16
- tx_hash: Optional[str] = None
17
- success: bool = False
18
- raw_log: Optional[str] = None
19
-
20
-
21
- def execute_tx(command: str, api_endpoint: str = config.stride.api_endpoint, verbose=False) -> TxResponse:
22
- """
23
- Execute a tx and query the tx hash
24
- """
25
- output = subprocess.run(command, shell=True, capture_output=True)
26
- standard_out = re.sub(r'\x1b\[[0-9;]*[mGKH]', '', output.stdout.decode('utf-8'))
27
- error_out = re.sub(r'\x1b\[[0-9;]*[mGKH]', '', output.stderr.decode('utf-8'))
28
- if verbose:
29
- print("Standard Out: ", standard_out)
30
- print("Error Out: ", error_out)
31
- tx_hash = ""
32
- for output_line in standard_out.split('\n'):
33
- if 'txhash' in output_line:
34
- tx_hash = output_line.split(': ')[-1]
35
-
36
- tx_response = None
37
- response_success = False
38
- raw_log = "Transaction not found on chain after one minute" # default message
39
- if not error_out:
40
- event_filters = [(f"tx.hash='{tx_hash}'")]
41
- for _ in range(30):
42
- time.sleep(2)
43
- tx_response = stride_requests.get_txs(event_filters, api_endpoint)
44
- if tx_response.get('tx_responses'):
45
- try:
46
- response_code = tx_response['tx_responses'][0]['code']
47
- raw_log = tx_response['tx_responses'][0]['raw_log']
48
- response_success = response_code == 0
49
- except KeyError:
50
- response_success = False
51
- break
52
-
53
- success = response_success and not error_out
54
- response = TxResponse(
55
- stdout=standard_out,
56
- stderr=error_out,
57
- tx_response=tx_response,
58
- success=success,
59
- tx_hash=tx_hash,
60
- raw_log=raw_log,
61
- )
62
- return response
@@ -1,80 +0,0 @@
1
- import pytest
2
- from unittest.mock import patch, MagicMock
3
- from twilio.rest import Client as TwilioRestClient
4
-
5
- from strideutils.twilio_connector import TwilioClient
6
-
7
- @pytest.fixture
8
- def mock_twilio_client():
9
- with patch('strideutils.twilio_connector.Client') as mock_client:
10
- yield mock_client.return_value
11
-
12
- @pytest.fixture
13
- def twilio_client(mock_twilio_client):
14
- with patch('strideutils.twilio_connector.get_env_or_raise') as mock_get_env:
15
- mock_get_env.side_effect = ['fake_account_id', 'fake_api_token', 'fake_alert_numbers']
16
- client = TwilioClient()
17
- client.client = mock_twilio_client # Replaced the real Twilio client with a mock, to not send to the real.
18
- yield client
19
-
20
- def test_twilio_client_singleton():
21
- with patch('strideutils.twilio_connector.Client'):
22
- with patch('strideutils.twilio_connector.get_env_or_raise'):
23
- client1 = TwilioClient()
24
- client2 = TwilioClient()
25
- assert client1 is client2
26
-
27
- def test_call_single_recipient(twilio_client, mock_twilio_client):
28
- with patch('strideutils.twilio_connector.config') as mock_config:
29
- mock_config.PHONE_NUMBERS = {'recipient': '+12223334444'}
30
- mock_config.TWILIO_ALERTS_NUMBER = '+15556667777'
31
-
32
- twilio_client.call("Test message", "recipient")
33
-
34
- expected_twiml = "<Response><Say>Test message</Say></Response>"
35
- mock_twilio_client.calls.create.assert_called_once_with(
36
- to='+12223334444',
37
- from_='+15556667777',
38
- twiml=expected_twiml
39
- )
40
-
41
- def test_call_multiple_recipients(twilio_client, mock_twilio_client):
42
- with patch('strideutils.twilio_connector.config') as mock_config:
43
- mock_config.PHONE_NUMBERS = {
44
- 'recipient1': '+12223334444',
45
- 'recipient2': '+13334445555'
46
- }
47
- mock_config.TWILIO_ALERTS_NUMBER = '+15556667777'
48
-
49
- twilio_client.call("Test message", ["recipient1", "recipient2"])
50
-
51
- expected_twiml = "<Response><Say>Test message</Say></Response>"
52
- assert mock_twilio_client.calls.create.call_count == 2
53
- mock_twilio_client.calls.create.assert_any_call(
54
- to='+12223334444',
55
- from_='+15556667777',
56
- twiml=expected_twiml
57
- )
58
- mock_twilio_client.calls.create.assert_any_call(
59
- to='+13334445555',
60
- from_='+15556667777',
61
- twiml=expected_twiml
62
- )
63
-
64
- def test_call_invalid_recipient(twilio_client, mock_twilio_client):
65
- with patch('strideutils.twilio_connector.config') as mock_config:
66
- mock_config.PHONE_NUMBERS = {}
67
-
68
- with pytest.raises(KeyError):
69
- twilio_client.call("Test message", "invalid_recipient")
70
-
71
- @patch('strideutils.twilio_connector.get_env_or_raise')
72
- def test_twilio_client_initialization(mock_get_env):
73
- mock_get_env.side_effect = ['fake_account_id', 'fake_api_token', 'fake_alert_numbers'] # just dummy values, so this should fail.
74
-
75
- client = TwilioClient()
76
-
77
- assert client.account_id == 'fake_account_id'
78
- assert client.api_token == 'fake_api_token'
79
- assert client.alert_numbers == 'fake_alert_numbers'
80
- assert isinstance(client.client, TwilioRestClient)
File without changes
File without changes
File without changes
File without changes