def test_generate_sku_for_coupon(self): """Verify the method generates a SKU for a coupon.""" data = { 'partner': self.partner, 'benefit_type': Benefit.PERCENTAGE, 'benefit_value': 100, 'catalog': self.catalog, 'end_date': datetime.date(2020, 1, 1), 'code': '', 'quantity': 5, 'start_date': datetime.date(2015, 1, 1), 'voucher_type': Voucher.SINGLE_USE } coupon = CouponViewSet().create_coupon_product( title='Test coupon', price=100, data=data ) _hash = ' '.join(( unicode(coupon.id), unicode(self.catalog.id), str(self.partner.id) )) _hash = md5(_hash.lower()).hexdigest()[-7:] expected = _hash.upper() actual = generate_sku(coupon, self.partner, catalog=self.catalog) self.assertEqual(actual, expected)
def test_generate_sku_for_course_seat(self): """Verify the method generates a SKU for a course seat.""" course_id = 'sku/test/course' course = Course.objects.create(id=course_id, name='Test Course') certificate_type = 'honor' product = course.create_or_update_seat(certificate_type, False, 0) _hash = md5(u'{} {} {}'.format(certificate_type, course_id, 'False')).hexdigest()[-7:] expected = _hash.upper() actual = generate_sku(product) self.assertEqual(actual, expected)
def test_generate_sku_for_coupon(self): """Verify the method generates a SKU for a coupon.""" coupon = self.create_coupon(partner=self.partner, catalog=self.catalog) _hash = ' '.join(( unicode(coupon.id), str(self.partner.id) )) digest = md5(_hash.lower()).hexdigest()[-7:] expected = digest.upper() actual = generate_sku(coupon, self.partner) self.assertEqual(actual, expected)
def test_generate_sku_for_course_seat(self): """Verify the method generates a SKU for a course seat.""" course_id = 'sku/test/course' course = Course.objects.create(id=course_id, name='Test Course') certificate_type = 'honor' product = course.create_or_update_seat(certificate_type, False, 0, self.partner) _hash = '{} {} {} {} {}'.format(certificate_type, course_id, 'False', '', self.partner.id) _hash = md5(_hash.lower()).hexdigest()[-7:] # verify that generated sku has partner 'short_code' as prefix expected = _hash.upper() actual = generate_sku(product, self.partner) self.assertEqual(actual, expected)
def _create_or_update_enrollment_code(self, seat_type, id_verification_required, partner, price): """ Creates an enrollment code product and corresponding stock record for the specified seat. Includes course ID and seat type as product attributes. Args: seat_type (str): Seat type. partner (Partner): Seat provider set in the stock record. price (Decimal): Price of the seat. Returns: Enrollment code product. """ enrollment_code_product_class = ProductClass.objects.get(name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME) enrollment_code = self.enrollment_code_product if not enrollment_code: title = 'Enrollment code for {seat_type} seat in {course_name}'.format( seat_type=seat_type, course_name=self.name ) enrollment_code = Product( title=title, product_class=enrollment_code_product_class, course=self ) enrollment_code.attr.course_key = self.id enrollment_code.attr.seat_type = seat_type enrollment_code.attr.id_verification_required = id_verification_required enrollment_code.save() try: stock_record = StockRecord.objects.get(product=enrollment_code, partner=partner) except StockRecord.DoesNotExist: enrollment_code_sku = generate_sku(enrollment_code, partner) stock_record = StockRecord( product=enrollment_code, partner=partner, partner_sku=enrollment_code_sku ) stock_record.price_excl_tax = price stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY stock_record.save() return enrollment_code
def create_or_update_seat(self, certificate_type, id_verification_required, price, partner, credit_provider=None, expires=None, credit_hours=None): """ Creates course seat products. Returns: Product: The seat that has been created or updated. """ certificate_type = certificate_type.lower() course_id = unicode(self.id) if certificate_type == self.certificate_type_for_mode('audit'): # Yields a match if attribute names do not include 'certificate_type'. certificate_type_query = ~Q(attributes__name='certificate_type') else: # Yields a match if attribute with name 'certificate_type' matches provided value. certificate_type_query = Q( attributes__name='certificate_type', attribute_values__value_text=certificate_type ) id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=id_verification_required ) if credit_provider is None: # Yields a match if attribute names do not include 'credit_provider'. credit_provider_query = ~Q(attributes__name='credit_provider') else: # Yields a match if attribute with name 'credit_provider' matches provided value. credit_provider_query = Q( attributes__name='credit_provider', attribute_values__value_text=credit_provider ) try: seat = self.seat_products.filter( certificate_type_query ).filter( id_verification_required_query ).get( credit_provider_query ) logger.info( 'Retrieved course seat child product with certificate type [%s] for [%s] from database.', certificate_type, course_id ) except Product.DoesNotExist: seat = Product() logger.info( 'Course seat product with certificate type [%s] for [%s] does not exist. Instantiated a new instance.', certificate_type, course_id ) seat.course = self seat.structure = Product.CHILD seat.parent = self.parent_seat_product seat.is_discountable = True seat.title = self._get_course_seat_name(certificate_type, id_verification_required) seat.expires = expires # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted. # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute. seat.attr.certificate_type = certificate_type seat.attr.course_key = course_id seat.attr.id_verification_required = id_verification_required if credit_provider: seat.attr.credit_provider = credit_provider if credit_hours: seat.attr.credit_hours = credit_hours seat.save() try: stock_record = StockRecord.objects.get(product=seat, partner=partner) logger.info( 'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.', certificate_type, course_id ) except StockRecord.DoesNotExist: partner_sku = generate_sku(seat, partner) stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku) logger.info( 'Course seat product stock record with certificate type [%s] for [%s] does not exist. ' 'Instantiated a new instance.', certificate_type, course_id ) stock_record.price_excl_tax = price # TODO Expose via setting stock_record.price_currency = 'USD' stock_record.save() return seat
def create_coupon_product(self, title, price, data): """Creates a coupon product and a stock record for it. Arguments: title (str): The name of the coupon. price (int): The price of the coupon(s). data (dict): Contains data needed to create vouchers,SKU and UPC: - partner (User) - benefit_type (str) - benefit_value (int) - catalog (Catalog) - end_date (Datetime) - code (str) - quantity (int) - start_date (Datetime) - voucher_type (str) - categories (list of Category objects) Returns: A coupon product object. Raises: IntegrityError: An error occured when create_vouchers method returns an IntegrityError exception ValidationError: An error occured clean() validation method returns a ValidationError exception """ coupon_slug = generate_coupon_slug(title=title, catalog=data['catalog'], partner=data['partner']) product_class = ProductClass.objects.get(slug='coupon') coupon_product, __ = Product.objects.get_or_create( title=title, product_class=product_class, slug=coupon_slug ) for category in data['categories']: ProductCategory.objects.get_or_create(product=coupon_product, category=category) # Vouchers are created during order and not fulfillment like usual # because we want vouchers to be part of the line in the order. try: create_vouchers( name=title, benefit_type=data['benefit_type'], benefit_value=Decimal(data['benefit_value']), catalog=data['catalog'], coupon=coupon_product, end_datetime=data['end_date'], code=data['code'] or None, quantity=int(data['quantity']), start_datetime=data['start_date'], voucher_type=data['voucher_type'] ) except IntegrityError as ex: logger.exception('Failed to create vouchers for [%s] coupon.', coupon_product.title) raise IntegrityError(ex) # pylint: disable=nonstandard-exception coupon_vouchers = CouponVouchers.objects.get(coupon=coupon_product) coupon_product.attr.coupon_vouchers = coupon_vouchers coupon_product.save() sku = generate_sku( product=coupon_product, partner=data['partner'], catalog=data['catalog'], ) stock_record, __ = StockRecord.objects.get_or_create( product=coupon_product, partner=data['partner'], partner_sku=sku ) stock_record.price_currency = 'USD' stock_record.price_excl_tax = price stock_record.save() return coupon_product
def test_generate_sku_with_unexpected_product_class(self): """Verify the method raises an exception for unsupported product class.""" product = ProductFactory() with self.assertRaises(Exception): generate_sku(product, self.partner)
def test_generate_sku_with_unexpected_product_class(self): """Verify the method raises an exception for unsupported product class.""" product = ProductFactory() with self.assertRaises(Exception): generate_sku(product, self.partner)
def create_coupon_product(self, title, price, data): """Creates a coupon product and a stock record for it. Arguments: title (str): The name of the coupon. price (int): The price of the coupon(s). data (dict): Contains data needed to create vouchers,SKU and UPC: - partner (User) - benefit_type (str) - benefit_value (int) - catalog (Catalog) - end_date (Datetime) - code (str) - quantity (int) - start_date (Datetime) - voucher_type (str) - categories (list of Category objects) - note (str) - max_uses (int) - catalog_query (str) - course_seat_types (str) Returns: A coupon product object. """ product_class = ProductClass.objects.get(slug='coupon') coupon_product = Product.objects.create(title=title, product_class=product_class) self.assign_categories_to_coupon(coupon=coupon_product, categories=data['categories']) # Vouchers are created during order and not fulfillment like usual # because we want vouchers to be part of the line in the order. create_vouchers( name=title, benefit_type=data['benefit_type'], benefit_value=Decimal(data['benefit_value']), catalog=data['catalog'], coupon=coupon_product, end_datetime=data['end_date'], code=data['code'] or None, quantity=int(data['quantity']), start_datetime=data['start_date'], voucher_type=data['voucher_type'], max_uses=data['max_uses'], catalog_query=data['catalog_query'], course_seat_types=data['course_seat_types'] ) coupon_vouchers = CouponVouchers.objects.get(coupon=coupon_product) coupon_product.attr.coupon_vouchers = coupon_vouchers coupon_product.attr.note = data['note'] coupon_product.save() sku = generate_sku(product=coupon_product, partner=data['partner']) StockRecord.objects.update_or_create( product=coupon_product, partner=data['partner'], partner_sku=sku, defaults={ 'price_currency': 'USD', 'price_excl_tax': price } ) return coupon_product
def create_or_update_seat(self, certificate_type, id_verification_required, price, partner, credit_provider=None, expires=None, credit_hours=None, remove_stale_modes=True): """ Creates course seat products. Returns: Product: The seat that has been created or updated. """ certificate_type = certificate_type.lower() course_id = unicode(self.id) if certificate_type == self.certificate_type_for_mode('audit'): # Yields a match if attribute names do not include 'certificate_type'. certificate_type_query = ~Q(attributes__name='certificate_type') else: # Yields a match if attribute with name 'certificate_type' matches provided value. certificate_type_query = Q( attributes__name='certificate_type', attribute_values__value_text=certificate_type ) id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=id_verification_required ) if credit_provider is None: # Yields a match if attribute names do not include 'credit_provider'. credit_provider_query = ~Q(attributes__name='credit_provider') else: # Yields a match if attribute with name 'credit_provider' matches provided value. credit_provider_query = Q( attributes__name='credit_provider', attribute_values__value_text=credit_provider ) seats = self.seat_products.filter(certificate_type_query) try: seat = seats.filter( id_verification_required_query ).get( credit_provider_query ) logger.info( 'Retrieved course seat child product with certificate type [%s] for [%s] from database.', certificate_type, course_id ) except Product.DoesNotExist: seat = Product() logger.info( 'Course seat product with certificate type [%s] for [%s] does not exist. Instantiated a new instance.', certificate_type, course_id ) seat.course = self seat.structure = Product.CHILD seat.parent = self.parent_seat_product seat.is_discountable = True seat.title = self.get_course_seat_name(certificate_type, id_verification_required) seat.expires = expires # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted. # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute. seat.attr.certificate_type = certificate_type seat.attr.course_key = course_id seat.attr.id_verification_required = id_verification_required if waffle.switch_is_active(ENROLLMENT_CODE_SWITCH) and certificate_type in ENROLLMENT_CODE_SEAT_TYPES: self._create_or_update_enrollment_code(certificate_type, id_verification_required, partner, price) if credit_provider: seat.attr.credit_provider = credit_provider if credit_hours: seat.attr.credit_hours = credit_hours seat.save() try: stock_record = StockRecord.objects.get(product=seat, partner=partner) logger.info( 'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.', certificate_type, course_id ) except StockRecord.DoesNotExist: partner_sku = generate_sku(seat, partner) stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku) logger.info( 'Course seat product stock record with certificate type [%s] for [%s] does not exist. ' 'Instantiated a new instance.', certificate_type, course_id ) stock_record.price_excl_tax = price stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY stock_record.save() if remove_stale_modes and self.certificate_type_for_mode(certificate_type) == 'professional': id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=not id_verification_required ) # Delete seats with a different verification requirement, assuming the seats # have not been purchased. seats.annotate(orders=Count('line')).filter( id_verification_required_query, orders=0 ).delete() return seat
def create_or_update_seat(self, certificate_type, id_verification_required, price, partner, credit_provider=None, expires=None, credit_hours=None, remove_stale_modes=True): """ Creates course seat products. Returns: Product: The seat that has been created or updated. """ certificate_type = certificate_type.lower() course_id = unicode(self.id) if certificate_type == self.certificate_type_for_mode('audit'): # Yields a match if attribute names do not include 'certificate_type'. certificate_type_query = ~Q(attributes__name='certificate_type') else: # Yields a match if attribute with name 'certificate_type' matches provided value. certificate_type_query = Q( attributes__name='certificate_type', attribute_values__value_text=certificate_type) id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=id_verification_required) if credit_provider is None: # Yields a match if attribute names do not include 'credit_provider'. credit_provider_query = ~Q(attributes__name='credit_provider') else: # Yields a match if attribute with name 'credit_provider' matches provided value. credit_provider_query = Q( attributes__name='credit_provider', attribute_values__value_text=credit_provider) seats = self.seat_products.filter(certificate_type_query) try: seat = seats.filter(id_verification_required_query).get( credit_provider_query) logger.info( 'Retrieved course seat child product with certificate type [%s] for [%s] from database.', certificate_type, course_id) except Product.DoesNotExist: seat = Product() logger.info( 'Course seat product with certificate type [%s] for [%s] does not exist. Instantiated a new instance.', certificate_type, course_id) seat.course = self seat.structure = Product.CHILD seat.parent = self.parent_seat_product seat.is_discountable = True seat.title = self.get_course_seat_name(certificate_type, id_verification_required) seat.expires = expires # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted. # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute. seat.attr.certificate_type = certificate_type seat.attr.course_key = course_id seat.attr.id_verification_required = id_verification_required if waffle.switch_is_active( ENROLLMENT_CODE_SWITCH ) and certificate_type in ENROLLMENT_CODE_SEAT_TYPES: self._create_or_update_enrollment_code(certificate_type, id_verification_required, partner, price) if credit_provider: seat.attr.credit_provider = credit_provider if credit_hours: seat.attr.credit_hours = credit_hours seat.save() try: stock_record = StockRecord.objects.get(product=seat, partner=partner) logger.info( 'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.', certificate_type, course_id) except StockRecord.DoesNotExist: partner_sku = generate_sku(seat, partner) stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku) logger.info( 'Course seat product stock record with certificate type [%s] for [%s] does not exist. ' 'Instantiated a new instance.', certificate_type, course_id) stock_record.price_excl_tax = price stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY stock_record.save() if remove_stale_modes and self.certificate_type_for_mode( certificate_type) == 'professional': id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=not id_verification_required) # Delete seats with a different verification requirement, assuming the seats # have not been purchased. seats.annotate(orders=Count('line')).filter( id_verification_required_query, orders=0).delete() return seat
def create_coupon_product(self, title, price, data): """Creates a coupon product and a stock record for it. Arguments: title (str): The name of the coupon. price (int): The price of the coupon(s). data (dict): Contains data needed to create vouchers,SKU and UPC: - partner (User) - benefit_type (str) - benefit_value (int) - catalog (Catalog) - end_date (Datetime) - code (str) - quantity (int) - start_date (Datetime) - voucher_type (str) - categories (list of Category objects) - note (str) - max_uses (int) Returns: A coupon product object. Raises: IntegrityError: An error occured when create_vouchers method returns an IntegrityError exception """ coupon_slug = generate_coupon_slug(title=title, catalog=data['catalog'], partner=data['partner']) product_class = ProductClass.objects.get(slug='coupon') coupon_product, __ = Product.objects.get_or_create( title=title, product_class=product_class, slug=coupon_slug ) self.assign_categories_to_coupon(coupon=coupon_product, categories=data['categories']) # Vouchers are created during order and not fulfillment like usual # because we want vouchers to be part of the line in the order. try: create_vouchers( name=title, benefit_type=data['benefit_type'], benefit_value=Decimal(data['benefit_value']), catalog=data['catalog'], coupon=coupon_product, end_datetime=data['end_date'], code=data['code'] or None, quantity=int(data['quantity']), start_datetime=data['start_date'], voucher_type=data['voucher_type'], max_uses=data['max_uses'], coupon_id=coupon_product.id ) except IntegrityError as ex: logger.exception('Failed to create vouchers for [%s] coupon.', coupon_product.title) raise IntegrityError(ex) # pylint: disable=nonstandard-exception coupon_vouchers = CouponVouchers.objects.get(coupon=coupon_product) coupon_product.attr.coupon_vouchers = coupon_vouchers coupon_product.attr.note = data['note'] coupon_product.save() sku = generate_sku( product=coupon_product, partner=data['partner'], catalog=data['catalog'], ) stock_record, __ = StockRecord.objects.get_or_create( product=coupon_product, partner=data['partner'], partner_sku=sku ) stock_record.price_currency = 'USD' stock_record.price_excl_tax = price stock_record.save() return coupon_product
def create_or_update_seat( self, certificate_type, id_verification_required, price, credit_provider=None, expires=None, credit_hours=None, remove_stale_modes=True, create_enrollment_code=False, sku=None, ): """ Creates and updates course seat products. IMPORTANT: Requires the Partner sku (from the stock record) to be passed in for updates. Arguments: certificate_type(str): The seat type. id_verification_required(bool): Whether an ID verification is required. price(int): Price of the seat. partner(Partner): Site partner. Optional arguments: credit_provider(str): Name of the organization that provides the credit expires(datetime): Date when the seat type expires. credit_hours(int): Number of credit hours provided. remove_stale_modes(bool): Remove stale modes. create_enrollment_code(bool): Whether an enrollment code is created in addition to the seat. sku(str): The partner_sku for the product stored as part of the Stock Record. This is used to perform a GET on the seat as a unique identifier both Ecommerce and Discovery know about. Returns: Product: The seat that has been created or updated. """ certificate_type = certificate_type.lower() course_id = six.text_type(self.id) try: product_id = StockRecord.objects.get( partner_sku=sku, partner=self.partner).product_id seat = self.seat_products.get(id=product_id) logger.info( 'Retrieved course seat child product with certificate type [%s] for [%s] from database.', certificate_type, course_id) except (StockRecord.DoesNotExist, Product.DoesNotExist): seat = Product() logger.info( 'Course seat product with certificate type [%s] for [%s] does not exist. Attempted look up using sku ' '[%s]. Instantiated a new instance.', certificate_type, course_id, sku) seat.course = self seat.structure = Product.CHILD seat.parent = self.parent_seat_product seat.is_discountable = True seat.expires = expires id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=id_verification_required) seat.title = self.get_course_seat_name(certificate_type, id_verification_required) seat.save() # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted. # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute. seat.attr.certificate_type = certificate_type seat.attr.course_key = course_id seat.attr.id_verification_required = id_verification_required if certificate_type in ENROLLMENT_CODE_SEAT_TYPES and create_enrollment_code: self._create_or_update_enrollment_code(certificate_type, id_verification_required, self.partner, price, expires) if credit_provider: seat.attr.credit_provider = credit_provider if credit_hours: seat.attr.credit_hours = credit_hours seat.attr.save() try: stock_record = StockRecord.objects.get(product=seat, partner=self.partner) logger.info( 'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.', certificate_type, course_id) except StockRecord.DoesNotExist: partner_sku = generate_sku(seat, self.partner) stock_record = StockRecord(product=seat, partner=self.partner, partner_sku=partner_sku) logger.info( 'Course seat product stock record with certificate type [%s] for [%s] does not exist. ' 'Instantiated a new instance.', certificate_type, course_id) stock_record.price_excl_tax = price stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY stock_record.save() if remove_stale_modes and self.certificate_type_for_mode( certificate_type) == 'professional': id_verification_required_query = Q( attributes__name='id_verification_required', attribute_values__value_boolean=not id_verification_required) certificate_type_query = Q( attributes__name='certificate_type', attribute_values__value_text=certificate_type) # Delete seats with a different verification requirement, assuming the seats # have not been purchased. self.seat_products.filter(certificate_type_query).annotate( orders=Count('line')).filter(id_verification_required_query, orders=0).delete() return seat
def create_or_update_seat(self, certificate_type, id_verification_required, price, credit_provider=None, expires=None, credit_hours=None): """ Creates course seat products. Returns: Product: The seat that has been created or updated. """ certificate_type = certificate_type.lower() course_id = unicode(self.id) slugs = [] slug = 'child-cs-{}-{}'.format(certificate_type, slugify(course_id)) # Note (CCB): Our previous method of slug generation did not account for ID verification. By using a list # we can update these seats. This should be removed after the courses have been re-migrated. if certificate_type == 'verified': slugs.append(slug) if id_verification_required: slug += '-id-verified' slugs.append(slug) slugs = set(slugs) try: seat = Product.objects.get(slug__in=slugs) logger.info('Retrieved [%s] course seat child product for [%s] from database.', certificate_type, course_id) except Product.DoesNotExist: seat = Product(slug=slug) logger.info('[%s] course seat product for [%s] does not exist. Instantiated a new instance.', certificate_type, course_id) seat.course = self seat.parent = self.parent_seat_product seat.is_discountable = True seat.structure = Product.CHILD seat.title = self._get_course_seat_name(certificate_type, id_verification_required) seat.expires = expires seat.attr.certificate_type = certificate_type seat.attr.course_key = course_id seat.attr.id_verification_required = id_verification_required if credit_provider: seat.attr.credit_provider = credit_provider if credit_hours: seat.attr.credit_hours = credit_hours seat.save() # TODO Expose via setting partner = Partner.objects.get(code='edx') try: stock_record = StockRecord.objects.get(product=seat, partner=partner) logger.info('Retrieved [%s] course seat child product stock record for [%s] from database.', certificate_type, course_id) except StockRecord.DoesNotExist: partner_sku = generate_sku(seat) stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku) logger.info( '[%s] course seat product stock record for [%s] does not exist. Instantiated a new instance.', certificate_type, course_id) stock_record.price_excl_tax = price # TODO Expose via setting stock_record.price_currency = 'USD' stock_record.save() return seat
def test_generate_sku_with_missing_product_class(self): """Verify the method raises an exception if the product class is missing.""" with self.assertRaises(AttributeError): generate_sku(Product(), self.partner)
def assert_stock_record_valid(self, stock_record, seat, price): """ Verify the given StockRecord is configured correctly. """ self.assertEqual(stock_record.partner, self.partner) self.assertEqual(stock_record.price_excl_tax, price) self.assertEqual(stock_record.price_currency, 'USD') self.assertEqual(stock_record.partner_sku, generate_sku(seat, self.partner))
def test_generate_sku_with_missing_product_class(self): """Verify the method raises an exception if the product class is missing.""" with self.assertRaises(AttributeError): generate_sku(Product(), self.partner)