зеркало из https://github.com/mozilla/subhub.git
Updating tests to use mocks
- Updated all test files to remove calls to Stripe - Updated all test files to use monkeypatch, mock or mockito
This commit is contained in:
Родитель
65cd703045
Коммит
0d29f8c1cf
|
@ -31,7 +31,6 @@ before_install:
|
|||
stages:
|
||||
- name: Graph
|
||||
- name: Unit Test
|
||||
- name: Docker Build
|
||||
|
||||
jobs:
|
||||
include:
|
||||
|
@ -41,7 +40,4 @@ jobs:
|
|||
- doit draw
|
||||
- stage: Unit Test
|
||||
script:
|
||||
- doit test
|
||||
- stage: Docker Build
|
||||
script:
|
||||
- docker-compose build
|
||||
- doit test
|
|
@ -101,6 +101,7 @@ def _get_all_plans():
|
|||
|
||||
|
||||
def retrieve_stripe_subscriptions(customer: Customer) -> list:
|
||||
logger.info("customer", customer=customer)
|
||||
try:
|
||||
customer_subscriptions_data = customer.subscriptions
|
||||
customer_subscriptions = customer_subscriptions_data.get("data")
|
||||
|
@ -129,7 +130,9 @@ def cancel_subscription(uid, sub_id) -> FlaskResponse:
|
|||
]:
|
||||
Subscription.modify(sub_id, cancel_at_period_end=True)
|
||||
updated_customer = fetch_customer(g.subhub_account, uid)
|
||||
logger.info("updated customer", updated_customer=updated_customer)
|
||||
subs = retrieve_stripe_subscriptions(updated_customer)
|
||||
logger.info("subs", subs=subs, type=type(subs))
|
||||
for sub in subs:
|
||||
if sub["cancel_at_period_end"] and sub["id"] == sub_id:
|
||||
return {"message": "Subscription cancellation successful"}, 201
|
||||
|
|
|
@ -6,7 +6,7 @@ from subhub.exceptions import SubHubError, IntermittentError, ClientError, Serve
|
|||
from subhub.exceptions import SecretStringMissingError
|
||||
|
||||
|
||||
def test_SubHubError():
|
||||
def test_subhub_error():
|
||||
message = "message"
|
||||
status_code = 513
|
||||
payload = dict(some="payload")
|
||||
|
@ -17,7 +17,7 @@ def test_SubHubError():
|
|||
)
|
||||
|
||||
|
||||
def test_IntermittentError():
|
||||
def test_intermittent_error():
|
||||
message = "message"
|
||||
ex1 = IntermittentError(message)
|
||||
assert ex1.args[0] == message
|
||||
|
@ -34,7 +34,7 @@ def test_IntermittentError():
|
|||
assert ex2.to_dict() == dict(message=message, some="payload")
|
||||
|
||||
|
||||
def test_ClientError():
|
||||
def test_client_error():
|
||||
message = "message"
|
||||
ex1 = ClientError(message)
|
||||
assert ex1.args[0] == message
|
||||
|
@ -51,7 +51,7 @@ def test_ClientError():
|
|||
assert ex2.to_dict() == dict(message=message, some="payload")
|
||||
|
||||
|
||||
def test_ServerError():
|
||||
def test_server_error():
|
||||
message = "message"
|
||||
ex1 = ServerError(message)
|
||||
assert ex1.args[0] == message
|
||||
|
|
|
@ -4,13 +4,19 @@
|
|||
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
import stripe
|
||||
|
||||
from flask import g
|
||||
from stripe.error import InvalidRequestError
|
||||
from unittest.mock import Mock, MagicMock, PropertyMock
|
||||
from pynamodb.exceptions import DeleteError
|
||||
from stripe import Customer
|
||||
from subhub.exceptions import ClientError
|
||||
from subhub.app import create_app
|
||||
from unittest.mock import Mock, MagicMock, PropertyMock, patch
|
||||
from mockito import when, mock, unstub, ANY
|
||||
|
||||
from subhub.sub import payments
|
||||
from subhub.customer import (
|
||||
|
@ -21,12 +27,34 @@ from subhub.customer import (
|
|||
from subhub.tests.unit.stripe.utils import MockSubhubUser
|
||||
from subhub.log import get_logger
|
||||
from subhub.cfg import CFG
|
||||
from pynamodb.exceptions import DeleteError
|
||||
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
def test_create_customer_invalid_origin_system():
|
||||
class MockCustomer:
|
||||
id = 123
|
||||
object = "customer"
|
||||
subscriptions = [{"data": "somedata"}]
|
||||
metadata = {"userid": "123"}
|
||||
|
||||
def properties(self, cls):
|
||||
return [i for i in cls.__dict__.keys() if i[:1] != "_"]
|
||||
|
||||
def get(self, key, default=None):
|
||||
properties = self.properties(MockCustomer)
|
||||
logger.info("mock properties", properties=properties)
|
||||
if key in properties:
|
||||
return key
|
||||
else:
|
||||
return default
|
||||
|
||||
def __iter__(self):
|
||||
yield "subscriptions", self.subscriptions
|
||||
|
||||
|
||||
def test_create_customer_invalid_origin_system(monkeypatch):
|
||||
"""
|
||||
GIVEN create a stripe customer
|
||||
WHEN An invalid origin system is provided
|
||||
|
@ -66,50 +94,85 @@ def test_existing_or_new_customer_invalid_origin_system():
|
|||
assert msg == str(request_error.value)
|
||||
|
||||
|
||||
def test_create_customer_tok_visa():
|
||||
def test_create_customer(monkeypatch):
|
||||
"""
|
||||
GIVEN create a stripe customer
|
||||
WHEN provided a test visa token and test fxa
|
||||
THEN validate the customer metadata is correct
|
||||
"""
|
||||
mock_possible_customers = MagicMock()
|
||||
data = PropertyMock(return_value=[])
|
||||
type(mock_possible_customers).data = data
|
||||
|
||||
mock_customer = MagicMock()
|
||||
id = PropertyMock(return_value="cust_123")
|
||||
type(mock_customer).id = id
|
||||
|
||||
subhub_account = MagicMock()
|
||||
|
||||
mock_user = MagicMock()
|
||||
user_id = PropertyMock(return_value="user_123")
|
||||
cust_id = PropertyMock(return_value="cust_123")
|
||||
type(mock_user).user_id = user_id
|
||||
type(mock_user).cust_id = cust_id
|
||||
|
||||
mock_save = MagicMock(return_value=True)
|
||||
|
||||
subhub_account.new_user = mock_user
|
||||
subhub_account.save_user = mock_save
|
||||
|
||||
monkeypatch.setattr("stripe.Customer.list", mock_possible_customers)
|
||||
monkeypatch.setattr("stripe.Customer.create", mock_customer)
|
||||
|
||||
customer = create_customer(
|
||||
g.subhub_account,
|
||||
user_id="test_mozilla",
|
||||
subhub_account,
|
||||
user_id="user_123",
|
||||
source_token="tok_visa",
|
||||
email="test_visa@tester.com",
|
||||
origin_system="Test_system",
|
||||
display_name="John Tester",
|
||||
)
|
||||
pytest.customer = customer
|
||||
assert customer["metadata"]["userid"] == "test_mozilla"
|
||||
|
||||
assert customer is not None
|
||||
|
||||
|
||||
def test_create_customer_tok_mastercard():
|
||||
"""
|
||||
GIVEN create a stripe customer
|
||||
WHEN provided a test mastercard token and test userid
|
||||
THEN validate the customer metadata is correct
|
||||
"""
|
||||
customer = create_customer(
|
||||
g.subhub_account,
|
||||
user_id="test_mozilla",
|
||||
source_token="tok_mastercard",
|
||||
email="test_mastercard@tester.com",
|
||||
origin_system="Test_system",
|
||||
display_name="John Tester",
|
||||
)
|
||||
assert customer["metadata"]["userid"] == "test_mozilla"
|
||||
|
||||
|
||||
def test_create_customer_tok_invalid():
|
||||
def test_create_customer_tok_invalid(monkeypatch):
|
||||
"""
|
||||
GIVEN create a stripe customer
|
||||
WHEN provided an invalid test token and test userid
|
||||
THEN validate the customer metadata is correct
|
||||
"""
|
||||
mock_possible_customers = MagicMock()
|
||||
data = PropertyMock(return_value=[])
|
||||
type(mock_possible_customers).data = data
|
||||
|
||||
mock_customer_error = Mock(
|
||||
side_effect=InvalidRequestError(
|
||||
message="Customer instance has invalid ID",
|
||||
param="customer_id",
|
||||
code="invalid",
|
||||
)
|
||||
)
|
||||
|
||||
subhub_account = MagicMock()
|
||||
|
||||
mock_user = MagicMock()
|
||||
user_id = PropertyMock(return_value="user_123")
|
||||
cust_id = PropertyMock(return_value="cust_123")
|
||||
type(mock_user).user_id = user_id
|
||||
type(mock_user).cust_id = cust_id
|
||||
|
||||
mock_save = MagicMock(return_value=True)
|
||||
|
||||
subhub_account.new_user = mock_user
|
||||
subhub_account.save_user = mock_save
|
||||
|
||||
monkeypatch.setattr("stripe.Customer.list", mock_possible_customers)
|
||||
monkeypatch.setattr("stripe.Customer.create", mock_customer_error)
|
||||
|
||||
with pytest.raises(InvalidRequestError):
|
||||
customer = create_customer(
|
||||
g.subhub_account,
|
||||
create_customer(
|
||||
subhub_account,
|
||||
user_id="test_mozilla",
|
||||
source_token="tok_invalid",
|
||||
email="test_invalid@tester.com",
|
||||
|
@ -118,388 +181,222 @@ def test_create_customer_tok_invalid():
|
|||
)
|
||||
|
||||
|
||||
def test_create_customer_tok_avsFail():
|
||||
"""
|
||||
GIVEN create a stripe customer
|
||||
WHEN provided an invalid test token and test userid
|
||||
THEN validate the customer metadata is correct
|
||||
"""
|
||||
customer = create_customer(
|
||||
g.subhub_account,
|
||||
user_id="test_mozilla",
|
||||
source_token="tok_avsFail",
|
||||
email="test_avsfail@tester.com",
|
||||
origin_system="Test_system",
|
||||
display_name="John Tester",
|
||||
)
|
||||
assert customer["metadata"]["userid"] == "test_mozilla"
|
||||
|
||||
|
||||
def test_create_customer_tok_avsUnchecked():
|
||||
"""
|
||||
GIVEN create a stripe customer
|
||||
WHEN provided an invalid test token and test userid
|
||||
THEN validate the customer metadata is correct
|
||||
"""
|
||||
customer = create_customer(
|
||||
g.subhub_account,
|
||||
user_id="test_mozilla",
|
||||
source_token="tok_avsUnchecked",
|
||||
email="test_avsunchecked@tester.com",
|
||||
origin_system="Test_system",
|
||||
display_name="John Tester",
|
||||
)
|
||||
assert customer["metadata"]["userid"] == "test_mozilla"
|
||||
|
||||
|
||||
def test_subscribe_customer(create_customer_for_processing):
|
||||
def test_subscribe_customer(monkeypatch):
|
||||
"""
|
||||
GIVEN create a subscription
|
||||
WHEN provided a customer and plan
|
||||
THEN validate subscription is created
|
||||
"""
|
||||
customer = create_customer_for_processing
|
||||
subscription = subscribe_customer(customer, "plan_EtMcOlFMNWW4nd")
|
||||
assert subscription["plan"]["active"]
|
||||
mock_customer = MagicMock()
|
||||
id = PropertyMock(return_value="cust_123")
|
||||
type(mock_customer).id = id
|
||||
|
||||
mock_subscription = MagicMock()
|
||||
|
||||
monkeypatch.setattr("stripe.Subscription.create", mock_subscription)
|
||||
|
||||
subscription = subscribe_customer(mock_customer, "plan_EtMcOlFMNWW4nd")
|
||||
assert subscription is not None
|
||||
|
||||
|
||||
def test_subscribe_customer_invalid_plan(create_customer_for_processing):
|
||||
def test_subscribe_customer_invalid_data(monkeypatch):
|
||||
"""
|
||||
GIVEN create a subscription
|
||||
WHEN provided a customer and plan
|
||||
THEN validate subscription is created
|
||||
"""
|
||||
customer = create_customer_for_processing
|
||||
try:
|
||||
subscribe_customer(customer, "plan_notvalid")
|
||||
except Exception as e:
|
||||
exception = e
|
||||
assert isinstance(exception, InvalidRequestError)
|
||||
assert "Unable to create plan" in exception.user_message
|
||||
mock_customer = MagicMock()
|
||||
id = PropertyMock(return_value="cust_123")
|
||||
type(mock_customer).id = id
|
||||
|
||||
mock_subscribe = Mock(side_effect=InvalidRequestError)
|
||||
|
||||
monkeypatch.setattr("stripe.Subscription.create", mock_subscribe)
|
||||
|
||||
with pytest.raises(InvalidRequestError):
|
||||
subscribe_customer(mock_customer, "invalid_plan_id")
|
||||
|
||||
|
||||
def test_create_subscription_with_valid_data():
|
||||
"""
|
||||
GIVEN create a subscription
|
||||
WHEN provided a api_token, userid, pmt_token, plan_id, cust_id
|
||||
THEN validate subscription is created
|
||||
"""
|
||||
uid = uuid.uuid4()
|
||||
subscription, code = payments.subscribe_to_plan(
|
||||
"validcustomer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_EtMcOlFMNWW4nd",
|
||||
"email": "valid@{}customer.com".format(uid),
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
assert 201 == code
|
||||
payments.cancel_subscription(
|
||||
"validcustomer", subscription["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
g.subhub_account.remove_from_db("validcustomer")
|
||||
|
||||
|
||||
def test_subscribe_customer_existing(create_customer_for_processing):
|
||||
def test_subscribe_customer_existing(app, monkeypatch):
|
||||
"""
|
||||
GIVEN create a subscription
|
||||
WHEN provided a customer and plan
|
||||
THEN validate subscription is created
|
||||
"""
|
||||
uid = uuid.uuid4()
|
||||
subscription, code = payments.subscribe_to_plan(
|
||||
"validcustomer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_EtMcOlFMNWW4nd",
|
||||
"email": f"valid@{uid}customer.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
subscription2, code2 = payments.subscribe_to_plan(
|
||||
"validcustomer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_EtMcOlFMNWW4nd",
|
||||
"email": f"valid@{uid}customer.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
assert 409 == code2
|
||||
payments.cancel_subscription(
|
||||
"validcustomer", subscription["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
g.subhub_account.remove_from_db("validcustomer")
|
||||
client = app.app.test_client()
|
||||
|
||||
plans_data = [
|
||||
{
|
||||
"id": "plan_123",
|
||||
"product": "prod_1",
|
||||
"interval": "month",
|
||||
"amount": 25,
|
||||
"currency": "usd",
|
||||
"nickname": "Plan 1",
|
||||
},
|
||||
{
|
||||
"id": "plan_2",
|
||||
"product": "prod_1",
|
||||
"interval": "year",
|
||||
"amount": 250,
|
||||
"currency": "usd",
|
||||
"nickname": "Plan 2",
|
||||
},
|
||||
]
|
||||
|
||||
def test_create_subscription_with_invalid_payment_token():
|
||||
"""
|
||||
GIVEN a api_token, userid, invalid pmt_token, plan_id, email
|
||||
WHEN the pmt_token is invalid
|
||||
THEN a StripeError should be raised
|
||||
"""
|
||||
exception = None
|
||||
try:
|
||||
subscription, code = payments.subscribe_to_plan(
|
||||
"invalid_test",
|
||||
{
|
||||
"pmt_token": "tok_invalid",
|
||||
"plan_id": "plan_EtMcOlFMNWW4nd",
|
||||
"email": "invalid_test@test.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
product_data = {"name": "Product 1"}
|
||||
|
||||
plans = Mock(return_value=plans_data)
|
||||
|
||||
product = Mock(return_value=product_data)
|
||||
|
||||
subhub_account = MagicMock()
|
||||
|
||||
get_user = MagicMock()
|
||||
user_id = PropertyMock(return_value="user123")
|
||||
cust_id = PropertyMock(return_value="cust123")
|
||||
type(get_user).user_id = user_id
|
||||
type(get_user).cust_id = cust_id
|
||||
|
||||
subhub_account.get_user = get_user
|
||||
|
||||
stripe_customer = Mock(
|
||||
return_value={
|
||||
"metadata": {"userid": "user123"},
|
||||
"subscriptions": {
|
||||
"data": [{"plan": {"id": "plan_123"}, "status": "active"}]
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
g.subhub_account.remove_from_db("invalid_test")
|
||||
|
||||
assert isinstance(exception, InvalidRequestError)
|
||||
assert "Unable to create customer." in exception.user_message
|
||||
|
||||
|
||||
def test_create_subscription_with_invalid_plan_id(app):
|
||||
"""
|
||||
GIVEN a api_token, userid, pmt_token, plan_id, email
|
||||
WHEN the plan_id provided is invalid
|
||||
THEN a StripeError is raised
|
||||
"""
|
||||
exception = None
|
||||
try:
|
||||
plan, code = payments.subscribe_to_plan(
|
||||
"invalid_plan",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_abc123",
|
||||
"email": "invalid_plan@tester.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"funding": "blah",
|
||||
"last4": "1234",
|
||||
"exp_month": "02",
|
||||
"exp_year": "2020",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
g.subhub_account.remove_from_db("invalid_plan")
|
||||
|
||||
assert isinstance(exception, InvalidRequestError)
|
||||
assert "No such plan:" in exception.user_message
|
||||
|
||||
|
||||
def test_list_all_plans_valid():
|
||||
"""
|
||||
GIVEN should list all available plans
|
||||
WHEN provided an api_token,
|
||||
THEN validate able to list all available plans
|
||||
"""
|
||||
plans, code = payments.list_all_plans()
|
||||
assert len(plans) > 0
|
||||
assert 200 == code
|
||||
|
||||
|
||||
def test_cancel_subscription_with_valid_data(app, create_subscription_for_processing):
|
||||
"""
|
||||
GIVEN should cancel an active subscription
|
||||
WHEN provided a api_token, and subscription id
|
||||
THEN validate should cancel subscription
|
||||
"""
|
||||
subscription, code = create_subscription_for_processing
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"process_test", subscription["subscriptions"][0]["subscription_id"]
|
||||
}
|
||||
)
|
||||
assert cancelled["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
mock_true = Mock(return_value=True)
|
||||
|
||||
monkeypatch.setattr("stripe.Plan.list", plans)
|
||||
monkeypatch.setattr("stripe.Product.retrieve", product)
|
||||
monkeypatch.setattr("subhub.sub.payments.has_existing_plan", mock_true)
|
||||
monkeypatch.setattr("flask.g.subhub_account", subhub_account)
|
||||
monkeypatch.setattr("stripe.Customer.retrieve", stripe_customer)
|
||||
|
||||
path = "v1/customer/user123/subscriptions"
|
||||
data = {
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_123",
|
||||
"origin_system": "Test_system",
|
||||
"email": "user123@example.com",
|
||||
"display_name": "John Tester",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
path,
|
||||
headers={"Authorization": "fake_payment_api_key"},
|
||||
data=json.dumps(data),
|
||||
content_type="application/json",
|
||||
)
|
||||
logger.info("response data", data=response.data)
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_delete_customer(app, create_subscription_for_processing):
|
||||
def test_cancel_subscription_no_subscription_found(monkeypatch):
|
||||
|
||||
"""
|
||||
GIVEN should cancel an active subscription,
|
||||
delete customer from payment provider and database
|
||||
WHEN provided a user id
|
||||
THEN validate user is deleted from payment provider and database
|
||||
GIVEN call to cancel subscription
|
||||
WHEN there is no active subscription
|
||||
THEN return the appropriate message
|
||||
Subscription.modify(sub_id, cancel_at_period_end=True)
|
||||
"""
|
||||
message, code = payments.delete_customer("process_test")
|
||||
assert message["message"] == "Customer deleted successfully"
|
||||
deleted_message, code = payments.subscription_status("process_test")
|
||||
assert "Customer does not exist" in deleted_message["message"]
|
||||
with monkeypatch.context() as m:
|
||||
user = Mock(return_value=MockSubhubUser())
|
||||
subscription_data = {
|
||||
"id": "sub_123",
|
||||
"status": "deleted",
|
||||
"current_period_end": 1566833524,
|
||||
"current_period_start": 1564155124,
|
||||
"ended_at": None,
|
||||
"plan": {"id": "plan_123", "nickname": "Monthly VPN Subscription"},
|
||||
"cancel_at_period_end": False,
|
||||
}
|
||||
customer = MagicMock(
|
||||
return_value={
|
||||
"id": "123",
|
||||
"cust_id": "cust_123",
|
||||
"metadata": {"userid": "123"},
|
||||
"subscriptions": {"data": [subscription_data]},
|
||||
}
|
||||
)
|
||||
cancel_response = mock(
|
||||
{
|
||||
"id": "cus_tester3",
|
||||
"object": "customer",
|
||||
"subscriptions": {"data": []},
|
||||
"sources": {"data": [{"id": "sub_123", "cancel_at_period_end": True}]},
|
||||
},
|
||||
spec=Customer,
|
||||
)
|
||||
delete_response = mock(
|
||||
{"id": "cus_tester3", "object": "customer", "sources": []}, spec=Customer
|
||||
)
|
||||
when(Customer).delete_source("cus_tester3", "src_123").thenReturn(
|
||||
delete_response
|
||||
)
|
||||
m.setattr("flask.g.subhub_account.get_user", user)
|
||||
m.setattr("stripe.Customer.retrieve", customer)
|
||||
m.setattr("subhub.customer.fetch_customer", customer)
|
||||
m.setattr("subhub.sub.payments.retrieve_stripe_subscriptions", cancel_response)
|
||||
cancel_sub, code = payments.cancel_subscription("123", "sub_123")
|
||||
logger.info("cancel sub", cancel_sub=cancel_sub)
|
||||
assert "Subscription not available." in cancel_sub["message"]
|
||||
assert code == 400
|
||||
|
||||
|
||||
def test_delete_customer_bad_user(app, create_subscription_for_processing):
|
||||
def test_cancel_subscription_without_valid_user(monkeypatch):
|
||||
"""
|
||||
GIVEN should cancel an active subscription,
|
||||
delete customer from payment provider and database
|
||||
WHEN provided a user id
|
||||
THEN validate user is deleted from payment provider and database
|
||||
GIVEN call to cancel subscription
|
||||
WHEN an invalid customer is sent
|
||||
THEN return the appropriate message
|
||||
"""
|
||||
message, code = payments.delete_customer("process_test2")
|
||||
assert message["message"] == "Customer does not exist."
|
||||
customer = Mock(return_value=None)
|
||||
monkeypatch.setattr("subhub.customer.fetch_customer", customer)
|
||||
cancel_sub, code = payments.cancel_subscription("bob_123", "sub_123")
|
||||
assert "Customer does not exist." in cancel_sub["message"]
|
||||
assert code == 404
|
||||
|
||||
|
||||
def test_delete_user_from_db(app, create_subscription_for_processing):
|
||||
"""
|
||||
GIVEN should delete user from user table
|
||||
WHEN provided with a valid user id
|
||||
THEN add to deleted users table
|
||||
"""
|
||||
deleted_user = payments.delete_user("process_test", "sub_id", "origin")
|
||||
logger.info("deleted user from db", deleted_user=deleted_user)
|
||||
assert deleted_user is True
|
||||
|
||||
|
||||
def test_delete_user_from_db2(app, create_subscription_for_processing, monkeypatch):
|
||||
"""
|
||||
GIVEN raise DeleteError
|
||||
WHEN an entry cannot be removed from the database
|
||||
THEN validate error message
|
||||
"""
|
||||
delete_error = Mock(side_effect=DeleteError())
|
||||
monkeypatch.setattr("subhub.db.SubHubAccount.remove_from_db", delete_error)
|
||||
|
||||
with pytest.raises(DeleteError) as request_error:
|
||||
payments.delete_user("process_test_2", "sub_id", "origin")
|
||||
msg = "Error deleting item"
|
||||
assert msg in str(request_error.value)
|
||||
|
||||
|
||||
def test_add_user_to_deleted_users_record(app, create_customer_for_processing):
|
||||
def test_add_user_to_deleted_users_record(monkeypatch):
|
||||
"""
|
||||
GIVEN Add user to deleted users record
|
||||
WHEN provided a user id, cust id and origin system
|
||||
THEN return subhud_deleted user
|
||||
"""
|
||||
customer = Mock(
|
||||
return_value={
|
||||
"user_id": "process_customer",
|
||||
"cust_id": "cus_123",
|
||||
"origin_system": "Test_system",
|
||||
}
|
||||
)
|
||||
monkeypatch.setattr("flask.g.subhub_account.get_user", customer)
|
||||
to_delete = g.subhub_account.get_user("process_customer")
|
||||
logger.info("delete", deleted_user=to_delete)
|
||||
deleted_user = payments.add_user_to_deleted_users_record(
|
||||
user_id=to_delete.user_id,
|
||||
cust_id=to_delete.cust_id,
|
||||
origin_system=to_delete.origin_system,
|
||||
user_id=to_delete["user_id"],
|
||||
cust_id=to_delete["cust_id"],
|
||||
origin_system=to_delete["origin_system"],
|
||||
)
|
||||
assert deleted_user.user_id == "process_customer"
|
||||
assert deleted_user.origin_system == "Test_system"
|
||||
assert "cus_" in deleted_user.cust_id
|
||||
|
||||
|
||||
def test_cancel_subscription_with_valid_data_multiple_subscriptions_remove_first():
|
||||
"""
|
||||
GIVEN a user with multiple subscriptions
|
||||
WHEN the first subscription is cancelled
|
||||
THEN the specified subscription is cancelled
|
||||
"""
|
||||
uid = uuid.uuid4()
|
||||
subscription1, code1 = payments.subscribe_to_plan(
|
||||
"valid_customer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_EtMcOlFMNWW4nd",
|
||||
"email": f"valid@{uid}customer.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
subscription2, code2 = payments.subscribe_to_plan(
|
||||
"valid_customer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_F4G9jB3x5i6Dpj",
|
||||
"email": f"valid@{uid}customer.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
|
||||
# cancel the first subscription created
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"valid_customer", subscription1["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
assert cancelled["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code
|
||||
|
||||
# clean up test data created
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"valid_customer", subscription2["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
g.subhub_account.remove_from_db("valid_customer")
|
||||
assert cancelled["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code
|
||||
|
||||
|
||||
def test_cancel_subscription_with_valid_data_multiple_subscriptions_remove_second():
|
||||
"""
|
||||
GIVEN a user with multiple subscriptions
|
||||
WHEN the second subscription is cancelled
|
||||
THEN the specified subscription is cancelled
|
||||
"""
|
||||
uid = uuid.uuid4()
|
||||
subscription1, code1 = payments.subscribe_to_plan(
|
||||
"valid_customer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_EtMcOlFMNWW4nd",
|
||||
"email": f"valid@{uid}customer.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
subscription2, code2 = payments.subscribe_to_plan(
|
||||
"valid_customer",
|
||||
{
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan_F4G9jB3x5i6Dpj",
|
||||
"email": f"valid@{uid}customer.com",
|
||||
"origin_system": "Test_system",
|
||||
"display_name": "Jon Tester",
|
||||
},
|
||||
)
|
||||
|
||||
# cancel the second subscription created
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"valid_customer", subscription2["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
assert cancelled["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code
|
||||
|
||||
# clean up test data created
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"valid_customer", subscription1["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
g.subhub_account.remove_from_db("valid_customer")
|
||||
assert cancelled["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code
|
||||
|
||||
|
||||
def test_cancel_subscription_with_invalid_data(app, create_subscription_for_processing):
|
||||
subscription, code = create_subscription_for_processing
|
||||
if subscription.get("subscriptions"):
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"process_test",
|
||||
subscription["subscriptions"][0]["subscription_id"] + "invalid",
|
||||
)
|
||||
assert cancelled["message"] == "Subscription not available."
|
||||
assert 400 == code
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
|
||||
|
||||
def test_cancel_subscription_already_cancelled(app, create_subscription_for_processing):
|
||||
subscription, code = create_subscription_for_processing
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"process_test", subscription["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
cancelled2, code2 = payments.cancel_subscription(
|
||||
"process_test", subscription["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
assert cancelled["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code
|
||||
assert cancelled2["message"] == "Subscription cancellation successful"
|
||||
assert 201 == code2
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
|
||||
|
||||
def test_cancel_subscription_with_invalid_subhub_user(app):
|
||||
def test_cancel_subscription_with_invalid_subhub_user(monkeypatch):
|
||||
"""
|
||||
GIVEN an active subscription
|
||||
WHEN provided an api_token and an invalid userid
|
||||
|
@ -510,87 +407,6 @@ def test_cancel_subscription_with_invalid_subhub_user(app):
|
|||
assert cancelled["message"] == "Customer does not exist."
|
||||
|
||||
|
||||
def test_cancel_subscription_with_invalid_stripe_customer(
|
||||
app, create_subscription_for_processing
|
||||
):
|
||||
"""
|
||||
GIVEN an userid and subscription id
|
||||
WHEN the user has an invalid stripe customer id
|
||||
THEN a StripeError is raised
|
||||
"""
|
||||
subscription, code = create_subscription_for_processing
|
||||
|
||||
subhub_user = g.subhub_account.get_user("process_test")
|
||||
subhub_user.cust_id = None
|
||||
g.subhub_account.save_user(subhub_user)
|
||||
|
||||
exception = None
|
||||
try:
|
||||
cancelled, code = payments.cancel_subscription(
|
||||
"process_test", subscription["subscriptions"][0]["subscription_id"]
|
||||
)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
|
||||
assert isinstance(exception, InvalidRequestError)
|
||||
assert "Customer instance has invalid ID" in exception.user_message
|
||||
|
||||
|
||||
def test_check_subscription_with_valid_parameters(
|
||||
app, create_subscription_for_processing
|
||||
):
|
||||
"""
|
||||
GIVEN should get a list of active subscriptions
|
||||
WHEN provided an api_token and a userid id
|
||||
THEN validate should return list of active subscriptions
|
||||
"""
|
||||
subscription, code = create_subscription_for_processing
|
||||
sub_status, code = payments.subscription_status("process_test")
|
||||
assert 200 == code
|
||||
assert len(sub_status) > 0
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
|
||||
|
||||
def test_update_payment_method_valid_parameters(
|
||||
app, create_subscription_for_processing
|
||||
):
|
||||
"""
|
||||
GIVEN api_token, userid, pmt_token
|
||||
WHEN all parameters are valid
|
||||
THEN update payment method for a customer
|
||||
"""
|
||||
subscription, code = create_subscription_for_processing
|
||||
updated_pmt, code = payments.update_payment_method(
|
||||
"process_test", {"pmt_token": "tok_mastercard"}
|
||||
)
|
||||
assert 201 == code
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
|
||||
|
||||
def test_update_payment_method_invalid_payment_token(
|
||||
app, create_subscription_for_processing
|
||||
):
|
||||
"""
|
||||
GIVEN api_token, userid, pmt_token
|
||||
WHEN invalid pmt_token
|
||||
THEN a StripeError exception is raised
|
||||
"""
|
||||
exception = None
|
||||
try:
|
||||
updated_pmt, code = payments.update_payment_method(
|
||||
"process_test", {"pmt_token": "tok_invalid"}
|
||||
)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
g.subhub_account.remove_from_db("process_test")
|
||||
|
||||
assert isinstance(exception, InvalidRequestError)
|
||||
assert "No such token:" in exception.user_message
|
||||
|
||||
|
||||
def test_update_payment_method_missing_stripe_customer(monkeypatch):
|
||||
"""
|
||||
GIVEN api_token, userid, pmt_token
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock, MagicMock
|
||||
import mock
|
||||
|
||||
import connexion
|
||||
|
@ -12,6 +12,10 @@ import stripe.error
|
|||
from subhub.app import create_app
|
||||
from subhub.tests.unit.stripe.utils import MockSubhubUser
|
||||
|
||||
from subhub.log import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class MockCustomer:
|
||||
id = 123
|
||||
|
@ -37,6 +41,53 @@ def test_subhub():
|
|||
assert isinstance(app, connexion.FlaskApp)
|
||||
|
||||
|
||||
def test_list_plans(app, monkeypatch):
|
||||
"""
|
||||
GIVEN a valid token
|
||||
WHEN a request for plans is made
|
||||
THEN a success status of 200 is returned
|
||||
"""
|
||||
client = app.app.test_client()
|
||||
|
||||
plans_data = [
|
||||
{
|
||||
"id": "plan_1",
|
||||
"product": "prod_1",
|
||||
"interval": "month",
|
||||
"amount": 25,
|
||||
"currency": "usd",
|
||||
"nickname": "Plan 1",
|
||||
},
|
||||
{
|
||||
"id": "plan_2",
|
||||
"product": "prod_1",
|
||||
"interval": "year",
|
||||
"amount": 250,
|
||||
"currency": "usd",
|
||||
"nickname": "Plan 2",
|
||||
},
|
||||
]
|
||||
|
||||
product_data = {"name": "Product 1"}
|
||||
|
||||
plans = Mock(return_value=plans_data)
|
||||
|
||||
product = Mock(return_value=product_data)
|
||||
|
||||
monkeypatch.setattr("stripe.Plan.list", plans)
|
||||
monkeypatch.setattr("stripe.Product.retrieve", product)
|
||||
|
||||
path = "v1/plans"
|
||||
|
||||
response = client.get(
|
||||
path,
|
||||
headers={"Authorization": "fake_payment_api_key"},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_update_customer_payment_server_stripe_error_with_params(app, monkeypatch):
|
||||
"""
|
||||
GIVEN the route POST v1/customer/{id} is called
|
||||
|
@ -115,6 +166,94 @@ def test_customer_signup_server_stripe_error_with_params(app, monkeypatch):
|
|||
assert "No such plan" in loaded_data["message"]
|
||||
|
||||
|
||||
def test_subscribe_success(app, monkeypatch):
|
||||
"""
|
||||
GIVEN a route that attempts to make a subscribe a customer
|
||||
WHEN valid data is provided
|
||||
THEN a success status of 201 will be returned
|
||||
"""
|
||||
|
||||
client = app.app.test_client()
|
||||
|
||||
subscription_data = {
|
||||
"id": "sub_123",
|
||||
"status": "active",
|
||||
"current_period_end": 1566833524,
|
||||
"current_period_start": 1564155124,
|
||||
"ended_at": None,
|
||||
"plan": {"id": "plan_123", "nickname": "Monthly VPN Subscription"},
|
||||
"cancel_at_period_end": False,
|
||||
}
|
||||
|
||||
mock_false = Mock(return_value=False)
|
||||
|
||||
customer = Mock(return_value=MockCustomer())
|
||||
|
||||
customer_updated = MagicMock(
|
||||
return_value={"id": "cust_123", "subscriptions": {"data": [subscription_data]}}
|
||||
)
|
||||
|
||||
create = Mock(return_value={"id": "sub_234"})
|
||||
|
||||
user = Mock(return_value=MockSubhubUser())
|
||||
|
||||
monkeypatch.setattr("flask.g.subhub_account.get_user", user)
|
||||
monkeypatch.setattr("stripe.Customer.retrieve", customer_updated)
|
||||
monkeypatch.setattr("subhub.sub.payments.has_existing_plan", mock_false)
|
||||
monkeypatch.setattr("subhub.sub.payments.existing_or_new_customer", customer)
|
||||
monkeypatch.setattr("stripe.Subscription.create", create)
|
||||
|
||||
path = "v1/customer/subtest/subscriptions"
|
||||
data = {
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan",
|
||||
"origin_system": "fake_origin1",
|
||||
"email": "subtest@example.com",
|
||||
"display_name": "John Tester",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
path,
|
||||
headers={"Authorization": "fake_payment_api_key"},
|
||||
data=json.dumps(data),
|
||||
content_type="application/json",
|
||||
)
|
||||
logger.info("response data", data=response.data)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_subscribe_customer_existing(app, monkeypatch):
|
||||
"""
|
||||
GIVEN a route that attempts to make a subscribe a customer
|
||||
WHEN the customer already exists
|
||||
THEN an error status of 409 will be returned
|
||||
"""
|
||||
|
||||
client = app.app.test_client()
|
||||
|
||||
mock_true = Mock(return_value=True)
|
||||
|
||||
monkeypatch.setattr("subhub.sub.payments.has_existing_plan", mock_true)
|
||||
|
||||
path = "v1/customer/subtest/subscriptions"
|
||||
data = {
|
||||
"pmt_token": "tok_visa",
|
||||
"plan_id": "plan",
|
||||
"origin_system": "Test_system",
|
||||
"email": "subtest@example.com",
|
||||
"display_name": "John Tester",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
path,
|
||||
headers={"Authorization": "fake_payment_api_key"},
|
||||
data=json.dumps(data),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_subscribe_card_declined_error_handler(app, monkeypatch):
|
||||
"""
|
||||
GIVEN a route that attempts to make a stripe payment
|
||||
|
|
107
yarn.lock
107
yarn.lock
|
@ -451,7 +451,12 @@ ansi-escapes@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
|
||||
integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
|
||||
|
||||
ansi-escapes@^4.2.0, ansi-escapes@^4.2.1:
|
||||
ansi-escapes@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
|
||||
|
||||
ansi-escapes@^4.2.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
|
||||
integrity sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==
|
||||
|
@ -637,9 +642,9 @@ aws-sdk@^2.177.0, aws-sdk@^2.7.0:
|
|||
xml2js "0.4.19"
|
||||
|
||||
aws-sdk@^2.496.0:
|
||||
version "2.508.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.508.0.tgz#19eeb9da0df41382d45568367837eeb6e7eab3b5"
|
||||
integrity sha512-VXMxsuwK31U+N259562VPiwQblwRQFQoVnv0d4Gp4Ji+JimCvV6qF73zobZy7cQK5ZPAB25BK4JVMdTYkpi+vA==
|
||||
version "2.507.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.507.0.tgz#afb30dd2a0a5166aa7c2f808064f2d03e4dc3619"
|
||||
integrity sha512-sOtaZONTfUx1jh9HzrWMLwoA2cZK2Xn2RAmGV4Y11NM2qhMePOQ501dhAq/ygKMZRffjw23b8mT1rAaDGTn05g==
|
||||
dependencies:
|
||||
buffer "4.9.1"
|
||||
events "1.1.1"
|
||||
|
@ -960,12 +965,12 @@ cli-cursor@^1.0.1:
|
|||
dependencies:
|
||||
restore-cursor "^1.0.1"
|
||||
|
||||
cli-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
||||
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
|
||||
cli-cursor@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
||||
integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
|
||||
dependencies:
|
||||
restore-cursor "^3.1.0"
|
||||
restore-cursor "^2.0.0"
|
||||
|
||||
cli-width@^2.0.0:
|
||||
version "2.2.0"
|
||||
|
@ -1445,11 +1450,6 @@ emoji-regex@^7.0.1:
|
|||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
|
@ -1749,6 +1749,13 @@ figures@^1.3.5:
|
|||
escape-string-regexp "^1.0.5"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
figures@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
|
||||
integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.5"
|
||||
|
||||
figures@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9"
|
||||
|
@ -2289,21 +2296,21 @@ inquirer@^1.0.2:
|
|||
through "^2.3.6"
|
||||
|
||||
inquirer@^6.5.0:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.1.tgz#8bfb7a5ac02dac6ff641ac4c5ff17da112fcdb42"
|
||||
integrity sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
|
||||
integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==
|
||||
dependencies:
|
||||
ansi-escapes "^4.2.1"
|
||||
ansi-escapes "^3.2.0"
|
||||
chalk "^2.4.2"
|
||||
cli-cursor "^3.1.0"
|
||||
cli-cursor "^2.1.0"
|
||||
cli-width "^2.0.0"
|
||||
external-editor "^3.0.3"
|
||||
figures "^3.0.0"
|
||||
lodash "^4.17.15"
|
||||
mute-stream "0.0.8"
|
||||
figures "^2.0.0"
|
||||
lodash "^4.17.12"
|
||||
mute-stream "0.0.7"
|
||||
run-async "^2.2.0"
|
||||
rxjs "^6.4.0"
|
||||
string-width "^4.1.0"
|
||||
string-width "^2.1.0"
|
||||
strip-ansi "^5.1.0"
|
||||
through "^2.3.6"
|
||||
|
||||
|
@ -2426,11 +2433,6 @@ is-fullwidth-code-point@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
|
||||
|
||||
is-fullwidth-code-point@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-installed-globally@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
|
||||
|
@ -2927,7 +2929,7 @@ lodash.values@^4.3.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347"
|
||||
integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=
|
||||
|
||||
lodash@4.17.x, lodash@^4.17.15:
|
||||
lodash@4.17.x, lodash@^4.17.12, lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
@ -3054,10 +3056,10 @@ mime@1.6.0, mime@^1.4.1:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
|
||||
|
||||
mimic-response@^1.0.0, mimic-response@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
@ -3136,10 +3138,10 @@ mute-stream@0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
|
||||
integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=
|
||||
|
||||
mute-stream@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||
mute-stream@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
|
||||
|
||||
nan@~2.12.1:
|
||||
version "2.12.1"
|
||||
|
@ -3376,12 +3378,12 @@ onetime@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
|
||||
integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
|
||||
|
||||
onetime@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5"
|
||||
integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==
|
||||
onetime@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
|
||||
integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
opn@^5.5.0:
|
||||
version "5.5.0"
|
||||
|
@ -3769,12 +3771,12 @@ restore-cursor@^1.0.1:
|
|||
exit-hook "^1.0.0"
|
||||
onetime "^1.0.0"
|
||||
|
||||
restore-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
||||
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
|
||||
restore-cursor@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
|
||||
integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
|
||||
dependencies:
|
||||
onetime "^5.1.0"
|
||||
onetime "^2.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
ret@~0.1.10:
|
||||
|
@ -4203,7 +4205,7 @@ string-width@^1.0.1:
|
|||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1:
|
||||
"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
|
||||
|
@ -4220,15 +4222,6 @@ string-width@^3.0.0:
|
|||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string-width@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff"
|
||||
integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^5.2.0"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
|
||||
|
|
Загрузка…
Ссылка в новой задаче