def create_course_and_seats(self): # Delete existing Courses and products so we can retry creation. Course.objects.all().delete() # Create a Course. course = CourseFactory( id=self.course_id, name=self.course_name, verification_deadline=EXPIRES, partner=self.partner ) # Create associated products. for product in self.data['products']: attrs = {'certificate_type': ''} attrs.update({attr['name']: attr['value'] for attr in product['attribute_values']}) if product['product_class'] == COURSE_ENTITLEMENT_PRODUCT_CLASS_NAME: create_or_update_course_entitlement( attrs['certificate_type'], Decimal(product['price']), self.partner, self.course_uuid, course.name, ) else: attrs['expires'] = EXPIRES if product['expires'] else None attrs['price'] = Decimal(product['price']) course.create_or_update_seat(**attrs)
def test_course_entitlement_update(self): """ Test course entitlement product update """ partner = self.site.siteconfiguration.partner product = create_or_update_course_entitlement('verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') stock_record = StockRecord.objects.get(product=product, partner=self.partner) self.assertEqual(stock_record.price_excl_tax, 100) self.assertEqual(product.title, 'Course Entitlement for Foo Bar Entitlement') product = create_or_update_course_entitlement('verified', 200, partner, 'foo-bar', 'Foo Bar Entitlement') stock_record = StockRecord.objects.get(product=product, partner=self.partner) self.assertEqual(stock_record.price_excl_tax, 200)
def save(partner, course, uuid, product): attrs = _flatten(product['attribute_values']) if not uuid: raise Exception( _(u"You need to provide a course UUID to create Course Entitlements." )) # Extract arguments required for Seat creation, deserializing as necessary. certificate_type = attrs.get('certificate_type') price = Decimal(product['price']) create_or_update_course_entitlement(certificate_type, price, partner, uuid, course.name)
def test_notify_purchaser_course_entielement(self, mock_task): """ Verify the notification is scheduled if the site has notifications enabled and the refund is for a course entitlement. """ site_configuration = self.site.siteconfiguration site_configuration.send_refund_notifications = True user = UserFactory() course_entitlement = create_or_update_course_entitlement( 'verified', 100, self.partner, '111-222-333-444', 'Course Entitlement') basket = create_basket(site=self.site, owner=user, empty=True) basket.add_product(course_entitlement, 1) order = create_order(number=1, 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, 100) mock_task.assert_called_once_with(user.email, refund.id, amount, course_entitlement.title, order.number, order_url, site_code=self.partner.short_code)
def test_get_course_info_from_catalog_cached(self): """ Verify that get_course_info_from_catalog is cached We expect 2 calls to set_all_tiers in the get_course_info_from_catalog method due to: - the site_configuration api setup - the result being cached """ self.mock_access_token_response() product = create_or_update_course_entitlement('verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') self.mock_course_detail_endpoint( product, discovery_api_url=self.site_configuration.discovery_api_url) with patch.object( TieredCache, 'set_all_tiers', wraps=TieredCache.set_all_tiers) as mocked_set_all_tiers: mocked_set_all_tiers.assert_not_called() _ = get_course_info_from_catalog(self.request.site, product) self.assertEqual(mocked_set_all_tiers.call_count, 2) _ = get_course_info_from_catalog(self.request.site, product) self.assertEqual(mocked_set_all_tiers.call_count, 2)
def test_get_course_run_info_from_catalog(self, course_run): """ Check to see if course info gets cached """ self.mock_access_token_response() if course_run: course = CourseFactory(partner=self.partner) product = course.create_or_update_seat('verified', None, 100) key = CourseKey.from_string(product.attr.course_key) self.mock_course_run_detail_endpoint( course, discovery_api_url=self.site_configuration.discovery_api_url) else: product = create_or_update_course_entitlement( 'verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') key = product.attr.UUID self.mock_course_detail_endpoint( product, discovery_api_url=self.site_configuration.discovery_api_url) cache_key = u'courses_api_detail_{}{}'.format(key, self.partner.short_code) cache_key = hashlib.md5(cache_key.encode('utf-8')).hexdigest() course_cached_response = TieredCache.get_cached_response(cache_key) self.assertFalse(course_cached_response.is_found) response = get_course_info_from_catalog(self.request.site, product) if course_run: self.assertEqual(response['title'], course.name) else: self.assertEqual(response['title'], product.title) course_cached_response = TieredCache.get_cached_response(cache_key) self.assertEqual(course_cached_response.value, response)
def test_get_course_run_info_from_catalog(self, course_run): """ Check to see if course info gets cached """ self.mock_access_token_response() if course_run: resource = "course_runs" course = CourseFactory(partner=self.partner) product = course.create_or_update_seat('verified', None, 100) key = CourseKey.from_string(product.attr.course_key) self.mock_course_run_detail_endpoint( course, discovery_api_url=self.site_configuration.discovery_api_url) else: resource = "courses" product = create_or_update_course_entitlement( 'verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') key = product.attr.UUID self.mock_course_detail_endpoint( discovery_api_url=self.site_configuration.discovery_api_url, course=product) cache_key = get_cache_key(site_domain=self.site.domain, resource="{}-{}".format(resource, key)) course_cached_response = TieredCache.get_cached_response(cache_key) self.assertFalse(course_cached_response.is_found) response = get_course_info_from_catalog(self.request.site, product) if course_run: self.assertEqual(response['title'], course.name) else: self.assertEqual(response['title'], product.title) course_cached_response = TieredCache.get_cached_response(cache_key) self.assertEqual(course_cached_response.value, response)
def create_order(self, user=None, credit=False, multiple_lines=False, free=False, entitlement=False, status=ORDER.COMPLETE, id_verification_required=False): user = user or self.user basket = BasketFactory(owner=user, site=self.site) if credit: basket.add_product(self.credit_product) elif multiple_lines: basket.add_product(self.verified_product) basket.add_product(self.honor_product) elif free: basket.add_product(self.honor_product) elif entitlement: course_entitlement = create_or_update_course_entitlement( certificate_type='verified', price=100, partner=self.partner, UUID='111', title='Foo', id_verification_required=id_verification_required ) basket.add_product(course_entitlement) else: basket.add_product(self.verified_product) order = create_order(basket=basket, user=user) order.status = status if entitlement: entitlement_option = Option.objects.get(code='course_entitlement') line = order.lines.first() line.attributes.create(option=entitlement_option, value='111') order.save() return order
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', partner=self.partner) self.verified_seat = self.course.create_or_update_seat( 'verified', False, 100) 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.entitlement = create_or_update_course_entitlement( 'verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') self.entitlement_stock_record = StockRecord.objects.filter( product=self.entitlement).first() self.entitlement_catalog = Catalog.objects.create(partner=self.partner) self.entitlement_catalog.stock_records.add( self.entitlement_stock_record) self.entitlement_coupon = self.create_coupon( title='Tešt Entitlement product', catalog=self.entitlement_catalog, note='Tešt Entitlement note', quantity=1, max_uses=1, voucher_type=Voucher.MULTI_USE) self.entitlement_coupon_vouchers = CouponVouchers.objects.filter( coupon=self.entitlement_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, 'enterprise_customer_catalog': None, 'name': "Test voucher", 'quantity': 10, 'start_datetime': datetime.datetime.now() - datetime.timedelta(days=1), 'voucher_type': Voucher.SINGLE_USE }
def setUp(self): super(ManualCourseEnrollmentOrderViewSetTests, 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_uuid = '620a5ce5-6ff4-4b2b-bea1-a273c6920ae5' 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.course_entitlement = create_or_update_course_entitlement( 'verified', 100, self.partner, self.course_uuid, 'Course Entitlement' ) 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': self.course_uuid } ) responses.start()
def create(self, request, *args, **kwargs): """ Create a Product """ data = request.data if data.get('product_class') == COURSE_ENTITLEMENT_PRODUCT_CLASS_NAME: product_creation_fields = { 'certificate_type': data.get('certificate_type'), 'price': data.get('price'), 'partner': request.site.siteconfiguration.partner, 'UUID': data.get('uuid'), 'title': data.get('title'), } missing_values = [ k for k, v in sorted(list(product_creation_fields.items())) if v is None ] if missing_values: return self.missing_values_response(missing_values) entitlement = create_or_update_course_entitlement( **product_creation_fields) entitlement_data = self.serializer_class(entitlement, context={ 'request': request }).data return Response(entitlement_data, status=status.HTTP_201_CREATED) else: return self.invalid_product_response('POST')
def test_order_details_entitlement_msg(self): """Verify the order details message is displayed for course entitlements.""" product = create_or_update_course_entitlement( 'verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') self.assert_order_details_in_context(product)
def test_create_refund_for_duplicate_orders_only(self, commit): """ Test that refund is generated for manual enrollment orders only if having some duplicate enrollments. """ orders = self.create_manual_order() filename = 'orders_file.txt' self.create_orders_file(orders, filename) # create duplicate order having course_entitlement product order_with_duplicate = orders[0] order_without_duplicate = orders[1] course_uuid = FAKER.uuid4() # pylint: disable=no-member order = Order.objects.get(number=order_with_duplicate['detail']) course_entitlement = create_or_update_course_entitlement( 'verified', 100, order.partner, course_uuid, 'Course Entitlement' ) basket = factories.BasketFactory(owner=order.user, site=order.site) basket.add_product(course_entitlement, 1) create_order(basket=basket, user=order.user) self.mock_access_token_response() self.mock_course_run_detail_endpoint( self.course, discovery_api_url=order.site.siteconfiguration.discovery_api_url, course_run_info={ 'course_uuid': course_uuid } ) self.assertFalse(Refund.objects.exists()) with LogCapture(LOGGER_NAME) as log_capture: params = ['create_refund_for_orders', '--order-numbers-file={}'.format(filename), '--refund-duplicate-only', '--sleep-time=0.5'] if not commit: params.append('--no-commit') call_command(*params) log_capture.check_present( (LOGGER_NAME, 'INFO', 'Sleeping for 0.5 second/seconds'), ) log_capture.check_present( ( LOGGER_NAME, 'ERROR', '[Ecommerce Order Refund]: Completed refund generation. 0 of 2 failed and 1 skipped.\n' 'Failed orders: \n' 'Skipped orders: {}\n'.format(order_without_duplicate['detail']), ), ) if commit: self.assertEqual(Refund.objects.count(), 1) refund = Refund.objects.get(order=order) self.assert_refund_matches_order(refund, order) order = Order.objects.get(number=order_without_duplicate['detail']) self.assertFalse(order.refunds.exists()) else: self.assertEqual(Refund.objects.count(), 0)
def test_course_entitlement_creation(self): """ Test course entitlement product creation """ product = create_or_update_course_entitlement( 'verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') self.assertEqual(product.title, 'Course Entitlement for Foo Bar Entitlement') self.assertEqual(product.attr.UUID, 'foo-bar') stock_record = StockRecord.objects.get(product=product, partner=self.partner) self.assertEqual(stock_record.price_excl_tax, 100)
def setUp(self): super(EnterpriseCustomerConditionTests, self).setUp() self.user = UserFactory() self.condition = factories.EnterpriseCustomerConditionFactory() self.test_product = ProductFactory(stockrecords__price_excl_tax=10, categories=[]) self.course_run = CourseFactory(partner=self.partner) self.course_run.create_or_update_seat('verified', True, Decimal(100)) self.entitlement = create_or_update_course_entitlement( 'verified', 100, self.partner, 'edX-DemoX', 'edX Demo Entitlement' ) self.entitlement_stock_record = StockRecord.objects.filter(product=self.entitlement).first() self.entitlement_catalog = Catalog.objects.create(partner=self.partner) self.entitlement_catalog.stock_records.add(self.entitlement_stock_record)
def test_basket_switch_data(self): """Verify the correct basket switch data (single vs. multi quantity) is retrieved.""" __, seat, enrollment_code = self.prepare_course_seat_and_enrollment_code() seat_sku = StockRecord.objects.get(product=seat).partner_sku ec_sku = StockRecord.objects.get(product=enrollment_code).partner_sku entitlement = create_or_update_course_entitlement( 'verified', 100, self.partner, 'foo-bar', 'Foo Bar Entitlement') __, 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) # Entitlement products should not return a sku for this function __, partner_sku = get_basket_switch_data(entitlement) self.assertIsNone(partner_sku)
def setUp(self): super(EntitlementFulfillmentModuleTests, self).setUp() self.user = UserFactory() self.course_entitlement = create_or_update_course_entitlement( 'verified', 100, self.partner, '111-222-333-444', 'Course Entitlement') basket = factories.BasketFactory(owner=self.user, site=self.site) basket.add_product(self.course_entitlement, 1) self.entitlement_option = Option.objects.get(name='Course Entitlement') self.order = create_order(number=1, basket=basket, user=self.user) self.logger_name = 'ecommerce.extensions.fulfillment.modules' self.return_data = { "user": "******", "course_uuid": "3b3123b8-d34b-44d8-9bbb-a12676e97123", "uuid": "111-222-333", "mode": "verified", "expired_at": "None" }
def create(self, request, *args, **kwargs): product_class = request.data.get('product_class') if product_class == COURSE_ENTITLEMENT_PRODUCT_CLASS_NAME: product_creation_fields = { 'partner': request.site.siteconfiguration.partner, 'name': request.data.get('title'), 'price': request.data.get('price'), 'certificate_type': self._fetch_value_from_attribute_values('certificate_type'), 'UUID': self._fetch_value_from_attribute_values('UUID') } for attribute_name, attribute_value in product_creation_fields.items( ): if attribute_value is None: bad_rqst = 'Missing or bad value for: {}, required for Entitlement creation.'.format( attribute_name) return HttpResponseBadRequest(bad_rqst) entitlement = create_or_update_course_entitlement( product_creation_fields['certificate_type'], product_creation_fields['price'], product_creation_fields['partner'], product_creation_fields['UUID'], product_creation_fields['name']) data = self.serializer_class(entitlement, context={ 'request': request }).data return Response(data, status=status.HTTP_201_CREATED) else: bad_rqst = "Product API only supports POST for {} products".format( COURSE_ENTITLEMENT_PRODUCT_CLASS_NAME) return HttpResponseBadRequest(bad_rqst)
def mock_program_detail_endpoint(self, program_uuid, discovery_api_url, empty=False, title='Test Program', include_entitlements=True): """ Mocks the program detail endpoint on the Catalog API. Args: program_uuid (uuid): UUID of the mocked program. Returns: dict: Mocked program data. """ partner = PartnerFactory() data = None if not empty: courses = [] for i in range(1, 5): uuid = '268afbfc-cc1e-415b-a5d8-c58d955bcfc' + str(i) entitlement = create_or_update_course_entitlement('verified', 10, partner, uuid, uuid) entitlements = [] if include_entitlements: entitlements.append( { "mode": "verified", "price": "10.00", "currency": "USD", "sku": entitlement.stockrecords.first().partner_sku } ) 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, 'uuid': uuid, 'course_runs': course_runs, 'entitlements': entitlements, }) 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