From bc86baeb7ce703eff5c6e561c265c2217d937c69 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:26:03 -0400 Subject: [PATCH 01/19] Create test_multi_factor_authentication.py 1st pass at writing integration tests --- .../test_multi_factor_authentication.py | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 test/integration/test_multi_factor_authentication.py diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py new file mode 100644 index 00000000..837b6f03 --- /dev/null +++ b/test/integration/test_multi_factor_authentication.py @@ -0,0 +1,146 @@ +""" +Integration test for Bandwidth's Multi-Factor Authentication API +""" + +import os +import json +import time +import unittest + +import bandwidth +from bandwidth.api import mfa_api +from bandwidth.model.code_request import CodeRequest +from bandwidth.model.messaging_code_response import MessagingCodeResponse +from bandwidth.model.verify_code_request import VerifyCodeRequest +from bandwidth.model.verify_code_response import VerifyCodeResponse +from bandwidth.model.mfa_request_error import MfaRequestError +from bandwidth.model.mfa_unauthorized_request_error import MfaUnauthorizedRequestError +from bandwidth.model.mfa_forbidden_request_error import MfaForbiddenRequestError +from bandwidth.model.voice_code_response import VoiceCodeResponse +from bandwidth.exceptions import ApiException, UnauthorizedException, ForbiddenException + + +class TestMultiFactorAuthentication(unittest.TestCase): + """Phone Number Lookup API integration test + """ + + def setUp(self): + configuration = bandwidth.Configuration( + username=os.environ['BW_USERNAME'], + password=os.environ['BW_PASSWORD'] + ) + api_client = bandwidth.ApiClient(configuration) + self.api_instance = mfa_api.MFAApi(api_client) + self.account_id = os.environ['BW_ACCOUNT_ID'] + + self.messaging_code_request = CodeRequest( + to=os.environ['USER_NUMBER'], + _from=os.environ['BW_NUMBER'], + application_id=os.environ['BW_MESSAGING_APPLICATION_ID'], + scope="scope", + message="Your temporary {NAME} {SCOPE} code is {CODE}", + digits=6, + ) + + self.voice_code_request = CodeRequest( + to=os.environ['USER_NUMBER'], + _from=os.environ['BW_NUMBER'], + application_id=os.environ['BW_VOICE_APPLICATION_ID'], + scope="scope", + message="Your temporary {NAME} {SCOPE} code is {CODE}", + digits=6, + ) + + self.bad_code_request = CodeRequest( + to=os.environ['USER_NUMBER'], + _from=os.environ['BW_NUMBER'], + application_id='not_an_application_id', + scope="scope", + message="Your temporary {NAME} {SCOPE} code is {CODE}", + digits=6, + ) + + def validateAuthException(self, context: ApiException, expectedException: ApiException, expected_status_code: int): + """Validates that an auth exception (401 or 403) is properly formatted + Args: + context (ApiException): Exception to validate + expectedException (ApiException): Expected exception type + expected_status_code (int): Expected status code + """ + self.assertIs(type(context.exception), expectedException) + self.assertIs(type(context.exception.status), int) + self.assertEqual(context.exception.status, expected_status_code) + self.assertIs(type(context.exception.body), str) + + def testSuccessfulMfaGenerateMessagingCodeRequest(self): + api_response_with_http_info = self.api_instance.generate_messaging_code( + self.account_id, self.messaging_code_request, _return_http_data_only=False) + self.assertEqual(api_response_with_http_info[1], 200) + + api_response: MessagingCodeResponse = self.api_instance.generate_messaging_code( + self.account_id, self.messaging_code_request) + self.assertIs(type(api_response.message_id), str) + + def testSuccessfulMfaGenerateVoiceCodeRequest(self): + api_response_with_http_info = self.api_instance.generate_voice_code( + self.account_id, self.voice_code_request, _return_http_data_only=False) + self.assertEqual(api_response_with_http_info[1], 200) + + api_response: VoiceCodeResponse = self.api_instance.generate_voice_code( + self.account_id, self.voice_code_request) + self.assertIs(type(api_response.call_id), str) + + # Will always have to test against False codes unless we incorporate the Manteca project into MFA + def testSuccessfulMfaGVerifyCodeRequest(self): + verify_code_request = VerifyCodeRequest( + to=os.environ['USER_NUMBER'], + scope="2FA", + expiration_time_in_minutes=3.0, + code="123456", + ) + api_response_with_http_info = self.api_instance.verify_code(self.account_id, verify_code_request, _return_http_data_only=False) + self.assertEqual(api_response_with_http_info[1], 200) + + api_response: VerifyCodeResponse = self.api_instance.verify_code(self.account_id, verify_code_request) + self.assertEqual(type(api_response), VerifyCodeResponse) + self.assertEqual(type(api_response.valid), bool) + self.assertIs(api_response.valid, False) + + def testBadRequest(self): + with self.assertRaises(ApiException) as context: + self.api_instance.generate_messaging_code(self.account_id, self.bad_code_request) + + self.validateAuthException(context, ApiException, 400) + + @unittest.skip('Skip while we determine how to force this API to return a 401') + def testUnauthorizedRequest(self): + """Validate an unauthorized (401) request + """ + configuration = bandwidth.Configuration( + username='bad_username', + password='bad_password' + ) + unauthorized_api_client = bandwidth.ApiClient(configuration) + unauthorized_api_instance = mfa_api.MFAApi(unauthorized_api_client) + + with self.assertRaises(UnauthorizedException) as context: + unauthorized_api_instance.generate_messaging_code( + self.account_id, self.messaging_code_request) + + self.validateAuthException(context, UnauthorizedException, 401) + + def testForbiddenRequest(self): + """Validate a forbidden (403) request + """ + configuration = bandwidth.Configuration( + username=os.environ['BW_USERNAME_FORBIDDEN'], + password='bad_password' + ) + forbidden_api_client = bandwidth.ApiClient(configuration) + forbidden_api_instance = mfa_api.MFAApi(forbidden_api_client) + + with self.assertRaises(ForbiddenException) as context: + forbidden_api_instance.generate_messaging_code( + self.account_id, self.messaging_code_request) + + self.validateAuthException(context, ForbiddenException, 403) From b220baf7fd93bed6ff1819f8be3b74f2b0f3e112 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:29:05 -0400 Subject: [PATCH 02/19] Add docstrings and format --- .../test_multi_factor_authentication.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 837b6f03..651b68cf 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -21,7 +21,7 @@ class TestMultiFactorAuthentication(unittest.TestCase): - """Phone Number Lookup API integration test + """Multi-Factor Authentication API integration test """ def setUp(self): @@ -73,6 +73,8 @@ def validateAuthException(self, context: ApiException, expectedException: ApiExc self.assertIs(type(context.exception.body), str) def testSuccessfulMfaGenerateMessagingCodeRequest(self): + """Test a successful MFA messaging code request + """ api_response_with_http_info = self.api_instance.generate_messaging_code( self.account_id, self.messaging_code_request, _return_http_data_only=False) self.assertEqual(api_response_with_http_info[1], 200) @@ -82,6 +84,8 @@ def testSuccessfulMfaGenerateMessagingCodeRequest(self): self.assertIs(type(api_response.message_id), str) def testSuccessfulMfaGenerateVoiceCodeRequest(self): + """Test a successful MFA voice code request + """ api_response_with_http_info = self.api_instance.generate_voice_code( self.account_id, self.voice_code_request, _return_http_data_only=False) self.assertEqual(api_response_with_http_info[1], 200) @@ -92,21 +96,27 @@ def testSuccessfulMfaGenerateVoiceCodeRequest(self): # Will always have to test against False codes unless we incorporate the Manteca project into MFA def testSuccessfulMfaGVerifyCodeRequest(self): + """Test a successful MFA verify code request + """ verify_code_request = VerifyCodeRequest( to=os.environ['USER_NUMBER'], scope="2FA", expiration_time_in_minutes=3.0, code="123456", ) - api_response_with_http_info = self.api_instance.verify_code(self.account_id, verify_code_request, _return_http_data_only=False) + api_response_with_http_info = self.api_instance.verify_code( + self.account_id, verify_code_request, _return_http_data_only=False) self.assertEqual(api_response_with_http_info[1], 200) - api_response: VerifyCodeResponse = self.api_instance.verify_code(self.account_id, verify_code_request) + api_response: VerifyCodeResponse = self.api_instance.verify_code( + self.account_id, verify_code_request) self.assertEqual(type(api_response), VerifyCodeResponse) self.assertEqual(type(api_response.valid), bool) self.assertIs(api_response.valid, False) def testBadRequest(self): + """Validates a bad (400) request + """ with self.assertRaises(ApiException) as context: self.api_instance.generate_messaging_code(self.account_id, self.bad_code_request) @@ -134,6 +144,7 @@ def testForbiddenRequest(self): """ configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME_FORBIDDEN'], + # password=os.environ['BW_PASSWORD_FORBIDDEN'], password='bad_password' ) forbidden_api_client = bandwidth.ApiClient(configuration) From 119391c8825f1fba628931703deced70d0ccfa0a Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:29:29 -0400 Subject: [PATCH 03/19] Remove unused imports --- test/integration/test_multi_factor_authentication.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 651b68cf..19d80d47 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -3,8 +3,6 @@ """ import os -import json -import time import unittest import bandwidth From 4ccd4c0d4daefebf1de482bf7ec2417f41ebb929 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:30:29 -0400 Subject: [PATCH 04/19] Change validateAuthException name Use assertAuthException to match unittest assert names --- test/integration/test_multi_factor_authentication.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 19d80d47..26bf1dd5 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -58,7 +58,7 @@ def setUp(self): digits=6, ) - def validateAuthException(self, context: ApiException, expectedException: ApiException, expected_status_code: int): + def assertAuthException(self, context: ApiException, expectedException: ApiException, expected_status_code: int): """Validates that an auth exception (401 or 403) is properly formatted Args: context (ApiException): Exception to validate @@ -118,7 +118,7 @@ def testBadRequest(self): with self.assertRaises(ApiException) as context: self.api_instance.generate_messaging_code(self.account_id, self.bad_code_request) - self.validateAuthException(context, ApiException, 400) + self.assertAuthException(context, ApiException, 400) @unittest.skip('Skip while we determine how to force this API to return a 401') def testUnauthorizedRequest(self): @@ -135,7 +135,7 @@ def testUnauthorizedRequest(self): unauthorized_api_instance.generate_messaging_code( self.account_id, self.messaging_code_request) - self.validateAuthException(context, UnauthorizedException, 401) + self.assertAuthException(context, UnauthorizedException, 401) def testForbiddenRequest(self): """Validate a forbidden (403) request @@ -152,4 +152,4 @@ def testForbiddenRequest(self): forbidden_api_instance.generate_messaging_code( self.account_id, self.messaging_code_request) - self.validateAuthException(context, ForbiddenException, 403) + self.assertAuthException(context, ForbiddenException, 403) From dfbd17e7737ff91f7101ded3dc6ee11eef9dbc22 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:38:39 -0400 Subject: [PATCH 05/19] Update test.yaml --- .github/workflows/test.yaml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 59017e06..51592a73 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,36 +13,34 @@ jobs: strategy: matrix: os: [windows-2022, windows-2019, ubuntu-18.04, ubuntu-20.04] - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] steps: - name: Checkout uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: python-version: ${{ matrix.python-version }} - name: Install Packages - run: pip install -r requirements_dev.txt - - - name: Test + run: | + pip install -r requirements.txt + pip install -r test-requirements.txt + + - name: Test env: BW_ACCOUNT_ID: ${{ secrets.BW_ACCOUNT_ID }} BW_USERNAME: ${{ secrets.BW_USERNAME }} BW_PASSWORD: ${{ secrets.BW_PASSWORD }} + BW_USERNAME_FORBIDDEN: ${{ secrets.BW_USERNAME_FORBIDDEN }} + BW_PASSWORD_FORBIDDEN: ${{ secrets.BW_PASSWORD_FORBIDDEN }} BW_VOICE_APPLICATION_ID: ${{ secrets.BW_VOICE_APPLICATION_ID }} BW_MESSAGING_APPLICATION_ID: ${{ secrets.BW_MESSAGING_APPLICATION_ID }} BW_NUMBER: ${{ secrets.BW_NUMBER }} USER_NUMBER: ${{ secrets.USER_NUMBER }} + VZW_NUMBER: ${{ secrets.VZW_NUMBER }} + ATT_NUMBER: ${{ secrets.ATT_NUMBER }} + T_MOBILE_NUMBER: ${{ secrets.T_MOBILE_NUMBER }} BASE_CALLBACK_URL: ${{ secrets.BASE_CALLBACK_URL }} run: pytest - name: Notify Slack of Failures uses: Bandwidth/build-notify-slack-action@v1.0.0 - if: failure() && !github.event.pull_request.draft - with: - job-status: ${{ job.status }} - slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} - slack-channel: ${{ secrets.SLACK_CHANNEL }} From b77b1006ac100674f906a7735f3b2044fecfab46 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:39:31 -0400 Subject: [PATCH 06/19] Update test.yaml --- .github/workflows/test.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 51592a73..2044a129 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -43,4 +43,8 @@ jobs: - name: Notify Slack of Failures uses: Bandwidth/build-notify-slack-action@v1.0.0 - + if: failure() && !github.event.pull_request.draft + with: + job-status: ${{ job.status }} + slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + slack-channel: ${{ secrets.SLACK_CHANNEL }} From 2cb8f6bb6987db381450d2c3d9e237cb6a112d21 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:40:22 -0400 Subject: [PATCH 07/19] Update test.yaml --- .github/workflows/test.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2044a129..6b028b64 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,14 +17,18 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: python-version: ${{ matrix.python-version }} - name: Install Packages run: | pip install -r requirements.txt pip install -r test-requirements.txt - - - name: Test + + - name: Test env: BW_ACCOUNT_ID: ${{ secrets.BW_ACCOUNT_ID }} BW_USERNAME: ${{ secrets.BW_USERNAME }} @@ -48,3 +52,4 @@ jobs: job-status: ${{ job.status }} slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} slack-channel: ${{ secrets.SLACK_CHANNEL }} + From 8bda0d4a33314f39b4a5b22417ca511115647c65 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:43:11 -0400 Subject: [PATCH 08/19] Get the 401 test working No auth header causes a 401 --- test/integration/test_multi_factor_authentication.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 26bf1dd5..b5572f76 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -120,15 +120,10 @@ def testBadRequest(self): self.assertAuthException(context, ApiException, 400) - @unittest.skip('Skip while we determine how to force this API to return a 401') def testUnauthorizedRequest(self): """Validate an unauthorized (401) request """ - configuration = bandwidth.Configuration( - username='bad_username', - password='bad_password' - ) - unauthorized_api_client = bandwidth.ApiClient(configuration) + unauthorized_api_client = bandwidth.ApiClient() unauthorized_api_instance = mfa_api.MFAApi(unauthorized_api_client) with self.assertRaises(UnauthorizedException) as context: From aaddeee6e05faa3c11eac7e8f60a85a4e27f8fd4 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:46:26 -0400 Subject: [PATCH 09/19] Use random number generator for the verify tn request Otherwise we hit a rate limit very quickly --- test/integration/test_multi_factor_authentication.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index b5572f76..f839fd7e 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -4,6 +4,8 @@ import os import unittest +from random import seed +from random import randint import bandwidth from bandwidth.api import mfa_api @@ -23,6 +25,9 @@ class TestMultiFactorAuthentication(unittest.TestCase): """ def setUp(self): + # seed the random number generator for the verify request + seed(randint(10, 500)) + configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] @@ -97,7 +102,7 @@ def testSuccessfulMfaGVerifyCodeRequest(self): """Test a successful MFA verify code request """ verify_code_request = VerifyCodeRequest( - to=os.environ['USER_NUMBER'], + to="+1" + str(randint(1111111111, 9999999999)), scope="2FA", expiration_time_in_minutes=3.0, code="123456", From fa29a726e662da1540caebc0d26ed619e9acf6b4 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:54:28 -0400 Subject: [PATCH 10/19] Newline for readability --- test/integration/test_multi_factor_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index f839fd7e..8300393b 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -27,7 +27,7 @@ class TestMultiFactorAuthentication(unittest.TestCase): def setUp(self): # seed the random number generator for the verify request seed(randint(10, 500)) - + configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] From d1297ed834ed2dddce1e678212f7f412a62b96e7 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:59:09 -0400 Subject: [PATCH 11/19] Move seed outside of class - still seeing 429 errors --- test/integration/test_multi_factor_authentication.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 8300393b..b44748b4 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -19,15 +19,15 @@ from bandwidth.model.voice_code_response import VoiceCodeResponse from bandwidth.exceptions import ApiException, UnauthorizedException, ForbiddenException +# seed the random number generator for the verify request +seed(randint(10, 500)) + class TestMultiFactorAuthentication(unittest.TestCase): """Multi-Factor Authentication API integration test """ def setUp(self): - # seed the random number generator for the verify request - seed(randint(10, 500)) - configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] From ca8caca3f8e378bf86ced6ee8d28d08294f55187 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 11 Jul 2022 15:10:59 -0400 Subject: [PATCH 12/19] Update test/integration/test_multi_factor_authentication.py --- test/integration/test_multi_factor_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index b44748b4..3ac71a09 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -24,7 +24,7 @@ class TestMultiFactorAuthentication(unittest.TestCase): - """Multi-Factor Authentication API integration test + """Multi-Factor Authentication API integration Test """ def setUp(self): From a0cf2b0f91ca588a78a457cb6435233d70bed003 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Thu, 14 Jul 2022 14:14:44 -0400 Subject: [PATCH 13/19] Add test to check that the rate limit clears after 30 seconds Requested by MFA team --- .../test_multi_factor_authentication.py | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 3ac71a09..437f763f 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -3,6 +3,7 @@ """ import os +import time import unittest from random import seed from random import randint @@ -20,7 +21,6 @@ from bandwidth.exceptions import ApiException, UnauthorizedException, ForbiddenException # seed the random number generator for the verify request -seed(randint(10, 500)) class TestMultiFactorAuthentication(unittest.TestCase): @@ -28,6 +28,7 @@ class TestMultiFactorAuthentication(unittest.TestCase): """ def setUp(self): + seed(randint(10, 500)) configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] @@ -75,22 +76,28 @@ def assertAuthException(self, context: ApiException, expectedException: ApiExcep self.assertEqual(context.exception.status, expected_status_code) self.assertIs(type(context.exception.body), str) + @unittest.skip("skippy skip") def testSuccessfulMfaGenerateMessagingCodeRequest(self): """Test a successful MFA messaging code request """ api_response_with_http_info = self.api_instance.generate_messaging_code( - self.account_id, self.messaging_code_request, _return_http_data_only=False) + self.account_id, self.messaging_code_request, + _return_http_data_only=False + ) self.assertEqual(api_response_with_http_info[1], 200) api_response: MessagingCodeResponse = self.api_instance.generate_messaging_code( self.account_id, self.messaging_code_request) self.assertIs(type(api_response.message_id), str) + @unittest.skip("skippy skip") def testSuccessfulMfaGenerateVoiceCodeRequest(self): """Test a successful MFA voice code request """ api_response_with_http_info = self.api_instance.generate_voice_code( - self.account_id, self.voice_code_request, _return_http_data_only=False) + self.account_id, self.voice_code_request, + _return_http_data_only=False + ) self.assertEqual(api_response_with_http_info[1], 200) api_response: VoiceCodeResponse = self.api_instance.generate_voice_code( @@ -98,6 +105,7 @@ def testSuccessfulMfaGenerateVoiceCodeRequest(self): self.assertIs(type(api_response.call_id), str) # Will always have to test against False codes unless we incorporate the Manteca project into MFA + @unittest.skip("skippy skip") def testSuccessfulMfaGVerifyCodeRequest(self): """Test a successful MFA verify code request """ @@ -108,7 +116,9 @@ def testSuccessfulMfaGVerifyCodeRequest(self): code="123456", ) api_response_with_http_info = self.api_instance.verify_code( - self.account_id, verify_code_request, _return_http_data_only=False) + self.account_id, verify_code_request, + _return_http_data_only=False + ) self.assertEqual(api_response_with_http_info[1], 200) api_response: VerifyCodeResponse = self.api_instance.verify_code( @@ -117,6 +127,7 @@ def testSuccessfulMfaGVerifyCodeRequest(self): self.assertEqual(type(api_response.valid), bool) self.assertIs(api_response.valid, False) + @unittest.skip("skippy skip") def testBadRequest(self): """Validates a bad (400) request """ @@ -125,6 +136,7 @@ def testBadRequest(self): self.assertAuthException(context, ApiException, 400) + @unittest.skip("skippy skip") def testUnauthorizedRequest(self): """Validate an unauthorized (401) request """ @@ -133,10 +145,12 @@ def testUnauthorizedRequest(self): with self.assertRaises(UnauthorizedException) as context: unauthorized_api_instance.generate_messaging_code( - self.account_id, self.messaging_code_request) + self.account_id, self.messaging_code_request + ) self.assertAuthException(context, UnauthorizedException, 401) + @unittest.skip("skippy skip") def testForbiddenRequest(self): """Validate a forbidden (403) request """ @@ -150,6 +164,33 @@ def testForbiddenRequest(self): with self.assertRaises(ForbiddenException) as context: forbidden_api_instance.generate_messaging_code( - self.account_id, self.messaging_code_request) + self.account_id, self.messaging_code_request + ) self.assertAuthException(context, ForbiddenException, 403) + + def testRateLimit(self): + """Validate that the API reutrns a 429 error, and that the 429 clears after 30 seconds + """ + verify_code_request = VerifyCodeRequest( + to="+1" + str(randint(1111111111, 9999999999)), + scope="2FA", + expiration_time_in_minutes=3.0, + code="123456", + ) + while True: + try: + api_response_with_http_info = self.api_instance.verify_code( + self.account_id, verify_code_request + ) + except ApiException as e: + if e.status == 429: + time.sleep(30) + api_response_with_http_info = self.api_instance.verify_code( + self.account_id, verify_code_request, + _return_http_data_only=False + ) + self.assertEqual(api_response_with_http_info[1], 200) + break + else: + raise ApiException From 944ee5a4580b67533dd97c7bb071592e8f73ddb3 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Thu, 14 Jul 2022 14:16:45 -0400 Subject: [PATCH 14/19] Remove unused error models These never get used due to the nature of the ApiException that gets raised. Overkill to initialize these by kwarg and test --- test/integration/test_multi_factor_authentication.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 437f763f..5b50f78b 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -14,9 +14,6 @@ from bandwidth.model.messaging_code_response import MessagingCodeResponse from bandwidth.model.verify_code_request import VerifyCodeRequest from bandwidth.model.verify_code_response import VerifyCodeResponse -from bandwidth.model.mfa_request_error import MfaRequestError -from bandwidth.model.mfa_unauthorized_request_error import MfaUnauthorizedRequestError -from bandwidth.model.mfa_forbidden_request_error import MfaForbiddenRequestError from bandwidth.model.voice_code_response import VoiceCodeResponse from bandwidth.exceptions import ApiException, UnauthorizedException, ForbiddenException From 43aad00a37156a2b46035f09a1fea32dd4c6d8c0 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Thu, 14 Jul 2022 14:18:06 -0400 Subject: [PATCH 15/19] Clean up spacing and remove test skips --- .../integration/test_multi_factor_authentication.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 5b50f78b..f8d4a5c9 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -17,15 +17,15 @@ from bandwidth.model.voice_code_response import VoiceCodeResponse from bandwidth.exceptions import ApiException, UnauthorizedException, ForbiddenException -# seed the random number generator for the verify request - class TestMultiFactorAuthentication(unittest.TestCase): """Multi-Factor Authentication API integration Test """ def setUp(self): + # seed the random number generator for the verify request seed(randint(10, 500)) + configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] @@ -33,7 +33,6 @@ def setUp(self): api_client = bandwidth.ApiClient(configuration) self.api_instance = mfa_api.MFAApi(api_client) self.account_id = os.environ['BW_ACCOUNT_ID'] - self.messaging_code_request = CodeRequest( to=os.environ['USER_NUMBER'], _from=os.environ['BW_NUMBER'], @@ -42,7 +41,6 @@ def setUp(self): message="Your temporary {NAME} {SCOPE} code is {CODE}", digits=6, ) - self.voice_code_request = CodeRequest( to=os.environ['USER_NUMBER'], _from=os.environ['BW_NUMBER'], @@ -51,7 +49,6 @@ def setUp(self): message="Your temporary {NAME} {SCOPE} code is {CODE}", digits=6, ) - self.bad_code_request = CodeRequest( to=os.environ['USER_NUMBER'], _from=os.environ['BW_NUMBER'], @@ -73,7 +70,6 @@ def assertAuthException(self, context: ApiException, expectedException: ApiExcep self.assertEqual(context.exception.status, expected_status_code) self.assertIs(type(context.exception.body), str) - @unittest.skip("skippy skip") def testSuccessfulMfaGenerateMessagingCodeRequest(self): """Test a successful MFA messaging code request """ @@ -87,7 +83,6 @@ def testSuccessfulMfaGenerateMessagingCodeRequest(self): self.account_id, self.messaging_code_request) self.assertIs(type(api_response.message_id), str) - @unittest.skip("skippy skip") def testSuccessfulMfaGenerateVoiceCodeRequest(self): """Test a successful MFA voice code request """ @@ -102,7 +97,6 @@ def testSuccessfulMfaGenerateVoiceCodeRequest(self): self.assertIs(type(api_response.call_id), str) # Will always have to test against False codes unless we incorporate the Manteca project into MFA - @unittest.skip("skippy skip") def testSuccessfulMfaGVerifyCodeRequest(self): """Test a successful MFA verify code request """ @@ -124,7 +118,6 @@ def testSuccessfulMfaGVerifyCodeRequest(self): self.assertEqual(type(api_response.valid), bool) self.assertIs(api_response.valid, False) - @unittest.skip("skippy skip") def testBadRequest(self): """Validates a bad (400) request """ @@ -133,7 +126,6 @@ def testBadRequest(self): self.assertAuthException(context, ApiException, 400) - @unittest.skip("skippy skip") def testUnauthorizedRequest(self): """Validate an unauthorized (401) request """ @@ -147,7 +139,6 @@ def testUnauthorizedRequest(self): self.assertAuthException(context, UnauthorizedException, 401) - @unittest.skip("skippy skip") def testForbiddenRequest(self): """Validate a forbidden (403) request """ From 16bdb5d9712398410e6f17cb6079660323fa6390 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Thu, 14 Jul 2022 14:50:09 -0400 Subject: [PATCH 16/19] Raise existing exception instead of a new ApiException Bump time.sleep from 30-35 --- test/integration/test_multi_factor_authentication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index f8d4a5c9..a57eff9f 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -173,7 +173,7 @@ def testRateLimit(self): ) except ApiException as e: if e.status == 429: - time.sleep(30) + time.sleep(35) api_response_with_http_info = self.api_instance.verify_code( self.account_id, verify_code_request, _return_http_data_only=False @@ -181,4 +181,4 @@ def testRateLimit(self): self.assertEqual(api_response_with_http_info[1], 200) break else: - raise ApiException + raise e From 4247e91c91d84cbf92a740c6f9099c53bcc6c96e Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Thu, 14 Jul 2022 15:17:46 -0400 Subject: [PATCH 17/19] test without seeding - use systime by default --- test/integration/test_multi_factor_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index a57eff9f..62447ccf 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -24,7 +24,7 @@ class TestMultiFactorAuthentication(unittest.TestCase): def setUp(self): # seed the random number generator for the verify request - seed(randint(10, 500)) + # seed(randint(10, 500)) configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], From 80389b891b1edd0e5d416526bb5350c57a1a074d Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Thu, 14 Jul 2022 15:25:57 -0400 Subject: [PATCH 18/19] Remove manual seed for randint sets seed value as system time by default - this works better for multiple tests in parallel --- test/integration/test_multi_factor_authentication.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 62447ccf..171a66f9 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -5,7 +5,6 @@ import os import time import unittest -from random import seed from random import randint import bandwidth @@ -23,9 +22,6 @@ class TestMultiFactorAuthentication(unittest.TestCase): """ def setUp(self): - # seed the random number generator for the verify request - # seed(randint(10, 500)) - configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] From 3665dc2b2b36c93862713fa9c0faf94ab83813a4 Mon Sep 17 00:00:00 2001 From: AJ Rice <53190766+ajrice6713@users.noreply.github.com> Date: Mon, 18 Jul 2022 11:08:14 -0400 Subject: [PATCH 19/19] Document function return types --- .gitignore | 3 +++ .../test_multi_factor_authentication.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 43995bd4..b8b9db74 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# MacOS Files +.DS_Store + # Distribution / packaging .Python env/ diff --git a/test/integration/test_multi_factor_authentication.py b/test/integration/test_multi_factor_authentication.py index 171a66f9..d62237ae 100644 --- a/test/integration/test_multi_factor_authentication.py +++ b/test/integration/test_multi_factor_authentication.py @@ -21,7 +21,7 @@ class TestMultiFactorAuthentication(unittest.TestCase): """Multi-Factor Authentication API integration Test """ - def setUp(self): + def setUp(self) -> None: configuration = bandwidth.Configuration( username=os.environ['BW_USERNAME'], password=os.environ['BW_PASSWORD'] @@ -54,7 +54,7 @@ def setUp(self): digits=6, ) - def assertAuthException(self, context: ApiException, expectedException: ApiException, expected_status_code: int): + def assertAuthException(self, context: ApiException, expectedException: ApiException, expected_status_code: int) -> None: """Validates that an auth exception (401 or 403) is properly formatted Args: context (ApiException): Exception to validate @@ -66,7 +66,7 @@ def assertAuthException(self, context: ApiException, expectedException: ApiExcep self.assertEqual(context.exception.status, expected_status_code) self.assertIs(type(context.exception.body), str) - def testSuccessfulMfaGenerateMessagingCodeRequest(self): + def testSuccessfulMfaGenerateMessagingCodeRequest(self) -> None: """Test a successful MFA messaging code request """ api_response_with_http_info = self.api_instance.generate_messaging_code( @@ -79,7 +79,7 @@ def testSuccessfulMfaGenerateMessagingCodeRequest(self): self.account_id, self.messaging_code_request) self.assertIs(type(api_response.message_id), str) - def testSuccessfulMfaGenerateVoiceCodeRequest(self): + def testSuccessfulMfaGenerateVoiceCodeRequest(self) -> None: """Test a successful MFA voice code request """ api_response_with_http_info = self.api_instance.generate_voice_code( @@ -93,7 +93,7 @@ def testSuccessfulMfaGenerateVoiceCodeRequest(self): self.assertIs(type(api_response.call_id), str) # Will always have to test against False codes unless we incorporate the Manteca project into MFA - def testSuccessfulMfaGVerifyCodeRequest(self): + def testSuccessfulMfaGVerifyCodeRequest(self) -> None: """Test a successful MFA verify code request """ verify_code_request = VerifyCodeRequest( @@ -114,7 +114,7 @@ def testSuccessfulMfaGVerifyCodeRequest(self): self.assertEqual(type(api_response.valid), bool) self.assertIs(api_response.valid, False) - def testBadRequest(self): + def testBadRequest(self) -> None: """Validates a bad (400) request """ with self.assertRaises(ApiException) as context: @@ -122,7 +122,7 @@ def testBadRequest(self): self.assertAuthException(context, ApiException, 400) - def testUnauthorizedRequest(self): + def testUnauthorizedRequest(self) -> None: """Validate an unauthorized (401) request """ unauthorized_api_client = bandwidth.ApiClient() @@ -135,7 +135,7 @@ def testUnauthorizedRequest(self): self.assertAuthException(context, UnauthorizedException, 401) - def testForbiddenRequest(self): + def testForbiddenRequest(self) -> None: """Validate a forbidden (403) request """ configuration = bandwidth.Configuration( @@ -153,7 +153,7 @@ def testForbiddenRequest(self): self.assertAuthException(context, ForbiddenException, 403) - def testRateLimit(self): + def testRateLimit(self) -> None: """Validate that the API reutrns a 429 error, and that the 429 clears after 30 seconds """ verify_code_request = VerifyCodeRequest(