refactored tests to use the stripe API more and use a list of accounts in the test context.
parent
b0f7f8cbf7
commit
869e7ece42
|
@ -12,11 +12,3 @@ Feature: Add a new account
|
|||
And user does not specify an email address
|
||||
When the new account request is submitted
|
||||
Then the request will fail with a bad request error
|
||||
|
||||
Scenario: Successful on-boarding with membership
|
||||
Given a new account without a membership
|
||||
And user with username bar is authenticated
|
||||
When a user opts into a membership during on-boarding
|
||||
Then the request will be successful
|
||||
And the account will be updated with the membership
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@ Feature: Pair a device
|
|||
Test the device add endpoint
|
||||
|
||||
Scenario: Add a device
|
||||
Given user with username foo is authenticated
|
||||
And a device pairing code
|
||||
When an API request is sent to add a device
|
||||
Then the request will be successful
|
||||
And the device is added to the database
|
||||
And the pairing code is removed from cache
|
||||
And the pairing token is added to cache
|
||||
Given an account
|
||||
And the account is authenticated
|
||||
And a device pairing code
|
||||
When an API request is sent to add a device
|
||||
Then the request will be successful
|
||||
And the device is added to the database
|
||||
And the pairing code is removed from cache
|
||||
And the pairing token is added to cache
|
||||
|
|
|
@ -9,18 +9,24 @@ Feature: Authentication with JWTs
|
|||
be the only place authentication logic needs to be tested.
|
||||
|
||||
Scenario: Request for user data includes valid access token
|
||||
Given user with username foo is authenticated
|
||||
Given an account with a valid access token
|
||||
When a user requests their profile
|
||||
Then the request will be successful
|
||||
And the authentication tokens will remain unchanged
|
||||
|
||||
Scenario: Access token expired
|
||||
Given an authenticated user with an expired access token
|
||||
Given an account with an expired access token
|
||||
When a user requests their profile
|
||||
Then the request will be successful
|
||||
And the authentication tokens will be refreshed
|
||||
|
||||
Scenario: Access token missing but refresh token valid
|
||||
Given an account with a refresh token but no access token
|
||||
When a user requests their profile
|
||||
Then the request will be successful
|
||||
And the authentication tokens will be refreshed
|
||||
|
||||
Scenario: Both access and refresh tokens expired
|
||||
Given a previously authenticated user with expired tokens
|
||||
Given an account with expired access and refresh tokens
|
||||
When a user requests their profile
|
||||
Then the request will fail with an unauthorized error
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
from behave import fixture, use_fixture
|
||||
|
||||
from account_api.api import acct
|
||||
from selene.data.account import (
|
||||
AccountRepository,
|
||||
)
|
||||
from selene.testing import (
|
||||
add_account,
|
||||
add_account_geography,
|
||||
add_agreements,
|
||||
remove_account,
|
||||
remove_agreements
|
||||
)
|
||||
from selene.testing.account import add_account, remove_account
|
||||
from selene.testing.account_geography import add_account_geography
|
||||
from selene.testing.agreement import add_agreements, remove_agreements
|
||||
from selene.util.cache import SeleneCache
|
||||
from selene.util.db import connect_to_db
|
||||
|
||||
|
@ -38,31 +31,22 @@ def after_all(context):
|
|||
|
||||
|
||||
def before_scenario(context, _):
|
||||
context.account = context.foo_account = add_account(context.db)
|
||||
context.geography_id = add_account_geography(context.db, context.account)
|
||||
account = add_account(context.db)
|
||||
context.accounts = dict(foo=account)
|
||||
context.geography_id = add_account_geography(context.db, account)
|
||||
|
||||
|
||||
def after_scenario(context, _):
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
_delete_account(context, db)
|
||||
_clean_cache()
|
||||
|
||||
|
||||
def _delete_account(context, db):
|
||||
"""Delete the account and all its related data.
|
||||
"""Scenario-level cleanup.
|
||||
|
||||
The database is setup with cascading deletes that take care of cleaning up[
|
||||
referential integrity for us. All we have to do here is delete the account
|
||||
and all rows on all tables related to that account will also be deleted.
|
||||
"""
|
||||
acct_repository = AccountRepository(db)
|
||||
remove_account(db, context.foo_account)
|
||||
bar_acct = acct_repository.get_account_by_email('bar@mycroft.ai')
|
||||
if bar_acct is not None:
|
||||
remove_account(db, bar_acct)
|
||||
test_acct = acct_repository.get_account_by_email('test@mycroft.ai')
|
||||
if test_acct is not None:
|
||||
remove_account(db, test_acct)
|
||||
|
||||
for account in context.accounts.values():
|
||||
remove_account(context.db, account)
|
||||
_clean_cache()
|
||||
|
||||
|
||||
def _clean_cache():
|
||||
|
|
|
@ -3,7 +3,29 @@ Feature: Manage account profiles
|
|||
settings.
|
||||
|
||||
Scenario: Retrieve authenticated user's account
|
||||
Given user with username foo is authenticated
|
||||
When a user requests their profile
|
||||
Then the request will be successful
|
||||
And user profile is returned
|
||||
Given an account with a monthly membership
|
||||
# And the account is authenticated
|
||||
When a user requests their profile
|
||||
Then the request will be successful
|
||||
And user profile is returned
|
||||
|
||||
Scenario: user with free account opts into a membership
|
||||
Given an account without a membership
|
||||
And the account is authenticated
|
||||
When a monthly membership is added
|
||||
Then the request will be successful
|
||||
And the account should have a monthly membership
|
||||
|
||||
Scenario: user opts out monthly membership
|
||||
Given an account with a monthly membership
|
||||
# And the account is authenticated
|
||||
When the membership is cancelled
|
||||
Then the request will be successful
|
||||
And the account should have no membership
|
||||
|
||||
Scenario: user changes from a monthly membership to yearly membership
|
||||
Given an account with a monthly membership
|
||||
# And the account is authenticated
|
||||
When the membership is changed to yearly
|
||||
Then the request will be successful
|
||||
And the account should have a yearly membership
|
||||
|
|
|
@ -2,7 +2,7 @@ Feature: Delete an account
|
|||
Test the API call to delete an account and all its related data from the database.
|
||||
|
||||
Scenario: Successful account deletion
|
||||
Given user with username foo is authenticated
|
||||
Given an account with a monthly membership
|
||||
When the user's account is deleted
|
||||
Then the request will be successful
|
||||
And the membership is removed from stripe
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
from binascii import b2a_base64
|
||||
from datetime import date, datetime
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import call, patch
|
||||
from datetime import date
|
||||
|
||||
from behave import given, then, when
|
||||
from flask import json
|
||||
from hamcrest import assert_that, equal_to, is_in, none, not_none
|
||||
from hamcrest import assert_that, equal_to, is_in, not_none
|
||||
|
||||
from selene.data.account import (
|
||||
AccountRepository,
|
||||
MONTHLY_MEMBERSHIP,
|
||||
PRIVACY_POLICY,
|
||||
TERMS_OF_USE
|
||||
)
|
||||
from selene.testing.account import add_account
|
||||
|
||||
new_account_request = dict(
|
||||
termsOfUse=True,
|
||||
|
@ -27,50 +23,16 @@ new_account_request = dict(
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StripeMock(object):
|
||||
"""Object intended to behave like stripe Customer/Subscription create"""
|
||||
id: str
|
||||
|
||||
|
||||
@given('a user completes new account setup')
|
||||
def build_new_account_request(context):
|
||||
context.new_account_request = new_account_request
|
||||
|
||||
|
||||
@given('user opts out of membership')
|
||||
def add_maybe_later_membership(context):
|
||||
context.new_account_request['support'].update(
|
||||
membership=None,
|
||||
paymentMethod=None,
|
||||
paymentAccountId=None
|
||||
)
|
||||
|
||||
|
||||
@given('user opts into a membership')
|
||||
def change_membership_option(context):
|
||||
context.new_account_request['support'].update(
|
||||
membership='Monthly Membership',
|
||||
paymentMethod='Stripe',
|
||||
paymentToken='tok_visa'
|
||||
)
|
||||
|
||||
|
||||
@given('user does not specify an email address')
|
||||
def remove_email_from_request(context):
|
||||
del(context.new_account_request['login']['email'])
|
||||
|
||||
|
||||
@given('a new account without a membership')
|
||||
def add_account_no_membership(context):
|
||||
new_account_overrides = dict(
|
||||
email_address='bar@mycroft.ai',
|
||||
username='bar',
|
||||
membership=None
|
||||
)
|
||||
context.bar_account = add_account(context.db, **new_account_overrides)
|
||||
|
||||
|
||||
@when('the new account request is submitted')
|
||||
def call_add_account_endpoint(context):
|
||||
context.client.content_type = 'application/json'
|
||||
|
@ -81,44 +43,12 @@ def call_add_account_endpoint(context):
|
|||
)
|
||||
|
||||
|
||||
@when('a user opts into a membership during on-boarding')
|
||||
def call_update_account_endpoint(context):
|
||||
onboarding_request = dict(
|
||||
membership=dict(
|
||||
newMembership=True,
|
||||
membershipType=MONTHLY_MEMBERSHIP,
|
||||
paymentToken='test_payment_token',
|
||||
paymentMethod='Stripe'
|
||||
)
|
||||
)
|
||||
with patch('stripe.Subscription.create') as subscription_patch:
|
||||
subscription_patch.return_value = StripeMock(id='test_subscription')
|
||||
with patch('stripe.Customer.create') as customer_patch:
|
||||
customer_patch.return_value = StripeMock(id='test_customer')
|
||||
context.response = context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(onboarding_request),
|
||||
content_type='application/json'
|
||||
)
|
||||
assert_that(
|
||||
customer_patch.mock_calls,
|
||||
equal_to([call(
|
||||
email='bar@mycroft.ai',
|
||||
source='test_payment_token'
|
||||
)])
|
||||
)
|
||||
assert_that(
|
||||
subscription_patch.mock_calls,
|
||||
equal_to([call(
|
||||
customer='test_customer',
|
||||
items=[{'plan': 'monthly_premium'}])
|
||||
]))
|
||||
|
||||
|
||||
@then('the account will be added to the system')
|
||||
def check_db_for_account(context):
|
||||
acct_repository = AccountRepository(context.db)
|
||||
account = acct_repository.get_account_by_email('bar@mycroft.ai')
|
||||
# add account to context so it will deleted by cleanup step
|
||||
context.accounts['bar'] = account
|
||||
assert_that(account, not_none())
|
||||
assert_that(
|
||||
account.email_address, equal_to('bar@mycroft.ai')
|
||||
|
@ -128,14 +58,3 @@ def check_db_for_account(context):
|
|||
for agreement in account.agreements:
|
||||
assert_that(agreement.type, is_in((PRIVACY_POLICY, TERMS_OF_USE)))
|
||||
assert_that(agreement.accept_date, equal_to(str(date.today())))
|
||||
|
||||
|
||||
@then('the account will be updated with the membership')
|
||||
def validate_membership_update(context):
|
||||
account_repository = AccountRepository(context.db)
|
||||
membership = account_repository.get_active_account_membership(
|
||||
context.account.id
|
||||
)
|
||||
assert_that(membership.type, equal_to(MONTHLY_MEMBERSHIP))
|
||||
assert_that(membership.start_date, equal_to(datetime.utcnow().date()))
|
||||
assert_that(membership.end_date, none())
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import json
|
||||
|
||||
from behave import given, when, then
|
||||
from hamcrest import assert_that, equal_to, has_key, none, not_none
|
||||
from hamcrest import assert_that, equal_to, none, not_none
|
||||
|
||||
from selene.data.device import DeviceRepository
|
||||
from selene.util.cache import SeleneCache
|
||||
|
@ -26,6 +26,11 @@ def set_device_pairing_code(context):
|
|||
context.pairing_code = 'ABC123'
|
||||
|
||||
|
||||
@given('an account')
|
||||
def define_account(context):
|
||||
context.username = 'foo'
|
||||
|
||||
|
||||
@when('an API request is sent to add a device')
|
||||
def add_device(context):
|
||||
device = dict(
|
||||
|
@ -57,6 +62,7 @@ def validate_pairing_code_removal(context):
|
|||
@then('the device is added to the database')
|
||||
def validate_response(context):
|
||||
device_id = context.response.data.decode()
|
||||
account = context.accounts['foo']
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
device_repository = DeviceRepository(db)
|
||||
device = device_repository.get_device_by_id(device_id)
|
||||
|
@ -64,7 +70,7 @@ def validate_response(context):
|
|||
assert_that(device, not_none())
|
||||
assert_that(device.name, equal_to('home'))
|
||||
assert_that(device.placement, equal_to('kitchen'))
|
||||
assert_that(device.account_id, equal_to(context.account.id))
|
||||
assert_that(device.account_id, equal_to(account.id))
|
||||
|
||||
|
||||
@then('the pairing token is added to cache')
|
||||
|
|
|
@ -1,27 +1,61 @@
|
|||
from behave import given, then
|
||||
from hamcrest import assert_that, equal_to, is_not
|
||||
|
||||
from selene.api.testing import (
|
||||
from selene.testing.api import (
|
||||
generate_access_token,
|
||||
generate_refresh_token,
|
||||
set_access_token_cookie,
|
||||
set_refresh_token_cookie,
|
||||
validate_token_cookies
|
||||
)
|
||||
from selene.data.account import AccountRepository
|
||||
from selene.util.auth import AuthenticationToken
|
||||
from selene.util.db import connect_to_db
|
||||
|
||||
EXPIRE_IMMEDIATELY = 0
|
||||
|
||||
|
||||
@given('an authenticated user with an expired access token')
|
||||
def generate_refresh_token_only(context):
|
||||
generate_access_token(context, expire=True)
|
||||
generate_refresh_token(context)
|
||||
@given('an account with a valid access token')
|
||||
def use_account_with_valid_access_token(context):
|
||||
context.username = 'foo'
|
||||
context.access_token = generate_access_token(context)
|
||||
set_access_token_cookie(context)
|
||||
context.refresh_token = generate_refresh_token(context)
|
||||
set_refresh_token_cookie(context)
|
||||
|
||||
|
||||
@given('an account with an expired access token')
|
||||
def generate_expired_access_token(context):
|
||||
context.username = 'foo'
|
||||
context.access_token = generate_access_token(
|
||||
context,
|
||||
duration=EXPIRE_IMMEDIATELY
|
||||
)
|
||||
set_access_token_cookie(context, duration=EXPIRE_IMMEDIATELY)
|
||||
context.refresh_token = generate_refresh_token(context)
|
||||
set_refresh_token_cookie(context)
|
||||
context.old_refresh_token = context.refresh_token.jwt
|
||||
|
||||
|
||||
@given('a previously authenticated user with expired tokens')
|
||||
@given('an account with a refresh token but no access token')
|
||||
def generate_refresh_token_only(context):
|
||||
context.username = 'foo'
|
||||
context.refresh_token = generate_refresh_token(context)
|
||||
set_refresh_token_cookie(context)
|
||||
context.old_refresh_token = context.refresh_token.jwt
|
||||
|
||||
|
||||
@given('an account with expired access and refresh tokens')
|
||||
def expire_both_tokens(context):
|
||||
generate_access_token(context, expire=True)
|
||||
generate_refresh_token(context, expire=True)
|
||||
context.username = 'foo'
|
||||
context.access_token = generate_access_token(
|
||||
context,
|
||||
duration=EXPIRE_IMMEDIATELY
|
||||
)
|
||||
set_access_token_cookie(context, duration=EXPIRE_IMMEDIATELY)
|
||||
context.refresh_token = generate_refresh_token(
|
||||
context,
|
||||
duration=EXPIRE_IMMEDIATELY
|
||||
)
|
||||
set_refresh_token_cookie(context, duration=EXPIRE_IMMEDIATELY)
|
||||
|
||||
|
||||
@then('the authentication tokens will remain unchanged')
|
||||
|
@ -37,10 +71,6 @@ def check_for_new_cookies(context):
|
|||
context.refresh_token,
|
||||
is_not(equal_to(context.old_refresh_token))
|
||||
)
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
acct_repository = AccountRepository(db)
|
||||
account = acct_repository.get_account_by_id(context.account.id)
|
||||
|
||||
refresh_token = AuthenticationToken(
|
||||
context.client_config['REFRESH_SECRET'],
|
||||
0
|
||||
|
@ -49,4 +79,6 @@ def check_for_new_cookies(context):
|
|||
refresh_token.validate()
|
||||
assert_that(refresh_token.is_valid, equal_to(True))
|
||||
assert_that(refresh_token.is_expired, equal_to(False))
|
||||
assert_that(refresh_token.account_id, equal_to(account.id))
|
||||
assert_that(
|
||||
refresh_token.account_id,
|
||||
equal_to(context.accounts['foo'].id))
|
||||
|
|
|
@ -3,13 +3,20 @@ from http import HTTPStatus
|
|||
from behave import given, then
|
||||
from hamcrest import assert_that, equal_to, is_in
|
||||
|
||||
from selene.api.testing import generate_access_token, generate_refresh_token
|
||||
from selene.testing.api import (
|
||||
generate_access_token,
|
||||
generate_refresh_token,
|
||||
set_access_token_cookie,
|
||||
set_refresh_token_cookie
|
||||
)
|
||||
|
||||
|
||||
@given('user with username {username} is authenticated')
|
||||
def setup_authenticated_user(context, username):
|
||||
generate_access_token(context, username)
|
||||
generate_refresh_token(context, username)
|
||||
@given('the account is authenticated')
|
||||
def use_account_with_valid_access_token(context):
|
||||
context.access_token = generate_access_token(context)
|
||||
set_access_token_cookie(context)
|
||||
context.refresh_token = generate_refresh_token(context)
|
||||
set_refresh_token_cookie(context)
|
||||
|
||||
|
||||
@then('the request will be successful')
|
||||
|
|
|
@ -1,9 +1,40 @@
|
|||
import json
|
||||
from datetime import date
|
||||
|
||||
from behave import then, when
|
||||
from hamcrest import assert_that, equal_to, has_item, none
|
||||
from behave import given, then, when
|
||||
from hamcrest import assert_that, equal_to, has_item, none, starts_with
|
||||
|
||||
from selene.data.account import PRIVACY_POLICY
|
||||
from selene.data.account import AccountRepository, PRIVACY_POLICY
|
||||
from selene.testing.api import (
|
||||
generate_access_token,
|
||||
generate_refresh_token,
|
||||
set_access_token_cookie,
|
||||
set_refresh_token_cookie
|
||||
)
|
||||
from selene.testing.membership import MONTHLY_MEMBERSHIP, YEARLY_MEMBERSHIP
|
||||
|
||||
BAR_EMAIL_ADDRESS = 'bar@mycroft.ai'
|
||||
STRIPE_METHOD = 'Stripe'
|
||||
VISA_TOKEN = 'tok_visa'
|
||||
|
||||
|
||||
@given('an account with a monthly membership')
|
||||
def add_membership_to_account(context):
|
||||
"""Use the API to add a monthly membership on Stripe
|
||||
|
||||
The API is used so that the Stripe API can be interacted with.
|
||||
"""
|
||||
context.username = 'foo'
|
||||
context.access_token = generate_access_token(context)
|
||||
set_access_token_cookie(context)
|
||||
context.refresh_token = generate_refresh_token(context)
|
||||
set_refresh_token_cookie(context)
|
||||
add_membership_via_api(context)
|
||||
|
||||
|
||||
@given('an account without a membership')
|
||||
def get_account_no_membership(context):
|
||||
context.username = 'foo'
|
||||
|
||||
|
||||
@when('a user requests their profile')
|
||||
|
@ -14,12 +45,58 @@ def call_account_endpoint(context):
|
|||
)
|
||||
|
||||
|
||||
@when('a monthly membership is added')
|
||||
def add_monthly_membership(context):
|
||||
context.response = add_membership_via_api(context)
|
||||
|
||||
|
||||
@when('the membership is cancelled')
|
||||
def cancel_membership(context):
|
||||
membership_data = dict(
|
||||
newMembership=False,
|
||||
membershipType=None
|
||||
)
|
||||
context.response = context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(dict(membership=membership_data)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
def add_membership_via_api(context):
|
||||
membership_data = dict(
|
||||
newMembership=True,
|
||||
membershipType=MONTHLY_MEMBERSHIP,
|
||||
paymentMethod=STRIPE_METHOD,
|
||||
paymentToken=VISA_TOKEN
|
||||
)
|
||||
return context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(dict(membership=membership_data)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
@when('the membership is changed to yearly')
|
||||
def change_to_yearly_account(context):
|
||||
membership_data = dict(
|
||||
newMembership=False,
|
||||
membershipType=YEARLY_MEMBERSHIP
|
||||
)
|
||||
context.response = context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(dict(membership=membership_data)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
@then('user profile is returned')
|
||||
def validate_response(context):
|
||||
response_data = context.response.json
|
||||
account = context.accounts['foo']
|
||||
assert_that(
|
||||
response_data['emailAddress'],
|
||||
equal_to(context.account.email_address)
|
||||
equal_to(account.email_address)
|
||||
)
|
||||
assert_that(
|
||||
response_data['membership']['type'],
|
||||
|
@ -38,3 +115,37 @@ def validate_response(context):
|
|||
equal_to(str(date.today().strftime('%B %d, %Y')))
|
||||
)
|
||||
assert_that(agreement, has_item('id'))
|
||||
|
||||
|
||||
@then('the account should have a monthly membership')
|
||||
def validate_monthly_account(context):
|
||||
acct_repository = AccountRepository(context.db)
|
||||
membership = acct_repository.get_active_account_membership(
|
||||
context.accounts['foo'].id
|
||||
)
|
||||
assert_that(
|
||||
membership.type,
|
||||
equal_to(MONTHLY_MEMBERSHIP)
|
||||
)
|
||||
assert_that(membership.payment_account_id, starts_with('cus'))
|
||||
assert_that(membership.start_date, equal_to(date.today()))
|
||||
assert_that(membership.end_date, none())
|
||||
|
||||
|
||||
@then('the account should have no membership')
|
||||
def validate_absence_of_membership(context):
|
||||
acct_repository = AccountRepository(context.db)
|
||||
membership = acct_repository.get_active_account_membership(
|
||||
context.accounts['foo'].id
|
||||
)
|
||||
assert_that(membership, none())
|
||||
|
||||
|
||||
@then('the account should have a yearly membership')
|
||||
def yearly_account(context):
|
||||
acct_repository = AccountRepository(context.db)
|
||||
membership = acct_repository.get_active_account_membership(
|
||||
context.accounts['foo'].id
|
||||
)
|
||||
assert_that(membership.type, equal_to(YEARLY_MEMBERSHIP))
|
||||
assert_that(membership.payment_account_id, starts_with('cus'))
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import os
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import stripe
|
||||
from behave import then, when
|
||||
from hamcrest import assert_that, equal_to, not_none
|
||||
from hamcrest import assert_that, equal_to
|
||||
from stripe.error import InvalidRequestError
|
||||
|
||||
from selene.data.account import AccountRepository
|
||||
|
||||
|
||||
@when('the user\'s account is deleted')
|
||||
def account_deleted(context):
|
||||
with patch('stripe.Subscription') as stripe_patch:
|
||||
context.response = context.client.delete('/api/account')
|
||||
assert_that(
|
||||
stripe_patch.mock_calls,
|
||||
equal_to([call.retrieve('bar'), call.retrieve().delete()])
|
||||
)
|
||||
acct_repository = AccountRepository(context.db)
|
||||
membership = acct_repository.get_active_account_membership(
|
||||
context.accounts['foo'].id
|
||||
)
|
||||
context.accounts['foo'].membership = membership
|
||||
context.response = context.client.delete('/api/account')
|
||||
|
||||
|
||||
@then('the membership is removed from stripe')
|
||||
def check_stripe(context):
|
||||
stripe_id = context.account.membership.payment_account_id
|
||||
assert_that(stripe_id, not_none())
|
||||
account = context.accounts['foo']
|
||||
stripe.api_key = os.environ['STRIPE_PRIVATE_KEY']
|
||||
subscription_not_found = False
|
||||
try:
|
||||
stripe.Subscription.retrieve(stripe_id)
|
||||
stripe.Subscription.retrieve(account.membership.payment_account_id)
|
||||
except InvalidRequestError:
|
||||
subscription_not_found = True
|
||||
assert_that(subscription_not_found, equal_to(True))
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
from binascii import b2a_base64
|
||||
from datetime import date
|
||||
import json
|
||||
|
||||
from behave import given, when, then
|
||||
from hamcrest import assert_that, equal_to, starts_with, none
|
||||
|
||||
from selene.api.testing import generate_access_token, generate_refresh_token
|
||||
from selene.data.account import (
|
||||
AccountRepository,
|
||||
Account,
|
||||
AccountAgreement,
|
||||
PRIVACY_POLICY
|
||||
)
|
||||
from selene.util.db import connect_to_db
|
||||
|
||||
TEST_EMAIL_ADDRESS = 'test@mycroft.ai'
|
||||
|
||||
new_account_request = dict(
|
||||
username='test',
|
||||
termsOfUse=True,
|
||||
privacyPolicy=True,
|
||||
login=dict(
|
||||
federatedPlatform=None,
|
||||
federatedToken=None,
|
||||
email=b2a_base64(b'test@mycroft.ai').decode(),
|
||||
password=b2a_base64(b'12345678').decode()
|
||||
),
|
||||
support=dict(
|
||||
openDataset=True,
|
||||
membership='Maybe Later',
|
||||
paymentMethod=None,
|
||||
paymentAccountId=None
|
||||
)
|
||||
)
|
||||
|
||||
MONTHLY_MEMBERSHIP = 'Monthly Membership'
|
||||
STRIPE_METHOD = 'Stripe'
|
||||
VISA_TOKEN = 'tok_visa'
|
||||
YEARLY_MEMBERSHIP = 'Yearly Membership'
|
||||
|
||||
|
||||
@given('a user with a free account')
|
||||
def create_account(context):
|
||||
context.account = Account(
|
||||
email_address='test@mycroft.ai',
|
||||
username='test',
|
||||
membership=None,
|
||||
agreements=[
|
||||
AccountAgreement(type=PRIVACY_POLICY, accept_date=date.today())
|
||||
]
|
||||
)
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
acct_repository = AccountRepository(db)
|
||||
account_id = acct_repository.add(context.account, 'foo')
|
||||
context.account.id = account_id
|
||||
generate_access_token(context)
|
||||
generate_refresh_token(context)
|
||||
|
||||
|
||||
@when('a monthly membership is added')
|
||||
def update_membership(context):
|
||||
membership_data = dict(
|
||||
newMembership=True,
|
||||
membershipType=MONTHLY_MEMBERSHIP,
|
||||
paymentMethod=STRIPE_METHOD,
|
||||
paymentToken=VISA_TOKEN
|
||||
)
|
||||
context.response = context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(dict(membership=membership_data)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
@when('the account is requested')
|
||||
def request_account(context):
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
context.response_account = AccountRepository(db).get_account_by_email(
|
||||
TEST_EMAIL_ADDRESS
|
||||
)
|
||||
|
||||
|
||||
@then('the account should have a monthly membership')
|
||||
def monthly_account(context):
|
||||
account = context.response_account
|
||||
assert_that(
|
||||
account.membership.type,
|
||||
equal_to(MONTHLY_MEMBERSHIP)
|
||||
)
|
||||
assert_that(account.membership.payment_account_id, starts_with('cus'))
|
||||
|
||||
|
||||
@given('a user with a monthly membership')
|
||||
def create_monthly_account(context):
|
||||
new_account_request['support'].update(
|
||||
membership=MONTHLY_MEMBERSHIP,
|
||||
paymentMethod=STRIPE_METHOD,
|
||||
paymentToken=VISA_TOKEN
|
||||
)
|
||||
context.client.post(
|
||||
'/api/account',
|
||||
data=json.dumps(new_account_request),
|
||||
content_type='application/json'
|
||||
)
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
account_repository = AccountRepository(db)
|
||||
account = account_repository.get_account_by_email(TEST_EMAIL_ADDRESS)
|
||||
context.account = account
|
||||
generate_access_token(context)
|
||||
generate_refresh_token(context)
|
||||
|
||||
|
||||
@when('the membership is cancelled')
|
||||
def cancel_membership(context):
|
||||
membership_data = dict(
|
||||
newMembership=False,
|
||||
membershipType=None
|
||||
)
|
||||
context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(dict(membership=membership_data)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
@then('the account should have no membership')
|
||||
def free_account(context):
|
||||
account = context.response_account
|
||||
assert_that(account.membership, none())
|
||||
|
||||
|
||||
@when('the membership is changed to yearly')
|
||||
def change_to_yearly_account(context):
|
||||
membership_data = dict(
|
||||
newMembership=False,
|
||||
membershipType=YEARLY_MEMBERSHIP
|
||||
)
|
||||
context.client.patch(
|
||||
'/api/account',
|
||||
data=json.dumps(dict(membership=membership_data)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
@then('the account should have a yearly membership')
|
||||
def yearly_account(context):
|
||||
account = context.response_account
|
||||
assert_that(account.membership.type, equal_to(YEARLY_MEMBERSHIP))
|
||||
assert_that(account.membership.payment_account_id, starts_with('cus'))
|
|
@ -1,19 +0,0 @@
|
|||
Feature: Test the API call to update a membership
|
||||
|
||||
Scenario: user with free account opts into a membership
|
||||
Given a user with a free account
|
||||
When a monthly membership is added
|
||||
And the account is requested
|
||||
Then the account should have a monthly membership
|
||||
|
||||
Scenario: user opts out monthly membership
|
||||
Given a user with a monthly membership
|
||||
When the membership is cancelled
|
||||
And the account is requested
|
||||
Then the account should have no membership
|
||||
|
||||
Scenario: user changes from a monthly membership to yearly membership
|
||||
Given a user with a monthly membership
|
||||
When the membership is changed to yearly
|
||||
And the account is requested
|
||||
Then the account should have a yearly membership
|
Loading…
Reference in New Issue