Merge remote-tracking branch 'remotes/origin/dev' into fix-device-last-update
commit
9ea5fd8e5d
|
@ -0,0 +1,14 @@
|
|||
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
|
|
@ -2,10 +2,11 @@ Feature: Pair a device
|
|||
Test the device add endpoint
|
||||
|
||||
Scenario: Add a device
|
||||
Given an authenticated user
|
||||
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
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
Feature: Get the active Profile Policy agreement
|
||||
Feature: Get the active agreements
|
||||
We need to be able to retrieve an agreement and display it on the web app.
|
||||
|
||||
Scenario: Multiple versions of an agreement exist
|
||||
When API request for Privacy Policy is made
|
||||
Then version 999 of Privacy Policy is returned
|
||||
Then the request will be successful
|
||||
And Privacy Policy version 999 is returned
|
||||
|
||||
|
||||
Scenario: Retrieve Terms of Use
|
||||
When API request for Terms of Use is made
|
||||
Then the request will be successful
|
||||
And Terms of Use version 999 is returned
|
||||
|
|
|
@ -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 an authenticated user
|
||||
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,19 +1,9 @@
|
|||
from datetime import date, timedelta
|
||||
|
||||
from behave import fixture, use_fixture
|
||||
|
||||
from account_api.api import acct
|
||||
from selene.data.account import (
|
||||
Account,
|
||||
AccountAgreement,
|
||||
AccountRepository,
|
||||
AccountMembership,
|
||||
Agreement,
|
||||
AgreementRepository,
|
||||
PRIVACY_POLICY,
|
||||
TERMS_OF_USE
|
||||
)
|
||||
from selene.data.device import Geography, GeographyRepository
|
||||
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
|
||||
|
||||
|
@ -27,96 +17,38 @@ def acct_api_client(context):
|
|||
yield context.client
|
||||
|
||||
|
||||
def before_feature(context, _):
|
||||
def before_all(context):
|
||||
use_fixture(acct_api_client, context)
|
||||
context.db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
context.terms_of_use, context.privacy_policy = add_agreements(context.db)
|
||||
|
||||
|
||||
def after_all(context):
|
||||
remove_agreements(
|
||||
context.db,
|
||||
[context.privacy_policy, context.terms_of_use]
|
||||
)
|
||||
|
||||
|
||||
def before_scenario(context, _):
|
||||
db = connect_to_db(context.client_config['DB_CONNECTION_CONFIG'])
|
||||
_add_agreements(context, db)
|
||||
_add_account(context, db)
|
||||
_add_geography(context, db)
|
||||
|
||||
|
||||
def _add_agreements(context, db):
|
||||
context.privacy_policy = Agreement(
|
||||
type=PRIVACY_POLICY,
|
||||
version='999',
|
||||
content='this is Privacy Policy version 999',
|
||||
effective_date=date.today() - timedelta(days=5)
|
||||
)
|
||||
context.terms_of_use = Agreement(
|
||||
type=TERMS_OF_USE,
|
||||
version='999',
|
||||
content='this is Terms of Use version 999',
|
||||
effective_date=date.today() - timedelta(days=5)
|
||||
)
|
||||
agreement_repository = AgreementRepository(db)
|
||||
agreement_id = agreement_repository.add(context.privacy_policy)
|
||||
context.privacy_policy.id = agreement_id
|
||||
agreement_id = agreement_repository.add(context.terms_of_use)
|
||||
context.terms_of_use.id = agreement_id
|
||||
|
||||
|
||||
def _add_account(context, db):
|
||||
context.account = Account(
|
||||
email_address='foo@mycroft.ai',
|
||||
username='foobar',
|
||||
membership=AccountMembership(
|
||||
type='Monthly Membership',
|
||||
start_date=date.today(),
|
||||
payment_method='Stripe',
|
||||
payment_account_id='foo',
|
||||
payment_id='bar'
|
||||
),
|
||||
agreements=[
|
||||
AccountAgreement(type=PRIVACY_POLICY, accept_date=date.today())
|
||||
]
|
||||
)
|
||||
|
||||
acct_repository = AccountRepository(db)
|
||||
account_id = acct_repository.add(context.account, 'foo')
|
||||
context.account.id = account_id
|
||||
|
||||
|
||||
def _add_geography(context, db):
|
||||
geography = Geography(
|
||||
country='United States',
|
||||
region='Missouri',
|
||||
city='Kansas City',
|
||||
time_zone='America/Chicago'
|
||||
)
|
||||
geo_repository = GeographyRepository(db, context.account.id)
|
||||
context.geography_id = geo_repository.add(geography)
|
||||
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)
|
||||
_delete_agreements(context, db)
|
||||
"""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.
|
||||
"""
|
||||
|
||||
for account in context.accounts.values():
|
||||
remove_account(context.db, account)
|
||||
_clean_cache()
|
||||
|
||||
|
||||
def _delete_account(context, db):
|
||||
acct_repository = AccountRepository(db)
|
||||
acct_repository.remove(context.account)
|
||||
bar_acct = acct_repository.get_account_by_email('bar@mycroft.ai')
|
||||
if bar_acct is not None:
|
||||
acct_repository.remove(bar_acct)
|
||||
foo_acct = acct_repository.get_account_by_email('foo@mycroft.ai')
|
||||
if foo_acct is not None:
|
||||
acct_repository.remove(foo_acct)
|
||||
test_acct = acct_repository.get_account_by_email('test@mycroft.ai')
|
||||
if test_acct is not None:
|
||||
acct_repository.remove(test_acct)
|
||||
|
||||
|
||||
def _delete_agreements(context, db):
|
||||
agreement_repository = AgreementRepository(db)
|
||||
agreement_repository.remove(context.privacy_policy, testing=True)
|
||||
agreement_repository.remove(context.terms_of_use, testing=True)
|
||||
|
||||
|
||||
def _clean_cache():
|
||||
cache = SeleneCache()
|
||||
cache.delete('pairing.token:this is a token')
|
||||
|
|
|
@ -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
|
|
@ -3,6 +3,29 @@ Feature: Manage account profiles
|
|||
settings.
|
||||
|
||||
Scenario: Retrieve authenticated user's account
|
||||
Given an authenticated user
|
||||
When a user requests their profile
|
||||
Then 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
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
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 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
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
from binascii import b2a_base64
|
||||
from datetime import date
|
||||
|
||||
from behave import given, then, when
|
||||
from flask import json
|
||||
from hamcrest import assert_that, equal_to, is_in, not_none
|
||||
|
||||
from selene.data.account import (
|
||||
AccountRepository,
|
||||
PRIVACY_POLICY,
|
||||
TERMS_OF_USE
|
||||
)
|
||||
|
||||
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()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@given('a user completes new account setup')
|
||||
def build_new_account_request(context):
|
||||
context.new_account_request = new_account_request
|
||||
|
||||
|
||||
@given('user does not specify an email address')
|
||||
def remove_email_from_request(context):
|
||||
del(context.new_account_request['login']['email'])
|
||||
|
||||
|
||||
@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')
|
||||
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')
|
||||
)
|
||||
|
||||
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())))
|
|
@ -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,20 +1,33 @@
|
|||
from dataclasses import asdict
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
|
||||
from behave import then, when
|
||||
from hamcrest import assert_that, equal_to
|
||||
|
||||
|
||||
@when('API request for Privacy Policy is made')
|
||||
def call_agreement_endpoint(context):
|
||||
context.response = context.client.get('/api/agreement/privacy-policy')
|
||||
from selene.data.account import PRIVACY_POLICY, TERMS_OF_USE
|
||||
|
||||
|
||||
@then('version {version} of Privacy Policy is returned')
|
||||
def validate_response(context, version):
|
||||
assert_that(context.response.status_code, equal_to(HTTPStatus.OK))
|
||||
@when('API request for {agreement} is made')
|
||||
def call_agreement_endpoint(context, agreement):
|
||||
if agreement == PRIVACY_POLICY:
|
||||
url = '/api/agreement/privacy-policy'
|
||||
elif agreement == TERMS_OF_USE:
|
||||
url = '/api/agreement/terms-of-use'
|
||||
else:
|
||||
raise ValueError('invalid agreement type')
|
||||
|
||||
context.response = context.client.get(url)
|
||||
|
||||
|
||||
@then('{agreement} version {version} is returned')
|
||||
def validate_response(context, agreement, version):
|
||||
response_data = json.loads(context.response.data)
|
||||
expected_response = asdict(context.privacy_policy)
|
||||
if agreement == PRIVACY_POLICY:
|
||||
expected_response = asdict(context.privacy_policy)
|
||||
elif agreement == TERMS_OF_USE:
|
||||
expected_response = asdict(context.terms_of_use)
|
||||
else:
|
||||
raise ValueError('invalid agreement type')
|
||||
|
||||
del(expected_response['effective_date'])
|
||||
assert_that(response_data, equal_to(expected_response))
|
||||
|
|
|
@ -1,27 +1,61 @@
|
|||
from behave import given, then
|
||||
from hamcrest import assert_that, equal_to, has_item, is_not
|
||||
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))
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
from http import HTTPStatus
|
||||
|
||||
from behave import given, then
|
||||
from hamcrest import assert_that, equal_to
|
||||
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('an authenticated user')
|
||||
def setup_authenticated_user(context):
|
||||
generate_access_token(context)
|
||||
generate_refresh_token(context)
|
||||
@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')
|
||||
def check_request_success(context):
|
||||
assert_that(context.response.status_code, equal_to(HTTPStatus.OK))
|
||||
assert_that(
|
||||
context.response.status_code,
|
||||
is_in([HTTPStatus.OK, HTTPStatus.NO_CONTENT])
|
||||
)
|
||||
|
||||
|
||||
@then('the request will fail with {error_type} error')
|
||||
|
|
|
@ -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))
|
|
@ -1,25 +1,102 @@
|
|||
from datetime import date
|
||||
from http import HTTPStatus
|
||||
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')
|
||||
def call_account_endpoint(context):
|
||||
context.response = context.client.get('/api/account')
|
||||
context.response = context.client.get(
|
||||
'/api/account',
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
@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):
|
||||
assert_that(context.response.status_code, equal_to(HTTPStatus.OK))
|
||||
response_data = json.loads(context.response.data)
|
||||
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'],
|
||||
|
@ -30,7 +107,7 @@ def validate_response(context):
|
|||
response_data['membership'], has_item('id')
|
||||
)
|
||||
|
||||
assert_that(len(response_data['agreements']), equal_to(1))
|
||||
assert_that(len(response_data['agreements']), equal_to(2))
|
||||
agreement = response_data['agreements'][0]
|
||||
assert_that(agreement['type'], equal_to(PRIVACY_POLICY))
|
||||
assert_that(
|
||||
|
@ -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'))
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import os
|
||||
|
||||
import stripe
|
||||
from behave import then, when
|
||||
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):
|
||||
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):
|
||||
account = context.accounts['foo']
|
||||
stripe.api_key = os.environ['STRIPE_PRIVATE_KEY']
|
||||
subscription_not_found = False
|
||||
try:
|
||||
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
|
|
@ -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)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
from .authentication import (
|
||||
ACCESS_TOKEN_COOKIE_KEY,
|
||||
generate_access_token,
|
||||
generate_refresh_token,
|
||||
get_account,
|
||||
REFRESH_TOKEN_COOKIE_KEY,
|
||||
validate_token_cookies
|
||||
)
|
|
@ -9,5 +9,9 @@ from .entity.membership import Membership
|
|||
from .entity.skill import AccountSkill
|
||||
from .repository.account import AccountRepository
|
||||
from .repository.agreement import AgreementRepository
|
||||
from .repository.membership import MembershipRepository
|
||||
from .repository.membership import (
|
||||
MembershipRepository,
|
||||
MONTHLY_MEMBERSHIP,
|
||||
YEARLY_MEMBERSHIP
|
||||
)
|
||||
from .repository.skill import AccountSkillRepository
|
||||
|
|
|
@ -64,20 +64,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(
|
||||
|
@ -287,3 +273,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
|
||||
|
|
|
@ -2,6 +2,9 @@ from selene.data.account import AccountMembership
|
|||
from ..entity.membership import Membership
|
||||
from ...repository_base import RepositoryBase
|
||||
|
||||
MONTHLY_MEMBERSHIP = 'Monthly Membership'
|
||||
YEARLY_MEMBERSHIP = 'Yearly Membership'
|
||||
|
||||
|
||||
class MembershipRepository(RepositoryBase):
|
||||
def __init__(self, db):
|
||||
|
@ -23,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',
|
||||
|
@ -54,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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
from .account import arthur, insert_account, delete_account
|
||||
from .agreement import insert_agreements, delete_agreements
|
||||
from .membership import insert_memberships, delete_memberships
|
||||
from .test_db import create_test_db, drop_test_db
|
|
@ -1,50 +1,55 @@
|
|||
from datetime import date
|
||||
|
||||
from selene.data.account import (
|
||||
Account,
|
||||
AccountAgreement,
|
||||
AccountMembership,
|
||||
AccountRepository,
|
||||
)
|
||||
|
||||
_agree_to_terms = AccountAgreement(
|
||||
type='Terms of Use',
|
||||
accept_date=date(1975, 3, 14)
|
||||
)
|
||||
|
||||
_agree_to_privacy = AccountAgreement(
|
||||
type='Privacy Policy',
|
||||
accept_date=date.today()
|
||||
)
|
||||
|
||||
_membership = AccountMembership(
|
||||
type='Monthly Supporter',
|
||||
start_date=date.today(),
|
||||
stripe_customer_id='killer_rabbit'
|
||||
)
|
||||
|
||||
arthur = dict(
|
||||
email_address='arthur@holy.grail',
|
||||
username='kingofthebritons',
|
||||
agreements=[_agree_to_terms, _agree_to_privacy],
|
||||
membership=_membership
|
||||
)
|
||||
|
||||
black_knight = dict(
|
||||
email_address='blackknight@holy.grail',
|
||||
username='fleshwound',
|
||||
agreements=[_agree_to_terms, _agree_to_privacy]
|
||||
PRIVACY_POLICY,
|
||||
TERMS_OF_USE
|
||||
)
|
||||
|
||||
|
||||
def insert_account(db, account_attrs: dict) -> Account:
|
||||
account = Account(**account_attrs)
|
||||
account_repository = AccountRepository(db)
|
||||
account.id = account_repository.add(account, password='holygrail')
|
||||
def build_test_account(**overrides):
|
||||
test_agreements = [
|
||||
AccountAgreement(type=PRIVACY_POLICY, accept_date=date.today()),
|
||||
AccountAgreement(type=TERMS_OF_USE, accept_date=date.today())
|
||||
]
|
||||
return Account(
|
||||
email_address=overrides.get('email_address') or 'foo@mycroft.ai',
|
||||
username=overrides.get('username') or 'foobar',
|
||||
agreements=overrides.get('agreements') or test_agreements
|
||||
)
|
||||
|
||||
|
||||
def add_account(db, **overrides):
|
||||
acct_repository = AccountRepository(db)
|
||||
account = build_test_account(**overrides)
|
||||
account.id = acct_repository.add(account, 'test_password')
|
||||
if account.membership is not None:
|
||||
acct_repository.add_membership(account.id, account.membership)
|
||||
|
||||
return account
|
||||
|
||||
|
||||
def delete_account(db, account: Account):
|
||||
def remove_account(db, account):
|
||||
account_repository = AccountRepository(db)
|
||||
account_repository.remove(account)
|
||||
|
||||
|
||||
def build_test_membership(**overrides):
|
||||
stripe_acct = 'test_stripe_acct_id'
|
||||
return AccountMembership(
|
||||
type=overrides.get('type') or 'Monthly Membership',
|
||||
start_date=overrides.get('start_date') or date.today(),
|
||||
payment_method=overrides.get('payment_method') or 'Stripe',
|
||||
payment_account_id=overrides.get('payment_account_id') or stripe_acct,
|
||||
payment_id=overrides.get('payment_id') or 'test_stripe_payment_id'
|
||||
)
|
||||
|
||||
|
||||
def add_account_membership(db, account_id, **overrides):
|
||||
membership = build_test_membership(**overrides)
|
||||
acct_repository = AccountRepository(db)
|
||||
acct_repository.add_membership(account_id, membership)
|
||||
|
||||
return membership
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
from selene.data.device import Geography, GeographyRepository
|
||||
|
||||
|
||||
def add_account_geography(db, account, **overrides):
|
||||
geography = Geography(
|
||||
country=overrides.get('country') or 'United States',
|
||||
region=overrides.get('region') or 'Missouri',
|
||||
city=overrides.get('city') or 'Kansas City',
|
||||
time_zone=overrides.get('time_zone') or 'America/Chicago'
|
||||
)
|
||||
geo_repository = GeographyRepository(db, account.id)
|
||||
account_geography_id = geo_repository.add(geography)
|
||||
|
||||
return account_geography_id
|
|
@ -1,36 +1,46 @@
|
|||
from datetime import date
|
||||
from datetime import date, timedelta
|
||||
from typing import Tuple, List
|
||||
|
||||
from selene.data.account import Agreement, AgreementRepository
|
||||
|
||||
terms_of_use_attrs = dict(
|
||||
type='Terms of Use',
|
||||
version='HolyGrail',
|
||||
content='I agree that all the tests I write for this application will be '
|
||||
'in the theme of Monty Python and the Holy Grail. If you do not '
|
||||
'agree with these terms, I will be forced to say "Ni!" until such '
|
||||
'time as you agree',
|
||||
effective_date=date(1975, 3, 14)
|
||||
)
|
||||
|
||||
privacy_policy_attrs = dict(
|
||||
type='Privacy Policy',
|
||||
version='GoT',
|
||||
content='First, shalt thou take out the Holy Pin. Then shalt thou count'
|
||||
'to three. No more. No less. Three shalt be the number thou'
|
||||
'shalt count and the number of the counting shall be three. Four'
|
||||
'shalt thou not count, nor either count thou two, excepting that'
|
||||
'thou then proceed to three. Five is right out. Once the number'
|
||||
'three, being the third number, be reached, then lobbest thou'
|
||||
'Holy Hand Grenade of Antioch towards thy foe, who, being naughty'
|
||||
'in My sight, shall snuff it.',
|
||||
effective_date=date(1975, 3, 14)
|
||||
from selene.data.account import (
|
||||
Agreement,
|
||||
AgreementRepository,
|
||||
PRIVACY_POLICY,
|
||||
TERMS_OF_USE
|
||||
)
|
||||
|
||||
|
||||
def insert_agreements(db) -> Tuple[Agreement, Agreement]:
|
||||
terms_of_use = Agreement(**terms_of_use_attrs)
|
||||
privacy_policy = Agreement(**privacy_policy_attrs)
|
||||
def _build_test_terms_of_use():
|
||||
return Agreement(
|
||||
type='Terms of Use',
|
||||
version='HolyGrail',
|
||||
content='I agree that all the tests I write for this application will '
|
||||
'be in the theme of Monty Python and the Holy Grail. If you '
|
||||
'do not agree with these terms, I will be forced to say "Ni!" '
|
||||
'until such time as you agree',
|
||||
effective_date=date.today() - timedelta(days=1)
|
||||
)
|
||||
|
||||
|
||||
def _build_test_privacy_policy():
|
||||
return Agreement(
|
||||
type='Privacy Policy',
|
||||
version='Holy Grail',
|
||||
content='First, shalt thou take out the Holy Pin. Then shalt thou '
|
||||
'count to three. No more. No less. Three shalt be the '
|
||||
'number thou shalt count and the number of the counting shall '
|
||||
'be three. Four shalt thou not count, nor either count thou '
|
||||
'two, excepting that thou then proceed to three. Five is '
|
||||
'right out. Once the number three, being the third number, '
|
||||
'be reached, then lobbest thou Holy Hand Grenade of Antioch '
|
||||
'towards thy foe, who, being naughty in My sight, '
|
||||
'shall snuff it.',
|
||||
effective_date=date.today() - timedelta(days=1)
|
||||
)
|
||||
|
||||
|
||||
def add_agreements(db) -> Tuple[Agreement, Agreement]:
|
||||
terms_of_use = _build_test_terms_of_use()
|
||||
privacy_policy = _build_test_privacy_policy()
|
||||
agreement_repository = AgreementRepository(db)
|
||||
terms_of_use.id = agreement_repository.add(terms_of_use)
|
||||
privacy_policy.id = agreement_repository.add(privacy_policy)
|
||||
|
@ -38,7 +48,7 @@ def insert_agreements(db) -> Tuple[Agreement, Agreement]:
|
|||
return terms_of_use, privacy_policy
|
||||
|
||||
|
||||
def delete_agreements(db, agreements: List[Agreement]):
|
||||
def remove_agreements(db, agreements: List[Agreement]):
|
||||
for agreement in agreements:
|
||||
agreement_repository = AgreementRepository(db)
|
||||
agreement_repository.remove(agreement, testing=True)
|
||||
|
|
|
@ -10,37 +10,43 @@ TWO_MINUTES = 120
|
|||
REFRESH_TOKEN_COOKIE_KEY = 'seleneRefresh'
|
||||
|
||||
|
||||
def generate_access_token(context, expire=False):
|
||||
def generate_access_token(context, duration=ONE_MINUTE):
|
||||
access_token = AuthenticationToken(
|
||||
context.client_config['ACCESS_SECRET'],
|
||||
ONE_MINUTE
|
||||
duration
|
||||
)
|
||||
if not expire:
|
||||
access_token.generate(context.account.id)
|
||||
context.access_token = access_token
|
||||
account = context.accounts[context.username]
|
||||
access_token.generate(account.id)
|
||||
|
||||
return access_token
|
||||
|
||||
|
||||
def set_access_token_cookie(context, duration=ONE_MINUTE):
|
||||
context.client.set_cookie(
|
||||
context.client_config['DOMAIN'],
|
||||
ACCESS_TOKEN_COOKIE_KEY,
|
||||
access_token.jwt,
|
||||
max_age=0 if expire else ONE_MINUTE
|
||||
context.access_token.jwt,
|
||||
max_age=duration
|
||||
)
|
||||
|
||||
|
||||
def generate_refresh_token(context, expire=False):
|
||||
def generate_refresh_token(context, duration=TWO_MINUTES):
|
||||
refresh_token = AuthenticationToken(
|
||||
context.client_config['REFRESH_SECRET'],
|
||||
TWO_MINUTES
|
||||
duration
|
||||
)
|
||||
if not expire:
|
||||
refresh_token.generate(context.account.id)
|
||||
context.refresh_token = refresh_token
|
||||
account = context.accounts[context.username]
|
||||
refresh_token.generate(account.id)
|
||||
|
||||
return refresh_token
|
||||
|
||||
|
||||
def set_refresh_token_cookie(context, duration=TWO_MINUTES):
|
||||
context.client.set_cookie(
|
||||
context.client_config['DOMAIN'],
|
||||
REFRESH_TOKEN_COOKIE_KEY,
|
||||
refresh_token.jwt,
|
||||
max_age=0 if expire else TWO_MINUTES
|
||||
context.refresh_token.jwt,
|
||||
max_age=duration
|
||||
)
|
||||
|
||||
|
|
@ -1,15 +1,20 @@
|
|||
from decimal import Decimal
|
||||
|
||||
from selene.data.account import Membership, MembershipRepository
|
||||
from selene.data.account import (
|
||||
Membership,
|
||||
MembershipRepository,
|
||||
MONTHLY_MEMBERSHIP,
|
||||
YEARLY_MEMBERSHIP
|
||||
)
|
||||
|
||||
monthly_membership = dict(
|
||||
type='Monthly Supporter',
|
||||
type=MONTHLY_MEMBERSHIP,
|
||||
rate=Decimal('1.99'),
|
||||
rate_period='monthly'
|
||||
)
|
||||
|
||||
yearly_membership = dict(
|
||||
type='Yearly Supporter',
|
||||
type=YEARLY_MEMBERSHIP,
|
||||
rate=Decimal('19.99'),
|
||||
rate_period='yearly'
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue