Ejemplo n.º 1
0
    def test_issue_credit_for_enterprise_offer_no_partial_credit(self):
        """
        Test that credit is not issued if not all of the lines in an order are refunded.
        """
        order = self.create_order(user=UserFactory(), multiple_lines=True)

        offer = EnterpriseOfferFactory(partner=PartnerFactory(),
                                       max_discount=150)

        discount = OrderDiscountFactory(order=order,
                                        offer_id=offer.id,
                                        frequency=1,
                                        amount=150)

        offer.record_usage({
            'freq': discount.frequency,
            'discount': discount.amount
        })
        offer.refresh_from_db()
        assert offer.status == ConditionalOffer.CONSUMED
        assert offer.total_discount == 150

        refund = RefundFactory(order=order, user=UserFactory())
        refund.lines.first().delete()

        with mock.patch.object(models,
                               'revoke_fulfillment_for_refund') as mock_revoke:
            mock_revoke.return_value = True
            self.assertTrue(refund.approve())

        offer.refresh_from_db()

        assert offer.status == ConditionalOffer.CONSUMED
        assert offer.total_discount == 150
Ejemplo n.º 2
0
    def test_issue_credit_for_enterprise_offer(self):
        """
        Test that enterprise offers are credited for the discounted amount.
        """
        order = self.create_order(user=UserFactory())

        offer = EnterpriseOfferFactory(partner=PartnerFactory(),
                                       max_discount=150)

        discount = OrderDiscountFactory(order=order,
                                        offer_id=offer.id,
                                        frequency=1,
                                        amount=150)

        offer.record_usage({
            'freq': discount.frequency,
            'discount': discount.amount
        })
        offer.refresh_from_db()
        assert offer.status == ConditionalOffer.CONSUMED
        assert offer.total_discount == 150

        refund = RefundFactory(order=order, user=UserFactory())

        with mock.patch.object(models,
                               'revoke_fulfillment_for_refund') as mock_revoke:
            mock_revoke.return_value = True
            self.assertTrue(refund.approve())

        offer.refresh_from_db()

        assert offer.status == ConditionalOffer.OPEN
        assert offer.total_discount == 0
Ejemplo n.º 3
0
    def test_sorting(self):
        """ The view should allow sorting by ID. """
        refunds = [RefundFactory(), RefundFactory(), RefundFactory()]
        self.client.login(username=self.user.username, password=self.password)

        response = self.client.get('{path}?sort=id&dir=asc'.format(path=self.path))
        self.assert_successful_response(response, refunds)

        response = self.client.get('{path}?sort=id&dir=desc'.format(path=self.path))
        self.assert_successful_response(response, list(reversed(refunds)))
Ejemplo n.º 4
0
    def test_suppress_revocation_for_zero_dollar_refund(self):
        """
        Verify that the function does not require use of fulfillment modules to mark lines in a refund
        corresponding to a total credit of $0 as complete.
        """
        refund = RefundFactory(status=REFUND.PAYMENT_REFUNDED)
        refund.total_credit_excl_tax = 0
        refund.save()

        self.assertTrue(revoke_fulfillment_for_refund(refund))
        self.assertEqual(refund.status, REFUND.PAYMENT_REFUNDED)
        self.assertEqual(set([line.status for line in refund.lines.all()]), {REFUND_LINE.COMPLETE})
Ejemplo n.º 5
0
    def test_suppress_revocation_for_zero_dollar_refund(self):
        """
        Verify that the function does not require use of fulfillment modules to mark lines in a refund
        corresponding to a total credit of $0 as complete.
        """
        refund = RefundFactory(status=REFUND.PAYMENT_REFUNDED)
        refund.total_credit_excl_tax = 0
        refund.save()

        self.assertTrue(revoke_fulfillment_for_refund(refund))
        self.assertEqual(refund.status, REFUND.PAYMENT_REFUNDED)
        self.assertEqual(set([line.status for line in refund.lines.all()]),
                         {REFUND_LINE.COMPLETE})
Ejemplo n.º 6
0
    def test_num_items(self):
        """ The method should return the total number of items being refunded. """
        refund = RefundFactory()
        self.assertEqual(refund.num_items, 1)

        RefundLineFactory(quantity=3, refund=refund)
        self.assertEqual(refund.num_items, 4)
Ejemplo n.º 7
0
 def test_max_user_discount_clean_with_refunded_enrollments(self):
     """
     Verify that `clean` for `max_user_discount` and `max_user_applications` does not raise error when total consumed
      discount and total max user applications after refund is still less than the value, and the existing offer
      updates with new per user limit and max user application values.
     """
     current_refund_count = 0
     data = self.generate_data(max_user_applications=3,
                               max_user_discount=300)
     self.mock_specific_enterprise_customer_api(
         data['enterprise_customer_uuid'])
     # create an enterprise offer that can provide max $500 discount and consume $400
     offer = factories.EnterpriseOfferFactory(max_user_applications=5,
                                              max_user_discount=500)
     for _ in range(4):
         order = OrderFactory(user=self.user, status=ORDER.COMPLETE)
         OrderDiscountFactory(order=order, offer_id=offer.id, amount=100)
         # create a refund of $200 so the total consumed discount becomes $200
         if current_refund_count < 2:
             RefundFactory(order=order,
                           user=self.user,
                           status=REFUND.COMPLETE)
             current_refund_count += 1
     # now try to update the offer with max_user_discount set to $300
     # which is still greater than the consumed discount after refund $200
     form = EnterpriseOfferForm(request=self.request,
                                data=data,
                                instance=offer)
     self.assertTrue(form.is_valid())
     offer = form.save()
     self.assertEqual(offer.max_user_applications,
                      data['max_user_applications'])
     self.assertEqual(offer.max_user_discount, data['max_user_discount'])
Ejemplo n.º 8
0
    def test_filtering(self):
        """ The view should allow filtering by ID, status, and username. """
        refund = RefundFactory()
        open_refund = RefundFactory(status=REFUND.OPEN)
        complete_refund = RefundFactory(status=REFUND.COMPLETE)
        denied_refund = RefundFactory(status=REFUND.DENIED)

        self.client.login(username=self.user.username, password=self.password)

        # Sanity check for an unfiltered query. Completed and denied refunds should be excluded.
        response = self.client.get(self.path)
        self.assert_successful_response(response, [refund, open_refund])

        # ID filtering
        response = self.client.get('{path}?id={id}'.format(path=self.path,
                                                           id=open_refund.id))
        self.assert_successful_response(response, [open_refund])

        # Single-choice status filtering
        response = self.client.get('{path}?status={status}'.format(
            path=self.path, status=REFUND.COMPLETE))
        self.assert_successful_response(response, [complete_refund])

        # Multiple-choice status filtering
        response = self.client.get(
            '{path}?status={complete_status}&status={denied_status}'.format(
                path=self.path,
                complete_status=REFUND.COMPLETE,
                denied_status=REFUND.DENIED))
        self.assert_successful_response(response,
                                        [complete_refund, denied_refund])

        new_user = self.create_user(username=self.username)
        new_refund = RefundFactory(user=new_user)

        # Username filtering
        response = self.client.get('{path}?username={username}'.format(
            path=self.path, username=self.username))
        self.assert_successful_response(response, [new_refund])

        # Validate case-insensitive, starts-with username filtering
        response = self.client.get('{path}?username={username}'.format(
            path=self.path,
            # Cut the configured username in half, then invert the fragment's casing.
            username=self.username[:len(self.username) / 2].swapcase()))
        self.assert_successful_response(response, [new_refund])
Ejemplo n.º 9
0
    def create_refund(self, processor_name=DummyProcessor.NAME, **kwargs):
        refund = RefundFactory(**kwargs)
        order = refund.order
        source_type, __ = SourceType.objects.get_or_create(name=processor_name)
        Source.objects.create(source_type=source_type, order=order, currency=refund.currency,
                              amount_allocated=order.total_incl_tax, amount_debited=order.total_incl_tax)

        return refund
Ejemplo n.º 10
0
 def test_edit_view_with_disable_switch(self):
     """ Test that edit refund page still works even if the switch is disabled. """
     toggle_switch(REFUND_LIST_VIEW_SWITCH, False)
     refund = RefundFactory()
     edit_page_url = reverse('admin:refund_refund_change',
                             args=(refund.id, ))
     response = self.client.get(edit_page_url)
     self.assertEqual(response.status_code, 200)
     self.assertContains(response, refund.order.number)
Ejemplo n.º 11
0
 def test_revoke_fulfillment_for_refund_revocation_error(self):
     """
     Verify the function sets the status of RefundLines and the Refund to "Revocation Error" if revocation fails.
     """
     refund = RefundFactory(status=REFUND.PAYMENT_REFUNDED)
     self.assertFalse(revoke_fulfillment_for_refund(refund))
     self.assertEqual(refund.status, REFUND.PAYMENT_REFUNDED)
     self.assertEqual(set([line.status for line in refund.lines.all()]),
                      {REFUND_LINE.REVOCATION_ERROR})
Ejemplo n.º 12
0
 def test_revoke_fulfillment_for_refund(self):
     """
     Verify the function revokes fulfillment for all lines in a refund.
     """
     refund = RefundFactory(status=REFUND.PAYMENT_REFUNDED)
     self.assertTrue(revoke_fulfillment_for_refund(refund))
     self.assertEqual(refund.status, REFUND.PAYMENT_REFUNDED)
     self.assertEqual(set([line.status for line in refund.lines.all()]),
                      {REFUND_LINE.COMPLETE})
Ejemplo n.º 13
0
 def test_is_order_line_refunded(self, refund_line_status, is_refunded):
     """
     Tests the functionality of is_order_line_refunded method.
     """
     user = self.create_user()
     refund = RefundFactory(user=user)
     refund_line = RefundLine.objects.get(refund=refund)
     refund_line.status = refund_line_status
     refund_line.save()
     self.assertEqual(
         UserAlreadyPlacedOrder.is_order_line_refunded(
             refund_line.order_line), is_refunded)
Ejemplo n.º 14
0
 def test_already_have_refunded_order(self):
     """
     Test the case that user have a refunded order for the product.
     """
     user = self.create_user()
     refund = RefundFactory(user=user)
     refund_line = RefundLine.objects.get(refund=refund)
     refund_line.status = 'Complete'
     refund_line.save()
     product = self.get_order_product(order=refund.order)
     self.assertFalse(
         UserAlreadyPlacedOrder.user_already_placed_order(user=user,
                                                          product=product))
Ejemplo n.º 15
0
    def test_offer_availability_with_max_user_discount_including_refunds(
            self, discount_type, num_prev_orders, benefit_value, refund_count,
            is_satisfied):
        """
        Verify that enterprise offer with discount type percentage and absolute, condition returns correct result
        based on user limits in the offer, even when user has refund history.
        """
        current_refund_count = 0
        benefits = {
            Benefit.PERCENTAGE:
            factories.EnterprisePercentageDiscountBenefitFactory(
                value=benefit_value),
            Benefit.FIXED:
            factories.EnterpriseAbsoluteDiscountBenefitFactory(
                value=benefit_value),
        }
        offer = factories.EnterpriseOfferFactory(
            partner=self.partner,
            benefit=benefits[discount_type],
            max_user_discount=150)
        for _ in range(num_prev_orders):
            order = OrderFactory(user=self.user, status=ORDER.COMPLETE)
            OrderDiscountFactory(order=order, offer_id=offer.id, amount=10)
            if current_refund_count < refund_count:
                RefundFactory(order=order,
                              user=self.user,
                              status=REFUND.COMPLETE)
                current_refund_count += 1

        basket = BasketFactory(site=self.site, owner=self.user)
        basket.add_product(self.course_run.seat_products[0])
        basket.add_product(self.entitlement)
        self.mock_course_detail_endpoint(
            discovery_api_url=self.site_configuration.discovery_api_url,
            course=self.entitlement)
        self.mock_catalog_contains_course_runs(
            [self.course_run.id],
            self.condition.enterprise_customer_uuid,
            enterprise_customer_catalog_uuid=self.condition.
            enterprise_customer_catalog_uuid,
        )
        self.assertEqual(self.condition.is_satisfied(offer, basket),
                         is_satisfied)
Ejemplo n.º 16
0
 def _get_instance(self, **kwargs):
     return RefundFactory(**kwargs)
Ejemplo n.º 17
0
    def setUp(self):
        super(RefundDetailViewTests, self).setUp()
        self.user = self.create_user(is_superuser=True, is_staff=True)

        refund = RefundFactory()
        self.path = reverse('dashboard:refunds:detail', args=[refund.id])
Ejemplo n.º 18
0
    def setUp(self):
        super(RefundProcessViewTests, self).setUp()

        self.user = self.create_user(is_staff=True)
        self.client.login(username=self.user.username, password=self.password)
        self.refund = RefundFactory(user=self.user)
Ejemplo n.º 19
0
class RefundProcessViewTests(ThrottlingMixin, TestCase):
    def setUp(self):
        super(RefundProcessViewTests, self).setUp()

        self.user = self.create_user(is_staff=True)
        self.client.login(username=self.user.username, password=self.password)
        self.refund = RefundFactory(user=self.user)

    def put(self, action):
        data = '{{"action": "{}"}}'.format(action)
        path = reverse('api:v2:refunds:process', kwargs={'pk': self.refund.id})
        return self.client.put(path, data, JSON_CONTENT_TYPE)

    def test_staff_only(self):
        """ The view should only be accessible to staff users. """
        user = self.create_user(is_staff=False)
        self.client.login(username=user.username, password=self.password)
        response = self.put('approve')
        self.assertEqual(response.status_code, 403)

    def test_invalid_action(self):
        """ If the action is neither approve nor deny, the view should return HTTP 400. """
        response = self.put('reject')
        self.assertEqual(response.status_code, 400)

    @ddt.data('approve', 'deny')
    def test_success(self, action):
        """ If the action succeeds, the view should return HTTP 200 and the serialized Refund. """
        with mock.patch('ecommerce.extensions.refund.models.Refund.{}'.format(action), mock.Mock(return_value=True)):
            response = self.put(action)
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.data, RefundSerializer(self.refund).data)

    @mock.patch('ecommerce.extensions.refund.models.Refund._revoke_lines')
    @mock.patch('ecommerce.extensions.refund.models.Refund._issue_credit')
    def test_success_approve_payment_only(self, mock_issue_credit, mock_revoke_lines):
        """ Verify the endpoint supports approving the refund, and issuing credit without revoking fulfillment. """
        mock_issue_credit.return_value = None

        with mock.patch('ecommerce.extensions.refund.models.logger') as patched_log:
            response = self.put('approve_payment_only')
            self.assertEqual(response.status_code, 200)

        self.refund.refresh_from_db()
        self.assertEqual(response.data['status'], self.refund.status)
        self.assertEqual(response.data['status'], 'Complete')
        patched_log.info.assert_called_with('Skipping the revocation step for refund [%d].', self.refund.id)
        self.assertFalse(mock_revoke_lines.called)

    @ddt.data(
        ('approve', 'approve'),
        ('approve', 'approve_payment_only'),
        ('deny', 'deny')
    )
    @ddt.unpack
    def test_failure(self, action, decision):
        """ If the action fails, the view should return HTTP 500 and the serialized Refund. """
        with mock.patch('ecommerce.extensions.refund.models.Refund.{}'.format(action), mock.Mock(return_value=False)):
            response = self.put(decision)
            self.assertEqual(response.status_code, 500)
            self.assertEqual(response.data, RefundSerializer(self.refund).data)

    @ddt.data(
        ('approve', REFUND.COMPLETE),
        ('approve_payment_only', REFUND.COMPLETE),
        ('deny', REFUND.DENIED)
    )
    @ddt.unpack
    def test_subsequent_approval(self, action, _status):
        """ Verify the endpoint supports reprocessing a previously-processed refund. """
        self.refund.status = _status
        self.refund.save()
        response = self.put(action)
        self.assertEqual(response.status_code, 200)