Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
fun-apps / payment / tests / test_payment.py
Size: Mime:
# -*- coding: utf-8 -*-

import json

from bs4 import BeautifulSoup
from django.contrib.auth.models import User
from django.core import mail
from django.core.urlresolvers import reverse
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
from mock import patch
from opaque_keys.edx.keys import CourseKey
from requests.exceptions import ConnectionError
from student.models import UserProfile
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import (
    ModuleStoreTestCase,
    TEST_DATA_XML_MODULESTORE,
)
from courses.models import Course, CourseUniversityRelation
from fun.tests.utils import skipUnlessLms
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from universities.tests.factories import UniversityFactory
from ..utils import (
    get_course,
    send_confirmation_email,
    get_order_context,
    format_date_order,
    get_order,
    register_user_verified_cohort,
)


def ecommerce_order_api_response(course_key_string):
    return {
        'number': 'FUN-100056',
        'total_excl_tax': '100.00',
        'date_placed': '2016-03-17T10:07:36Z',
        'lines': [
            {
                'product': {
                    'attribute_values': [
                        {
                            'name': 'course_key',
                            'value': course_key_string,
                        }
                    ]
                }
            }
        ]
    }


FAKE_COURSE_ID = {"org": "FUN", "number": "0002", "run": "session1"}

FAKE_COURSE_ID_STRING = "{}/{}/{}".format(
    FAKE_COURSE_ID.get("org"), FAKE_COURSE_ID.get("number"), FAKE_COURSE_ID.get("run")
)

ecommerce_basket_api_response = {
    "id": 130,
    "date_created": "2016-04-01T12:32:34Z",
    "lines": [
        {
            "product": {
                "attribute_values": [
                    {
                        "name": "course_key",
                        "value": FAKE_COURSE_ID_STRING
                    },
                ],
            },
            "price_excl_tax": "100.00",
        }
    ]
}


def ecommerce_api_listing_course(course):
    return {u'count': 0, u'previous': None, u'results': [course] if course else [], u'next': None}


@override_settings(FUN_ECOMMERCE_DEBUG_NO_NOTIFICATION=False)
@skipUnlessLms
class AbstractPaymentTest(TestCase):
    def setUp(self):
        super(AbstractPaymentTest, self).setUp()
        self.user = User.objects.create(username='user', first_name='first_name',
                                        last_name='last_name', email='richard@example.com', is_active=True)
        self.user.set_password('test')
        self.user.save()
        UserProfile.objects.create(user=self.user, language='fr', name=u"Robert Cash")
        self.client.login(username=self.user.username, password='test')
        self.course = Course.objects.create(key=FAKE_COURSE_ID_STRING, title=u"course title", )
        self.university = UniversityFactory(name=u"FUN")
        CourseUniversityRelation.objects.create(
            course=self.course, university=self.university)
        self.api_response = ecommerce_order_api_response(self.course.key)

class PayboxSystemViewsTest(AbstractPaymentTest):
    def setUp(self):
        super(PayboxSystemViewsTest, self).setUp()
        self.params = {
            'amount': '10000',
            'reference-fun': 'FUN-100056',
            'autorisation': 'XXXXXX',
            'reponse-paybox': '',
            'appel-paybox': '16047443',
            'transaction-paybox': '7558206',
        }

    @patch('payment.views.get_order_or_404')
    def test_callback_page_success(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        self.params['reponse-paybox'] = '00000'
        response = self.client.get(reverse('payment:success'), self.params)
        self.assertEqual(200, response.status_code)
        soup = BeautifulSoup(response.content)
        self.assertEqual(u"Paiement réussi", soup.find('h2').text)

    @patch('payment.views.get_order_or_404')
    def test_callbackpage_success_wrong_api_return_value(self, get_order_mock):
        get_order_mock.return_value = {}
        self.params['reponse-paybox'] = '00000'
        response = self.client.get(reverse('payment:success'), self.params)
        self.assertEqual(404, response.status_code)

    @patch('payment.views.get_order_or_404')
    def test_callbackpage_success_empty_api_return_value(self, get_order_mock):
        get_order_mock.return_value = {
            'lines': []
        }
        self.params['reponse-paybox'] = '00000'
        response = self.client.get(reverse('payment:success'), self.params)
        self.assertEqual(404, response.status_code)

    @patch('payment.views.get_order_or_404')
    def test_callbackpage_false_success(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        self.params['reponse-paybox'] = '0000X'
        response = self.client.get(reverse('payment:success'), self.params)
        self.assertEqual(400, response.status_code)

    @patch('payment.views.get_order_or_404')
    def test_callbackpage_success_missing_argument(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        self.params.pop('reponse-paybox')
        response = self.client.get(reverse('payment:success'), self.params)
        self.assertEqual(400, response.status_code)

    @patch('payment.views.get_basket_or_404')
    def test_callbackpage_cancel(self, get_order_mock):
        get_order_mock.return_value = ecommerce_basket_api_response
        self.params['reponse-paybox'] = '00001'
        response = self.client.get(reverse('payment:cancel'), self.params)
        self.assertEqual(200, response.status_code)
        soup = BeautifulSoup(response.content)
        self.assertEqual(u"Abandon du paiement", soup.find('h2').text)

    @patch('payment.views.get_order_or_404')
    def test_callbackpage_cancel_missing_argument(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        self.params.pop('reponse-paybox')
        response = self.client.get(reverse('payment:cancel'), self.params)
        self.assertEqual(400, response.status_code)

    @patch('payment.views.get_basket_or_404')
    def test_callbackpage_error(self, get_basket_mock):
        get_basket_mock.return_value = ecommerce_basket_api_response
        errorcode = '00002'
        self.params['reponse-paybox'] = errorcode
        response = self.client.get(reverse('payment:error'), self.params)
        self.assertEqual(200, response.status_code)
        soup = BeautifulSoup(response.content)
        self.assertEqual(u"Une erreur a eu lieu", soup.find('h2').text)
        self.assertEqual(errorcode, soup.find('strong', class_='errorcode').text)

    @patch('payment.views.get_order_or_404')
    def test_callbackpage_error_missing_argument(self, get_basket_mock):
        get_basket_mock.return_value = self.api_response
        self.params.pop('reponse-paybox')
        response = self.client.get(reverse('payment:error'), self.params)
        self.assertEqual(400, response.status_code)

    @patch('payment.utils.register_user_verified_cohort')
    @patch('payment.utils.get_order')
    def test_payment_notification_api(self, get_order_mock, register_user_verified_cohort_mock):
        get_order_mock.return_value = self.api_response
        data = {
            'username': self.user.username,
            'email': self.user.email,
            'order_number': '0'
        }

        self.client.post(
            reverse('fun-payment-api:payment-notification'),
            json.dumps(data),
            content_type="application/json"
        )
        self.assertEqual(1, SoftwareSecurePhotoVerification.objects.filter(user=self.user).count())
        self.assertEqual(1, len(mail.outbox))
        self.assertTrue(mail.outbox[0].message().is_multipart())
        register_user_verified_cohort_mock.assert_called_once()

    @patch('payment.utils.get_order')
    def test_payment_notification_api_already_verified(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        SoftwareSecurePhotoVerification.objects.create(
            user=self.user, display=False,
            status='approved', reviewing_user=self.user,
            reviewing_service='created by unit test')
        get_order_mock.return_value = self.api_response
        data = {'username': self.user.username,
                'email': self.user.email,
                'order_number': '0'}
        self.client.post(reverse('fun-payment-api:payment-notification'),
                json.dumps(data),
                content_type="application/json")
        self.assertEqual(1,
                         SoftwareSecurePhotoVerification.objects.filter(
                             user=self.user).count())
        self.assertEqual(1, len(mail.outbox))

    def test_utils_get_course(self):
        self.assertEqual(self.course,
                         get_course(self.api_response))

    @patch('payment.utils.get_order')
    def test_confirmation_email_language(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        send_confirmation_email(self.user, self.api_response)
        self.assertEqual(1, len(mail.outbox))
        soup = BeautifulSoup(mail.outbox[0].message().as_string())
        # default user's language is fr
        self.assertEqual(u"Confirmation de paiement",
                         soup.find('h1', class_='title').text.strip())
        # create new user, with language en
        mail.outbox = []
        user_en = User.objects.create(username='user_en', first_name='first_name',
                                      last_name='last_name', email='richard@example.co.uk', is_active=True)
        UserProfile.objects.create(user=user_en, language='en')
        send_confirmation_email(user_en, ecommerce_order_api_response(self.course.key))
        soup = BeautifulSoup(mail.outbox[0].message().as_string())
        self.assertEqual(u"Payment confirmation",
                         soup.find('h1', class_='title').text.strip())

    @patch('payment.utils.get_order')
    def test_confirmation_email_bill(self, get_order_mock):
        get_order_mock.return_value = self.api_response
        send_confirmation_email(self.user,
                                ecommerce_order_api_response(self.course.key))
        self.assertEqual(1, len(mail.outbox))
        soup = BeautifulSoup(mail.outbox[0].message().as_string())
        self.assertEqual(self.course.title,
                         soup.find('span', class_='line-1-course-title').text.strip())
        self.assertEqual(1,
                         int(soup.find('span', class_='line-1-quantity').text.strip()))
        self.assertEqual(self.university.name,
                         soup.find('span', class_='line-1-course-university').text.strip())
        self.assertEqual(self.user.profile.name,
                         soup.find('span', class_='user-full-name').text.strip())


@override_settings(FUN_ECOMMERCE_DEBUG_NO_NOTIFICATION=False)
@skipUnlessLms
class RegisterUserTest(ModuleStoreTestCase):

    MODULESTORE = TEST_DATA_XML_MODULESTORE

    def setUp(self):
        super(RegisterUserTest, self).setUp()
        self.course = Course.objects.create(
            key="edX/toy/2012_Fall",
            title=u"course title",
        )  # Fun Course Model
        self.course_key = CourseKey.from_string(self.course.key)
        self.user = UserFactory(
            username="user",
            first_name="first_name",
            last_name="last_name",
            email="richard@example.com",
            is_active=True,
        )
        self.cohort = CourseUserGroup.objects.create(
            name="Certificat",
            course_id=self.course_key,
            group_type=CourseUserGroup.COHORT,
        )

    @patch("payment.utils.add_user_to_cohort")
    def test_register_user_no_verified_cohort(self, add_user_to_cohort_mock):
        """
        Don't add user to any cohort when VERIFIED_COHORTS is not set.
        """
        register_user_verified_cohort(
            self.user, ecommerce_order_api_response("edX/toy/2012_Fall")
        )
        add_user_to_cohort_mock.assert_not_called()

    @patch("payment.utils.add_user_to_cohort")
    def test_register_user_add_to_cohort(self, add_user_to_cohort_mock):
        """
        Add user to default cohort when VERIFIED_COHORTS includes the catch-all pattern.
        """
        with override_settings(VERIFIED_COHORTS=[(r".*", "Certificat")]):
            register_user_verified_cohort(
                self.user, ecommerce_order_api_response("edX/toy/2012_Fall")
            )
        add_user_to_cohort_mock.assert_called_once_with(self.cohort, self.user.email)

    @patch("payment.utils.add_user_to_cohort")
    def test_register_user_verified_cohort_does_not_exist(
        self, add_user_to_cohort_mock
    ):
        """
        Don't add user to any cohort when VERIFIED_COHORTS points to a non-existing cohort.
        """
        with override_settings(VERIFIED_COHORTS=[(r".*", "Certificat qui n'existe pas")]):
            register_user_verified_cohort(
                self.user, ecommerce_order_api_response("edX/toy/2012_Fall")
            )
        add_user_to_cohort_mock.assert_not_called()

    @patch("payment.utils.add_user_to_cohort")
    def test_register_user_custom_course_cohort(self, add_user_to_cohort_mock):
        """
        Add user to the right course cohort (but not to the default cohort).
        """
        cohort2 = CourseUserGroup.objects.create(
            name="Certificat 2",
            course_id=self.course_key,
            group_type=CourseUserGroup.COHORT,
        )
        with override_settings(
            VERIFIED_COHORTS=[(self.course.key, "Certificat 2"), (r".*", "Certificat")]
        ):
            register_user_verified_cohort(
                self.user, ecommerce_order_api_response("edX/toy/2012_Fall")
            )
        add_user_to_cohort_mock.assert_called_once_with(cohort2, self.user.email)


class PaymentUtilsTest(AbstractPaymentTest):
    def test_date_format(self):
        date = u"2016-03-24T11:11:09Z"
        order = {"date_placed": date}
        date1 = format_date_order(order, "%m/%d/%y")
        date2 = format_date_order(order, "%d/%m/%y")

        self.assertEqual(date1, "03/24/16")
        self.assertEqual(date2, "24/03/16")

    def test_get_order_context(self):
        order = {"total_excl_tax": 123}
        course = "cours"
        user = self.user
        context = get_order_context(user, order, course)

        self.assertEqual(order, context['order'])
        self.assertEqual(course, context['ordered_course'])
        self.assertEqual(user, context['user'])
        self.assertEqual(order['total_excl_tax'], context['total_incl_tax'])


class GetOrderTest(AbstractPaymentTest):
    @patch('slumber.Resource.get')
    def test_get_order_ok(self, api_mock):
        api_mock.return_value = self.api_response
        order = get_order(self.user, "FUN-100056")

        self.assertEqual(self.api_response, order)

    @patch('slumber.Resource.get')
    def test_get_order_connection_error(self, api_mock):
        api_mock.side_effect = ConnectionError
        order = get_order(self.user, "FUN-100056")

        self.assertEqual(None, order)


class FacturationViewsTest(AbstractPaymentTest):
    @patch("payment.views.get_order")
    def test_detail_receipt_ok(self, patch_get_order):
        patch_get_order.return_value = self.api_response
        order_id = "FUN-100056"
        response = self.client.get(reverse('payment:detail-receipt', kwargs={"order_id": order_id}))
        soup = BeautifulSoup(response.content)

        self.assertEqual(u"Reçu : {}".format(order_id), soup.find("h1").text)
        self.assertIn(u"course title",
                      soup.find("span", {"class": "course-display-name"}).text)

    @patch("payment.views.get_order")
    def test_detail_receipt_with_connection_error(self, patch_get_order):
        patch_get_order.return_value = None
        order_id = "FUN-100056"
        response = self.client.get(reverse('payment:detail-receipt', kwargs={"order_id": order_id}))
        soup = BeautifulSoup(response.content)

        self.assertEqual(u"Reçu : FUN-100056", soup.find("h1").text)
        self.assertIn(u"Une erreur a eu lieu, veuillez ré-essayer plus tard.",
                      soup.find("div", {"class": "order-error"}).text)

    @patch('slumber.Resource.get')
    def test_list_receipt_1_course(self, api_mock):
        api_mock.return_value = ecommerce_api_listing_course(self.api_response)
        response = self.client.get(reverse('payment:list-receipts'))
        soup = BeautifulSoup(response.content)

        self.assertEqual(self.api_response["number"],
                         soup.find("td", {"class": "order_id"}).text)
        self.assertEqual(u"{p}".format(p=self.api_response["total_excl_tax"]),
                         soup.find("td", {"class": "order_price"}).text)
        self.assertEqual(format_date_order(self.api_response, "%d/%m/%y"),
                         soup.find("td", {"class": "order_date"}).text)

    @patch('slumber.Resource.get')
    def test_list_receipt_with_connection_error(self, api_mock):
        api_mock.side_effect = ConnectionError
        response = self.client.get(reverse('payment:list-receipts'))
        soup = BeautifulSoup(response.content)

        self.assertIn(u"Une erreur a eu lieu, veuillez ré-essayer plus tard.",
                      soup.find("p", {"class": "order-error"}).text)

    @patch('slumber.Resource.get')
    def test_list_receipt_no_course(self, api_mock):
        api_mock.return_value = ecommerce_api_listing_course(None)
        response = self.client.get(reverse('payment:list-receipts'))
        soup = BeautifulSoup(response.content)

        self.assertIn(u"Vous n'avez pas encore souscrit à un examen certifiant.",
                soup.find("p", {"class": "order-empty"}).text)