Skip to content
Merged

Beta #63

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .github/workflows/deploy-infra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,27 @@ jobs:
ParameterKey=UniqueIdentifier,ParameterValue='v1' \
ParameterKey=ResaleServiceEndpoint,ParameterValue=$RESALE_SERVICE_ENDPOINT \
ParameterKey=SenderEmail,ParameterValue=$SENDER_EMAIL \
ParameterKey=PagerDutyEndpoint,ParameterValue=$PAGERDUTY_ENDPOINT
ParameterKey=PagerDutyEndpoint,ParameterValue=$PAGERDUTY_ENDPOINT

integration-tests:
needs: deploy-aws
if: github.event_name == 'push'
runs-on: ubuntu-latest
environment: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v3
- name: Run Integration Tests
run: |
cd tests/integration
pip install -r requirements.txt
python -m pytest test_transfer_flow.py -v
env:
API_ENDPOINT: ${{ vars.ZENOBIA_API_ENDPOINT }}
AUTH0_DOMAIN: ${{ vars.AUTH0_DOMAIN }}
AUTH0_M2M_MERCHANT_CLIENT_ID: ${{ secrets.AUTH0_M2M_MERCHANT_CLIENT_ID }}
AUTH0_M2M_MERCHANT_CLIENT_SECRET: ${{ secrets.AUTH0_M2M_MERCHANT_CLIENT_SECRET }}
CUSTOMER_SUB: ${{ vars.CUSTOMER_SUB }}
CUSTOMER_REFRESH_TOKEN: ${{ secrets.CUSTOMER_REFRESH_TOKEN }}
CUSTOMER_PRIVATE_KEY: ${{ secrets.CUSTOMER_PRIVATE_KEY }}
CUSTOMER_DEVICE_ID: ${{ secrets.CUSTOMER_DEVICE_ID }}
38 changes: 0 additions & 38 deletions __tests__/unit/handlers/get-all-items.test.mjs

This file was deleted.

43 changes: 0 additions & 43 deletions __tests__/unit/handlers/get-by-id.test.mjs

This file was deleted.

40 changes: 0 additions & 40 deletions __tests__/unit/handlers/put-item.test.mjs

This file was deleted.

1 change: 1 addition & 0 deletions golang/authorizer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func handler(ctx context.Context, event events.APIGatewayCustomAuthorizerRequest
} else if authorization, ok := event.Headers["Authorization"]; ok {
hasUnauthenticatedHeader = authorization == "NONE" // Explicitly set by app if no auth is provided
}
println("Has unauthenticated header: ", hasUnauthenticatedHeader)
if isValidPath(event.Path, validOrumRoutes) {
return handleOrumWebhookEndpoint(ctx, event)
} else if isValidPath(event.Path, validPlaidRoutes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ class GetAdminTransferOperation @Inject constructor(
context: Context,
userId: String?
): GetAdminTransfer200Response {
val transferId = input.queryStringParameters?.get("id")
?: throw ResourceNotFoundException("Missing transfer ID")
val transferId = request.id ?: throw ResourceNotFoundException("Missing transfer ID")

val transferItem = transferDao.getTransfer(transferId) ?: throw ResourceNotFoundException("TRANSFER")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.zenobiapay.table.user.dao.UserDao
import com.zenobiapay.table.user.model.MerchantData
import com.zenobiapay.table.user.model.UserItem
import com.zenobiapay.table.user.model.UserItemData
import com.zenobiapay.transfer.model.FulfillTransferRequestMixin
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
Expand Down Expand Up @@ -140,6 +141,22 @@ class FulfillTransferOperationTest {
}
}

@Test
fun testSignatureOrdering() {
val mixin = objectMapper.copy()
.addMixIn(FulfillTransferRequest::class.java, FulfillTransferRequestMixin::class.java)

val request = FulfillTransferRequest()
.transferRequestId("4a573cdf-8e70-4e38-8c93-02f98439db88")
.bankAccountId("BjKzqRAad9TLKWE6D7DEFMjrDBDLa4S4xPAzo")
.deviceId("AFB81FDD-DC90-4BD0-857D-9C21BA5BD65B")
.signature(FulfillTransferRequestSignature()
.signatureType(SIGNATURE_TYPE)
.signatureValue(SIGNATURE))
val json = mixin.writeValueAsString(request)
println(json)
}

private fun createMockGatewayEvent(): APIGatewayProxyRequestEvent {
val mockEvent = mockk<APIGatewayProxyRequestEvent>()
return mockEvent
Expand Down
12 changes: 12 additions & 0 deletions tests/integration/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# API endpoint (e.g., https://your-api-endpoint.execute-api.us-east-1.amazonaws.com/prod)
API_ENDPOINT=

# Auth0 configuration
AUTH0_DOMAIN=
AUTH0_M2M_CLIENT_ID=
AUTH0_M2M_CLIENT_SECRET=

CUSTOMER_CLIENT_ID=
CUSTOMER_REFRESH_TOKEN=
CUSTOMER_PRIVATE_KEY=
CUSTOMER_DEVICE_ID=
Empty file added tests/integration/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os
import pytest
import boto3
from dotenv import load_dotenv

def pytest_sessionstart(session):
"""
Called after the Session object has been created and before tests are executed.
"""
# Load environment variables from .env file if it exists (for local testing)
load_dotenv()

# Verify required environment variables
required_vars = [
'API_ENDPOINT',
'AUTH0_DOMAIN',
'AUTH0_M2M_MERCHANT_CLIENT_ID',
'AUTH0_M2M_MERCHANT_CLIENT_SECRET',
]

# Check for either M2M credentials or customer refresh token
customer_auth_vars = ['CUSTOMER_SUB', 'CUSTOMER_REFRESH_TOKEN']
if not any(os.environ.get(var) for var in customer_auth_vars):
print("Warning: No customer authentication variables found. Tests may fail if they require customer authentication.")
print("Consider setting CUSTOMER_SUB and CUSTOMER_REFRESH_TOKEN in your .env file.")

missing = [var for var in required_vars if not os.environ.get(var)]
if missing:
pytest.exit(f"Missing required environment variables: {', '.join(missing)}")
8 changes: 8 additions & 0 deletions tests/integration/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pytest==7.4.0
pytest-xdist==3.3.1
requests==2.31.0
python-dotenv==1.0.0
boto3==1.28.38
cryptography==41.0.5
ecdsa==0.18.0
asn1crypto==1.5.1
24 changes: 24 additions & 0 deletions tests/integration/run_local_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

# Activate virtual environment if not already activated
if [[ "$VIRTUAL_ENV" == "" ]]; then
echo "Activating virtual environment..."
source ../../.venv/bin/activate
fi

# Check if .env file exists, if not, create from template
if [ ! -f ".env" ]; then
echo "No .env file found. Creating from template..."
if [ -f ".env.template" ]; then
cp .env.template .env
echo "Please edit .env file with your actual values before running tests."
exit 1
else
echo "Error: .env.template not found. Please create a .env file manually."
exit 1
fi
fi

# Run the tests (python-dotenv will automatically load .env file)
# Adding -s flag to show print statements
python -m pytest test_transfer_flow.py -v -s
94 changes: 94 additions & 0 deletions tests/integration/test_transfer_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import pytest
import time
import uuid
import os
import sys
from utils.api import MerchantApi, CustomerApi

def test_full_transfer_flow():
"""Test the full transfer flow from creation to completion"""

transfer_amount = 1 # 1 cent

# Step 1: Create a transfer request as merchant
merchant_api = MerchantApi()
statement_items = [
{
"name": "Test Item",
"amount": transfer_amount,
"itemId": f"test-item-{uuid.uuid4()}"
}
]
transfer_metadata = {}
create_response = merchant_api.create_transfer_request(
amount=transfer_amount,
statement_items=statement_items,
transfer_metadata=transfer_metadata
)
assert create_response.status_code == 200, f"Failed to create transfer: {create_response.text}"
transfer_id = create_response.json()["transferRequestId"]
print(f"Created transfer with ID: {transfer_id}")

# Step 2: List bank accounts as customer
customer_api = CustomerApi()

bank_accounts_response = customer_api.list_bank_accounts()
assert bank_accounts_response.status_code == 200, f"Failed to list bank accounts: {bank_accounts_response.text}"

# Get a bank account ID for fulfillment
try:
response_json = bank_accounts_response.json()
print(f"JSON response: {response_json}")
# The API returns bank accounts in the 'items' field, not 'bankAccounts'
bank_accounts = response_json.get("items", [])
print(f"Bank accounts: {bank_accounts}")
except Exception as e:
print(f"Error parsing JSON: {e}")
bank_accounts = []
print("--- END DEBUG ---\n")

if not bank_accounts:
print("\n*** SKIPPING TEST: No bank accounts available for testing ***\n")
pytest.skip("No bank accounts available for testing")
bank_account_id = bank_accounts[0].get("bankAccountId")

# Step 3: Get customer transfer (authenticated)
transfer_authed_response = customer_api.get_customer_transfer(
transfer_id=transfer_id,
authed=True
)
assert transfer_authed_response.status_code == 200, f"Failed to get transfer (authed): {transfer_authed_response.text}"
print(f"Successfully retrieved transfer (authenticated)")

# Step 4: Get customer transfer (unauthenticated)
unauthed_api = CustomerApi() # No customer_id = no token
transfer_unauthed_response = unauthed_api.get_customer_transfer(
transfer_id=transfer_id,
authed=False
)
assert transfer_unauthed_response.status_code == 200, f"Failed to get transfer (unauthed): {transfer_unauthed_response.text}"
print(f"Successfully retrieved transfer (unauthenticated)")

# Step 5: Fulfill the transfer
fulfill_response = customer_api.fulfill_transfer(
transfer_id=transfer_id,
bank_account_id=bank_account_id,
)
assert fulfill_response.status_code == 200, f"Failed to fulfill transfer: {fulfill_response.text}"
print(f"Successfully fulfilled transfer")

# Wait for transfer processing (adjust as needed)
print(f"Waiting for transfer processing...")
time.sleep(10) # Increased wait time to ensure processing completes

# Step 6: List customer transfers and verify success
transfers_response = customer_api.list_customer_transfers(continuation_token=None)
assert transfers_response.status_code == 200, f"Failed to list transfers: {transfers_response.text}"

# Verify the transfer is in the list and has status "PAID"
transfers = transfers_response.json().get("items", [])
matching_transfers = [t for t in transfers if t.get("transferRequestId") == transfer_id]
assert len(matching_transfers) == 1, f"Transfer {transfer_id} not found in list"
assert matching_transfers[0].get("status") == "PAID", f"Transfer status is not PAID: {matching_transfers[0].get('status')}"
print(f"Successfully verified transfer status is PAID")

Empty file.
Loading