update stripe tests

master
alice 4 years ago
parent aa86dd632b
commit e63afcdc6e

@ -334,6 +334,8 @@ OPENVPN_CONFIG_HEADER = """\
# +----------------------------+ # +----------------------------+
""" """
RUN_ONLINE_TESTS = False
# Local settings # Local settings
try: try:
from .local_settings import * # noqa from .local_settings import * # noqa

@ -3,7 +3,7 @@ from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from django.core.management import call_command from django.core.management import call_command
from django.core import mail from django.core import mail
from django.utils.six import StringIO from io import StringIO
from constance import config as site_config from constance import config as site_config
from constance.test import override_config from constance.test import override_config
@ -175,64 +175,72 @@ class SignupViewTest(TestCase):
class AccountViewsTest(TestCase, UserTestMixin): class AccountViewsTest(TestCase, UserTestMixin):
def setUp(self): def setUp(self):
User.objects.create_user('test', None, 'testpw') User.objects.create_user('test', None, 'test_pw')
self.client.login(username='test', password='testpw') self.client.login(username='test', password='test_pw')
def test_account(self): def test_account(self):
response = self.client.get('/account/') response = self.client.get('/account/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_trial_get(self): def test_settings_form(self):
response = self.client.get('/account/trial') response = self.client.get('/account/settings')
self.assertRedirects(response, '/account/') self.assertEqual(response.status_code, 200)
def test_trial(self): def print_message(self, response):
p = timedelta(days=1) from django.contrib.messages import get_messages
with self.settings(RECAPTCHA_API='TEST'): messages = list(get_messages(response.wsgi_request))
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2): for m in messages:
good_data = {'g-recaptcha-response': 'TEST-TOKEN'} print(f"[message: {m.message!r} level={m.level} tags={m.tags!r}]")
response = self.client.post('/account/trial', good_data) def test_settings_post_email(self):
self.assertRedirects(response, '/account/') response = self.client.post('/account/settings', {
'action': 'email',
'current_password': 'test_pw',
'email': 'new_email@example.com'})
self.assertEqual(response.status_code, 302)
user = User.objects.get(username='test') user = User.objects.get(username='test')
self.assertRemaining(user.vpnuser, p) self.assertEqual(user.email, 'new_email@example.com')
def test_trial_fail(self):
p = timedelta(days=1)
with self.settings(RECAPTCHA_API='TEST'):
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2):
bad_data = {'g-recaptcha-response': 'TOTALLY-NOT-TEST-TOKEN'}
response = self.client.post('/account/trial', bad_data) def test_settings_post_email_fail(self):
self.assertRedirects(response, '/account/') response = self.client.post('/account/settings', {
'action': 'email',
'current_password': 'not_test_pw',
'email': 'new_email@example.com'})
self.assertEqual(response.status_code, 302)
user = User.objects.get(username='test') user = User.objects.get(username='test')
self.assertRemaining(user.vpnuser, timedelta()) self.assertNotEqual(user.email, 'new_email@example.com')
def test_settings_form(self): def test_settings_post_pw(self):
response = self.client.get('/account/settings')
self.assertEqual(response.status_code, 200)
def test_settings_post(self):
response = self.client.post('/account/settings', { response = self.client.post('/account/settings', {
'password': 'new_test_pw', 'password2': 'new_test_pw', 'action': 'password',
'email': 'new_email@example.com'}) 'current_password': 'test_pw',
self.assertEqual(response.status_code, 200) 'password': 'new_test_pw', 'password2': 'new_test_pw'})
self.assertEqual(response.status_code, 302)
user = User.objects.get(username='test') user = User.objects.get(username='test')
self.assertTrue(user.check_password('new_test_pw')) self.assertTrue(user.check_password('new_test_pw'))
self.assertEqual(user.email, 'new_email@example.com')
def test_settings_post_fail(self): def test_settings_post_pw_fail(self):
response = self.client.post('/account/settings', { response = self.client.post('/account/settings', {
'password': 'new_test_pw', 'password2': 'new_test_pw_qsdfg', 'action': 'password',
'email': 'new_email@example.com'}) 'current_password': 'oops',
self.assertEqual(response.status_code, 200) 'password': 'new_test_pw',
'password2': 'new_test_pw'})
self.assertEqual(response.status_code, 302)
response = self.client.post('/account/settings', {
'action': 'password',
'current_password': 'test_pw',
'password': 'new_test_pw2',
'password2': 'new_test_pw_qsdfg'})
self.assertEqual(response.status_code, 302)
user = User.objects.get(username='test') user = User.objects.get(username='test')
self.assertFalse(user.check_password('new_test_pw')) self.assertFalse(user.check_password('new_test_pw'))
self.assertEqual(user.email, 'new_email@example.com') self.assertFalse(user.check_password('new_test_pw2'))
self.assertTrue(user.check_password('test_pw'))
def test_giftcode_use_single(self): def test_giftcode_use_single(self):
gc = GiftCode.objects.create(time=timedelta(days=42), single_use=True) gc = GiftCode.objects.create(time=timedelta(days=42), single_use=True)

@ -96,7 +96,19 @@ def period_months(p):
}[p] }[p]
class Payment(models.Model): class BackendData:
backend_data = None
def set_data(self, key, value):
""" adds a backend data key to this instance's dict """
if not self.backend_data:
self.backend_data = {}
if not isinstance(self.backend_data, dict):
raise Exception("self.backend_data is not a dict (%r)" % self.backend_data)
self.backend_data[key] = value
class Payment(models.Model, BackendData):
""" Just a payment. """ Just a payment.
If subscription is not null, it has been automatically issued. If subscription is not null, it has been automatically issued.
backend_extid is the external transaction ID, backend_data is other backend_extid is the external transaction ID, backend_data is other
@ -170,7 +182,7 @@ class Payment(models.Model):
return payment return payment
class Subscription(models.Model): class Subscription(models.Model, BackendData):
""" Recurring payment subscription. """ """ Recurring payment subscription. """
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
backend_id = models.CharField(max_length=16, choices=BACKEND_CHOICES) backend_id = models.CharField(max_length=16, choices=BACKEND_CHOICES)

@ -2,6 +2,9 @@
from .bitcoin import * from .bitcoin import *
from .paypal import * from .paypal import *
from .stripe import *
from .coingate import * from .coingate import *
from django.conf import settings
if settings.RUN_ONLINE_TESTS:
from .online.stripe import *

@ -0,0 +1,112 @@
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.expected_conditions import presence_of_element_located
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from django.conf import settings
from django.utils import timezone
from datetime import timedelta
from lambdainst.models import User
from payments.models import Payment, Subscription
class BaseOnlineTest(StaticLiveServerTestCase):
# using a fixed port because you're supposed to forward a public ip to it
# for the web hooks
# $ ssh relaybox -R 12345:127.0.0.1:8000
# (set ROOT_URL to the right public url and RUN_ONLINE_TESTS to True)
port = 8000
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver(firefox_binary="/usr/bin/firefox")
cls.selenium.implicitly_wait(6)
cls.root_url = settings.ROOT_URL
cls.wait = WebDriverWait(cls.selenium, 6)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def _signup(self):
self.selenium.get('%s%s' % (self.root_url, '/account/signup'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('test-user')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('test-password')
password_input = self.selenium.find_element_by_name("password2")
password_input.send_keys('test-password')
self.selenium.find_element_by_xpath('//input[@value="Sign up"]').click()
class OnlineStripeTests(BaseOnlineTest):
fixtures = []
def test_payment(self):
self._signup()
self.selenium.find_element_by_xpath('//label[@for="tab_onetime"]').click()
self.wait.until(EC.visibility_of(self.selenium.find_element_by_xpath('//label[@for="tab_onetime"]/..//select[@name="method"]')))
self.selenium.find_element_by_xpath('//label[@for="tab_onetime"]/..//select[@name="time"]/option[@value="3"]').click()
self.selenium.find_element_by_xpath('//label[@for="tab_onetime"]/..//select[@name="method"]/option[@value="stripe"]').click()
self.selenium.find_element_by_xpath('//label[@for="tab_onetime"]/..//input[@value="Buy Now"]').click()
assert self.selenium.find_element_by_xpath('//span[text()="Test Mode"]')
self.selenium.find_element_by_xpath('//input[@name="email"]').send_keys("test@ccrypto.org")
self.selenium.find_element_by_xpath('//input[@name="cardNumber"]').send_keys("4242424242424242")
self.selenium.find_element_by_xpath('//input[@name="cardExpiry"]').send_keys("6/66")
self.selenium.find_element_by_xpath('//input[@name="cardCvc"]').send_keys("420")
self.selenium.find_element_by_xpath('//input[@name="billingName"]').send_keys("Test User")
self.selenium.find_element_by_xpath('//button[@type="submit"]').click()
self.selenium.find_element_by_xpath('//h2[contains(text(),"Confirmed")]')
user = User.objects.get(username="test-user")
assert user.vpnuser.is_paid
assert user.vpnuser.expiration >= (timezone.now() + timedelta(days=89))
def test_subscription(self):
self._signup()
self.selenium.find_element_by_xpath('//label[@for="tab_subscr"]').click()
self.wait.until(EC.visibility_of(self.selenium.find_element_by_xpath('//label[@for="tab_subscr"]/..//select[@name="method"]')))
self.selenium.find_element_by_xpath('//label[@for="tab_subscr"]/..//select[@name="time"]/option[@value="12"]').click()
self.selenium.find_element_by_xpath('//label[@for="tab_subscr"]/..//select[@name="method"]/option[@value="stripe"]').click()
self.selenium.find_element_by_xpath('//label[@for="tab_subscr"]/..//input[@value="Subscribe"]').click()
assert self.selenium.find_element_by_xpath('//span[text()="Test Mode"]')
self.selenium.find_element_by_xpath('//input[@name="email"]').send_keys("test@ccrypto.org")
self.selenium.find_element_by_xpath('//input[@name="cardNumber"]').send_keys("4242424242424242")
self.selenium.find_element_by_xpath('//input[@name="cardExpiry"]').send_keys("6/66")
self.selenium.find_element_by_xpath('//input[@name="cardCvc"]').send_keys("420")
self.selenium.find_element_by_xpath('//input[@name="billingName"]').send_keys("Test User")
self.selenium.find_element_by_xpath('//button[@type="submit"]').click()
sub_status = self.selenium.find_element_by_xpath('//td[text()="Subscription"]//following-sibling::td')
assert sub_status.text.startswith('ACTIVE')
user = User.objects.get(username="test-user")
assert user.vpnuser.is_paid
assert user.vpnuser.expiration >= (timezone.now() + timedelta(days=359))
sub_status.find_element_by_xpath('a[text()="cancel"]').click()
self.selenium.find_element_by_xpath('//input[@value="Cancel Subscription"]').click()
sub_status = self.selenium.find_element_by_xpath('//td[text()="Subscription"]//following-sibling::td')
assert not sub_status.text.startswith('ACTIVE')
user = User.objects.get(username="test-user")
assert user.vpnuser.is_paid
assert user.vpnuser.expiration >= (timezone.now() + timedelta(days=359))
assert not user.vpnuser.get_subscription()

@ -1,400 +0,0 @@
from datetime import timedelta
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User
from payments.models import Payment, Subscription
from payments.backends import StripeBackend
def MockSession(id):
class MockSession(dict):
def __init__(self, args):
args['id'] = id
super().__init__(args)
@classmethod
def create(cls, **kwargs):
c = MockSession(kwargs)
cls._INST = c
return c
return MockSession
class MockWebhook:
@classmethod
def construct_event(cls, payload, sig, key):
assert sig == "hewwo", sig
assert key == "test_wh_key", key
assert payload == b"opak", payload
return cls.evt
class StripeBackendTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', 'test_user@example.com', None)
def test_stripe(self):
payment = Payment.objects.create(
user=self.user,
time=timedelta(days=30),
backend_id='stripe',
amount=300
)
payment.save()
sess_id = 'cs_test_UKHmmetjaiperdu0wxGppeerrdduuhSslYaaaaaa0eU6aaaaaaaaaWSX'
settings = dict(
secret_key='test_secret_key',
public_key='test_public_key',
wh_key='test_wh_key',
currency='EUR',
name='Test Name',
)
backend = StripeBackend(settings)
assert backend.backend_enabled
backend.stripe = type('Stripe', (object, ), {
'checkout': type('checkout', (object, ), {
'Session': MockSession(sess_id),
}),
'Webhook': MockWebhook,
'error': type('error', (object, ), {
'InvalidRequestError': type('', (Exception, ), {}),
'SignatureVerificationError': type('', (Exception, ), {}),
'CardError': type('CardError', (Exception, ), {}),
}),
})
with self.settings(ROOT_URL='root'):
form_html = backend.new_payment(payment)
assert 'test_public_key' in form_html
sess = backend.stripe.checkout.Session._INST
self.assertEqual(sess['success_url'], 'root/payments/view/%d' % payment.id)
self.assertEqual(sess['line_items'][0]['amount'], payment.amount)
request = RequestFactory().post('', 'opak', content_type='text/plain', HTTP_STRIPE_SIGNATURE="hewwo")
backend.stripe.Webhook.evt = CHECKOUT_SESSION_COMPLETED__ONE
backend.webhook(request)
payment = Payment.objects.get(id=payment.id)
self.assertEqual(payment.status, 'confirmed')
self.assertEqual(payment.paid_amount, 300)
self.assertEqual(payment.backend_extid, sess_id)
def test_stripe_sub(self):
sub = Subscription.objects.create(
user=self.user,
period='3m',
backend_id='stripe',
)
settings = dict(
secret_key='test_secret_key',
public_key='test_public_key',
wh_key='test_wh_key',
currency='EUR',
name='Test Name',
)
backend = StripeBackend(settings)
assert backend.backend_enabled
backend.stripe = type('Stripe', (object, ), {
'checkout': type('checkout', (object, ), {
'Session': MockSession("in_FjSMAw7ufaEBOG"),
}),
'Webhook': MockWebhook,
'error': type('error', (object, ), {
'InvalidRequestError': type('', (Exception, ), {}),
'SignatureVerificationError': type('', (Exception, ), {}),
'CardError': type('CardError', (Exception, ), {}),
}),
})
with self.settings(ROOT_URL='root'):
form_html = backend.new_subscription(sub)
assert 'test_public_key' in form_html
sess = backend.stripe.checkout.Session._INST
self.assertEqual(sess['success_url'], 'root/payments/return_subscr/%d' % sub.id)
self.assertEqual(sess['subscription_data']['items'][0]['plan'], 'ccvpn_3m')
request = RequestFactory().post('', 'opak', content_type='text/plain', HTTP_STRIPE_SIGNATURE="hewwo")
backend.stripe.Webhook.evt = checkout_session_completed__sub(sub)
backend.webhook(request)
backend.stripe.Webhook.evt = INVOICE_PAYMENT_SUCCEEDED
backend.webhook(request)
sub = Subscription.objects.get(id=sub.id)
payment = sub.payment_set.all()[0]
self.assertEqual(sub.status, 'active')
self.assertEqual(payment.status, 'confirmed')
self.assertEqual(payment.paid_amount, 900)
self.assertEqual(payment.backend_extid, 'in_FjSMAw7ufaEBOG')
CHECKOUT_SESSION_COMPLETED__ONE = {
"id": "evt_FkzwrsBSh0da66",
"object": "event",
"api_version": "2019-08-14",
"created": 1567705834,
"data": {
"object": {
"id": "cs_test_UKHmmetjaiperdu0wxGppeerrdduuhSslYaaaaaa0eU6aaaaaaaaaWSX",
"object": "checkout.session",
"billing_address_collection": None,
"cancel_url": "https://test/payments/cancel/19",
"client_reference_id": None,
"customer": "cus_FgusdomerZ8gdP",
"customer_email": None,
"display_items": [
{
"amount": 300,
"currency": "eur",
"custom": {
"description": "One month for admin",
"images": None,
"name": "VPN Payment"
},
"quantity": 1,
"type": "custom"
}
],
"livemode": False,
"locale": None,
"mode": "payment",
"payment_intent": "pi_0FmmmmmmmmmmmmmmmpyplM53",
"payment_method_types": [
"card"
],
"setup_intent": None,
"submit_type": None,
"subscription": None,
"success_url": "https://test/payments/view/19"
}
},
"livemode": False,
"pending_webhooks": 1,
"request": {
"id": None,
"idempotency_key": None
},
"type": "checkout.session.completed"
}
def checkout_session_completed__sub(sub):
return {
"id": "evt_FjSIjKT3MvONr8",
"object": "event",
"api_version": "2019-08-14",
"created": 1567368634,
"data": {
"object": {
"id": "cs_test_03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"object": "checkout.session",
"billing_address_collection": None,
"cancel_url": "https://test/payments/view/2",
"client_reference_id": "sub_%d" % sub.id,
"customer": "cus_FkSIg5BNIdrv6v",
"customer_email": None,
"display_items": [
{
"amount": 900,
"currency": "eur",
"plan": {
"id": "ccvpn_3m",
"object": "plan",
"active": True,
"aggregate_usage": None,
"amount": 900,
"amount_decimal": "900",
"billing_scheme": "per_unit",
"created": 1557830755,
"currency": "eur",
"interval": "month",
"interval_count": 3,
"livemode": False,
"metadata": {
},
"nickname": None,
"product": "prod_F062eB2kCukpUX",
"tiers": None,
"tiers_mode": None,
"transform_usage": None,
"trial_period_days": None,
"usage_type": "licensed"
},
"quantity": 1,
"type": "plan"
}
],
"livemode": False,
"locale": None,
"mode": "subscription",
"payment_intent": None,
"payment_method_types": [
"card"
],
"setup_intent": None,
"submit_type": None,
"subscription": "sub_Fjmeowmeowmeow",
"success_url": "https://test/payments/return/2"
}
},
"livemode": False,
"pending_webhooks": 1,
"request": {
"id": "req_xQY51312421312",
"idempotency_key": None
},
"type": "checkout.session.completed"
}
INVOICE_PAYMENT_SUCCEEDED = {
"id": "evt_FjSMpT5zIn8G9z",
"object": "event",
"api_version": "2019-08-14",
"created": 1567368905,
"data": {
"object": {
"id": "in_FjSMAw7ufaEBOG",
"object": "invoice",
"account_country": "FR",
"account_name": "Cognitive Cryptography",
"amount_due": 900,
"amount_paid": 900,
"amount_remaining": 0,
"application_fee_amount": None,
"attempt_count": 1,
"attempted": True,
"auto_advance": False,
"billing": "charge_automatically",
"billing_reason": "subscription_create",
"charge": "ch_FjSMFp6fbsc45t",
"collection_method": "charge_automatically",
"created": 1567368904,
"currency": "eur",
"custom_fields": None,
"customer": "cus_FjSMgbAnXmL4Go",
"customer_address": None,
"customer_email": "test2@test.test",
"customer_name": None,
"customer_phone": None,
"customer_shipping": None,
"customer_tax_exempt": "none",
"customer_tax_ids": [
],
"default_payment_method": None,
"default_source": None,
"default_tax_rates": [
],
"description": None,
"discount": None,
"due_date": None,
"ending_balance": 0,
"footer": None,
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_wmhbGo7OofOofOofOofOofOofd",
"invoice_pdf": "https://pay.stripe.com/invoice/invst_wmhbGo7OofOofOofOofOofOofd/pdf",
"lines": {
"object": "list",
"data": [
{
"id": "sli_c20baf5dcf8472",
"object": "line_item",
"amount": 900,
"currency": "eur",
"description": "1 × VPN Subscription (3m) (at €9.00 / every 3 months)",
"discountable": True,
"livemode": False,
"metadata": {
},
"period": {
"end": 1575231304,
"start": 1567368904
},
"plan": {
"id": "ccvpn_3m",
"object": "plan",
"active": True,
"aggregate_usage": None,
"amount": 900,
"amount_decimal": "900",
"billing_scheme": "per_unit",
"created": 1557830755,
"currency": "eur",
"interval": "month",
"interval_count": 3,
"livemode": False,
"metadata": {
},
"nickname": None,
"product": "prod_F46LeBpkCuRpUX",
"tiers": None,
"tiers_mode": None,
"transform_usage": None,
"trial_period_days": None,
"usage_type": "licensed"
},
"proration": False,
"quantity": 1,
"subscription": "sub_Fjmeowmeowmeow",
"subscription_item": "si_FjSMgsAsZURoWf",
"tax_amounts": [
],
"tax_rates": [
],
"type": "subscription"
}
],
"has_more": False,
"total_count": 1,
"url": "/v1/invoices/in_FjSMAw7ufaEBOG/lines"
},
"livemode": False,
"metadata": {
},
"next_payment_attempt": None,
"number": "AD4E509A-0001",
"paid": True,
"payment_intent": "pi_0FDzS0UmBGT3Of2k4ol85K2G",
"period_end": 1567368904,
"period_start": 1567368904,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"receipt_number": None,
"starting_balance": 0,
"statement_descriptor": None,
"status": "paid",
"status_transitions": {
"finalized_at": 1567368904,
"marked_uncollectible_at": None,
"paid_at": 1567368905,
"voided_at": None
},
"subscription": "sub_Fjmeowmeowmeow",
"subtotal": 900,
"tax": None,
"tax_percent": None,
"total": 900,
"total_tax_amounts": [
],
"webhooks_delivered_at": None
}
},
"livemode": False,
"pending_webhooks": 1,
"request": {
"id": "req_6O128256512102",
"idempotency_key": None
},
"type": "invoice.payment_succeeded"
}
Loading…
Cancel
Save