def test_notify_purchaser(self, mock_task): """ Verify the notification is scheduled if the site has notifications enabled and the refund is for a course seat. """ site_configuration = self.site.siteconfiguration site_configuration.send_refund_notifications = True user = UserFactory() course = CourseFactory() price = Decimal(100.00) product = course.create_or_update_seat('verified', True, price, self.partner) basket = create_basket(empty=True) basket.site = self.site basket.add_product(product) order = create_order(basket=basket, user=user) order_url = get_receipt_page_url(site_configuration, order.number) refund = Refund.create_with_lines(order, order.lines.all()) with LogCapture(REFUND_MODEL_LOGGER_NAME) as l: refund._notify_purchaser() # pylint: disable=protected-access msg = 'Course refund notification scheduled for Refund [{}].'.format(refund.id) l.check( (REFUND_MODEL_LOGGER_NAME, 'INFO', msg) ) amount = format_currency(order.currency, price) mock_task.assert_called_once_with( user.email, refund.id, amount, course.name, order.number, order_url, site_code=self.partner.short_code )
def test_proper_code(self): """ Verify that proper information is returned when a valid code is provided. """ course = CourseFactory() seat = course.create_or_update_seat('verified', True, 50, self.partner) sr = StockRecord.objects.get(product=seat) catalog = Catalog.objects.create(name='Test catalog', partner=self.partner) catalog.stock_records.add(sr) range_ = RangeFactory(catalog=catalog) self.prepare_voucher(range_=range_) course_info = { "media": { "course_image": { "uri": "/asset-v1:edX+DemoX+Demo_Course+type@asset+block@images_course_image.jpg" } }, "name": "edX Demonstration Course", } course_info_json = json.dumps(course_info) course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id)) httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type='application/json') url = self.offer_url + '?code={}'.format('COUPONTEST') response = self.client.get(url) self.assertEqual(response.context['course']['name'], _('edX Demonstration Course')) self.assertEqual(response.context['code'], _('COUPONTEST'))
def setUp(self): super(CouponViewSetTest, self).setUp() self.user = self.create_user(is_staff=True) self.client.login(username=self.user.username, password=self.password) course = CourseFactory(id='edx/Demo_Course/DemoX') self.seat = course.create_or_update_seat('verified', True, 50, self.partner) self.catalog = Catalog.objects.create(partner=self.partner) self.coupon_data = { 'title': 'Tešt Čoupon', 'partner': self.partner, 'benefit_type': Benefit.PERCENTAGE, 'benefit_value': 100, 'catalog': self.catalog, 'end_datetime': str(now() + datetime.timedelta(days=10)), 'enterprise_customer': {'id': str(uuid4()).decode('utf-8')}, 'code': '', 'quantity': 2, 'start_datetime': str(now() - datetime.timedelta(days=1)), 'voucher_type': Voucher.ONCE_PER_CUSTOMER, 'category': {'name': self.category.name}, 'note': None, 'max_uses': None, 'catalog_query': None, 'course_seat_types': None, 'email_domains': None, }
def setUp(self): super(CouponViewSetTest, self).setUp() self.user = self.create_user(is_staff=True) self.client.login(username=self.user.username, password=self.password) course = CourseFactory(id='edx/Demo_Course/DemoX') course.create_or_update_seat('verified', True, 50, self.partner) self.catalog = Catalog.objects.create(partner=self.partner) self.product_class, __ = ProductClass.objects.get_or_create(name='Coupon') self.coupon_data = { 'title': 'Test Coupon', 'partner': self.partner, 'benefit_type': Benefit.PERCENTAGE, 'benefit_value': 100, 'catalog': self.catalog, 'end_date': '2020-1-1', 'code': '', 'quantity': 2, 'start_date': '2015-1-1', 'voucher_type': Voucher.ONCE_PER_CUSTOMER, 'categories': [self.category], 'note': None, 'max_uses': None, }
class VoucherRemoveMessagesViewTests(CouponMixin, CourseCatalogTestMixin, TestCase): """ VoucherRemoveMessagesView view tests. """ def setUp(self): super(VoucherRemoveMessagesViewTests, self).setUp() self.user = self.create_user() self.client.login(username=self.user.username, password=self.password) self.course = CourseFactory() self.course.create_or_update_seat('verified', True, 50, self.partner) self.product = self.course.create_or_update_seat('verified', False, 0, self.partner) self.voucher, __ = prepare_voucher(code=COUPON_CODE) self.request = RequestFactory().request() # Fallback storage is needed in tests with messages setattr(self.request, 'session', 'session') messages = FallbackStorage(self.request) setattr(self.request, '_messages', messages) self.request.user = self.user basket = factories.BasketFactory(owner=self.user, site=self.site) basket.add_product(self.product, 1) self.request.basket = basket self.voucher_remove_view = VoucherRemoveMessagesView() def test_remove_voucher_pk_conversion(self): """ Verify that voucher primary key is converted to integer """ self.voucher_remove_view.post(self.request, pk=self.voucher.id) request_message = list(get_messages(self.request))[-1].message self.assertEqual( str(request_message), "No voucher found with id '{0}'".format(self.voucher.id) )
def create_course_and_seat( self, course_id=None, seat_type='verified', id_verification=False, price=10, partner=None ): """ Create a course and a seat from that course. Arguments: course_name (str): name of the course seat_type (str): the seat type id_verification (bool): if id verification is required price (int): seat price partner(Partner): the site partner Returns: The created course and seat. """ if not partner: partner = PartnerFactory() if not course_id: course = CourseFactory() else: course = CourseFactory(id=course_id) seat = course.create_or_update_seat(seat_type, id_verification, price, partner) return course, seat
def test_restricted_course_mode(self, mode): """Test that an exception is raised when a black-listed course mode is used.""" course = CourseFactory(id='black/list/mode') seat = course.create_or_update_seat(mode, False, 0, self.partner) # Seats derived from a migrated "audit" mode do not have a certificate_type attribute. if mode == 'audit': seat = ProductFactory() self.data.update({'stock_record_ids': [StockRecord.objects.get(product=seat).id]}) self.assert_post_response_status(self.data)
def setUp(self): super(CouponRedeemViewTests, self).setUp() self.user = self.create_user() self.client.login(username=self.user.username, password=self.password) course = CourseFactory() self.seat = course.create_or_update_seat('verified', True, 50, self.partner) self.catalog = Catalog.objects.create(partner=self.partner) self.catalog.stock_records.add(StockRecord.objects.get(product=self.seat))
def setUp(self): super(EnrollmentCodeFulfillmentModuleTests, self).setUp() toggle_switch(ENROLLMENT_CODE_SWITCH, True) course = CourseFactory() course.create_or_update_seat('verified', True, 50, self.partner) enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) user = UserFactory() basket = BasketFactory() basket.add_product(enrollment_code, self.QUANTITY) self.order = factories.create_order(number=1, basket=basket, user=user)
def prepare_course_information(self): """ Helper function to prepare an API endpoint that provides course information. """ course = CourseFactory(name='Test course') seat = course.create_or_update_seat('verified', True, 50, self.partner) stock_record = StockRecord.objects.get(product=seat) catalog = Catalog.objects.create(name='Test catalog', partner=self.partner) catalog.stock_records.add(stock_record) _range = RangeFactory(catalog=catalog) self.mock_course_api_response(course=course) return _range
def test_filter_product_class(self): """ Verify the method supports filtering by product class or the parent product's class. """ course = CourseFactory() seat = course.create_or_update_seat('verified', True, 1, self.partner) parent = course.parent_seat_product product_class_name = self.seat_product_class.name queryset = Product.objects.all() actual = list(self.filter.filter_product_class(queryset, product_class_name)) self.assertListEqual(actual, [seat, parent])
def test_restricted_course_mode(self, mode): """Test that an exception is raised when a black-listed course mode is used.""" course = CourseFactory(id='black/list/mode') seat = course.create_or_update_seat(mode, False, 0, self.partner) # Seats derived from a migrated "audit" mode do not have a certificate_type attribute. if mode == 'audit': seat = ProductFactory() self.data.update({'stock_record_ids': [StockRecord.objects.get(product=seat).id]}) response = self.client.post(COUPONS_LINK, data=self.data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def setUp(self): super(BasketSingleItemViewTests, self).setUp() self.user = self.create_user() self.client.login(username=self.user.username, password=self.password) course = CourseFactory() course.create_or_update_seat('verified', True, 50, self.partner) product = course.create_or_update_seat('verified', False, 0, self.partner) self.stock_record = StockRecordFactory(product=product, partner=self.partner) self.catalog = Catalog.objects.create(partner=self.partner) self.catalog.stock_records.add(self.stock_record)
def test_enrollment_code_seat_type(self): """Verify the correct seat type attribute is retrieved.""" course = CourseFactory() toggle_switch(ENROLLMENT_CODE_SWITCH, True) course.create_or_update_seat('verified', False, 10, self.partner) enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) self.create_basket_and_add_product(enrollment_code) self.mock_course_api_response(course) response = self.client.get(self.path) self.assertEqual(response.status_code, 200) line_data = response.context['formset_lines_data'][0][1] self.assertEqual(line_data['seat_type'], _(enrollment_code.attr.seat_type.capitalize()))
def test_get_transaction_parameters_with_quoted_product_title(self): """ Verify quotes are removed from item name """ course = CourseFactory(id='a/b/c/d', name='Course with "quotes"') product = course.create_or_update_seat(self.CERTIFICATE_TYPE, False, 20, self.partner) basket = factories.create_basket(empty=True) basket.add_product(product) basket.owner = factories.UserFactory() basket.site = self.site basket.save() response = self.processor.get_transaction_parameters(basket) self.assertEqual(response['item_0_name'], 'Seat in Course with quotes with test-certificate-type certificate')
def test_basket_switch_data(self): """Verify the correct basket switch data (single vs. multi quantity) is retrieved.""" course = CourseFactory() toggle_switch(ENROLLMENT_CODE_SWITCH, True) course.create_or_update_seat('invalid', False, 10, self.partner) seat = course.create_or_update_seat('verified', False, 10, self.partner) seat_sku = StockRecord.objects.get(product=seat).partner_sku enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) ec_sku = StockRecord.objects.get(product=enrollment_code).partner_sku __, partner_sku = get_basket_switch_data(seat) self.assertEqual(partner_sku, ec_sku) __, partner_sku = get_basket_switch_data(enrollment_code) self.assertEqual(partner_sku, seat_sku)
def test_prof_ed_stale_product_removal(self): """ Verify that stale professional education seats are deleted if they have not been purchased. """ course = CourseFactory() course.create_or_update_seat('professional', False, 0, self.partner) self.assertEqual(course.products.count(), 2) course.create_or_update_seat('professional', True, 0, self.partner) self.assertEqual(course.products.count(), 2) product_mode = course.products.first() self.assertEqual(product_mode.attr.id_verification_required, True) self.assertEqual(product_mode.attr.certificate_type, 'professional')
def test_create_seat_with_enrollment_code(self): """Verify an enrollment code product is created.""" course = CourseFactory() seat_type = 'verified' price = 5 toggle_switch(ENROLLMENT_CODE_SWITCH, True) course.create_or_update_seat(seat_type, True, price, self.partner) enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) self.assertEqual(enrollment_code.attr.course_key, course.id) self.assertEqual(enrollment_code.attr.seat_type, seat_type) stock_record = StockRecord.objects.get(product=enrollment_code) self.assertEqual(stock_record.price_excl_tax, price) self.assertEqual(stock_record.price_currency, settings.OSCAR_DEFAULT_CURRENCY) self.assertEqual(stock_record.partner, self.partner)
def test_course_publish_successfully(self): """ Verify all courses are successfully published.""" second_course = CourseFactory.create() self.create_course_ids_file(self.tmp_file_path, [self.course.id, second_course.id]) expected = ( ( LOGGER_NAME, "INFO", "Publishing 2 courses." ), ( LOGGER_NAME, "INFO", u"(1/2) Successfully published {}.".format(self.course.id) ), ( LOGGER_NAME, "INFO", u"(2/2) Successfully published {}.".format(second_course.id)), ( LOGGER_NAME, "INFO", "All 2 courses successfully published." ) ) with mock.patch.object(Course, 'publish_to_lms', autospec=True) as mock_publish: mock_publish.return_value = None with LogCapture(LOGGER_NAME) as lc: call_command('publish_to_lms', course_ids_file=self.tmp_file_path) lc.check(*expected) # Check that the mocked function was called twice. self.assertListEqual(mock_publish.call_args_list, [call(self.course), call(second_course)])
class UtilTests(CourseCatalogTestMixin, TestCase): def setUp(self): super(UtilTests, self).setUp() self.course = CourseFactory() self.verified_seat = self.course.create_or_update_seat('verified', False, 100, self.partner) self.stock_record = StockRecord.objects.filter(product=self.verified_seat).first() self.seat_price = self.stock_record.price_excl_tax self._range = RangeFactory(products=[self.verified_seat, ]) self.percentage_benefit = BenefitFactory(type=Benefit.PERCENTAGE, range=self._range, value=35.00) self.value_benefit = BenefitFactory(type=Benefit.FIXED, range=self._range, value=self.seat_price - 10) def test_format_benefit_value(self): """ format_benefit_value(benefit) should format benefit value based on benefit type """ benefit_value = format_benefit_value(self.percentage_benefit) self.assertEqual(benefit_value, '35%') benefit_value = format_benefit_value(self.value_benefit) self.assertEqual(benefit_value, currency(self.seat_price - 10)) @ddt.data( ('1.0', '1'), ('5000.0', '5000'), ('1.45000', '1.45'), ('5000.40000', '5000.4'), ) @ddt.unpack def test_remove_exponent_and_trailing_zeros(self, value, expected): """ _remove_exponent_and_trailing_zeros(decimal) should remove exponent and trailing zeros from decimal number """ decimal = _remove_exponent_and_trailing_zeros(Decimal(value)) self.assertEqual(decimal, Decimal(expected))
def test_already_verified_student(self, mode, id_verification): """ Verify the view return HTTP 400 if the student is already enrolled as verified student in the course """ course = CourseFactory() self.mock_enrollment_api_success(course.id, mode=mode) product = course.create_or_update_seat(mode, id_verification, 0, self.partner) stock_record = StockRecordFactory(product=product, partner=self.partner) catalog = Catalog.objects.create(partner=self.partner) catalog.stock_records.add(stock_record) url = '{path}?sku={sku}'.format(path=self.path, sku=stock_record.partner_sku) expected_content = 'You are already enrolled in {product}.'.format(product=product.course.name) response = self.client.get(url) self.assertEqual(response.status_code, 400) self.assertEqual(response.content, expected_content)
def test_course_information_error(self): """ Verify a response is returned when course information is not accessable. """ course = CourseFactory() seat = course.create_or_update_seat('verified', True, 50, self.partner) _range = RangeFactory(products=[seat, ]) prepare_voucher(code=COUPON_CODE, _range=_range) course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id)) httpretty.register_uri(httpretty.GET, course_url, status=404, content_type=CONTENT_TYPE) response = self.client.get(self.path_with_code) response_text = ( 'Could not get course information. ' '[Client Error 404: http://127.0.0.1:8000/api/courses/v1/courses/{}/]' ).format(course.id) self.assertEqual(response.context['error'], _(response_text))
def setUp(self): super(CheckoutPageTest, self).setUp() user = self.create_user(is_superuser=False) self.create_access_token(user) self.client.login(username=user.username, password=self.password) self.provider = 'ASU' self.price = 100 self.credit_hours = 2 self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/') self.provider_url = get_lms_url('/api/credit/v1/providers/') self.course = CourseFactory(thumbnail_url='http://www.edx.org/course.jpg') self.provider_data = [ { 'enable_integration': False, 'description': 'Arizona State University', 'url': 'https://credit.example.com/', 'status_url': 'https://credit.example.com/status', 'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg', 'fulfillment_instructions': 'Sample fulfilment requirement.', 'display_name': 'Arizona State University', 'id': 'ASU' } ] self.eligibilities = [ { 'deadline': '2016-10-28T09:56:44Z', 'course_key': 'edx/cs01/2015' } ]
def test_multiple_providers(self): """ Verify offer contains information about credit providers. """ course = CourseFactory() seat1 = course.create_or_update_seat( 'credit', False, 100, partner=self.partner, credit_provider='test_provider_1' ) seat2 = course.create_or_update_seat( 'credit', False, 100, partner=self.partner, credit_provider='test_provider_2' ) self.assertEqual(Product.objects.filter(parent=seat1.parent).count(), 2) __, request, voucher = self.prepare_get_offers_response(seats=[seat1, seat2], seat_type='credit') self.mock_eligibility_api(request, self.user, course.id) offers = VoucherViewSet().get_offers(request=request, voucher=voucher)['results'] for offer in offers: self.assertTrue(offer['multiple_credit_providers']) self.assertIsNone(offer['credit_provider_price'])
def prepare_course_seat_and_enrollment_code(self, seat_type='verified', id_verification=False): """Helper function that creates a new course, enables enrollment codes and creates a new seat and enrollment code for it. Args: seat_type (str): Seat/certification type. is_verification (bool): Whether or not id verification is required for the seat. Returns: The newly created course, seat and enrollment code. """ course = CourseFactory() toggle_switch(ENROLLMENT_CODE_SWITCH, True) self.site.siteconfiguration.enable_enrollment_codes = True self.site.siteconfiguration.save() seat = course.create_or_update_seat(seat_type, id_verification, 10, self.partner, create_enrollment_code=True) enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) return course, seat, enrollment_code
def prepare_order(self, seat_type, credit_provider_id=None): """ Prepares order for a post-checkout test. Args: seat_type (str): Course seat type credit_provider_id (str): Credit provider associated with the course seat. Returns: Order """ course = CourseFactory() seat = course.create_or_update_seat(seat_type, False, 50, self.partner, credit_provider_id, None, 2) basket = BasketFactory(site=self.site) basket.add_product(seat, 1) order = factories.create_order(basket=basket, user=self.user) return order
def test_prepare_basket_enrollment_with_voucher(self): """Verify the basket does not contain a voucher if enrollment code is added to it.""" course = CourseFactory() toggle_switch(ENROLLMENT_CODE_SWITCH, True) course.create_or_update_seat('verified', False, 10, self.partner, create_enrollment_code=True) enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) voucher, product = prepare_voucher() basket = prepare_basket(self.request, product, voucher) self.assertIsNotNone(basket) self.assertEqual(basket.all_lines()[0].product, product) self.assertTrue(basket.contains_a_voucher) basket = prepare_basket(self.request, enrollment_code, voucher) self.assertIsNotNone(basket) self.assertEqual(basket.all_lines()[0].product, enrollment_code) self.assertFalse(basket.contains_a_voucher)
def test_seat_products(self): """ Verify the method returns a list containing purchasable course seats. These seats should be the child products. """ # Create a new course and verify it has a parent product, but no children. course = CourseFactory() self.assertEqual(course.products.count(), 1) self.assertEqual(len(course.seat_products), 0) # Create the seat products seats = [course.create_or_update_seat('honor', False, 0, self.partner), course.create_or_update_seat('verified', True, 50, self.partner)] self.assertEqual(course.products.count(), 3) # The property should return only the child seats. self.assertEqual(set(course.seat_products), set(seats))
def test_create_or_update_seat_without_stale_seat_removal(self): """ Verify that professional education seats are not deleted if remove_stale_modes flag is not set. """ course = CourseFactory() course.create_or_update_seat('professional', False, 0, self.partner) self.assertEqual(course.products.count(), 2) course.create_or_update_seat('professional', True, 0, self.partner, remove_stale_modes=False) self.assertEqual(course.products.count(), 3) product_mode = course.products.all()[0] self.assertEqual(product_mode.attr.id_verification_required, True) self.assertEqual(product_mode.attr.certificate_type, 'professional') product_mode = course.products.all()[1] self.assertEqual(product_mode.attr.id_verification_required, False) self.assertEqual(product_mode.attr.certificate_type, 'professional')
def setUp(self): super(LMSPublisherTests, self).setUp() self.course = CourseFactory(verification_deadline=datetime.datetime.now() + datetime.timedelta(days=7)) self.course.create_or_update_seat('honor', False, 0, self.partner) self.course.create_or_update_seat('verified', True, 50, self.partner) self.publisher = LMSPublisher() self.error_message = u'Failed to publish commerce data for {course_id} to LMS.'.format( course_id=self.course.id )
def test_track_completed_enrollment_order(self): """ Make sure we are sending GA events for Enrollment Code orders """ with mock.patch( 'ecommerce.extensions.checkout.signals.track_segment_event' ) as mock_track: course = CourseFactory(partner=self.partner) course.create_or_update_seat('verified', True, 50, create_enrollment_code=True) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) basket = factories.BasketFactory(owner=self.user, site=self.site) basket.add_product(enrollment_code) order = factories.create_order(basket=basket, user=self.user) track_completed_order(None, order) assert mock_track.called
def test_successful_order_for_bulk_purchase(self): """ Verify the view redirects to the Receipt page when the Order has been successfully placed for bulk purchase and also that the order is linked to the provided business client. """ toggle_switch(ENROLLMENT_CODE_SWITCH, True) course = CourseFactory() course.create_or_update_seat('verified', True, 50, create_enrollment_code=True) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) self.basket = create_basket(owner=self.user, site=self.site) self.basket.add_product(enrollment_code, quantity=1) # The basket should not have an associated order if no payment was made. self.assertFalse(Order.objects.filter(basket=self.basket).exists()) request_data = self.generate_notification( self.basket, billing_address=self.billing_address, ) request_data.update({'organization': 'Dummy Business Client'}) request_data.update({PURCHASER_BEHALF_ATTRIBUTE: "False"}) # Manually add organization and purchaser attributes on the basket for testing basket_add_organization_attribute(self.basket, request_data) response = self.client.post(self.path, request_data) self.assertTrue(Order.objects.filter(basket=self.basket).exists()) self.assertEqual(response.status_code, 302) # Now verify that a new business client has been created and current # order is now linked with that client through Invoice model. order = Order.objects.filter(basket=self.basket).first() business_client = BusinessClient.objects.get( name=request_data['organization']) assert Invoice.objects.get( order=order).business_client == business_client
def setUp(self): super(UtilTests, self).setUp() self.user = self.create_user(full_name="Tešt Ušer", is_staff=True) self.client.login(username=self.user.username, password=self.password) self.course = CourseFactory(id='course-v1:test-org+course+run') self.verified_seat = self.course.create_or_update_seat( 'verified', False, 100, self.partner) self.catalog = Catalog.objects.create(partner=self.partner) self.stock_record = StockRecord.objects.filter( product=self.verified_seat).first() self.seat_price = self.stock_record.price_excl_tax self.catalog.stock_records.add(self.stock_record) self.coupon = self.create_coupon(title='Tešt product', catalog=self.catalog, note='Tešt note', quantity=1, max_uses=1, voucher_type=Voucher.MULTI_USE) self.coupon.history.all().update(history_user=self.user) self.coupon_vouchers = CouponVouchers.objects.filter( coupon=self.coupon) self.data = { 'benefit_type': Benefit.PERCENTAGE, 'benefit_value': 100.00, 'catalog': self.catalog, 'coupon': self.coupon, 'end_datetime': datetime.datetime.now() + datetime.timedelta(days=1), 'enterprise_customer': None, 'name': "Test voucher", 'quantity': 10, 'start_datetime': datetime.datetime.now() - datetime.timedelta(days=1), 'voucher_type': Voucher.SINGLE_USE }
def test_execution_for_bulk_purchase(self): """ Verify redirection to LMS receipt page after attempted payment execution if the Otto receipt page is disabled for bulk purchase and also that the order is linked to the provided business client.. """ toggle_switch(ENROLLMENT_CODE_SWITCH, True) self.mock_oauth2_response() course = CourseFactory() course.create_or_update_seat('verified', True, 50, self.partner, create_enrollment_code=True) self.basket = create_basket(owner=factories.UserFactory(), site=self.site) enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) factories.create_stockrecord(enrollment_code, num_in_stock=2, price_excl_tax='10.00') self.basket.add_product(enrollment_code, quantity=1) # Create a payment record the view can use to retrieve a basket self.mock_payment_creation_response(self.basket) self.processor.get_transaction_parameters(self.basket, request=self.request) self.mock_payment_execution_response(self.basket) self.mock_payment_creation_response(self.basket, find=True) # Manually add organization attribute on the basket for testing self.RETURN_DATA.update({'organization': 'Dummy Business Client'}) basket_add_organization_attribute(self.basket, self.RETURN_DATA) response = self.client.get(reverse('paypal:execute'), self.RETURN_DATA) self.assertRedirects( response, get_receipt_page_url( order_number=self.basket.order_number, site_configuration=self.basket.site.siteconfiguration ), fetch_redirect_response=False ) # Now verify that a new business client has been created and current # order is now linked with that client through Invoice model. order = Order.objects.filter(basket=self.basket).first() business_client = BusinessClient.objects.get(name=self.RETURN_DATA['organization']) assert Invoice.objects.get(order=order).business_client == business_client
def test_convert_course(self, initial_cert_type, direction, new_cert_type): """Verify that an honor course can be converted to audit correctly.""" course = CourseFactory() seat_to_convert = course.create_or_update_seat(initial_cert_type, False, 0, self.partner) stock_record = StockRecord.objects.get(product=seat_to_convert) order_line = OrderLineFactory(stockrecord=stock_record, product=seat_to_convert) old_stock_record_sku = stock_record.partner_sku old_order_line_sku = order_line.partner_sku # Mock the LMS call with mock.patch.object(LMSPublisher, 'publish') as mock_publish: mock_publish.return_value = True call_command('convert_course', course.id, access_token=ACCESS_TOKEN, commit=True, direction=direction, partner=self.partner.code) # Calling refresh_from_db doesn't seem to update the product's attributes seat_to_convert = Product.objects.get(pk=seat_to_convert.pk) stock_record.refresh_from_db() order_line.refresh_from_db() self.assertEqual(getattr(seat_to_convert.attr, 'certificate_type', ''), new_cert_type) if new_cert_type == '': self.assertNotIn('with honor certificate', seat_to_convert.title) else: self.assertIn(' with honor certificate', seat_to_convert.title) # Verify that partner SKUs are correctly updated self.assertNotEqual(old_stock_record_sku, stock_record.partner_sku) self.assertNotEqual(old_order_line_sku, order_line.partner_sku) self.assertEqual(order_line.partner_sku, stock_record.partner_sku) self.assertTrue(mock_publish.called)
def test_prepare_basket_enrollment_with_voucher(self): """Verify the basket does not contain a voucher if enrollment code is added to it.""" course = CourseFactory() course.create_or_update_seat('verified', False, 10, self.partner, create_enrollment_code=True) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) voucher, product = prepare_voucher() basket = prepare_basket(self.request, [product], voucher) self.assertIsNotNone(basket) self.assertEqual(basket.all_lines()[0].product, product) self.assertTrue(basket.contains_a_voucher) basket = prepare_basket(self.request, [enrollment_code], voucher) self.assertIsNotNone(basket) self.assertEqual(basket.all_lines()[0].product, enrollment_code) self.assertFalse(basket.contains_a_voucher)
def test_enrolled_verified_student(self, mode, id_verification): """ Verify the view return HTTP 400 if the student is already enrolled as verified student in the course (The Enrollment API call being used returns an active enrollment record in this case) """ course = CourseFactory() self.mock_enrollment_api_success_enrolled(course.id, mode=mode) product = course.create_or_update_seat(mode, id_verification, 0, self.partner) stock_record = StockRecordFactory(product=product, partner=self.partner) catalog = Catalog.objects.create(partner=self.partner) catalog.stock_records.add(stock_record) url = '{path}?sku={sku}'.format(path=self.path, sku=stock_record.partner_sku) expected_content = 'You are already enrolled in {product}.'.format( product=product.course.name) response = self.client.get(url) self.assertEqual(response.status_code, 400) self.assertEqual(response.content, expected_content)
def setUp(self): super(BasketSummaryViewTests, self).setUp() self.user = self.create_user() self.client.login(username=self.user.username, password=self.password) self.course = CourseFactory(name='BasketSummaryTest') site_configuration = self.site.siteconfiguration site_configuration.payment_processors = DummyProcessor.NAME site_configuration.client_side_payment_processor = DummyProcessor.NAME site_configuration.save() toggle_switch(settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + DummyProcessor.NAME, True)
def test_handle_with_existing_course(self): """ The command should create the demo course with audit and verified seats, and publish that data to the LMS. """ self.mock_access_token_response() course = CourseFactory( id='course-v1:edX+DemoX+Demo_Course', name='edX Demonstration Course', verification_deadline=datetime(year=2022, month=4, day=24, tzinfo=pytz.utc), partner=self.partner ) seat_attrs = {'certificate_type': '', 'expires': None, 'price': 0.00, 'id_verification_required': False} course.create_or_update_seat(**seat_attrs) with mock.patch.object(Course, 'publish_to_lms', return_value=None) as mock_publish: call_command('create_demo_data', '--partner={}'.format(self.partner.short_code)) mock_publish.assert_called_once_with() self.assert_seats_created('course-v1:edX+DemoX+Demo_Course', 'edX Demonstration Course', 149)
def setUp(self): super(CreateRefundForOrdersTests, self).setUp() self.url = reverse('api:v2:manual-course-enrollment-order-list') self.user = self.create_user(is_staff=True) self.client.login(username=self.user.username, password=self.password) self.course = CourseFactory(id='course-v1:MAX+CX+Course', partner=self.partner) self.course_price = 50 self.course.create_or_update_seat(certificate_type='verified', id_verification_required=True, price=self.course_price) self.course.create_or_update_seat(certificate_type='audit', id_verification_required=False, price=0) self.mock_access_token_response() self.mock_course_run_detail_endpoint( self.course, discovery_api_url=self.site.siteconfiguration.discovery_api_url, course_run_info={ 'course_uuid': '620a5ce5-6ff4-4b2b-bea1-a273c6920ae5' })
def test_omitting_expired_courses(self): """Verify professional courses who's enrollment end datetime have passed are omitted.""" no_date_seat = CourseFactory().create_or_update_seat('professional', False, 100, partner=self.partner) valid_seat = CourseFactory().create_or_update_seat('professional', False, 100, partner=self.partner) expired_seat = CourseFactory().create_or_update_seat('professional', False, 100, partner=self.partner) course_discovery_results = [{ 'key': no_date_seat.attr.course_key, 'enrollment_end': None, }, { 'key': valid_seat.attr.course_key, 'enrollment_end': str(now() + datetime.timedelta(days=1)), }, { 'key': expired_seat.attr.course_key, 'enrollment_end': str(now() - datetime.timedelta(days=1)), }] products, __ = VoucherViewSet().retrieve_course_objects(course_discovery_results, 'professional') self.assertIn(no_date_seat, products) self.assertIn(valid_seat, products) self.assertNotIn(expired_seat, products)
def setUp(self): super(EnterpriseAPITests, self).setUp() self.course_run = CourseFactory() self.learner = self.create_user(is_staff=True) self.client.login(username=self.learner.username, password=self.password) # Enable enterprise functionality toggle_switch(settings.ENABLE_ENTERPRISE_ON_RUNTIME_SWITCH, True) self.request.user = self.learner self.request.site = self.site self.request.strategy = DefaultStrategy()
def test_already_purchased_product(self): """ Verify student can not place multiple orders for single course seat """ course = CourseFactory() product = course.create_or_update_seat("Verified", True, 0, self.partner) stock_record = StockRecordFactory(product=product, partner=self.partner) catalog = Catalog.objects.create(partner=self.partner) catalog.stock_records.add(stock_record) sku = stock_record.partner_sku basket = factories.BasketFactory(owner=self.user, site=self.site) basket.add_product(product, 1) create_order(user=self.user, basket=basket) url = '{path}?sku={sku}'.format(path=self.path, sku=sku) expected_content = 'You have already purchased {course} seat.'.format( course=product.course.name) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['error'], expected_content)
def setUp(self): super(EnrollmentFulfillmentModuleTests, self).setUp() self.user = factories.UserFactory() self.user.tracking_context = { 'ga_client_id': 'test-client-id', 'lms_user_id': 'test-user-id', 'lms_ip': '127.0.0.1' } self.user.save() self.course = CourseFactory(id=self.course_id, name='Demo Course', partner=self.partner) self.seat = self.course.create_or_update_seat(self.certificate_type, False, 100, self.provider) basket = factories.BasketFactory(owner=self.user, site=self.site) basket.add_product(self.seat, 1) self.order = create_order(number=1, basket=basket, user=self.user)
def setUp(self): super(EntitlementsTests, self).setUp() self.learner = self.create_user(is_staff=True) self.client.login(username=self.learner.username, password=self.password) # Enable enterprise functionality toggle_switch(settings.ENABLE_ENTERPRISE_ON_RUNTIME_SWITCH, True) self.course = CourseFactory(id='edx/Demo_Course/DemoX') course_seat = self.course.create_or_update_seat('verified', False, 100, partner=self.partner) stock_record = StockRecord.objects.get(product=course_seat) self.catalog = Catalog.objects.create(partner=self.partner) self.catalog.stock_records.add(stock_record) self.request.user = self.learner self.request.site = self.site self.request.strategy = DefaultStrategy()
def setUp(self): super(CouponReportCSVViewTest, self).setUp() self.user = self.create_user(full_name="Test User", is_staff=True) self.client.login(username=self.user.username, password=self.password) self.course = CourseFactory() self.verified_seat = self.course.create_or_update_seat('verified', False, 0, self.partner) self.stock_record = StockRecord.objects.filter(product=self.verified_seat).first() partner1 = PartnerFactory(name='Tester1') catalog1 = Catalog.objects.create(name="Test catalog 1", partner=partner1) catalog1.stock_records.add(self.stock_record) self.coupon1 = self.create_coupon(partner=partner1, catalog=catalog1) self.coupon1.history.all().update(history_user=self.user) partner2 = PartnerFactory(name='Tester2') catalog2 = Catalog.objects.create(name="Test catalog 2", partner=partner2) catalog2.stock_records.add(self.stock_record) self.coupon2 = self.create_coupon(partner=partner2, catalog=catalog2) self.coupon2.history.all().update(history_user=self.user)
def test_flush_with_product(self): """ Verify the method fires 'Product Removed' Segment event with the correct information when basket is not empty """ basket = create_basket(empty=True, site=self.site) course = CourseFactory() seat = course.create_or_update_seat('verified', True, 100, self.partner) basket.add_product(seat) properties = translate_basket_line_for_segment(basket.lines.first()) user_tracking_id, ga_client_id, lms_ip = parse_tracking_context(basket.owner) context = { 'ip': lms_ip, 'Google Analytics': { 'clientId': ga_client_id } } with mock.patch.object(Client, 'track') as mock_track: basket.flush() mock_track.assert_called_once_with(user_tracking_id, 'Product Removed', properties, context=context)
def prepare_course_seat_and_enrollment_code(self, seat_type='verified', id_verification=False): """Helper function that creates a new course, enables enrollment codes and creates a new seat and enrollment code for it. Args: seat_type (str): Seat/certification type. is_verification (bool): Whether or not id verification is required for the seat. Returns: The newly created course, seat and enrollment code. """ course = CourseFactory() seat = course.create_or_update_seat(seat_type, id_verification, 10, self.partner, create_enrollment_code=True) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) return course, seat, enrollment_code
def test_without_seats(self, direction): """Verify that the command fails when the course does not have the correct seat type.""" course = CourseFactory() call_command('convert_course', course.id, access_token=ACCESS_TOKEN, commit=True, direction=direction, partner=self.partner) self.assertEqual(len(course.seat_products), 0)
def test_save_creates_parent_seat(self): """ Verify the save method creates a parent seat if one does not exist. """ course = CourseFactory(id='a/b/c', name='Test Course', partner=self.partner) self.assertEqual(course.products.count(), 1) parent = course.parent_seat_product self.assertEqual(parent.structure, Product.PARENT) self.assertEqual(parent.title, 'Seat in Test Course') self.assertEqual(parent.get_product_class(), self.seat_product_class) self.assertEqual(parent.attr.course_key, course.id)
def mock_program_detail_endpoint(self, program_uuid, discovery_api_url, empty=False, title='Test Program'): """ Mocks the program detail endpoint on the Catalog API. Args: program_uuid (uuid): UUID of the mocked program. Returns: dict: Mocked program data. """ data = None if not empty: courses = [] for i in range(1, 5): key = 'course-v1:test-org+course+' + str(i) course_runs = [] for __ in range(1, 4): course_run = CourseFactory() course_run.create_or_update_seat('audit', False, Decimal(0), self.partner) course_run.create_or_update_seat('verified', True, Decimal(100), self.partner) course_runs.append({ 'key': course_run.id, 'seats': [{ 'type': mode_for_product(seat), 'sku': seat.stockrecords.get( partner=self.partner).partner_sku, } for seat in course_run.seat_products] }) courses.append({ 'key': key, 'course_runs': course_runs, }) program_uuid = str(program_uuid) data = { 'uuid': program_uuid, 'title': title, 'type': 'MicroMockers', 'courses': courses, 'applicable_seat_types': ['verified', 'professional', 'credit'], } self.mock_access_token_response() httpretty.register_uri(method=httpretty.GET, uri='{base}/programs/{uuid}/'.format( base=discovery_api_url.strip('/'), uuid=program_uuid), body=json.dumps(data), content_type='application/json') return data
def test_no_switch_link(self): """Verify response does not contain variables for the switch link if seat does not have an EC.""" toggle_switch(ENROLLMENT_CODE_SWITCH, True) ec_course = CourseFactory() no_ec_course = CourseFactory() seat_without_ec = no_ec_course.create_or_update_seat( 'verified', False, 10, self.partner) seat_with_ec = ec_course.create_or_update_seat( 'verified', False, 10, self.partner, create_enrollment_code=True) self.create_basket_and_add_product(seat_without_ec) self.mock_dynamic_catalog_course_runs_api(course_run=no_ec_course) response = self.client.get(self.path) self.assertFalse(response.context['switch_link_text']) self.assertFalse(response.context['partner_sku']) # Enable enrollment codes self.site.siteconfiguration.enable_enrollment_codes = True self.site.siteconfiguration.save() Basket.objects.all().delete() self.create_basket_and_add_product(seat_with_ec) self.mock_dynamic_catalog_course_runs_api(course_run=ec_course) response = self.client.get(self.path) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) enrollment_code_stockrecord = StockRecord.objects.get( product=enrollment_code) self.assertTrue(response.context['switch_link_text']) self.assertEqual(response.context['partner_sku'], enrollment_code_stockrecord.partner_sku)
def test_generate_coupon_report_for_query_coupon_with_multi_line_order( self): """ Test that coupon report for a query coupon that was used on multi-line order contains ids from all courses in that order. """ course1 = CourseFactory() course2 = CourseFactory() order = OrderFactory(number='TESTORDER') order.lines.add( OrderLineFactory(product=course1.create_or_update_seat( 'verified', False, 101), partner_sku=self.partner_sku)) order.lines.add( OrderLineFactory(product=course2.create_or_update_seat( 'verified', False, 110), partner_sku=self.partner_sku)) query_coupon = self.create_catalog_coupon(catalog_query='*:*') voucher = query_coupon.attr.coupon_vouchers.vouchers.first() voucher.record_usage(order, self.user) field_names, rows = generate_coupon_report( [query_coupon.attr.coupon_vouchers]) expected_redemed_course_ids = '{}, {}'.format(course1.id, course2.id) self.assertEqual(rows[-1]['Redeemed For Course IDs'], expected_redemed_course_ids) self.assertEqual(rows[-1].get('Redeemed For Course ID'), None) self.assertIn('Redeemed For Course ID', field_names) self.assertIn('Redeemed For Course IDs', field_names)
def test_create_seat_with_enrollment_code(self): """Verify an enrollment code product is created.""" course = CourseFactory() seat_type = 'verified' price = 5 toggle_switch(ENROLLMENT_CODE_SWITCH, True) course.create_or_update_seat(seat_type, True, price, self.partner, create_enrollment_code=True) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) self.assertEqual(enrollment_code.attr.course_key, course.id) self.assertEqual(enrollment_code.attr.seat_type, seat_type) # Second time should skip over the enrollment code creation logic but result in the same data course.create_or_update_seat(seat_type, True, price, self.partner) enrollment_code = Product.objects.get( product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) self.assertEqual(enrollment_code.attr.course_key, course.id) self.assertEqual(enrollment_code.attr.seat_type, seat_type) stock_record = StockRecord.objects.get(product=enrollment_code) self.assertEqual(stock_record.price_excl_tax, price) self.assertEqual(stock_record.price_currency, settings.OSCAR_DEFAULT_CURRENCY) self.assertEqual(stock_record.partner, self.partner)
def test_create_or_update_seat(self): """ Verify the method creates or updates a seat Product. """ course = CourseFactory(id='a/b/c', name='Test Course', partner=self.partner) # Test seat creation certificate_type = 'verified' id_verification_required = True price = 5 course.create_or_update_seat(certificate_type, id_verification_required, price) # Two seats: one verified, the other the parent seat product self.assertEqual(course.products.count(), 2) seat = course.seat_products[0] self.assert_course_seat_valid(seat, course, certificate_type, id_verification_required, price) # Test seat update price = 10 course.create_or_update_seat(certificate_type, id_verification_required, price, sku=seat.stockrecords.first().partner_sku) # Again, only two seats with one being the parent seat product. self.assertEqual(course.products.count(), 2) seat = course.seat_products[0] self.assert_course_seat_valid(seat, course, certificate_type, id_verification_required, price)
class ConvertHonorToAuditTests(CourseCatalogTestMixin, TestCase): def setUp(self): super(ConvertHonorToAuditTests, self).setUp() self.course = CourseFactory() self.honor_seat = self.course.create_or_update_seat( 'honor', False, 0, self.partner) def test_honor_course(self): """ The command should delete the honor seat, and create a new audit seat. """ # Mock the LMS call with mock.patch.object(LMSPublisher, 'publish') as mock_publish: mock_publish.return_value = True call_command('convert_honor_to_audit', self.course.id, access_token=ACCESS_TOKEN, commit=True) # Verify honor seat deleted self.assertFalse( Product.objects.filter(id=self.honor_seat.id).exists()) # Verify audit seat created audit_seats = [ seat for seat in self.course.seat_products if getattr(seat.attr, 'certificate_type', '') == '' ] self.assertEqual(len(audit_seats), 1) # Verify data published to LMS self.assertTrue(mock_publish.called) def test_honor_course_without_commit(self): """ The command should raise an error and change no data if the commit flag is not set. """ try: call_command('convert_honor_to_audit', self.course.id, access_token=ACCESS_TOKEN, commit=False) self.fail( 'An exception should be raised if the commit flag is not set.') except Exception: # pylint: disable=broad-except pass # Verify honor seat still exists self.assertTrue(Product.objects.filter(id=self.honor_seat.id).exists()) # Verify audit seat not in database audit_seats = [ seat for seat in self.course.seat_products if getattr(seat.attr, 'certificate_type', '') == '' ] self.assertEqual(len(audit_seats), 0)
def test_prof_ed_stale_product_removal_with_orders(self): """ Verify that professional education seats are never deleted if they have been purchased. """ user = self.create_user() course = CourseFactory() professional_product_no_verification = course.create_or_update_seat('professional', False, 0, self.partner) self.assertEqual(course.products.count(), 2) basket = BasketFactory(owner=user) basket.add_product(professional_product_no_verification) create_order(basket=basket, user=user) course.create_or_update_seat('professional', True, 0, self.partner) self.assertEqual(course.products.count(), 3) product_mode = course.products.all()[0] self.assertEqual(product_mode.attr.id_verification_required, True) self.assertEqual(product_mode.attr.certificate_type, 'professional') product_mode = course.products.all()[1] self.assertEqual(product_mode.attr.id_verification_required, False) self.assertEqual(product_mode.attr.certificate_type, 'professional')
def test_more_than_one_product(self): """ Test that we do not send email if basket contains more than one product """ coupon = self.create_coupon() course = CourseFactory(partner=self.partner) seat = course.create_or_update_seat('verified', False, 50, None, None, 2) basket = factories.BasketFactory(owner=self.user, site=self.site) basket.add_product(seat) basket.add_product(coupon) order = create_order(basket=basket, user=self.user) with LogCapture(LOGGER_NAME) as logger: send_course_purchase_email(None, user=self.user, order=order) logger.check( ( LOGGER_NAME, 'INFO', 'Currently support receipt emails for order with one item.' ) )
def test_translate_basket_line_for_segment(self): """ The method should return a dict formatted for Segment. """ basket = create_basket(empty=True) basket.site = self.site basket.owner = factories.UserFactory() basket.save() course = CourseFactory() seat = course.create_or_update_seat('verified', True, 100, self.partner) basket.add_product(seat) line = basket.lines.first() expected = { 'product_id': seat.stockrecords.first().partner_sku, 'sku': 'verified', 'name': course.id, 'price': '100.00', 'quantity': 1, 'category': 'Seat', } self.assertEqual(translate_basket_line_for_segment(line), expected) # Products not associated with a Course should still be reported with the product's title instead of # the course ID. seat.course = None seat.save() # Refresh the basket basket.flush() basket.add_product(seat) line = basket.lines.first() expected['name'] = seat.title self.assertEqual(translate_basket_line_for_segment(line), expected) seat.course = None seat.save() course.delete() expected['name'] = seat.title self.assertEqual(translate_basket_line_for_segment(line), expected)
def test_course_information_error(self): """ Verify a response is returned when course information is not accessable. """ course = CourseFactory() seat = course.create_or_update_seat('verified', True, 50, self.partner) _range = RangeFactory(products=[ seat, ]) prepare_voucher(code=COUPON_CODE, _range=_range) course_url = get_lms_url('api/courses/v1/courses/{}/'.format( course.id)) httpretty.register_uri(httpretty.GET, course_url, status=404, content_type=CONTENT_TYPE) response = self.client.get(self.path_with_code) response_text = ( 'Could not get course information. ' '[Client Error 404: http://lms.testserver.fake/api/courses/v1/courses/{}/]' ).format(course.id) self.assertEqual(response.context['error'], _(response_text))