Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .github/workflows/style-and-test-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jobs:
run: make test
env:
JINA_AUTH_TOKEN: ${{secrets.JINA_AUTH_TOKEN}}
- name: Run payment tests
run: JINA_HUBBLE_REGISTRY=https://apihubble.staging.jina.ai make test-payment
env:
STRIPE_SECRET_KEY: ${{secrets.STRIPE_SECRET_KEY}}
M2M_TOKEN: ${{secrets.M2M_TOKEN}}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ init:

# ---------------------------------------------------------------- Test related targets

PYTEST_ARGS = --show-capture no --full-trace --verbose --cov hubble/ --cov-report term-missing --cov-report xml
PYTEST_ARGS = --show-capture no --full-trace --verbose --cov hubble/ --cov-report term-missing --cov-report xml
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

unnecessary change (' ' at the end of line)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm moving these two PR's back to draft mode and re-factor after payment v4.


## Run tests
test:
pytest $(PYTEST_ARGS) $(TESTS_PATH)
pytest $(PYTEST_ARGS) --ignore-glob=**/payment/* $(TESTS_PATH)

test-payment:
pytest $(PYTEST_ARGS) tests/unit/payment/ tests/integration/payment

# ---------------------------------------------------------- Code style related targets

Expand Down
28 changes: 17 additions & 11 deletions hubble/payment/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,35 @@

class PaymentClient(PaymentBaseClient):
def get_user_token(self, user_id) -> dict:
"""Create a user session and token for a user.

:param user_id: The _id of the user
:returns: Object
"""

return self.handle_request(
url=self._base_url + PaymentEndpoints.get_user_token,
data={'userId': user_id},
)

def get_authorized_jwt(
self, user_token: str, expiration_seconds: int = 15 * 60 * 1000
self, token: str, expiration_seconds: int = 15 * 60 * 1000
) -> dict:
"""Create a payment authorized JWT for user.

:param user_id: The _id of the user.
:param expiration_seconds: Number of seconds until the JWT expires.
:returns: Object.
:param user_id: The _id of the user
:param expiration_seconds: Number of seconds until the JWT expires
:returns: Object
"""
return self.handle_request(
url=self._base_url + PaymentEndpoints.get_authorized_jwt,
data={'token': user_token, 'ttl': expiration_seconds},
data={'token': token, 'ttl': expiration_seconds},
)

def verify_authorized_jwt(self, token: str) -> bool:
"""Verify if a token is a payment authorized JWT

:param token: User token.
:param token: User token
:returns: Boolean (true if payment authorized, false otherwise)
"""

Expand All @@ -42,8 +48,8 @@ def verify_authorized_jwt(self, token: str) -> bool:
def get_summary(self, token: str, app_id: str) -> object:
"""Get a list of a user's subscriptions and consumption for a given app.

:param token: User token.
:param app_id: ID of the application.
:param token: User token
:param app_id: ID of the application
:returns: Object
"""

Expand All @@ -58,9 +64,9 @@ def report_usage(

"""Report usage for a given app.

:param token: User token.
:param app_id: ID of the application.
:param product_id: ID of the product.
:param token: User token
:param app_id: ID of the application
:param product_id: ID of the product
:returns: Object
"""

Expand Down
2 changes: 1 addition & 1 deletion hubble/payment/jwks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def get_keys(kid: str):
def get_keys_from_config():
"""Get cached JWK list from config file."""
jwks = config.get('jwks')
return jwks
return jwks if jwks is not None else []

@staticmethod
def get_keys_from_hubble():
Expand Down
4 changes: 3 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ pytest==7.0.0
pytest-asyncio==0.19.0
pytest-cov==3.0.0
pytest-mock==3.7.0
mock==4.0.3
mock==4.0.3
stripe==5.0.0
python-dateutil==2.8.2
30 changes: 30 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
import os
import tempfile

import pytest
from hubble.payment.client import PaymentClient

from .utils.stripe import StripeClient


@pytest.fixture(autouse=True)
def tmpfile(tmpdir):
tmpfile = f'jina_test_{next(tempfile._get_candidate_names())}.db'
return tmpdir / tmpfile


@pytest.fixture(scope='session')
def m2m_token():
return os.environ.get('M2M_TOKEN', None)


# fixture for acquiring a 'cached' instance of StripeClient
@pytest.fixture(scope='session')
def stripe_client():
api_key = os.environ.get('STRIPE_SECRET_KEY', None)
client = StripeClient(api_key)
yield client
client.cleanup()


@pytest.fixture()
def payment_client(m2m_token):
payment_client = PaymentClient(m2m_token=m2m_token)
yield payment_client


@pytest.fixture()
def user_token(payment_client, request):
user_token = payment_client.get_user_token(user_id=request.param)['data']
yield user_token
105 changes: 105 additions & 0 deletions tests/integration/payment/test_non_paying_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import time
from datetime import datetime

import pytest
from dateutil.relativedelta import relativedelta
from mock import patch # noqa: F401

INTERNAL_APP_ID = 'hubble-sdk'
INTERNAL_PRODUCT_ID = 'hubble-sdk'

PRICE_STRIPE_ID = 'price_1MTl37AkuPxeor9kLZxJ5lfd'

NON_PAYING_USER_ID_1 = '63d75509234f12b36dbd8b36'
NON_PAYING_USER_EMAIL_1 = 'hubble_sdk_user_3@jina.ai'

NON_PAYING_USER_ID_2 = '63d7551c234f12b36dbd8dca'
NON_PAYING_USER_EMAIL_2 = 'hubble_sdk_user_4@jina.ai'


@pytest.mark.parametrize('user_token', [NON_PAYING_USER_ID_1], indirect=True)
def test_get_summary(stripe_client, payment_client, user_token):

# creating stripe customer for user
customer = stripe_client.get_customer(email=NON_PAYING_USER_EMAIL_1)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)
expected_result = {'subscriptionItems': [], 'hasPaymentMethod': False}
assert summary['data'] == expected_result

# creating subscription
stripe_client.create_subscription(
customer_id=customer['customer_id'], items=[PRICE_STRIPE_ID]
)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)

expected_result = {
'subscriptionItems': [
{
'internalAppId': INTERNAL_APP_ID,
'internalProductId': INTERNAL_PRODUCT_ID,
'usageQuantity': 0,
}
],
'hasPaymentMethod': False,
}

assert summary['data'] == expected_result


@pytest.mark.parametrize('user_token', [NON_PAYING_USER_ID_2], indirect=True)
def test_submit_usage_report(stripe_client, payment_client, user_token):

# try to submit a usage report
customer = stripe_client.get_customer(email=NON_PAYING_USER_EMAIL_2)

stripe_client.create_subscription(
customer_id=customer['customer_id'], items=[PRICE_STRIPE_ID]
)

payment_client.report_usage(
token=user_token,
app_id=INTERNAL_APP_ID,
product_id=INTERNAL_PRODUCT_ID,
quantity=100,
)

# NOTE: sleeping to wait for the usage report to be processed
time.sleep(75)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)

expected_result = {
'subscriptionItems': [
{
'internalAppId': INTERNAL_APP_ID,
'internalProductId': INTERNAL_PRODUCT_ID,
'usageQuantity': 100,
}
],
'hasPaymentMethod': False,
}

assert summary['data'] == expected_result

# advancing test clock by one month
# this will trigger a new subscription period
now = datetime.now()
later = now + relativedelta(days=+35)
stripe_client.advance_clock(test_clock_id=customer['test_clock_id'], date=later)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)

expected_result = {'subscriptionItems': [], 'hasPaymentMethod': False}

assert summary['data'] == expected_result


@pytest.mark.parametrize(
'user_token', [NON_PAYING_USER_ID_1, NON_PAYING_USER_ID_2], indirect=True
)
def test_get_authorized_jwt(payment_client, user_token):
jwt = payment_client.get_authorized_jwt(token=user_token)['data']
is_authorized = payment_client.verify_authorized_jwt(token=jwt)
assert is_authorized is True
118 changes: 118 additions & 0 deletions tests/integration/payment/test_paying_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import time
from datetime import datetime

import pytest
from dateutil.relativedelta import relativedelta
from mock import patch # noqa: F401

INTERNAL_APP_ID = 'hubble-sdk'
INTERNAL_PRODUCT_ID = 'hubble-sdk'

PRICE_STRIPE_ID = 'price_1MTl37AkuPxeor9kLZxJ5lfd'

PAYING_USER_ID_1 = '63d754c6234f12b36dbd827f'
PAYING_USER_EMAIL_1 = 'hubble_sdk_user_1@jina.ai'

PAYING_USER_ID_2 = '63d754de234f12b36dbd8580'
PAYING_USER_EMAIL_2 = 'hubble_sdk_user_2@jina.ai'


@pytest.mark.parametrize('user_token', [PAYING_USER_ID_1], indirect=True)
def test_get_summary(stripe_client, payment_client, user_token):

# creating stripe customer for user
customer = stripe_client.get_customer(
email=PAYING_USER_EMAIL_1, payment_method='pm_card_visa'
)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)
expected_result = {'subscriptionItems': [], 'hasPaymentMethod': True}
assert summary['data'] == expected_result

# creating subscription
stripe_client.create_subscription(
customer_id=customer['customer_id'], items=[PRICE_STRIPE_ID]
)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)

expected_result = {
'subscriptionItems': [
{
'internalAppId': INTERNAL_APP_ID,
'internalProductId': INTERNAL_PRODUCT_ID,
'usageQuantity': 0,
}
],
'hasPaymentMethod': True,
}

assert summary['data'] == expected_result


@pytest.mark.parametrize('user_token', [PAYING_USER_ID_2], indirect=True)
def test_submit_usage_report(stripe_client, payment_client, user_token):

# try to submit a usage report
customer = stripe_client.get_customer(
email=PAYING_USER_EMAIL_2, payment_method='pm_card_visa'
)

stripe_client.create_subscription(
customer_id=customer['customer_id'], items=[PRICE_STRIPE_ID]
)

payment_client.report_usage(
token=user_token,
app_id=INTERNAL_APP_ID,
product_id=INTERNAL_PRODUCT_ID,
quantity=100,
)

# NOTE: sleeping to wait for the usage report to be processed
time.sleep(75)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)

expected_result = {
'subscriptionItems': [
{
'internalAppId': INTERNAL_APP_ID,
'internalProductId': INTERNAL_PRODUCT_ID,
'usageQuantity': 100,
}
],
'hasPaymentMethod': True,
}

assert summary['data'] == expected_result

# advancing test clock by one month
# this will trigger a new subscription period
now = datetime.now()
later = now + relativedelta(days=+35)
stripe_client.advance_clock(test_clock_id=customer['test_clock_id'], date=later)

summary = payment_client.get_summary(token=user_token, app_id=INTERNAL_APP_ID)

expected_result = {
'subscriptionItems': [
{
'internalAppId': INTERNAL_APP_ID,
'internalProductId': INTERNAL_PRODUCT_ID,
'usageQuantity': 0,
}
],
'hasPaymentMethod': True,
}

assert summary['data'] == expected_result


@pytest.mark.parametrize(
'user_token', [PAYING_USER_ID_1, PAYING_USER_ID_2], indirect=True
)
def test_get_authorized_jwt(payment_client, user_token):
jwt = payment_client.get_authorized_jwt(token=user_token)['data']
is_authorized = payment_client.verify_authorized_jwt(token=jwt)
assert is_authorized is True
Empty file added tests/utils/__init__.py
Empty file.
Loading