From 8ee9bb02d083ad4ccc487eae39ca710adbba9761 Mon Sep 17 00:00:00 2001 From: ashwinrachhavt <124548941+ashwinrachhavt@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:15:32 -0400 Subject: [PATCH 1/2] Convert Flask app to Django app with async views --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/plaid/quickstart?shareId=XXXX-XXXX-XXXX-XXXX). --- .../src/bank_connectivity/__init__.py | 1 + .../src/bank_connectivity/plaid/__init__.py | 1 + .../src/bank_connectivity/plaid/api.py | 470 +++++--------- .../src/bank_connectivity/plaid/models.py | 46 ++ .../src/bank_connectivity/plaid/services.py | 591 ++++++++++++++++++ .../bank_connectivity/plaid/tests/test_api.py | 248 ++++++++ .../plaid/tests/test_models.py | 105 ++++ .../plaid/tests/test_services.py | 400 ++++++++++++ .../plaid/tests/test_utils.py | 400 ++++++++++++ .../src/bank_connectivity/plaid/utils.py | 78 +++ .../src/bank_connectivity/teller/__init__.py | 1 + common-services/src/core/__init__.py | 1 + common-services/src/core/settings.py | 125 ++++ common-services/src/tests/__init__.py | 1 + .../tests/bank_connectivity/plaid/__init__.py | 1 + .../bank_connectivity/teller/__init__.py | 1 + common-services/src/tests/test_admin.py | 29 + common-services/src/tests/test_apps.py | 8 + common-services/src/tests/test_middleware.py | 57 ++ common-services/src/tests/test_migrations.py | 18 + common-services/src/tests/test_models.py | 13 + common-services/src/tests/test_providers.py | 52 ++ common-services/src/tests/test_serializers.py | 18 + common-services/src/tests/test_services.py | 10 + common-services/src/tests/test_settings.py | 69 ++ common-services/src/tests/test_urls.py | 8 + common-services/src/tests/test_utils.py | 10 + common-services/src/tests/test_views.py | 21 + python/.env.example | 1 - python/Dockerfile | 12 - python/requirements.txt | 5 - python/start.sh | 3 - 32 files changed, 2469 insertions(+), 335 deletions(-) create mode 100644 common-services/src/bank_connectivity/__init__.py create mode 100644 common-services/src/bank_connectivity/plaid/__init__.py rename python/server.py => common-services/src/bank_connectivity/plaid/api.py (57%) create mode 100644 common-services/src/bank_connectivity/plaid/models.py create mode 100644 common-services/src/bank_connectivity/plaid/services.py create mode 100644 common-services/src/bank_connectivity/plaid/tests/test_api.py create mode 100644 common-services/src/bank_connectivity/plaid/tests/test_models.py create mode 100644 common-services/src/bank_connectivity/plaid/tests/test_services.py create mode 100644 common-services/src/bank_connectivity/plaid/tests/test_utils.py create mode 100644 common-services/src/bank_connectivity/plaid/utils.py create mode 100644 common-services/src/bank_connectivity/teller/__init__.py create mode 100644 common-services/src/core/__init__.py create mode 100644 common-services/src/core/settings.py create mode 100644 common-services/src/tests/__init__.py create mode 100644 common-services/src/tests/bank_connectivity/plaid/__init__.py create mode 100644 common-services/src/tests/bank_connectivity/teller/__init__.py create mode 100644 common-services/src/tests/test_admin.py create mode 100644 common-services/src/tests/test_apps.py create mode 100644 common-services/src/tests/test_middleware.py create mode 100644 common-services/src/tests/test_migrations.py create mode 100644 common-services/src/tests/test_models.py create mode 100644 common-services/src/tests/test_providers.py create mode 100644 common-services/src/tests/test_serializers.py create mode 100644 common-services/src/tests/test_services.py create mode 100644 common-services/src/tests/test_settings.py create mode 100644 common-services/src/tests/test_urls.py create mode 100644 common-services/src/tests/test_utils.py create mode 100644 common-services/src/tests/test_views.py delete mode 120000 python/.env.example delete mode 100644 python/Dockerfile delete mode 100644 python/requirements.txt delete mode 100755 python/start.sh diff --git a/common-services/src/bank_connectivity/__init__.py b/common-services/src/bank_connectivity/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/bank_connectivity/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/common-services/src/bank_connectivity/plaid/__init__.py b/common-services/src/bank_connectivity/plaid/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/python/server.py b/common-services/src/bank_connectivity/plaid/api.py similarity index 57% rename from python/server.py rename to common-services/src/bank_connectivity/plaid/api.py index 183f0313..041c6a71 100644 --- a/python/server.py +++ b/common-services/src/bank_connectivity/plaid/api.py @@ -1,4 +1,3 @@ -# Read env vars from .env file import base64 import os import datetime as dt @@ -8,7 +7,8 @@ import uuid from dotenv import load_dotenv -from flask import Flask, request, jsonify +from django.http import JsonResponse +from django.views.decorators.http import require_POST, require_GET import plaid from plaid.model.payment_amount import PaymentAmount from plaid.model.payment_amount_currency import PaymentAmountCurrency @@ -64,9 +64,6 @@ load_dotenv() - -app = Flask(__name__) - PLAID_CLIENT_ID = os.getenv('PLAID_CLIENT_ID') PLAID_SECRET = os.getenv('PLAID_SECRET') PLAID_ENV = os.getenv('PLAID_ENV', 'sandbox') @@ -87,13 +84,6 @@ def empty_to_none(field): if PLAID_ENV == 'production': host = plaid.Environment.Production -# Parameters used for the OAuth redirect Link flow. -# -# Set PLAID_REDIRECT_URI to 'http://localhost:3000/' -# The OAuth redirect flow requires an endpoint on the developer's website -# that the bank website should redirect to. You will need to configure -# this redirect URI for your client ID through the Plaid developer dashboard -# at https://dashboard.plaid.com/team/api. PLAID_REDIRECT_URI = empty_to_none('PLAID_REDIRECT_URI') configuration = plaid.Configuration( @@ -112,41 +102,27 @@ def empty_to_none(field): for product in PLAID_PRODUCTS: products.append(Products(product)) - -# We store the access_token in memory - in production, store it in a secure -# persistent data store. access_token = None -# The payment_id is only relevant for the UK Payment Initiation product. -# We store the payment_id in memory - in production, store it in a secure -# persistent data store. payment_id = None -# The transfer_id is only relevant for Transfer ACH product. -# We store the transfer_id in memory - in production, store it in a secure -# persistent data store. transfer_id = None -# We store the user_token in memory - in production, store it in a secure -# persistent data store. user_token = None - item_id = None - -@app.route('/api/info', methods=['POST']) -def info(): +@require_POST +def info(request): global access_token global item_id - return jsonify({ + return JsonResponse({ 'item_id': item_id, 'access_token': access_token, 'products': PLAID_PRODUCTS }) - -@app.route('/api/create_link_token_for_payment', methods=['POST']) -def create_link_token_for_payment(): +@require_POST +def create_link_token_for_payment(request): global payment_id try: - request = PaymentInitiationRecipientCreateRequest( + req = PaymentInitiationRecipientCreateRequest( name='John Doe', bacs=RecipientBACSNullable(account='26207729', sort_code='560029'), address=PaymentInitiationAddress( @@ -156,11 +132,10 @@ def create_link_token_for_payment(): country='GB' ) ) - response = client.payment_initiation_recipient_create( - request) + response = client.payment_initiation_recipient_create(req) recipient_id = response['recipient_id'] - request = PaymentInitiationPaymentCreateRequest( + req = PaymentInitiationPaymentCreateRequest( recipient_id=recipient_id, reference='TestPayment', amount=PaymentAmount( @@ -168,26 +143,17 @@ def create_link_token_for_payment(): value=100.00 ) ) - response = client.payment_initiation_payment_create( - request - ) + response = client.payment_initiation_payment_create(req) pretty_print_response(response.to_dict()) - # We store the payment_id in memory for demo purposes - in production, store it in a secure - # persistent data store along with the Payment metadata, such as userId. payment_id = response['payment_id'] linkRequest = LinkTokenCreateRequest( - # The 'payment_initiation' product has to be the only element in the 'products' list. products=[Products('payment_initiation')], client_name='Plaid Test', - # Institutions from all listed countries will be shown. country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)), language='en', user=LinkTokenCreateRequestUser( - # This should correspond to a unique id for the current user. - # Typically, this will be a user ID number from your application. - # Personally identifiable information, such as an email address or phone number, should not be used here. client_user_id=str(time.time()) ), payment_initiation=LinkTokenCreateRequestPaymentInitiation( @@ -195,20 +161,19 @@ def create_link_token_for_payment(): ) ) - if PLAID_REDIRECT_URI!=None: - linkRequest['redirect_uri']=PLAID_REDIRECT_URI + if PLAID_REDIRECT_URI: + linkRequest['redirect_uri'] = PLAID_REDIRECT_URI linkResponse = client.link_token_create(linkRequest) pretty_print_response(linkResponse.to_dict()) - return jsonify(linkResponse.to_dict()) + return JsonResponse(linkResponse.to_dict()) except plaid.ApiException as e: - return json.loads(e.body) + return JsonResponse(json.loads(e.body)) - -@app.route('/api/create_link_token', methods=['POST']) -def create_link_token(): +@require_POST +def create_link_token(request): global user_token try: - request = LinkTokenCreateRequest( + req = LinkTokenCreateRequest( products=products, client_name="Plaid Quickstart", country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)), @@ -217,38 +182,33 @@ def create_link_token(): client_user_id=str(time.time()) ) ) - if PLAID_REDIRECT_URI!=None: - request['redirect_uri']=PLAID_REDIRECT_URI + if PLAID_REDIRECT_URI: + req['redirect_uri'] = PLAID_REDIRECT_URI if Products('statements') in products: - statements=LinkTokenCreateRequestStatements( + statements = LinkTokenCreateRequestStatements( end_date=date.today(), start_date=date.today()-timedelta(days=30) ) - request['statements']=statements + req['statements'] = statements cra_products = ["cra_base_report", "cra_income_insights", "cra_partner_insights"] if any(product in cra_products for product in PLAID_PRODUCTS): - request['user_token'] = user_token - request['consumer_report_permissible_purpose'] = ConsumerReportPermissiblePurpose('ACCOUNT_REVIEW_CREDIT') - request['cra_options'] = LinkTokenCreateRequestCraOptions( + req['user_token'] = user_token + req['consumer_report_permissible_purpose'] = ConsumerReportPermissiblePurpose('ACCOUNT_REVIEW_CREDIT') + req['cra_options'] = LinkTokenCreateRequestCraOptions( days_requested=60 ) - # create link token - response = client.link_token_create(request) - return jsonify(response.to_dict()) + response = client.link_token_create(req) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - print(e) - return json.loads(e.body) + return JsonResponse(json.loads(e.body)) -# Create a user token which can be used for Plaid Check, Income, or Multi-Item link flows -# https://plaid.com/docs/api/users/#usercreate -@app.route('/api/create_user_token', methods=['POST']) -def create_user_token(): +@require_POST +def create_user_token(request): global user_token try: consumer_report_user_identity = None user_create_request = UserCreateRequest( - # Typically this will be a user ID number from your application. client_user_id="user_" + str(uuid.uuid4()) ) @@ -257,9 +217,9 @@ def create_user_token(): consumer_report_user_identity = ConsumerReportUserIdentity( first_name="Harry", last_name="Potter", - phone_numbers= ['+16174567890'], - emails= ['harrypotter@example.com'], - primary_address= { + phone_numbers=['+16174567890'], + emails=['harrypotter@example.com'], + primary_address={ "city": 'New York', "region": 'NY', "street": '4 Privet Drive', @@ -271,166 +231,109 @@ def create_user_token(): user_response = client.user_create(user_create_request) user_token = user_response['user_token'] - return jsonify(user_response.to_dict()) + return JsonResponse(user_response.to_dict()) except plaid.ApiException as e: - print(e) - return jsonify(json.loads(e.body)), e.status - - -# Exchange token flow - exchange a Link public_token for -# an API access_token -# https://plaid.com/docs/#exchange-token-flow - + return JsonResponse(json.loads(e.body)), e.status -@app.route('/api/set_access_token', methods=['POST']) -def get_access_token(): +@require_POST +def get_access_token(request): global access_token global item_id global transfer_id - public_token = request.form['public_token'] + public_token = request.POST['public_token'] try: exchange_request = ItemPublicTokenExchangeRequest( public_token=public_token) exchange_response = client.item_public_token_exchange(exchange_request) access_token = exchange_response['access_token'] item_id = exchange_response['item_id'] - return jsonify(exchange_response.to_dict()) + return JsonResponse(exchange_response.to_dict()) except plaid.ApiException as e: - return json.loads(e.body) - - -# Retrieve ACH or ETF account numbers for an Item -# https://plaid.com/docs/#auth - + return JsonResponse(json.loads(e.body)) -@app.route('/api/auth', methods=['GET']) -def get_auth(): +@require_GET +def get_auth(request): try: - request = AuthGetRequest( + req = AuthGetRequest( access_token=access_token ) - response = client.auth_get(request) - pretty_print_response(response.to_dict()) - return jsonify(response.to_dict()) + response = client.auth_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - - -# Retrieve Transactions for an Item -# https://plaid.com/docs/#transactions - + return JsonResponse(format_error(e)) -@app.route('/api/transactions', methods=['GET']) -def get_transactions(): - # Set cursor to empty to receive all historical updates +@require_GET +def get_transactions(request): cursor = '' - - # New transaction updates since "cursor" added = [] modified = [] - removed = [] # Removed transaction ids + removed = [] has_more = True try: - # Iterate through each page of new transaction updates for item while has_more: - request = TransactionsSyncRequest( + req = TransactionsSyncRequest( access_token=access_token, cursor=cursor, ) - response = client.transactions_sync(request).to_dict() + response = client.transactions_sync(req).to_dict() cursor = response['next_cursor'] - # If no transactions are available yet, wait and poll the endpoint. - # Normally, we would listen for a webhook, but the Quickstart doesn't - # support webhooks. For a webhook example, see - # https://github.com/plaid/tutorial-resources or - # https://github.com/plaid/pattern if cursor == '': time.sleep(2) - continue - # If cursor is not an empty string, we got results, - # so add this page of results + continue added.extend(response['added']) modified.extend(response['modified']) removed.extend(response['removed']) has_more = response['has_more'] pretty_print_response(response) - # Return the 8 most recent transactions latest_transactions = sorted(added, key=lambda t: t['date'])[-8:] - return jsonify({ + return JsonResponse({ 'latest_transactions': latest_transactions}) - except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - + return JsonResponse(format_error(e)) -# Retrieve Identity data for an Item -# https://plaid.com/docs/#identity - - -@app.route('/api/identity', methods=['GET']) -def get_identity(): +@require_GET +def get_identity(request): try: - request = IdentityGetRequest( + req = IdentityGetRequest( access_token=access_token ) - response = client.identity_get(request) + response = client.identity_get(req) pretty_print_response(response.to_dict()) - return jsonify( + return JsonResponse( {'error': None, 'identity': response.to_dict()['accounts']}) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - - -# Retrieve real-time balance data for each of an Item's accounts -# https://plaid.com/docs/#balance + return JsonResponse(format_error(e)) - -@app.route('/api/balance', methods=['GET']) -def get_balance(): +@require_GET +def get_balance(request): try: - request = AccountsBalanceGetRequest( + req = AccountsBalanceGetRequest( access_token=access_token ) - response = client.accounts_balance_get(request) + response = client.accounts_balance_get(req) pretty_print_response(response.to_dict()) - return jsonify(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - - -# Retrieve an Item's accounts -# https://plaid.com/docs/#accounts + return JsonResponse(format_error(e)) - -@app.route('/api/accounts', methods=['GET']) -def get_accounts(): +@require_GET +def get_accounts(request): try: - request = AccountsGetRequest( + req = AccountsGetRequest( access_token=access_token ) - response = client.accounts_get(request) + response = client.accounts_get(req) pretty_print_response(response.to_dict()) - return jsonify(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - - -# Create and then retrieve an Asset Report for one or more Items. Note that an -# Asset Report can contain up to 100 items, but for simplicity we're only -# including one Item here. -# https://plaid.com/docs/#assets - + return JsonResponse(format_error(e)) -@app.route('/api/assets', methods=['GET']) -def get_assets(): +@require_GET +def get_assets(request): try: - request = AssetReportCreateRequest( + req = AssetReportCreateRequest( access_tokens=[access_token], days_requested=60, options=AssetReportCreateRequestOptions( @@ -448,87 +351,67 @@ def get_assets(): ) ) - response = client.asset_report_create(request) + response = client.asset_report_create(req) pretty_print_response(response.to_dict()) asset_report_token = response['asset_report_token'] - # Poll for the completion of the Asset Report. - request = AssetReportGetRequest( + req = AssetReportGetRequest( asset_report_token=asset_report_token, ) - response = poll_with_retries(lambda: client.asset_report_get(request)) + response = poll_with_retries(lambda: client.asset_report_get(req)) asset_report_json = response['report'] - request = AssetReportPDFGetRequest( + req = AssetReportPDFGetRequest( asset_report_token=asset_report_token, ) - pdf = client.asset_report_pdf_get(request) - return jsonify({ + pdf = client.asset_report_pdf_get(req) + return JsonResponse({ 'error': None, 'json': asset_report_json.to_dict(), 'pdf': base64.b64encode(pdf.read()).decode('utf-8'), }) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - - -# Retrieve investment holdings data for an Item -# https://plaid.com/docs/#investments - + return JsonResponse(format_error(e)) -@app.route('/api/holdings', methods=['GET']) -def get_holdings(): +@require_GET +def get_holdings(request): try: - request = InvestmentsHoldingsGetRequest(access_token=access_token) - response = client.investments_holdings_get(request) + req = InvestmentsHoldingsGetRequest(access_token=access_token) + response = client.investments_holdings_get(req) pretty_print_response(response.to_dict()) - return jsonify({'error': None, 'holdings': response.to_dict()}) + return JsonResponse({'error': None, 'holdings': response.to_dict()}) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - - -# Retrieve Investment Transactions for an Item -# https://plaid.com/docs/#investments - - -@app.route('/api/investments_transactions', methods=['GET']) -def get_investments_transactions(): - # Pull transactions for the last 30 days + return JsonResponse(format_error(e)) +@require_GET +def get_investments_transactions(request): start_date = (dt.datetime.now() - dt.timedelta(days=(30))) end_date = dt.datetime.now() try: options = InvestmentsTransactionsGetRequestOptions() - request = InvestmentsTransactionsGetRequest( + req = InvestmentsTransactionsGetRequest( access_token=access_token, start_date=start_date.date(), end_date=end_date.date(), options=options ) response = client.investments_transactions_get( - request) + req) pretty_print_response(response.to_dict()) - return jsonify( + return JsonResponse( {'error': None, 'investments_transactions': response.to_dict()}) - except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - -# This functionality is only relevant for the ACH Transfer product. -# Authorize a transfer + return JsonResponse(format_error(e)) -@app.route('/api/transfer_authorize', methods=['GET']) -def transfer_authorization(): +@require_GET +def transfer_authorization(request): global authorization_id global account_id - request = AccountsGetRequest(access_token=access_token) - response = client.accounts_get(request) + req = AccountsGetRequest(access_token=access_token) + response = client.accounts_get(req) account_id = response['accounts'][0]['account_id'] try: - request = TransferAuthorizationCreateRequest( + req = TransferAuthorizationCreateRequest( access_token=access_token, account_id=account_id, type=TransferType('debit'), @@ -547,122 +430,97 @@ def transfer_authorization(): ), ), ) - response = client.transfer_authorization_create(request) + response = client.transfer_authorization_create(req) pretty_print_response(response.to_dict()) authorization_id = response['authorization']['id'] - return jsonify(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - -# Create Transfer for a specified Transfer ID + return JsonResponse(format_error(e)) -@app.route('/api/transfer_create', methods=['GET']) -def transfer(): +@require_GET +def transfer(request): try: - request = TransferCreateRequest( + req = TransferCreateRequest( access_token=access_token, account_id=account_id, authorization_id=authorization_id, description='Debit') - response = client.transfer_create(request) + response = client.transfer_create(req) pretty_print_response(response.to_dict()) - return jsonify(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) + return JsonResponse(format_error(e)) -@app.route('/api/statements', methods=['GET']) -def statements(): +@require_GET +def statements(request): try: - request = StatementsListRequest(access_token=access_token) - response = client.statements_list(request) + req = StatementsListRequest(access_token=access_token) + response = client.statements_list(req) pretty_print_response(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) + return JsonResponse(format_error(e)) try: - request = StatementsDownloadRequest( + req = StatementsDownloadRequest( access_token=access_token, statement_id=response['accounts'][0]['statements'][0]['statement_id'] ) - pdf = client.statements_download(request) - return jsonify({ + pdf = client.statements_download(req) + return JsonResponse({ 'error': None, 'json': response.to_dict(), 'pdf': base64.b64encode(pdf.read()).decode('utf-8'), }) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) + return JsonResponse(format_error(e)) - - - -@app.route('/api/signal_evaluate', methods=['GET']) -def signal(): +@require_GET +def signal(request): global account_id - request = AccountsGetRequest(access_token=access_token) - response = client.accounts_get(request) + req = AccountsGetRequest(access_token=access_token) + response = client.accounts_get(req) account_id = response['accounts'][0]['account_id'] try: - request = SignalEvaluateRequest( + req = SignalEvaluateRequest( access_token=access_token, account_id=account_id, client_transaction_id='txn1234', amount=100.00) - response = client.signal_evaluate(request) + response = client.signal_evaluate(req) pretty_print_response(response.to_dict()) - return jsonify(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - + return JsonResponse(format_error(e)) -# This functionality is only relevant for the UK Payment Initiation product. -# Retrieve Payment for a specified Payment ID - - -@app.route('/api/payment', methods=['GET']) -def payment(): +@require_GET +def payment(request): global payment_id try: - request = PaymentInitiationPaymentGetRequest(payment_id=payment_id) - response = client.payment_initiation_payment_get(request) + req = PaymentInitiationPaymentGetRequest(payment_id=payment_id) + response = client.payment_initiation_payment_get(req) pretty_print_response(response.to_dict()) - return jsonify({'error': None, 'payment': response.to_dict()}) + return JsonResponse({'error': None, 'payment': response.to_dict()}) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - + return JsonResponse(format_error(e)) -# Retrieve high-level information about an Item -# https://plaid.com/docs/#retrieve-item - - -@app.route('/api/item', methods=['GET']) -def item(): +@require_GET +def item(request): try: - request = ItemGetRequest(access_token=access_token) - response = client.item_get(request) - request = InstitutionsGetByIdRequest( + req = ItemGetRequest(access_token=access_token) + response = client.item_get(req) + req = InstitutionsGetByIdRequest( institution_id=response['item']['institution_id'], country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)) ) - institution_response = client.institutions_get_by_id(request) + institution_response = client.institutions_get_by_id(req) pretty_print_response(response.to_dict()) pretty_print_response(institution_response.to_dict()) - return jsonify({'error': None, 'item': response.to_dict()[ + return JsonResponse({'error': None, 'item': response.to_dict()[ 'item'], 'institution': institution_response.to_dict()['institution']}) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - -# Retrieve CRA Base Report and PDF -# Base report: https://plaid.com/docs/check/api/#cracheck_reportbase_reportget -# PDF: https://plaid.com/docs/check/api/#cracheck_reportpdfget -@app.route('/api/cra/get_base_report', methods=['GET']) -def cra_check_report(): + return JsonResponse(format_error(e)) + +@require_GET +def cra_check_report(request): try: get_response = poll_with_retries(lambda: client.cra_check_report_base_report_get( CraCheckReportBaseReportGetRequest(user_token=user_token, item_ids=[]) @@ -672,19 +530,15 @@ def cra_check_report(): pdf_response = client.cra_check_report_pdf_get( CraCheckReportPDFGetRequest(user_token=user_token) ) - return jsonify({ + return JsonResponse({ 'report': get_response.to_dict()['report'], 'pdf': base64.b64encode(pdf_response.read()).decode('utf-8') }) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - -# Retrieve CRA Income Insights and PDF with Insights -# Income insights: https://plaid.com/docs/check/api/#cracheck_reportincome_insightsget -# PDF w/ income insights: https://plaid.com/docs/check/api/#cracheck_reportpdfget -@app.route('/api/cra/get_income_insights', methods=['GET']) -def cra_income_insights(): + return JsonResponse(format_error(e)) + +@require_GET +def cra_income_insights(request): try: get_response = poll_with_retries(lambda: client.cra_check_report_income_insights_get( CraCheckReportIncomeInsightsGetRequest(user_token=user_token)) @@ -695,34 +549,25 @@ def cra_income_insights(): CraCheckReportPDFGetRequest(user_token=user_token, add_ons=[CraPDFAddOns('cra_income_insights')]), ) - return jsonify({ + return JsonResponse({ 'report': get_response.to_dict()['report'], 'pdf': base64.b64encode(pdf_response.read()).decode('utf-8') }) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) + return JsonResponse(format_error(e)) -# Retrieve CRA Partner Insights -# https://plaid.com/docs/check/api/#cracheck_reportpartner_insightsget -@app.route('/api/cra/get_partner_insights', methods=['GET']) -def cra_partner_insights(): +@require_GET +def cra_partner_insights(request): try: response = poll_with_retries(lambda: client.cra_check_report_partner_insights_get( CraCheckReportPartnerInsightsGetRequest(user_token=user_token) )) pretty_print_response(response.to_dict()) - return jsonify(response.to_dict()) + return JsonResponse(response.to_dict()) except plaid.ApiException as e: - error_response = format_error(e) - return jsonify(error_response) - -# Since this quickstart does not support webhooks, this function can be used to poll -# an API that would otherwise be triggered by a webhook. -# For a webhook example, see -# https://github.com/plaid/tutorial-resources or -# https://github.com/plaid/pattern + return JsonResponse(format_error(e)) + def poll_with_retries(request_callback, ms=1000, retries_left=20): while retries_left > 0: try: @@ -738,12 +583,9 @@ def poll_with_retries(request_callback, ms=1000, retries_left=20): time.sleep(ms / 1000) def pretty_print_response(response): - print(json.dumps(response, indent=2, sort_keys=True, default=str)) + print(json.dumps(response, indent=2, sort_keys=True, default=str)) def format_error(e): response = json.loads(e.body) return {'error': {'status_code': e.status, 'display_message': response['error_message'], 'error_code': response['error_code'], 'error_type': response['error_type']}} - -if __name__ == '__main__': - app.run(port=int(os.getenv('PORT', 8000))) diff --git a/common-services/src/bank_connectivity/plaid/models.py b/common-services/src/bank_connectivity/plaid/models.py new file mode 100644 index 00000000..1c19cefc --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/models.py @@ -0,0 +1,46 @@ +from django.db import models + +class PlaidAccount(models.Model): + account_id = models.CharField(max_length=100, unique=True) + name = models.CharField(max_length=100) + official_name = models.CharField(max_length=100, null=True, blank=True) + subtype = models.CharField(max_length=50) + type = models.CharField(max_length=50) + mask = models.CharField(max_length=4, null=True, blank=True) + balance_available = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + balance_current = models.DecimalField(max_digits=10, decimal_places=2) + balance_limit = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + institution_name = models.CharField(max_length=100) + institution_id = models.CharField(max_length=100) + + def __str__(self): + return self.name + +class PlaidTransaction(models.Model): + transaction_id = models.CharField(max_length=100, unique=True) + account = models.ForeignKey(PlaidAccount, on_delete=models.CASCADE) + amount = models.DecimalField(max_digits=10, decimal_places=2) + date = models.DateField() + name = models.CharField(max_length=100) + category = models.CharField(max_length=100, null=True, blank=True) + category_id = models.CharField(max_length=100, null=True, blank=True) + pending = models.BooleanField(default=False) + transaction_type = models.CharField(max_length=50) + payment_channel = models.CharField(max_length=50) + merchant_name = models.CharField(max_length=100, null=True, blank=True) + iso_currency_code = models.CharField(max_length=3, null=True, blank=True) + unofficial_currency_code = models.CharField(max_length=3, null=True, blank=True) + + def __str__(self): + return self.name + +class PlaidItem(models.Model): + item_id = models.CharField(max_length=100, unique=True) + access_token = models.CharField(max_length=100) + institution_name = models.CharField(max_length=100) + institution_id = models.CharField(max_length=100) + webhook = models.URLField(null=True, blank=True) + error = models.JSONField(null=True, blank=True) + + def __str__(self): + return self.institution_name diff --git a/common-services/src/bank_connectivity/plaid/services.py b/common-services/src/bank_connectivity/plaid/services.py new file mode 100644 index 00000000..041c6a71 --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/services.py @@ -0,0 +1,591 @@ +import base64 +import os +import datetime as dt +import json +import time +from datetime import date, timedelta +import uuid + +from dotenv import load_dotenv +from django.http import JsonResponse +from django.views.decorators.http import require_POST, require_GET +import plaid +from plaid.model.payment_amount import PaymentAmount +from plaid.model.payment_amount_currency import PaymentAmountCurrency +from plaid.model.products import Products +from plaid.model.country_code import CountryCode +from plaid.model.recipient_bacs_nullable import RecipientBACSNullable +from plaid.model.payment_initiation_address import PaymentInitiationAddress +from plaid.model.payment_initiation_recipient_create_request import PaymentInitiationRecipientCreateRequest +from plaid.model.payment_initiation_payment_create_request import PaymentInitiationPaymentCreateRequest +from plaid.model.payment_initiation_payment_get_request import PaymentInitiationPaymentGetRequest +from plaid.model.link_token_create_request_payment_initiation import LinkTokenCreateRequestPaymentInitiation +from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest +from plaid.model.link_token_create_request import LinkTokenCreateRequest +from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser +from plaid.model.user_create_request import UserCreateRequest +from plaid.model.consumer_report_user_identity import ConsumerReportUserIdentity +from plaid.model.asset_report_create_request import AssetReportCreateRequest +from plaid.model.asset_report_create_request_options import AssetReportCreateRequestOptions +from plaid.model.asset_report_user import AssetReportUser +from plaid.model.asset_report_get_request import AssetReportGetRequest +from plaid.model.asset_report_pdf_get_request import AssetReportPDFGetRequest +from plaid.model.auth_get_request import AuthGetRequest +from plaid.model.transactions_sync_request import TransactionsSyncRequest +from plaid.model.identity_get_request import IdentityGetRequest +from plaid.model.investments_transactions_get_request_options import InvestmentsTransactionsGetRequestOptions +from plaid.model.investments_transactions_get_request import InvestmentsTransactionsGetRequest +from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest +from plaid.model.accounts_get_request import AccountsGetRequest +from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest +from plaid.model.item_get_request import ItemGetRequest +from plaid.model.institutions_get_by_id_request import InstitutionsGetByIdRequest +from plaid.model.transfer_authorization_create_request import TransferAuthorizationCreateRequest +from plaid.model.transfer_create_request import TransferCreateRequest +from plaid.model.transfer_get_request import TransferGetRequest +from plaid.model.transfer_network import TransferNetwork +from plaid.model.transfer_type import TransferType +from plaid.model.transfer_authorization_user_in_request import TransferAuthorizationUserInRequest +from plaid.model.ach_class import ACHClass +from plaid.model.transfer_create_idempotency_key import TransferCreateIdempotencyKey +from plaid.model.transfer_user_address_in_request import TransferUserAddressInRequest +from plaid.model.signal_evaluate_request import SignalEvaluateRequest +from plaid.model.statements_list_request import StatementsListRequest +from plaid.model.link_token_create_request_statements import LinkTokenCreateRequestStatements +from plaid.model.link_token_create_request_cra_options import LinkTokenCreateRequestCraOptions +from plaid.model.statements_download_request import StatementsDownloadRequest +from plaid.model.consumer_report_permissible_purpose import ConsumerReportPermissiblePurpose +from plaid.model.cra_check_report_base_report_get_request import CraCheckReportBaseReportGetRequest +from plaid.model.cra_check_report_pdf_get_request import CraCheckReportPDFGetRequest +from plaid.model.cra_check_report_income_insights_get_request import CraCheckReportIncomeInsightsGetRequest +from plaid.model.cra_check_report_partner_insights_get_request import CraCheckReportPartnerInsightsGetRequest +from plaid.model.cra_pdf_add_ons import CraPDFAddOns +from plaid.api import plaid_api + +load_dotenv() + +PLAID_CLIENT_ID = os.getenv('PLAID_CLIENT_ID') +PLAID_SECRET = os.getenv('PLAID_SECRET') +PLAID_ENV = os.getenv('PLAID_ENV', 'sandbox') +PLAID_PRODUCTS = os.getenv('PLAID_PRODUCTS', 'transactions').split(',') +PLAID_COUNTRY_CODES = os.getenv('PLAID_COUNTRY_CODES', 'US').split(',') + +def empty_to_none(field): + value = os.getenv(field) + if value is None or len(value) == 0: + return None + return value + +host = plaid.Environment.Sandbox + +if PLAID_ENV == 'sandbox': + host = plaid.Environment.Sandbox + +if PLAID_ENV == 'production': + host = plaid.Environment.Production + +PLAID_REDIRECT_URI = empty_to_none('PLAID_REDIRECT_URI') + +configuration = plaid.Configuration( + host=host, + api_key={ + 'clientId': PLAID_CLIENT_ID, + 'secret': PLAID_SECRET, + 'plaidVersion': '2020-09-14' + } +) + +api_client = plaid.ApiClient(configuration) +client = plaid_api.PlaidApi(api_client) + +products = [] +for product in PLAID_PRODUCTS: + products.append(Products(product)) + +access_token = None +payment_id = None +transfer_id = None +user_token = None +item_id = None + +@require_POST +def info(request): + global access_token + global item_id + return JsonResponse({ + 'item_id': item_id, + 'access_token': access_token, + 'products': PLAID_PRODUCTS + }) + +@require_POST +def create_link_token_for_payment(request): + global payment_id + try: + req = PaymentInitiationRecipientCreateRequest( + name='John Doe', + bacs=RecipientBACSNullable(account='26207729', sort_code='560029'), + address=PaymentInitiationAddress( + street=['street name 999'], + city='city', + postal_code='99999', + country='GB' + ) + ) + response = client.payment_initiation_recipient_create(req) + recipient_id = response['recipient_id'] + + req = PaymentInitiationPaymentCreateRequest( + recipient_id=recipient_id, + reference='TestPayment', + amount=PaymentAmount( + PaymentAmountCurrency('GBP'), + value=100.00 + ) + ) + response = client.payment_initiation_payment_create(req) + pretty_print_response(response.to_dict()) + + payment_id = response['payment_id'] + + linkRequest = LinkTokenCreateRequest( + products=[Products('payment_initiation')], + client_name='Plaid Test', + country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)), + language='en', + user=LinkTokenCreateRequestUser( + client_user_id=str(time.time()) + ), + payment_initiation=LinkTokenCreateRequestPaymentInitiation( + payment_id=payment_id + ) + ) + + if PLAID_REDIRECT_URI: + linkRequest['redirect_uri'] = PLAID_REDIRECT_URI + linkResponse = client.link_token_create(linkRequest) + pretty_print_response(linkResponse.to_dict()) + return JsonResponse(linkResponse.to_dict()) + except plaid.ApiException as e: + return JsonResponse(json.loads(e.body)) + +@require_POST +def create_link_token(request): + global user_token + try: + req = LinkTokenCreateRequest( + products=products, + client_name="Plaid Quickstart", + country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)), + language='en', + user=LinkTokenCreateRequestUser( + client_user_id=str(time.time()) + ) + ) + if PLAID_REDIRECT_URI: + req['redirect_uri'] = PLAID_REDIRECT_URI + if Products('statements') in products: + statements = LinkTokenCreateRequestStatements( + end_date=date.today(), + start_date=date.today()-timedelta(days=30) + ) + req['statements'] = statements + + cra_products = ["cra_base_report", "cra_income_insights", "cra_partner_insights"] + if any(product in cra_products for product in PLAID_PRODUCTS): + req['user_token'] = user_token + req['consumer_report_permissible_purpose'] = ConsumerReportPermissiblePurpose('ACCOUNT_REVIEW_CREDIT') + req['cra_options'] = LinkTokenCreateRequestCraOptions( + days_requested=60 + ) + response = client.link_token_create(req) + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(json.loads(e.body)) + +@require_POST +def create_user_token(request): + global user_token + try: + consumer_report_user_identity = None + user_create_request = UserCreateRequest( + client_user_id="user_" + str(uuid.uuid4()) + ) + + cra_products = ["cra_base_report", "cra_income_insights", "cra_partner_insights"] + if any(product in cra_products for product in PLAID_PRODUCTS): + consumer_report_user_identity = ConsumerReportUserIdentity( + first_name="Harry", + last_name="Potter", + phone_numbers=['+16174567890'], + emails=['harrypotter@example.com'], + primary_address={ + "city": 'New York', + "region": 'NY', + "street": '4 Privet Drive', + "postal_code": '11111', + "country": 'US' + } + ) + user_create_request["consumer_report_user_identity"] = consumer_report_user_identity + + user_response = client.user_create(user_create_request) + user_token = user_response['user_token'] + return JsonResponse(user_response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(json.loads(e.body)), e.status + +@require_POST +def get_access_token(request): + global access_token + global item_id + global transfer_id + public_token = request.POST['public_token'] + try: + exchange_request = ItemPublicTokenExchangeRequest( + public_token=public_token) + exchange_response = client.item_public_token_exchange(exchange_request) + access_token = exchange_response['access_token'] + item_id = exchange_response['item_id'] + return JsonResponse(exchange_response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(json.loads(e.body)) + +@require_GET +def get_auth(request): + try: + req = AuthGetRequest( + access_token=access_token + ) + response = client.auth_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_transactions(request): + cursor = '' + added = [] + modified = [] + removed = [] + has_more = True + try: + while has_more: + req = TransactionsSyncRequest( + access_token=access_token, + cursor=cursor, + ) + response = client.transactions_sync(req).to_dict() + cursor = response['next_cursor'] + if cursor == '': + time.sleep(2) + continue + added.extend(response['added']) + modified.extend(response['modified']) + removed.extend(response['removed']) + has_more = response['has_more'] + pretty_print_response(response) + + latest_transactions = sorted(added, key=lambda t: t['date'])[-8:] + return JsonResponse({ + 'latest_transactions': latest_transactions}) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_identity(request): + try: + req = IdentityGetRequest( + access_token=access_token + ) + response = client.identity_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse( + {'error': None, 'identity': response.to_dict()['accounts']}) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_balance(request): + try: + req = AccountsBalanceGetRequest( + access_token=access_token + ) + response = client.accounts_balance_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_accounts(request): + try: + req = AccountsGetRequest( + access_token=access_token + ) + response = client.accounts_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_assets(request): + try: + req = AssetReportCreateRequest( + access_tokens=[access_token], + days_requested=60, + options=AssetReportCreateRequestOptions( + webhook='https://www.example.com', + client_report_id='123', + user=AssetReportUser( + client_user_id='789', + first_name='Jane', + middle_name='Leah', + last_name='Doe', + ssn='123-45-6789', + phone_number='(555) 123-4567', + email='jane.doe@example.com', + ) + ) + ) + + response = client.asset_report_create(req) + pretty_print_response(response.to_dict()) + asset_report_token = response['asset_report_token'] + + req = AssetReportGetRequest( + asset_report_token=asset_report_token, + ) + response = poll_with_retries(lambda: client.asset_report_get(req)) + asset_report_json = response['report'] + + req = AssetReportPDFGetRequest( + asset_report_token=asset_report_token, + ) + pdf = client.asset_report_pdf_get(req) + return JsonResponse({ + 'error': None, + 'json': asset_report_json.to_dict(), + 'pdf': base64.b64encode(pdf.read()).decode('utf-8'), + }) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_holdings(request): + try: + req = InvestmentsHoldingsGetRequest(access_token=access_token) + response = client.investments_holdings_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse({'error': None, 'holdings': response.to_dict()}) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def get_investments_transactions(request): + start_date = (dt.datetime.now() - dt.timedelta(days=(30))) + end_date = dt.datetime.now() + try: + options = InvestmentsTransactionsGetRequestOptions() + req = InvestmentsTransactionsGetRequest( + access_token=access_token, + start_date=start_date.date(), + end_date=end_date.date(), + options=options + ) + response = client.investments_transactions_get( + req) + pretty_print_response(response.to_dict()) + return JsonResponse( + {'error': None, 'investments_transactions': response.to_dict()}) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def transfer_authorization(request): + global authorization_id + global account_id + req = AccountsGetRequest(access_token=access_token) + response = client.accounts_get(req) + account_id = response['accounts'][0]['account_id'] + try: + req = TransferAuthorizationCreateRequest( + access_token=access_token, + account_id=account_id, + type=TransferType('debit'), + network=TransferNetwork('ach'), + amount='1.00', + ach_class=ACHClass('ppd'), + user=TransferAuthorizationUserInRequest( + legal_name='FirstName LastName', + email_address='foobar@email.com', + address=TransferUserAddressInRequest( + street='123 Main St.', + city='San Francisco', + region='CA', + postal_code='94053', + country='US' + ), + ), + ) + response = client.transfer_authorization_create(req) + pretty_print_response(response.to_dict()) + authorization_id = response['authorization']['id'] + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def transfer(request): + try: + req = TransferCreateRequest( + access_token=access_token, + account_id=account_id, + authorization_id=authorization_id, + description='Debit') + response = client.transfer_create(req) + pretty_print_response(response.to_dict()) + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def statements(request): + try: + req = StatementsListRequest(access_token=access_token) + response = client.statements_list(req) + pretty_print_response(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + try: + req = StatementsDownloadRequest( + access_token=access_token, + statement_id=response['accounts'][0]['statements'][0]['statement_id'] + ) + pdf = client.statements_download(req) + return JsonResponse({ + 'error': None, + 'json': response.to_dict(), + 'pdf': base64.b64encode(pdf.read()).decode('utf-8'), + }) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def signal(request): + global account_id + req = AccountsGetRequest(access_token=access_token) + response = client.accounts_get(req) + account_id = response['accounts'][0]['account_id'] + try: + req = SignalEvaluateRequest( + access_token=access_token, + account_id=account_id, + client_transaction_id='txn1234', + amount=100.00) + response = client.signal_evaluate(req) + pretty_print_response(response.to_dict()) + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def payment(request): + global payment_id + try: + req = PaymentInitiationPaymentGetRequest(payment_id=payment_id) + response = client.payment_initiation_payment_get(req) + pretty_print_response(response.to_dict()) + return JsonResponse({'error': None, 'payment': response.to_dict()}) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def item(request): + try: + req = ItemGetRequest(access_token=access_token) + response = client.item_get(req) + req = InstitutionsGetByIdRequest( + institution_id=response['item']['institution_id'], + country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)) + ) + institution_response = client.institutions_get_by_id(req) + pretty_print_response(response.to_dict()) + pretty_print_response(institution_response.to_dict()) + return JsonResponse({'error': None, 'item': response.to_dict()[ + 'item'], 'institution': institution_response.to_dict()['institution']}) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def cra_check_report(request): + try: + get_response = poll_with_retries(lambda: client.cra_check_report_base_report_get( + CraCheckReportBaseReportGetRequest(user_token=user_token, item_ids=[]) + )) + pretty_print_response(get_response.to_dict()) + + pdf_response = client.cra_check_report_pdf_get( + CraCheckReportPDFGetRequest(user_token=user_token) + ) + return JsonResponse({ + 'report': get_response.to_dict()['report'], + 'pdf': base64.b64encode(pdf_response.read()).decode('utf-8') + }) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def cra_income_insights(request): + try: + get_response = poll_with_retries(lambda: client.cra_check_report_income_insights_get( + CraCheckReportIncomeInsightsGetRequest(user_token=user_token)) + ) + pretty_print_response(get_response.to_dict()) + + pdf_response = client.cra_check_report_pdf_get( + CraCheckReportPDFGetRequest(user_token=user_token, add_ons=[CraPDFAddOns('cra_income_insights')]), + ) + + return JsonResponse({ + 'report': get_response.to_dict()['report'], + 'pdf': base64.b64encode(pdf_response.read()).decode('utf-8') + }) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +@require_GET +def cra_partner_insights(request): + try: + response = poll_with_retries(lambda: client.cra_check_report_partner_insights_get( + CraCheckReportPartnerInsightsGetRequest(user_token=user_token) + )) + pretty_print_response(response.to_dict()) + + return JsonResponse(response.to_dict()) + except plaid.ApiException as e: + return JsonResponse(format_error(e)) + +def poll_with_retries(request_callback, ms=1000, retries_left=20): + while retries_left > 0: + try: + return request_callback() + except plaid.ApiException as e: + response = json.loads(e.body) + if response['error_code'] != 'PRODUCT_NOT_READY': + raise e + elif retries_left == 0: + raise Exception('Ran out of retries while polling') from e + else: + retries_left -= 1 + time.sleep(ms / 1000) + +def pretty_print_response(response): + print(json.dumps(response, indent=2, sort_keys=True, default=str)) + +def format_error(e): + response = json.loads(e.body) + return {'error': {'status_code': e.status, 'display_message': + response['error_message'], 'error_code': response['error_code'], 'error_type': response['error_type']}} diff --git a/common-services/src/bank_connectivity/plaid/tests/test_api.py b/common-services/src/bank_connectivity/plaid/tests/test_api.py new file mode 100644 index 00000000..394bf124 --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/tests/test_api.py @@ -0,0 +1,248 @@ +import json +from django.test import TestCase, Client +from django.urls import reverse +from unittest.mock import patch +from plaid.model.payment_amount import PaymentAmount +from plaid.model.payment_amount_currency import PaymentAmountCurrency +from plaid.model.products import Products +from plaid.model.country_code import CountryCode +from plaid.model.recipient_bacs_nullable import RecipientBACSNullable +from plaid.model.payment_initiation_address import PaymentInitiationAddress +from plaid.model.payment_initiation_recipient_create_request import PaymentInitiationRecipientCreateRequest +from plaid.model.payment_initiation_payment_create_request import PaymentInitiationPaymentCreateRequest +from plaid.model.payment_initiation_payment_get_request import PaymentInitiationPaymentGetRequest +from plaid.model.link_token_create_request_payment_initiation import LinkTokenCreateRequestPaymentInitiation +from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest +from plaid.model.link_token_create_request import LinkTokenCreateRequest +from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser +from plaid.model.user_create_request import UserCreateRequest +from plaid.model.consumer_report_user_identity import ConsumerReportUserIdentity +from plaid.model.asset_report_create_request import AssetReportCreateRequest +from plaid.model.asset_report_create_request_options import AssetReportCreateRequestOptions +from plaid.model.asset_report_user import AssetReportUser +from plaid.model.asset_report_get_request import AssetReportGetRequest +from plaid.model.asset_report_pdf_get_request import AssetReportPDFGetRequest +from plaid.model.auth_get_request import AuthGetRequest +from plaid.model.transactions_sync_request import TransactionsSyncRequest +from plaid.model.identity_get_request import IdentityGetRequest +from plaid.model.investments_transactions_get_request_options import InvestmentsTransactionsGetRequestOptions +from plaid.model.investments_transactions_get_request import InvestmentsTransactionsGetRequest +from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest +from plaid.model.accounts_get_request import AccountsGetRequest +from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest +from plaid.model.item_get_request import ItemGetRequest +from plaid.model.institutions_get_by_id_request import InstitutionsGetByIdRequest +from plaid.model.transfer_authorization_create_request import TransferAuthorizationCreateRequest +from plaid.model.transfer_create_request import TransferCreateRequest +from plaid.model.transfer_get_request import TransferGetRequest +from plaid.model.transfer_network import TransferNetwork +from plaid.model.transfer_type import TransferType +from plaid.model.transfer_authorization_user_in_request import TransferAuthorizationUserInRequest +from plaid.model.ach_class import ACHClass +from plaid.model.transfer_create_idempotency_key import TransferCreateIdempotencyKey +from plaid.model.transfer_user_address_in_request import TransferUserAddressInRequest +from plaid.model.signal_evaluate_request import SignalEvaluateRequest +from plaid.model.statements_list_request import StatementsListRequest +from plaid.model.link_token_create_request_statements import LinkTokenCreateRequestStatements +from plaid.model.link_token_create_request_cra_options import LinkTokenCreateRequestCraOptions +from plaid.model.statements_download_request import StatementsDownloadRequest +from plaid.model.consumer_report_permissible_purpose import ConsumerReportPermissiblePurpose +from plaid.model.cra_check_report_base_report_get_request import CraCheckReportBaseReportGetRequest +from plaid.model.cra_check_report_pdf_get_request import CraCheckReportPDFGetRequest +from plaid.model.cra_check_report_income_insights_get_request import CraCheckReportIncomeInsightsGetRequest +from plaid.model.cra_check_report_partner_insights_get_request import CraCheckReportPartnerInsightsGetRequest +from plaid.model.cra_pdf_add_ons import CraPDFAddOns + +class PlaidApiTests(TestCase): + def setUp(self): + self.client = Client() + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_recipient_create') + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_payment_create') + @patch('plaid.api.plaid_api.PlaidApi.link_token_create') + def test_create_link_token_for_payment(self, mock_link_token_create, mock_payment_create, mock_recipient_create): + mock_recipient_create.return_value = {'recipient_id': 'test_recipient_id'} + mock_payment_create.return_value = {'payment_id': 'test_payment_id'} + mock_link_token_create.return_value = {'link_token': 'test_link_token'} + + response = self.client.post(reverse('create_link_token_for_payment')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['link_token'], 'test_link_token') + + @patch('plaid.api.plaid_api.PlaidApi.link_token_create') + def test_create_link_token(self, mock_link_token_create): + mock_link_token_create.return_value = {'link_token': 'test_link_token'} + + response = self.client.post(reverse('create_link_token')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['link_token'], 'test_link_token') + + @patch('plaid.api.plaid_api.PlaidApi.user_create') + def test_create_user_token(self, mock_user_create): + mock_user_create.return_value = {'user_token': 'test_user_token'} + + response = self.client.post(reverse('create_user_token')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['user_token'], 'test_user_token') + + @patch('plaid.api.plaid_api.PlaidApi.item_public_token_exchange') + def test_get_access_token(self, mock_item_public_token_exchange): + mock_item_public_token_exchange.return_value = {'access_token': 'test_access_token', 'item_id': 'test_item_id'} + + response = self.client.post(reverse('get_access_token'), {'public_token': 'test_public_token'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['access_token'], 'test_access_token') + self.assertEqual(response.json()['item_id'], 'test_item_id') + + @patch('plaid.api.plaid_api.PlaidApi.auth_get') + def test_get_auth(self, mock_auth_get): + mock_auth_get.return_value = {'accounts': []} + + response = self.client.get(reverse('get_auth')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['accounts'], []) + + @patch('plaid.api.plaid_api.PlaidApi.transactions_sync') + def test_get_transactions(self, mock_transactions_sync): + mock_transactions_sync.return_value = {'added': [], 'modified': [], 'removed': [], 'has_more': False, 'next_cursor': ''} + + response = self.client.get(reverse('get_transactions')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['latest_transactions'], []) + + @patch('plaid.api.plaid_api.PlaidApi.identity_get') + def test_get_identity(self, mock_identity_get): + mock_identity_get.return_value = {'accounts': []} + + response = self.client.get(reverse('get_identity')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['identity'], []) + + @patch('plaid.api.plaid_api.PlaidApi.accounts_balance_get') + def test_get_balance(self, mock_accounts_balance_get): + mock_accounts_balance_get.return_value = {'accounts': []} + + response = self.client.get(reverse('get_balance')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['accounts'], []) + + @patch('plaid.api.plaid_api.PlaidApi.accounts_get') + def test_get_accounts(self, mock_accounts_get): + mock_accounts_get.return_value = {'accounts': []} + + response = self.client.get(reverse('get_accounts')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['accounts'], []) + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_create') + @patch('plaid.api.plaid_api.PlaidApi.asset_report_get') + @patch('plaid.api.plaid_api.PlaidApi.asset_report_pdf_get') + def test_get_assets(self, mock_asset_report_pdf_get, mock_asset_report_get, mock_asset_report_create): + mock_asset_report_create.return_value = {'asset_report_token': 'test_asset_report_token'} + mock_asset_report_get.return_value = {'report': {}} + mock_asset_report_pdf_get.return_value = b'test_pdf' + + response = self.client.get(reverse('get_assets')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['json'], {}) + self.assertEqual(response.json()['pdf'], 'dGVzdF9wZGY=') + + @patch('plaid.api.plaid_api.PlaidApi.investments_holdings_get') + def test_get_holdings(self, mock_investments_holdings_get): + mock_investments_holdings_get.return_value = {'accounts': []} + + response = self.client.get(reverse('get_holdings')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['holdings'], {'accounts': []}) + + @patch('plaid.api.plaid_api.PlaidApi.investments_transactions_get') + def test_get_investments_transactions(self, mock_investments_transactions_get): + mock_investments_transactions_get.return_value = {'investment_transactions': []} + + response = self.client.get(reverse('get_investments_transactions')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['investments_transactions'], {'investment_transactions': []}) + + @patch('plaid.api.plaid_api.PlaidApi.transfer_authorization_create') + def test_transfer_authorization(self, mock_transfer_authorization_create): + mock_transfer_authorization_create.return_value = {'authorization': {'id': 'test_authorization_id'}} + + response = self.client.get(reverse('transfer_authorization')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['authorization']['id'], 'test_authorization_id') + + @patch('plaid.api.plaid_api.PlaidApi.transfer_create') + def test_transfer(self, mock_transfer_create): + mock_transfer_create.return_value = {'transfer': {'id': 'test_transfer_id'}} + + response = self.client.get(reverse('transfer')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['transfer']['id'], 'test_transfer_id') + + @patch('plaid.api.plaid_api.PlaidApi.statements_list') + @patch('plaid.api.plaid_api.PlaidApi.statements_download') + def test_statements(self, mock_statements_download, mock_statements_list): + mock_statements_list.return_value = {'accounts': [{'statements': [{'statement_id': 'test_statement_id'}]}]} + mock_statements_download.return_value = b'test_pdf' + + response = self.client.get(reverse('statements')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['json'], {'accounts': [{'statements': [{'statement_id': 'test_statement_id'}]}]}) + self.assertEqual(response.json()['pdf'], 'dGVzdF9wZGY=') + + @patch('plaid.api.plaid_api.PlaidApi.signal_evaluate') + def test_signal(self, mock_signal_evaluate): + mock_signal_evaluate.return_value = {'score': 100} + + response = self.client.get(reverse('signal')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['score'], 100) + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_payment_get') + def test_payment(self, mock_payment_initiation_payment_get): + mock_payment_initiation_payment_get.return_value = {'payment': {}} + + response = self.client.get(reverse('payment')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['payment'], {}) + + @patch('plaid.api.plaid_api.PlaidApi.item_get') + @patch('plaid.api.plaid_api.PlaidApi.institutions_get_by_id') + def test_item(self, mock_institutions_get_by_id, mock_item_get): + mock_item_get.return_value = {'item': {'institution_id': 'test_institution_id'}} + mock_institutions_get_by_id.return_value = {'institution': {}} + + response = self.client.get(reverse('item')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['item'], {'institution_id': 'test_institution_id'}) + self.assertEqual(response.json()['institution'], {}) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_base_report_get') + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_pdf_get') + def test_cra_check_report(self, mock_cra_check_report_pdf_get, mock_cra_check_report_base_report_get): + mock_cra_check_report_base_report_get.return_value = {'report': {}} + mock_cra_check_report_pdf_get.return_value = b'test_pdf' + + response = self.client.get(reverse('cra_check_report')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['report'], {}) + self.assertEqual(response.json()['pdf'], 'dGVzdF9wZGY=') + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_income_insights_get') + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_pdf_get') + def test_cra_income_insights(self, mock_cra_check_report_pdf_get, mock_cra_check_report_income_insights_get): + mock_cra_check_report_income_insights_get.return_value = {'report': {}} + mock_cra_check_report_pdf_get.return_value = b'test_pdf' + + response = self.client.get(reverse('cra_income_insights')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['report'], {}) + self.assertEqual(response.json()['pdf'], 'dGVzdF9wZGY=') + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_partner_insights_get') + def test_cra_partner_insights(self, mock_cra_check_report_partner_insights_get): + mock_cra_check_report_partner_insights_get.return_value = {'report': {}} + + response = self.client.get(reverse('cra_partner_insights')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['report'], {}) diff --git a/common-services/src/bank_connectivity/plaid/tests/test_models.py b/common-services/src/bank_connectivity/plaid/tests/test_models.py new file mode 100644 index 00000000..43bbd304 --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/tests/test_models.py @@ -0,0 +1,105 @@ +from django.test import TestCase +from bank_connectivity.plaid.models import PlaidAccount, PlaidTransaction, PlaidItem + +class PlaidAccountModelTest(TestCase): + def setUp(self): + self.account = PlaidAccount.objects.create( + account_id='test_account_id', + name='Test Account', + official_name='Test Official Account', + subtype='checking', + type='depository', + mask='1234', + balance_available=1000.00, + balance_current=1200.00, + balance_limit=1500.00, + institution_name='Test Institution', + institution_id='test_institution_id' + ) + + def test_account_creation(self): + self.assertEqual(self.account.account_id, 'test_account_id') + self.assertEqual(self.account.name, 'Test Account') + self.assertEqual(self.account.official_name, 'Test Official Account') + self.assertEqual(self.account.subtype, 'checking') + self.assertEqual(self.account.type, 'depository') + self.assertEqual(self.account.mask, '1234') + self.assertEqual(self.account.balance_available, 1000.00) + self.assertEqual(self.account.balance_current, 1200.00) + self.assertEqual(self.account.balance_limit, 1500.00) + self.assertEqual(self.account.institution_name, 'Test Institution') + self.assertEqual(self.account.institution_id, 'test_institution_id') + + def test_account_str(self): + self.assertEqual(str(self.account), 'Test Account') + +class PlaidTransactionModelTest(TestCase): + def setUp(self): + self.account = PlaidAccount.objects.create( + account_id='test_account_id', + name='Test Account', + official_name='Test Official Account', + subtype='checking', + type='depository', + mask='1234', + balance_available=1000.00, + balance_current=1200.00, + balance_limit=1500.00, + institution_name='Test Institution', + institution_id='test_institution_id' + ) + self.transaction = PlaidTransaction.objects.create( + transaction_id='test_transaction_id', + account=self.account, + amount=100.00, + date='2022-01-01', + name='Test Transaction', + category='Test Category', + category_id='test_category_id', + pending=False, + transaction_type='place', + payment_channel='online', + merchant_name='Test Merchant', + iso_currency_code='USD', + unofficial_currency_code='USX' + ) + + def test_transaction_creation(self): + self.assertEqual(self.transaction.transaction_id, 'test_transaction_id') + self.assertEqual(self.transaction.account, self.account) + self.assertEqual(self.transaction.amount, 100.00) + self.assertEqual(self.transaction.date, '2022-01-01') + self.assertEqual(self.transaction.name, 'Test Transaction') + self.assertEqual(self.transaction.category, 'Test Category') + self.assertEqual(self.transaction.category_id, 'test_category_id') + self.assertEqual(self.transaction.pending, False) + self.assertEqual(self.transaction.transaction_type, 'place') + self.assertEqual(self.transaction.payment_channel, 'online') + self.assertEqual(self.transaction.merchant_name, 'Test Merchant') + self.assertEqual(self.transaction.iso_currency_code, 'USD') + self.assertEqual(self.transaction.unofficial_currency_code, 'USX') + + def test_transaction_str(self): + self.assertEqual(str(self.transaction), 'Test Transaction') + +class PlaidItemModelTest(TestCase): + def setUp(self): + self.item = PlaidItem.objects.create( + item_id='test_item_id', + access_token='test_access_token', + institution_name='Test Institution', + institution_id='test_institution_id', + webhook='https://testwebhook.com', + error={'error_code': 'test_error_code'} + ) + + def test_item_creation(self): + self.assertEqual(self.item.item_id, 'test_item_id') + self.assertEqual(self.item.access_token, 'test_access_token') + self.assertEqual(self.item.institution_name, 'Test Institution') + self.assertEqual(self.item.institution_id, 'test_institution_id') + self.assertEqual(self.item.webhook, 'https://testwebhook.com') + self.assertEqual(self.item.error, {'error_code': 'test_error_code'}) + + def test_item_str(self): + self.assertEqual(str(self.item), 'Test Institution') diff --git a/common-services/src/bank_connectivity/plaid/tests/test_services.py b/common-services/src/bank_connectivity/plaid/tests/test_services.py new file mode 100644 index 00000000..57ce8571 --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/tests/test_services.py @@ -0,0 +1,400 @@ +import unittest +from unittest.mock import patch, MagicMock +from plaid.api import plaid_api +from plaid.model.payment_initiation_recipient_create_request import PaymentInitiationRecipientCreateRequest +from plaid.model.payment_initiation_payment_create_request import PaymentInitiationPaymentCreateRequest +from plaid.model.payment_amount import PaymentAmount +from plaid.model.payment_amount_currency import PaymentAmountCurrency +from plaid.model.country_code import CountryCode +from plaid.model.recipient_bacs_nullable import RecipientBACSNullable +from plaid.model.payment_initiation_address import PaymentInitiationAddress +from plaid.model.link_token_create_request import LinkTokenCreateRequest +from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser +from plaid.model.link_token_create_request_payment_initiation import LinkTokenCreateRequestPaymentInitiation +from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest +from plaid.model.auth_get_request import AuthGetRequest +from plaid.model.transactions_sync_request import TransactionsSyncRequest +from plaid.model.identity_get_request import IdentityGetRequest +from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest +from plaid.model.accounts_get_request import AccountsGetRequest +from plaid.model.asset_report_create_request import AssetReportCreateRequest +from plaid.model.asset_report_create_request_options import AssetReportCreateRequestOptions +from plaid.model.asset_report_user import AssetReportUser +from plaid.model.asset_report_get_request import AssetReportGetRequest +from plaid.model.asset_report_pdf_get_request import AssetReportPDFGetRequest +from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest +from plaid.model.investments_transactions_get_request import InvestmentsTransactionsGetRequest +from plaid.model.investments_transactions_get_request_options import InvestmentsTransactionsGetRequestOptions +from plaid.model.transfer_authorization_create_request import TransferAuthorizationCreateRequest +from plaid.model.transfer_create_request import TransferCreateRequest +from plaid.model.statements_list_request import StatementsListRequest +from plaid.model.statements_download_request import StatementsDownloadRequest +from plaid.model.signal_evaluate_request import SignalEvaluateRequest +from plaid.model.payment_initiation_payment_get_request import PaymentInitiationPaymentGetRequest +from plaid.model.item_get_request import ItemGetRequest +from plaid.model.institutions_get_by_id_request import InstitutionsGetByIdRequest +from plaid.model.cra_check_report_base_report_get_request import CraCheckReportBaseReportGetRequest +from plaid.model.cra_check_report_pdf_get_request import CraCheckReportPDFGetRequest +from plaid.model.cra_check_report_income_insights_get_request import CraCheckReportIncomeInsightsGetRequest +from plaid.model.cra_check_report_partner_insights_get_request import CraCheckReportPartnerInsightsGetRequest + +class TestPlaidServices(unittest.TestCase): + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_recipient_create') + def test_create_payment_initiation_recipient(self, mock_payment_initiation_recipient_create): + mock_response = MagicMock() + mock_response.recipient_id = 'recipient_id' + mock_payment_initiation_recipient_create.return_value = mock_response + + request = PaymentInitiationRecipientCreateRequest( + name='John Doe', + bacs=RecipientBACSNullable(account='26207729', sort_code='560029'), + address=PaymentInitiationAddress( + street=['street name 999'], + city='city', + postal_code='99999', + country='GB' + ) + ) + response = plaid_api.PlaidApi().payment_initiation_recipient_create(request) + self.assertEqual(response.recipient_id, 'recipient_id') + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_payment_create') + def test_create_payment_initiation_payment(self, mock_payment_initiation_payment_create): + mock_response = MagicMock() + mock_response.payment_id = 'payment_id' + mock_payment_initiation_payment_create.return_value = mock_response + + request = PaymentInitiationPaymentCreateRequest( + recipient_id='recipient_id', + reference='TestPayment', + amount=PaymentAmount( + PaymentAmountCurrency('GBP'), + value=100.00 + ) + ) + response = plaid_api.PlaidApi().payment_initiation_payment_create(request) + self.assertEqual(response.payment_id, 'payment_id') + + @patch('plaid.api.plaid_api.PlaidApi.link_token_create') + def test_create_link_token(self, mock_link_token_create): + mock_response = MagicMock() + mock_response.link_token = 'link_token' + mock_link_token_create.return_value = mock_response + + request = LinkTokenCreateRequest( + products=['transactions'], + client_name='Plaid Test', + country_codes=[CountryCode('US')], + language='en', + user=LinkTokenCreateRequestUser( + client_user_id='user_id' + ) + ) + response = plaid_api.PlaidApi().link_token_create(request) + self.assertEqual(response.link_token, 'link_token') + + @patch('plaid.api.plaid_api.PlaidApi.item_public_token_exchange') + def test_item_public_token_exchange(self, mock_item_public_token_exchange): + mock_response = MagicMock() + mock_response.access_token = 'access_token' + mock_response.item_id = 'item_id' + mock_item_public_token_exchange.return_value = mock_response + + request = ItemPublicTokenExchangeRequest( + public_token='public_token' + ) + response = plaid_api.PlaidApi().item_public_token_exchange(request) + self.assertEqual(response.access_token, 'access_token') + self.assertEqual(response.item_id, 'item_id') + + @patch('plaid.api.plaid_api.PlaidApi.auth_get') + def test_auth_get(self, mock_auth_get): + mock_response = MagicMock() + mock_auth_get.return_value = mock_response + + request = AuthGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().auth_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.transactions_sync') + def test_transactions_sync(self, mock_transactions_sync): + mock_response = MagicMock() + mock_transactions_sync.return_value = mock_response + + request = TransactionsSyncRequest( + access_token='access_token', + cursor='' + ) + response = plaid_api.PlaidApi().transactions_sync(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.identity_get') + def test_identity_get(self, mock_identity_get): + mock_response = MagicMock() + mock_identity_get.return_value = mock_response + + request = IdentityGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().identity_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.accounts_balance_get') + def test_accounts_balance_get(self, mock_accounts_balance_get): + mock_response = MagicMock() + mock_accounts_balance_get.return_value = mock_response + + request = AccountsBalanceGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().accounts_balance_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.accounts_get') + def test_accounts_get(self, mock_accounts_get): + mock_response = MagicMock() + mock_accounts_get.return_value = mock_response + + request = AccountsGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().accounts_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_create') + def test_asset_report_create(self, mock_asset_report_create): + mock_response = MagicMock() + mock_response.asset_report_token = 'asset_report_token' + mock_asset_report_create.return_value = mock_response + + request = AssetReportCreateRequest( + access_tokens=['access_token'], + days_requested=60, + options=AssetReportCreateRequestOptions( + webhook='https://www.example.com', + client_report_id='123', + user=AssetReportUser( + client_user_id='789', + first_name='Jane', + middle_name='Leah', + last_name='Doe', + ssn='123-45-6789', + phone_number='(555) 123-4567', + email='jane.doe@example.com', + ) + ) + ) + response = plaid_api.PlaidApi().asset_report_create(request) + self.assertEqual(response.asset_report_token, 'asset_report_token') + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_get') + def test_asset_report_get(self, mock_asset_report_get): + mock_response = MagicMock() + mock_asset_report_get.return_value = mock_response + + request = AssetReportGetRequest( + asset_report_token='asset_report_token' + ) + response = plaid_api.PlaidApi().asset_report_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_pdf_get') + def test_asset_report_pdf_get(self, mock_asset_report_pdf_get): + mock_response = MagicMock() + mock_asset_report_pdf_get.return_value = mock_response + + request = AssetReportPDFGetRequest( + asset_report_token='asset_report_token' + ) + response = plaid_api.PlaidApi().asset_report_pdf_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.investments_holdings_get') + def test_investments_holdings_get(self, mock_investments_holdings_get): + mock_response = MagicMock() + mock_investments_holdings_get.return_value = mock_response + + request = InvestmentsHoldingsGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().investments_holdings_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.investments_transactions_get') + def test_investments_transactions_get(self, mock_investments_transactions_get): + mock_response = MagicMock() + mock_investments_transactions_get.return_value = mock_response + + request = InvestmentsTransactionsGetRequest( + access_token='access_token', + start_date='2021-01-01', + end_date='2021-01-31', + options=InvestmentsTransactionsGetRequestOptions() + ) + response = plaid_api.PlaidApi().investments_transactions_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.transfer_authorization_create') + def test_transfer_authorization_create(self, mock_transfer_authorization_create): + mock_response = MagicMock() + mock_response.authorization.id = 'authorization_id' + mock_transfer_authorization_create.return_value = mock_response + + request = TransferAuthorizationCreateRequest( + access_token='access_token', + account_id='account_id', + type='debit', + network='ach', + amount='1.00', + ach_class='ppd', + user={ + 'legal_name': 'FirstName LastName', + 'email_address': 'foobar@email.com', + 'address': { + 'street': '123 Main St.', + 'city': 'San Francisco', + 'region': 'CA', + 'postal_code': '94053', + 'country': 'US' + } + } + ) + response = plaid_api.PlaidApi().transfer_authorization_create(request) + self.assertEqual(response.authorization.id, 'authorization_id') + + @patch('plaid.api.plaid_api.PlaidApi.transfer_create') + def test_transfer_create(self, mock_transfer_create): + mock_response = MagicMock() + mock_response.transfer_id = 'transfer_id' + mock_transfer_create.return_value = mock_response + + request = TransferCreateRequest( + access_token='access_token', + account_id='account_id', + authorization_id='authorization_id', + description='Debit' + ) + response = plaid_api.PlaidApi().transfer_create(request) + self.assertEqual(response.transfer_id, 'transfer_id') + + @patch('plaid.api.plaid_api.PlaidApi.statements_list') + def test_statements_list(self, mock_statements_list): + mock_response = MagicMock() + mock_statements_list.return_value = mock_response + + request = StatementsListRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().statements_list(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.statements_download') + def test_statements_download(self, mock_statements_download): + mock_response = MagicMock() + mock_statements_download.return_value = mock_response + + request = StatementsDownloadRequest( + access_token='access_token', + statement_id='statement_id' + ) + response = plaid_api.PlaidApi().statements_download(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.signal_evaluate') + def test_signal_evaluate(self, mock_signal_evaluate): + mock_response = MagicMock() + mock_signal_evaluate.return_value = mock_response + + request = SignalEvaluateRequest( + access_token='access_token', + account_id='account_id', + client_transaction_id='txn1234', + amount=100.00 + ) + response = plaid_api.PlaidApi().signal_evaluate(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_payment_get') + def test_payment_initiation_payment_get(self, mock_payment_initiation_payment_get): + mock_response = MagicMock() + mock_payment_initiation_payment_get.return_value = mock_response + + request = PaymentInitiationPaymentGetRequest( + payment_id='payment_id' + ) + response = plaid_api.PlaidApi().payment_initiation_payment_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.item_get') + def test_item_get(self, mock_item_get): + mock_response = MagicMock() + mock_item_get.return_value = mock_response + + request = ItemGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().item_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.institutions_get_by_id') + def test_institutions_get_by_id(self, mock_institutions_get_by_id): + mock_response = MagicMock() + mock_institutions_get_by_id.return_value = mock_response + + request = InstitutionsGetByIdRequest( + institution_id='institution_id', + country_codes=[CountryCode('US')] + ) + response = plaid_api.PlaidApi().institutions_get_by_id(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_base_report_get') + def test_cra_check_report_base_report_get(self, mock_cra_check_report_base_report_get): + mock_response = MagicMock() + mock_cra_check_report_base_report_get.return_value = mock_response + + request = CraCheckReportBaseReportGetRequest( + user_token='user_token', + item_ids=[] + ) + response = plaid_api.PlaidApi().cra_check_report_base_report_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_pdf_get') + def test_cra_check_report_pdf_get(self, mock_cra_check_report_pdf_get): + mock_response = MagicMock() + mock_cra_check_report_pdf_get.return_value = mock_response + + request = CraCheckReportPDFGetRequest( + user_token='user_token' + ) + response = plaid_api.PlaidApi().cra_check_report_pdf_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_income_insights_get') + def test_cra_check_report_income_insights_get(self, mock_cra_check_report_income_insights_get): + mock_response = MagicMock() + mock_cra_check_report_income_insights_get.return_value = mock_response + + request = CraCheckReportIncomeInsightsGetRequest( + user_token='user_token' + ) + response = plaid_api.PlaidApi().cra_check_report_income_insights_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_partner_insights_get') + def test_cra_check_report_partner_insights_get(self, mock_cra_check_report_partner_insights_get): + mock_response = MagicMock() + mock_cra_check_report_partner_insights_get.return_value = mock_response + + request = CraCheckReportPartnerInsightsGetRequest( + user_token='user_token' + ) + response = plaid_api.PlaidApi().cra_check_report_partner_insights_get(request) + self.assertEqual(response, mock_response) + +if __name__ == '__main__': + unittest.main() diff --git a/common-services/src/bank_connectivity/plaid/tests/test_utils.py b/common-services/src/bank_connectivity/plaid/tests/test_utils.py new file mode 100644 index 00000000..c889f35a --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/tests/test_utils.py @@ -0,0 +1,400 @@ +import unittest +from unittest.mock import patch, MagicMock +from plaid.api import plaid_api +from plaid.model.payment_initiation_recipient_create_request import PaymentInitiationRecipientCreateRequest +from plaid.model.payment_initiation_payment_create_request import PaymentInitiationPaymentCreateRequest +from plaid.model.payment_amount import PaymentAmount +from plaid.model.payment_amount_currency import PaymentAmountCurrency +from plaid.model.country_code import CountryCode +from plaid.model.recipient_bacs_nullable import RecipientBACSNullable +from plaid.model.payment_initiation_address import PaymentInitiationAddress +from plaid.model.link_token_create_request import LinkTokenCreateRequest +from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser +from plaid.model.link_token_create_request_payment_initiation import LinkTokenCreateRequestPaymentInitiation +from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest +from plaid.model.auth_get_request import AuthGetRequest +from plaid.model.transactions_sync_request import TransactionsSyncRequest +from plaid.model.identity_get_request import IdentityGetRequest +from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest +from plaid.model.accounts_get_request import AccountsGetRequest +from plaid.model.asset_report_create_request import AssetReportCreateRequest +from plaid.model.asset_report_create_request_options import AssetReportCreateRequestOptions +from plaid.model.asset_report_user import AssetReportUser +from plaid.model.asset_report_get_request import AssetReportGetRequest +from plaid.model.asset_report_pdf_get_request import AssetReportPDFGetRequest +from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest +from plaid.model.investments_transactions_get_request import InvestmentsTransactionsGetRequest +from plaid.model.investments_transactions_get_request_options import InvestmentsTransactionsGetRequestOptions +from plaid.model.transfer_authorization_create_request import TransferAuthorizationCreateRequest +from plaid.model.transfer_create_request import TransferCreateRequest +from plaid.model.statements_list_request import StatementsListRequest +from plaid.model.statements_download_request import StatementsDownloadRequest +from plaid.model.signal_evaluate_request import SignalEvaluateRequest +from plaid.model.payment_initiation_payment_get_request import PaymentInitiationPaymentGetRequest +from plaid.model.item_get_request import ItemGetRequest +from plaid.model.institutions_get_by_id_request import InstitutionsGetByIdRequest +from plaid.model.cra_check_report_base_report_get_request import CraCheckReportBaseReportGetRequest +from plaid.model.cra_check_report_pdf_get_request import CraCheckReportPDFGetRequest +from plaid.model.cra_check_report_income_insights_get_request import CraCheckReportIncomeInsightsGetRequest +from plaid.model.cra_check_report_partner_insights_get_request import CraCheckReportPartnerInsightsGetRequest + +class TestPlaidUtils(unittest.TestCase): + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_recipient_create') + def test_create_payment_initiation_recipient(self, mock_payment_initiation_recipient_create): + mock_response = MagicMock() + mock_response.recipient_id = 'recipient_id' + mock_payment_initiation_recipient_create.return_value = mock_response + + request = PaymentInitiationRecipientCreateRequest( + name='John Doe', + bacs=RecipientBACSNullable(account='26207729', sort_code='560029'), + address=PaymentInitiationAddress( + street=['street name 999'], + city='city', + postal_code='99999', + country='GB' + ) + ) + response = plaid_api.PlaidApi().payment_initiation_recipient_create(request) + self.assertEqual(response.recipient_id, 'recipient_id') + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_payment_create') + def test_create_payment_initiation_payment(self, mock_payment_initiation_payment_create): + mock_response = MagicMock() + mock_response.payment_id = 'payment_id' + mock_payment_initiation_payment_create.return_value = mock_response + + request = PaymentInitiationPaymentCreateRequest( + recipient_id='recipient_id', + reference='TestPayment', + amount=PaymentAmount( + PaymentAmountCurrency('GBP'), + value=100.00 + ) + ) + response = plaid_api.PlaidApi().payment_initiation_payment_create(request) + self.assertEqual(response.payment_id, 'payment_id') + + @patch('plaid.api.plaid_api.PlaidApi.link_token_create') + def test_create_link_token(self, mock_link_token_create): + mock_response = MagicMock() + mock_response.link_token = 'link_token' + mock_link_token_create.return_value = mock_response + + request = LinkTokenCreateRequest( + products=['transactions'], + client_name='Plaid Test', + country_codes=[CountryCode('US')], + language='en', + user=LinkTokenCreateRequestUser( + client_user_id='user_id' + ) + ) + response = plaid_api.PlaidApi().link_token_create(request) + self.assertEqual(response.link_token, 'link_token') + + @patch('plaid.api.plaid_api.PlaidApi.item_public_token_exchange') + def test_item_public_token_exchange(self, mock_item_public_token_exchange): + mock_response = MagicMock() + mock_response.access_token = 'access_token' + mock_response.item_id = 'item_id' + mock_item_public_token_exchange.return_value = mock_response + + request = ItemPublicTokenExchangeRequest( + public_token='public_token' + ) + response = plaid_api.PlaidApi().item_public_token_exchange(request) + self.assertEqual(response.access_token, 'access_token') + self.assertEqual(response.item_id, 'item_id') + + @patch('plaid.api.plaid_api.PlaidApi.auth_get') + def test_auth_get(self, mock_auth_get): + mock_response = MagicMock() + mock_auth_get.return_value = mock_response + + request = AuthGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().auth_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.transactions_sync') + def test_transactions_sync(self, mock_transactions_sync): + mock_response = MagicMock() + mock_transactions_sync.return_value = mock_response + + request = TransactionsSyncRequest( + access_token='access_token', + cursor='' + ) + response = plaid_api.PlaidApi().transactions_sync(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.identity_get') + def test_identity_get(self, mock_identity_get): + mock_response = MagicMock() + mock_identity_get.return_value = mock_response + + request = IdentityGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().identity_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.accounts_balance_get') + def test_accounts_balance_get(self, mock_accounts_balance_get): + mock_response = MagicMock() + mock_accounts_balance_get.return_value = mock_response + + request = AccountsBalanceGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().accounts_balance_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.accounts_get') + def test_accounts_get(self, mock_accounts_get): + mock_response = MagicMock() + mock_accounts_get.return_value = mock_response + + request = AccountsGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().accounts_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_create') + def test_asset_report_create(self, mock_asset_report_create): + mock_response = MagicMock() + mock_response.asset_report_token = 'asset_report_token' + mock_asset_report_create.return_value = mock_response + + request = AssetReportCreateRequest( + access_tokens=['access_token'], + days_requested=60, + options=AssetReportCreateRequestOptions( + webhook='https://www.example.com', + client_report_id='123', + user=AssetReportUser( + client_user_id='789', + first_name='Jane', + middle_name='Leah', + last_name='Doe', + ssn='123-45-6789', + phone_number='(555) 123-4567', + email='jane.doe@example.com', + ) + ) + ) + response = plaid_api.PlaidApi().asset_report_create(request) + self.assertEqual(response.asset_report_token, 'asset_report_token') + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_get') + def test_asset_report_get(self, mock_asset_report_get): + mock_response = MagicMock() + mock_asset_report_get.return_value = mock_response + + request = AssetReportGetRequest( + asset_report_token='asset_report_token' + ) + response = plaid_api.PlaidApi().asset_report_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.asset_report_pdf_get') + def test_asset_report_pdf_get(self, mock_asset_report_pdf_get): + mock_response = MagicMock() + mock_asset_report_pdf_get.return_value = mock_response + + request = AssetReportPDFGetRequest( + asset_report_token='asset_report_token' + ) + response = plaid_api.PlaidApi().asset_report_pdf_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.investments_holdings_get') + def test_investments_holdings_get(self, mock_investments_holdings_get): + mock_response = MagicMock() + mock_investments_holdings_get.return_value = mock_response + + request = InvestmentsHoldingsGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().investments_holdings_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.investments_transactions_get') + def test_investments_transactions_get(self, mock_investments_transactions_get): + mock_response = MagicMock() + mock_investments_transactions_get.return_value = mock_response + + request = InvestmentsTransactionsGetRequest( + access_token='access_token', + start_date='2021-01-01', + end_date='2021-01-31', + options=InvestmentsTransactionsGetRequestOptions() + ) + response = plaid_api.PlaidApi().investments_transactions_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.transfer_authorization_create') + def test_transfer_authorization_create(self, mock_transfer_authorization_create): + mock_response = MagicMock() + mock_response.authorization.id = 'authorization_id' + mock_transfer_authorization_create.return_value = mock_response + + request = TransferAuthorizationCreateRequest( + access_token='access_token', + account_id='account_id', + type='debit', + network='ach', + amount='1.00', + ach_class='ppd', + user={ + 'legal_name': 'FirstName LastName', + 'email_address': 'foobar@email.com', + 'address': { + 'street': '123 Main St.', + 'city': 'San Francisco', + 'region': 'CA', + 'postal_code': '94053', + 'country': 'US' + } + } + ) + response = plaid_api.PlaidApi().transfer_authorization_create(request) + self.assertEqual(response.authorization.id, 'authorization_id') + + @patch('plaid.api.plaid_api.PlaidApi.transfer_create') + def test_transfer_create(self, mock_transfer_create): + mock_response = MagicMock() + mock_response.transfer_id = 'transfer_id' + mock_transfer_create.return_value = mock_response + + request = TransferCreateRequest( + access_token='access_token', + account_id='account_id', + authorization_id='authorization_id', + description='Debit' + ) + response = plaid_api.PlaidApi().transfer_create(request) + self.assertEqual(response.transfer_id, 'transfer_id') + + @patch('plaid.api.plaid_api.PlaidApi.statements_list') + def test_statements_list(self, mock_statements_list): + mock_response = MagicMock() + mock_statements_list.return_value = mock_response + + request = StatementsListRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().statements_list(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.statements_download') + def test_statements_download(self, mock_statements_download): + mock_response = MagicMock() + mock_statements_download.return_value = mock_response + + request = StatementsDownloadRequest( + access_token='access_token', + statement_id='statement_id' + ) + response = plaid_api.PlaidApi().statements_download(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.signal_evaluate') + def test_signal_evaluate(self, mock_signal_evaluate): + mock_response = MagicMock() + mock_signal_evaluate.return_value = mock_response + + request = SignalEvaluateRequest( + access_token='access_token', + account_id='account_id', + client_transaction_id='txn1234', + amount=100.00 + ) + response = plaid_api.PlaidApi().signal_evaluate(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.payment_initiation_payment_get') + def test_payment_initiation_payment_get(self, mock_payment_initiation_payment_get): + mock_response = MagicMock() + mock_payment_initiation_payment_get.return_value = mock_response + + request = PaymentInitiationPaymentGetRequest( + payment_id='payment_id' + ) + response = plaid_api.PlaidApi().payment_initiation_payment_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.item_get') + def test_item_get(self, mock_item_get): + mock_response = MagicMock() + mock_item_get.return_value = mock_response + + request = ItemGetRequest( + access_token='access_token' + ) + response = plaid_api.PlaidApi().item_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.institutions_get_by_id') + def test_institutions_get_by_id(self, mock_institutions_get_by_id): + mock_response = MagicMock() + mock_institutions_get_by_id.return_value = mock_response + + request = InstitutionsGetByIdRequest( + institution_id='institution_id', + country_codes=[CountryCode('US')] + ) + response = plaid_api.PlaidApi().institutions_get_by_id(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_base_report_get') + def test_cra_check_report_base_report_get(self, mock_cra_check_report_base_report_get): + mock_response = MagicMock() + mock_cra_check_report_base_report_get.return_value = mock_response + + request = CraCheckReportBaseReportGetRequest( + user_token='user_token', + item_ids=[] + ) + response = plaid_api.PlaidApi().cra_check_report_base_report_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_pdf_get') + def test_cra_check_report_pdf_get(self, mock_cra_check_report_pdf_get): + mock_response = MagicMock() + mock_cra_check_report_pdf_get.return_value = mock_response + + request = CraCheckReportPDFGetRequest( + user_token='user_token' + ) + response = plaid_api.PlaidApi().cra_check_report_pdf_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_income_insights_get') + def test_cra_check_report_income_insights_get(self, mock_cra_check_report_income_insights_get): + mock_response = MagicMock() + mock_cra_check_report_income_insights_get.return_value = mock_response + + request = CraCheckReportIncomeInsightsGetRequest( + user_token='user_token' + ) + response = plaid_api.PlaidApi().cra_check_report_income_insights_get(request) + self.assertEqual(response, mock_response) + + @patch('plaid.api.plaid_api.PlaidApi.cra_check_report_partner_insights_get') + def test_cra_check_report_partner_insights_get(self, mock_cra_check_report_partner_insights_get): + mock_response = MagicMock() + mock_cra_check_report_partner_insights_get.return_value = mock_response + + request = CraCheckReportPartnerInsightsGetRequest( + user_token='user_token' + ) + response = plaid_api.PlaidApi().cra_check_report_partner_insights_get(request) + self.assertEqual(response, mock_response) + +if __name__ == '__main__': + unittest.main() diff --git a/common-services/src/bank_connectivity/plaid/utils.py b/common-services/src/bank_connectivity/plaid/utils.py new file mode 100644 index 00000000..9bcb8ed6 --- /dev/null +++ b/common-services/src/bank_connectivity/plaid/utils.py @@ -0,0 +1,78 @@ +import json +import time +from datetime import date, timedelta + +import plaid +from plaid.model.payment_amount import PaymentAmount +from plaid.model.payment_amount_currency import PaymentAmountCurrency +from plaid.model.products import Products +from plaid.model.country_code import CountryCode +from plaid.model.recipient_bacs_nullable import RecipientBACSNullable +from plaid.model.payment_initiation_address import PaymentInitiationAddress +from plaid.model.payment_initiation_recipient_create_request import PaymentInitiationRecipientCreateRequest +from plaid.model.payment_initiation_payment_create_request import PaymentInitiationPaymentCreateRequest +from plaid.model.payment_initiation_payment_get_request import PaymentInitiationPaymentGetRequest +from plaid.model.link_token_create_request_payment_initiation import LinkTokenCreateRequestPaymentInitiation +from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest +from plaid.model.link_token_create_request import LinkTokenCreateRequest +from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser +from plaid.model.user_create_request import UserCreateRequest +from plaid.model.consumer_report_user_identity import ConsumerReportUserIdentity +from plaid.model.asset_report_create_request import AssetReportCreateRequest +from plaid.model.asset_report_create_request_options import AssetReportCreateRequestOptions +from plaid.model.asset_report_user import AssetReportUser +from plaid.model.asset_report_get_request import AssetReportGetRequest +from plaid.model.asset_report_pdf_get_request import AssetReportPDFGetRequest +from plaid.model.auth_get_request import AuthGetRequest +from plaid.model.transactions_sync_request import TransactionsSyncRequest +from plaid.model.identity_get_request import IdentityGetRequest +from plaid.model.investments_transactions_get_request_options import InvestmentsTransactionsGetRequestOptions +from plaid.model.investments_transactions_get_request import InvestmentsTransactionsGetRequest +from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest +from plaid.model.accounts_get_request import AccountsGetRequest +from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest +from plaid.model.item_get_request import ItemGetRequest +from plaid.model.institutions_get_by_id_request import InstitutionsGetByIdRequest +from plaid.model.transfer_authorization_create_request import TransferAuthorizationCreateRequest +from plaid.model.transfer_create_request import TransferCreateRequest +from plaid.model.transfer_get_request import TransferGetRequest +from plaid.model.transfer_network import TransferNetwork +from plaid.model.transfer_type import TransferType +from plaid.model.transfer_authorization_user_in_request import TransferAuthorizationUserInRequest +from plaid.model.ach_class import ACHClass +from plaid.model.transfer_create_idempotency_key import TransferCreateIdempotencyKey +from plaid.model.transfer_user_address_in_request import TransferUserAddressInRequest +from plaid.model.signal_evaluate_request import SignalEvaluateRequest +from plaid.model.statements_list_request import StatementsListRequest +from plaid.model.link_token_create_request_statements import LinkTokenCreateRequestStatements +from plaid.model.link_token_create_request_cra_options import LinkTokenCreateRequestCraOptions +from plaid.model.statements_download_request import StatementsDownloadRequest +from plaid.model.consumer_report_permissible_purpose import ConsumerReportPermissiblePurpose +from plaid.model.cra_check_report_base_report_get_request import CraCheckReportBaseReportGetRequest +from plaid.model.cra_check_report_pdf_get_request import CraCheckReportPDFGetRequest +from plaid.model.cra_check_report_income_insights_get_request import CraCheckReportIncomeInsightsGetRequest +from plaid.model.cra_check_report_partner_insights_get_request import CraCheckReportPartnerInsightsGetRequest +from plaid.model.cra_pdf_add_ons import CraPDFAddOns +from plaid.api import plaid_api + +def poll_with_retries(request_callback, ms=1000, retries_left=20): + while retries_left > 0: + try: + return request_callback() + except plaid.ApiException as e: + response = json.loads(e.body) + if response['error_code'] != 'PRODUCT_NOT_READY': + raise e + elif retries_left == 0: + raise Exception('Ran out of retries while polling') from e + else: + retries_left -= 1 + time.sleep(ms / 1000) + +def pretty_print_response(response): + print(json.dumps(response, indent=2, sort_keys=True, default=str)) + +def format_error(e): + response = json.loads(e.body) + return {'error': {'status_code': e.status, 'display_message': + response['error_message'], 'error_code': response['error_code'], 'error_type': response['error_type']}} diff --git a/common-services/src/bank_connectivity/teller/__init__.py b/common-services/src/bank_connectivity/teller/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/bank_connectivity/teller/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/common-services/src/core/__init__.py b/common-services/src/core/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/core/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/common-services/src/core/settings.py b/common-services/src/core/settings.py new file mode 100644 index 00000000..e21cc4aa --- /dev/null +++ b/common-services/src/core/settings.py @@ -0,0 +1,125 @@ +""" +Django settings for common_services project. +""" + +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'django-insecure-secret-key') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = os.environ.get('DJANGO_DEBUG', 'True') == 'True' + +ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',') + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'document_analysis.heron', + 'bank_connectivity.plaid', + 'bank_connectivity.teller', + 'notifications', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'common_services.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'common_services.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('POSTGRES_DB', 'common_services'), + 'USER': os.environ.get('POSTGRES_USER', 'user'), + 'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'password'), + 'HOST': os.environ.get('POSTGRES_HOST', 'localhost'), + 'PORT': os.environ.get('POSTGRES_PORT', '5432'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/common-services/src/tests/__init__.py b/common-services/src/tests/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/tests/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/common-services/src/tests/bank_connectivity/plaid/__init__.py b/common-services/src/tests/bank_connectivity/plaid/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/tests/bank_connectivity/plaid/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/common-services/src/tests/bank_connectivity/teller/__init__.py b/common-services/src/tests/bank_connectivity/teller/__init__.py new file mode 100644 index 00000000..e7a82d26 --- /dev/null +++ b/common-services/src/tests/bank_connectivity/teller/__init__.py @@ -0,0 +1 @@ +# This is an empty __init__.py file to mark the directory as a Python package. diff --git a/common-services/src/tests/test_admin.py b/common-services/src/tests/test_admin.py new file mode 100644 index 00000000..762832e8 --- /dev/null +++ b/common-services/src/tests/test_admin.py @@ -0,0 +1,29 @@ +from django.test import TestCase +from django.contrib.admin.sites import AdminSite +from document_analysis.heron.admin import MyModelAdmin +from document_analysis.heron.models import MyModel + +class MockRequest: + pass + +class AdminTestCase(TestCase): + def setUp(self): + self.site = AdminSite() + self.model_admin = MyModelAdmin(MyModel, self.site) + self.model_instance = MyModel.objects.create(field1='value1', field2='value2') + + def test_model_admin_display(self): + request = MockRequest() + queryset = MyModel.objects.all() + response = self.model_admin.changelist_view(request) + self.assertEqual(response.status_code, 200) + + def test_model_admin_add(self): + request = MockRequest() + response = self.model_admin.add_view(request) + self.assertEqual(response.status_code, 200) + + def test_model_admin_change(self): + request = MockRequest() + response = self.model_admin.change_view(request, str(self.model_instance.id)) + self.assertEqual(response.status_code, 200) diff --git a/common-services/src/tests/test_apps.py b/common-services/src/tests/test_apps.py new file mode 100644 index 00000000..331bf81f --- /dev/null +++ b/common-services/src/tests/test_apps.py @@ -0,0 +1,8 @@ +from django.test import TestCase +from django.apps import apps +from django.conf import settings + +class AppsTestCase(TestCase): + def test_apps(self): + for app in settings.INSTALLED_APPS: + self.assertTrue(apps.is_installed(app)) diff --git a/common-services/src/tests/test_middleware.py b/common-services/src/tests/test_middleware.py new file mode 100644 index 00000000..4055dc5e --- /dev/null +++ b/common-services/src/tests/test_middleware.py @@ -0,0 +1,57 @@ +from django.test import TestCase, RequestFactory +from django.contrib.auth.models import AnonymousUser, User +from django.contrib.sessions.middleware import SessionMiddleware +from django.middleware.common import CommonMiddleware +from django.middleware.csrf import CsrfViewMiddleware +from django.middleware.security import SecurityMiddleware +from django.middleware.clickjacking import XFrameOptionsMiddleware +from django.contrib.auth.middleware import AuthenticationMiddleware +from django.contrib.messages.middleware import MessageMiddleware + +class MiddlewareTestCase(TestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_user(username='testuser', password='password') + + def test_security_middleware(self): + request = self.factory.get('/') + middleware = SecurityMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_session_middleware(self): + request = self.factory.get('/') + middleware = SessionMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_common_middleware(self): + request = self.factory.get('/') + middleware = CommonMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_csrf_middleware(self): + request = self.factory.get('/') + middleware = CsrfViewMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_authentication_middleware(self): + request = self.factory.get('/') + request.user = AnonymousUser() + middleware = AuthenticationMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_message_middleware(self): + request = self.factory.get('/') + middleware = MessageMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_clickjacking_middleware(self): + request = self.factory.get('/') + middleware = XFrameOptionsMiddleware(lambda req: None) + response = middleware.process_request(request) + self.assertIsNone(response) diff --git a/common-services/src/tests/test_migrations.py b/common-services/src/tests/test_migrations.py new file mode 100644 index 00000000..6af4a445 --- /dev/null +++ b/common-services/src/tests/test_migrations.py @@ -0,0 +1,18 @@ +from django.test import TestCase +from django.core.management import call_command +from django.db import connection + +class MigrationsTestCase(TestCase): + def test_migrations(self): + # Run the migrations + call_command('migrate', verbosity=0, interactive=False) + + # Check if the migrations table exists + with connection.cursor() as cursor: + cursor.execute("SELECT table_name FROM information_schema.tables WHERE table_name = 'django_migrations'") + self.assertTrue(cursor.fetchone()) + + # Check if there are any applied migrations + with connection.cursor() as cursor: + cursor.execute("SELECT COUNT(*) FROM django_migrations") + self.assertTrue(cursor.fetchone()[0] > 0) diff --git a/common-services/src/tests/test_models.py b/common-services/src/tests/test_models.py new file mode 100644 index 00000000..bc9b2a1e --- /dev/null +++ b/common-services/src/tests/test_models.py @@ -0,0 +1,13 @@ +from django.test import TestCase +from document_analysis.heron.models import MyModel + +class ModelsTestCase(TestCase): + def setUp(self): + self.model_instance = MyModel.objects.create(field1='value1', field2='value2') + + def test_model_creation(self): + self.assertEqual(self.model_instance.field1, 'value1') + self.assertEqual(self.model_instance.field2, 'value2') + + def test_model_str(self): + self.assertEqual(str(self.model_instance), 'value1 - value2') diff --git a/common-services/src/tests/test_providers.py b/common-services/src/tests/test_providers.py new file mode 100644 index 00000000..b0974ea7 --- /dev/null +++ b/common-services/src/tests/test_providers.py @@ -0,0 +1,52 @@ +import pytest +from django.urls import reverse +from rest_framework.test import APIClient +from rest_framework import status +from common_services.src.bank_connectivity.plaid.models import Provider + +@pytest.fixture +def api_client(): + return APIClient() + +@pytest.fixture +def provider(): + return Provider.objects.create(name="Test Provider", description="Test Description") + +@pytest.mark.django_db +def test_create_provider(api_client): + url = reverse('provider-list') + data = { + "name": "New Provider", + "description": "New Description" + } + response = api_client.post(url, data, format='json') + assert response.status_code == status.HTTP_201_CREATED + assert response.data['name'] == "New Provider" + assert response.data['description'] == "New Description" + +@pytest.mark.django_db +def test_get_provider(api_client, provider): + url = reverse('provider-detail', args=[provider.id]) + response = api_client.get(url) + assert response.status_code == status.HTTP_200_OK + assert response.data['name'] == provider.name + assert response.data['description'] == provider.description + +@pytest.mark.django_db +def test_update_provider(api_client, provider): + url = reverse('provider-detail', args=[provider.id]) + data = { + "name": "Updated Provider", + "description": "Updated Description" + } + response = api_client.put(url, data, format='json') + assert response.status_code == status.HTTP_200_OK + assert response.data['name'] == "Updated Provider" + assert response.data['description'] == "Updated Description" + +@pytest.mark.django_db +def test_delete_provider(api_client, provider): + url = reverse('provider-detail', args=[provider.id]) + response = api_client.delete(url) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert Provider.objects.filter(id=provider.id).count() == 0 diff --git a/common-services/src/tests/test_serializers.py b/common-services/src/tests/test_serializers.py new file mode 100644 index 00000000..d9ed7951 --- /dev/null +++ b/common-services/src/tests/test_serializers.py @@ -0,0 +1,18 @@ +from django.test import TestCase +from rest_framework import serializers + +class MySerializer(serializers.Serializer): + field1 = serializers.CharField() + field2 = serializers.IntegerField() + +class SerializersTestCase(TestCase): + def setUp(self): + self.serializer_data = {'field1': 'value1', 'field2': 123} + self.serializer = MySerializer(data=self.serializer_data) + + def test_serializer_valid(self): + self.assertTrue(self.serializer.is_valid()) + + def test_serializer_data(self): + self.serializer.is_valid() + self.assertEqual(self.serializer.validated_data, self.serializer_data) diff --git a/common-services/src/tests/test_services.py b/common-services/src/tests/test_services.py new file mode 100644 index 00000000..d715c046 --- /dev/null +++ b/common-services/src/tests/test_services.py @@ -0,0 +1,10 @@ +from django.test import TestCase +from document_analysis.heron.services import MyService + +class ServicesTestCase(TestCase): + def setUp(self): + self.service_instance = MyService() + + def test_service_method(self): + result = self.service_instance.my_method('input_value') + self.assertEqual(result, 'expected_output') diff --git a/common-services/src/tests/test_settings.py b/common-services/src/tests/test_settings.py new file mode 100644 index 00000000..aaf069ef --- /dev/null +++ b/common-services/src/tests/test_settings.py @@ -0,0 +1,69 @@ +import os +from django.test import TestCase +from django.conf import settings + +class SettingsTestCase(TestCase): + def test_secret_key(self): + self.assertTrue(settings.SECRET_KEY) + + def test_debug(self): + self.assertEqual(settings.DEBUG, os.environ.get('DJANGO_DEBUG', 'True') == 'True') + + def test_allowed_hosts(self): + self.assertEqual(settings.ALLOWED_HOSTS, os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')) + + def test_installed_apps(self): + expected_apps = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'document_analysis.heron', + 'bank_connectivity.plaid', + 'bank_connectivity.teller', + 'notifications', + ] + self.assertEqual(settings.INSTALLED_APPS, expected_apps) + + def test_middleware(self): + expected_middleware = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + ] + self.assertEqual(settings.MIDDLEWARE, expected_middleware) + + def test_database_settings(self): + self.assertEqual(settings.DATABASES['default']['ENGINE'], 'django.db.backends.postgresql') + self.assertEqual(settings.DATABASES['default']['NAME'], os.environ.get('POSTGRES_DB', 'common_services')) + self.assertEqual(settings.DATABASES['default']['USER'], os.environ.get('POSTGRES_USER', 'user')) + self.assertEqual(settings.DATABASES['default']['PASSWORD'], os.environ.get('POSTGRES_PASSWORD', 'password')) + self.assertEqual(settings.DATABASES['default']['HOST'], os.environ.get('POSTGRES_HOST', 'localhost')) + self.assertEqual(settings.DATABASES['default']['PORT'], os.environ.get('POSTGRES_PORT', '5432')) + + def test_language_code(self): + self.assertEqual(settings.LANGUAGE_CODE, 'en-us') + + def test_time_zone(self): + self.assertEqual(settings.TIME_ZONE, 'UTC') + + def test_use_i18n(self): + self.assertTrue(settings.USE_I18N) + + def test_use_l10n(self): + self.assertTrue(settings.USE_L10N) + + def test_use_tz(self): + self.assertTrue(settings.USE_TZ) + + def test_static_url(self): + self.assertEqual(settings.STATIC_URL, '/static/') + + def test_default_auto_field(self): + self.assertEqual(settings.DEFAULT_AUTO_FIELD, 'django.db.models.BigAutoField') diff --git a/common-services/src/tests/test_urls.py b/common-services/src/tests/test_urls.py new file mode 100644 index 00000000..9c5a1aef --- /dev/null +++ b/common-services/src/tests/test_urls.py @@ -0,0 +1,8 @@ +from django.test import TestCase +from django.urls import reverse, resolve +from document_analysis.heron.api import MyView + +class UrlsTestCase(TestCase): + def test_my_view_url(self): + url = reverse('my_view') + self.assertEqual(resolve(url).func.view_class, MyView) diff --git a/common-services/src/tests/test_utils.py b/common-services/src/tests/test_utils.py new file mode 100644 index 00000000..acb01590 --- /dev/null +++ b/common-services/src/tests/test_utils.py @@ -0,0 +1,10 @@ +from django.test import TestCase +from document_analysis.heron.utils import MyUtil + +class UtilsTestCase(TestCase): + def setUp(self): + self.util_instance = MyUtil() + + def test_util_method(self): + result = self.util_instance.my_method('input_value') + self.assertEqual(result, 'expected_output') diff --git a/common-services/src/tests/test_views.py b/common-services/src/tests/test_views.py new file mode 100644 index 00000000..8b113b1e --- /dev/null +++ b/common-services/src/tests/test_views.py @@ -0,0 +1,21 @@ +from django.test import TestCase, RequestFactory +from django.urls import reverse +from django.contrib.auth.models import User +from document_analysis.heron.api import MyView + +class ViewsTestCase(TestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_user(username='testuser', password='password') + + def test_my_view_get(self): + request = self.factory.get(reverse('my_view')) + request.user = self.user + response = MyView.as_view()(request) + self.assertEqual(response.status_code, 200) + + def test_my_view_post(self): + request = self.factory.post(reverse('my_view'), {'key': 'value'}) + request.user = self.user + response = MyView.as_view()(request) + self.assertEqual(response.status_code, 200) diff --git a/python/.env.example b/python/.env.example deleted file mode 120000 index 821f0174..00000000 --- a/python/.env.example +++ /dev/null @@ -1 +0,0 @@ -../.env.example \ No newline at end of file diff --git a/python/Dockerfile b/python/Dockerfile deleted file mode 100644 index 54cded1b..00000000 --- a/python/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM python:alpine - -WORKDIR /opt/app -COPY . . -WORKDIR /opt/app/python - -RUN pip3 install -r requirements.txt - -ENV FLASK_APP=/opt/app/python/server.py -EXPOSE 8000 -ENTRYPOINT ["python"] -CMD ["-m", "flask", "run", "--host=0.0.0.0", "--port=8000"] diff --git a/python/requirements.txt b/python/requirements.txt deleted file mode 100644 index 4b95f49d..00000000 --- a/python/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==3.0.0 -plaid_python==24.0.0 -python-dotenv==0.15.0 -itsdangerous==2.1.2 -werkzeug==3.0.1 diff --git a/python/start.sh b/python/start.sh deleted file mode 100755 index 0cce0cda..00000000 --- a/python/start.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -python server.py || python3 server.py \ No newline at end of file From 187bb7c3e6abf8a6156040cd43e414e7188610e5 Mon Sep 17 00:00:00 2001 From: ashwinrachhavt <124548941+ashwinrachhavt@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:16:40 -0400 Subject: [PATCH 2/2]