added test condition for finishing on-boarding as a separate step from new account creation

pull/183/head
Chris Veilleux 2019-06-12 10:32:53 -05:00
parent ca272af1a9
commit 4e5bf29eb4
9 changed files with 207 additions and 183 deletions

View File

@ -0,0 +1,22 @@
Feature: Add a new account
Test the API call to add an account to the database.
Scenario: Successful account addition
Given a user completes new account setup
When the new account request is submitted
Then the request will be successful
And the account will be added to the system
Scenario: Request missing a required field
Given a user completes new account setup
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

View File

@ -1,29 +0,0 @@
Feature: Add a new account
Test the API call to add an account to the database.
Scenario: Successful account addition with membership
Given a user completes on-boarding
And user opts into a membership
When the new account request is submitted
Then the request will be successful
And the account will be added to the system with a membership
Scenario: Successful account addition without membership
Given a user completes on-boarding
And user opts out of membership
When the new account request is submitted
Then the account will be added to the system without a membership
Scenario: Request missing a required field
Given a user completes on-boarding
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 account deletion with membership
Given a user completes on-boarding
And user opts into a membership
When the new account request is submitted
And the account is deleted
Then the request will be successful
And the membership is removed from stripe

View File

@ -0,0 +1,141 @@
from binascii import b2a_base64
from datetime import date, datetime
from dataclasses import dataclass
from unittest.mock import call, patch
from behave import given, then, when
from flask import json
from hamcrest import assert_that, equal_to, is_in, none, 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,
privacyPolicy=True,
login=dict(
federatedPlatform=None,
federatedToken=None,
email=b2a_base64(b'bar@mycroft.ai').decode(),
password=b2a_base64(b'bar').decode()
)
)
@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'
context.response = context.client.post(
'/api/account',
data=json.dumps(context.new_account_request),
content_type='application/json'
)
@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')
assert_that(account, not_none())
assert_that(
account.email_address, equal_to('bar@mycroft.ai')
)
assert_that(len(account.agreements), equal_to(2))
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())

View File

@ -1,110 +0,0 @@
from binascii import b2a_base64
from datetime import date
import os
import stripe
from behave import given, then, when
from flask import json
from hamcrest import assert_that, equal_to, is_in, none, not_none, starts_with
from stripe.error import InvalidRequestError
from selene.data.account import AccountRepository, PRIVACY_POLICY, TERMS_OF_USE
from selene.util.db import connect_to_db
new_account_request = dict(
username='barfoo',
termsOfUse=True,
privacyPolicy=True,
login=dict(
federatedPlatform=None,
federatedToken=None,
userEnteredEmail=b2a_base64(b'bar@mycroft.ai').decode(),
password=b2a_base64(b'bar').decode()
),
support=dict(openDataset=True)
)
@given('a user completes on-boarding')
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']['userEnteredEmail'])
@when('the new account request is submitted')
def call_add_account_endpoint(context):
context.client.content_type = 'application/json'
context.response = context.client.post(
'/api/account',
data=json.dumps(context.new_account_request),
content_type='application/json'
)
@then('the account will be added to the system {membership_option}')
def check_db_for_account(context, membership_option):
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
acct_repository = AccountRepository(db)
account = acct_repository.get_account_by_email('bar@mycroft.ai')
assert_that(account, not_none())
assert_that(
account.email_address, equal_to('bar@mycroft.ai')
)
assert_that(account.username, equal_to('barfoo'))
if membership_option == 'with a membership':
assert_that(account.membership.type, equal_to('Monthly Membership'))
assert_that(
account.membership.payment_account_id,
starts_with('cus')
)
elif membership_option == 'without a membership':
assert_that(account.membership, none())
assert_that(len(account.agreements), equal_to(2))
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())))
@when('the account is deleted')
def account_deleted(context):
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
acct_repository = AccountRepository(db)
account = acct_repository.get_account_by_email('bar@mycroft.ai')
context.stripe_id = account.membership.payment_id
context.response = context.client.delete('/api/account')
@then('he membership is removed from stripe')
def check_stripe(context):
stripe_id = context.stripe_id
assert_that(stripe_id, not_none())
stripe.api_key = os.environ['STRIPE_PRIVATE_KEY']
subscription_not_found = False
try:
stripe.Subscription.retrieve(stripe_id)
except InvalidRequestError:
subscription_not_found = True
assert_that(subscription_not_found, equal_to(True))

View File

@ -286,8 +286,8 @@ class AccountEndpoint(SeleneEndpoint):
self._add_membership(membership_change, active_membership)
def _get_active_membership(self):
membership_repo = MembershipRepository(self.db)
active_membership = membership_repo.get_active_account_membership(
acct_repository = AccountRepository(self.db)
active_membership = acct_repository.get_active_account_membership(
self.account.id
)
@ -325,8 +325,8 @@ class AccountEndpoint(SeleneEndpoint):
def _cancel_membership(self, active_membership):
cancel_stripe_subscription(active_membership.payment_id)
active_membership.end_date = datetime.utcnow()
membership_repository = MembershipRepository(self.db)
membership_repository.finish_membership(active_membership)
account_repository = AccountRepository(self.db)
account_repository.end_membership(active_membership)
def _update_username(self, username):
self.account_repository.update_username(self.account.id, username)

View File

@ -63,20 +63,6 @@ class AccountRepository(RepositoryBase):
)
self.cursor.insert(request)
def add_membership(self, acct_id: str, membership: AccountMembership):
"""A membership is optional, add it if one was selected"""
request = self._build_db_request(
sql_file_name='add_account_membership.sql',
args=dict(
account_id=acct_id,
membership_type=membership.type,
payment_method=membership.payment_method,
payment_account_id=membership.payment_account_id,
payment_id=membership.payment_id
)
)
self.cursor.insert(request)
def remove(self, account: Account):
"""Delete and account and all of its children"""
request = self._build_db_request(
@ -278,3 +264,42 @@ class AccountRepository(RepositoryBase):
'thirtyDaysMinus': report_30_days['paid_minus']
}]
return report_table
def add_membership(self, acct_id: str, membership: AccountMembership):
"""A membership is optional, add it if one was selected"""
request = self._build_db_request(
sql_file_name='add_account_membership.sql',
args=dict(
account_id=acct_id,
membership_type=membership.type,
payment_method=membership.payment_method,
payment_account_id=membership.payment_account_id,
payment_id=membership.payment_id
)
)
self.cursor.insert(request)
def end_membership(self, membership: AccountMembership):
db_request = self._build_db_request(
sql_file_name='end_membership.sql',
args=dict(
id=membership.id,
membership_ts_range='[{start},{end}]'.format(
start=membership.start_date,
end=membership.end_date
)
)
)
self.cursor.update(db_request)
def get_active_account_membership(self, account_id) -> AccountMembership:
account_membership = None
db_request = self._build_db_request(
sql_file_name='get_active_membership_by_account_id.sql',
args=dict(account_id=account_id)
)
db_result = self.cursor.select_one(db_request)
if db_result:
account_membership = AccountMembership(**db_result)
return account_membership

View File

@ -26,18 +26,6 @@ class MembershipRepository(RepositoryBase):
db_result = self.cursor.select_one(db_request)
return Membership(**db_result)
def get_active_account_membership(self, account_id) -> AccountMembership:
account_membership = None
db_request = self._build_db_request(
sql_file_name='get_active_membership_by_account_id.sql',
args=dict(account_id=account_id)
)
db_result = self.cursor.select_one(db_request)
if db_result:
account_membership = AccountMembership(**db_result)
return account_membership
def add(self, membership: Membership):
db_request = self._build_db_request(
'add_membership.sql',
@ -57,16 +45,3 @@ class MembershipRepository(RepositoryBase):
args=dict(membership_id=membership.id)
)
self.cursor.delete(db_request)
def finish_membership(self, membership: AccountMembership):
db_request = self._build_db_request(
sql_file_name='finish_membership.sql',
args=dict(
id=membership.id,
membership_ts_range='[{start},{end}]'.format(
start=membership.start_date,
end=membership.end_date
)
)
)
self.cursor.update(db_request)

View File

@ -1,7 +1,7 @@
SELECT
acc_mem.id,
mem.type,
LOWER(acc_mem.membership_ts_range) start_date,
LOWER(acc_mem.membership_ts_range)::date start_date,
acc_mem.payment_method,
payment_account_id,
payment_id