Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bc86bae
Create test_multi_factor_authentication.py
ajrice6713 Jul 11, 2022
b220baf
Add docstrings and format
ajrice6713 Jul 11, 2022
119391c
Remove unused imports
ajrice6713 Jul 11, 2022
4ccd4c0
Change validateAuthException name
ajrice6713 Jul 11, 2022
dfbd17e
Update test.yaml
ajrice6713 Jul 11, 2022
b77b100
Update test.yaml
ajrice6713 Jul 11, 2022
2cb8f6b
Update test.yaml
ajrice6713 Jul 11, 2022
8bda0d4
Get the 401 test working
ajrice6713 Jul 11, 2022
aaddeee
Use random number generator for the verify tn request
ajrice6713 Jul 11, 2022
fa29a72
Newline for readability
ajrice6713 Jul 11, 2022
5e64491
Merge branch 'feature/openapi-generator-sdk' into DX-2689
ajrice6713 Jul 11, 2022
d1297ed
Move seed outside of class - still seeing 429 errors
ajrice6713 Jul 11, 2022
ca8caca
Update test/integration/test_multi_factor_authentication.py
ajrice6713 Jul 11, 2022
20b7a12
Merge branch 'feature/openapi-generator-sdk' into DX-2689
ajrice6713 Jul 12, 2022
a0cf2b0
Add test to check that the rate limit clears after 30 seconds
ajrice6713 Jul 14, 2022
944ee5a
Remove unused error models
ajrice6713 Jul 14, 2022
43aad00
Clean up spacing and remove test skips
ajrice6713 Jul 14, 2022
16bdb5d
Raise existing exception instead of a new ApiException
ajrice6713 Jul 14, 2022
4247e91
test without seeding - use systime by default
ajrice6713 Jul 14, 2022
80389b8
Remove manual seed for randint
ajrice6713 Jul 14, 2022
3665dc2
Document function return types
ajrice6713 Jul 18, 2022
9a1dd86
Merge branch 'feature/openapi-generator-sdk' into DX-2689
ajrice6713 Jul 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ __pycache__/
# C extensions
*.so

# MacOS Files
# MacOS Files
.DS_Store

# Distribution / packaging
Expand Down
180 changes: 180 additions & 0 deletions test/integration/test_multi_factor_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""
Integration test for Bandwidth's Multi-Factor Authentication API
"""

import os
import time
import unittest
from random import randint

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.voice_code_response import VoiceCodeResponse
from bandwidth.exceptions import ApiException, UnauthorizedException, ForbiddenException


class TestMultiFactorAuthentication(unittest.TestCase):
"""Multi-Factor Authentication API integration Test
"""

def setUp(self) -> None:
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,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setup function looks clean and variable names are expressive, nice work.

Any chance to apply DRY principle?


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
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) -> None:
"""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)

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) -> None:
"""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)

api_response: VoiceCodeResponse = self.api_instance.generate_voice_code(
self.account_id, self.voice_code_request)
self.assertIs(type(api_response.call_id), str)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would empty string be valid as per spec?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No - same as above - will add a check to ensure its a non empty string


# Will always have to test against False codes unless we incorporate the Manteca project into MFA
def testSuccessfulMfaGVerifyCodeRequest(self) -> None:
"""Test a successful MFA verify code request
"""
verify_code_request = VerifyCodeRequest(
to="+1" + str(randint(1111111111, 9999999999)),
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) -> None:
"""Validates a bad (400) request
"""
with self.assertRaises(ApiException) as context:
self.api_instance.generate_messaging_code(self.account_id, self.bad_code_request)

self.assertAuthException(context, ApiException, 400)

def testUnauthorizedRequest(self) -> None:
"""Validate an unauthorized (401) request
"""
unauthorized_api_client = bandwidth.ApiClient()
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.assertAuthException(context, UnauthorizedException, 401)

def testForbiddenRequest(self) -> None:
"""Validate a forbidden (403) request
"""
configuration = bandwidth.Configuration(
username=os.environ['BW_USERNAME_FORBIDDEN'],
# password=os.environ['BW_PASSWORD_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.assertAuthException(context, ForbiddenException, 403)

def testRateLimit(self) -> None:
"""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(35)
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 e