def test_type_string(): """ type_string should return a string representation of the Product type """ program = ProgramFactory.create() run = CourseRunFactory.create() program_product = ProductFactory.create(content_object=program) assert program_product.type_string == "program" run_product = ProductFactory.create(content_object=run) assert run_product.type_string == "courserun"
def test_title(): """ title should return a string representation of the Product's title """ program = ProgramFactory.create(title="test title of the program") course = CourseFactory.create(title="test title of the course") run = CourseRunFactory.create(course=course) program_product = ProductFactory.create(content_object=program) assert program_product.title == "test title of the program" run_product = ProductFactory.create(content_object=run) assert run_product.title == "test title of the course"
def test_course_runs_without_product_in_programs_api(client, has_product): """Regardless of whether course runs have a product, runs should **not** be filtered out of the programs API""" run = CourseRunFactory.create(live=True, course__live=True) ProductVersionFactory.create(product=ProductFactory( content_object=run.course.program)) if has_product: ProductVersionFactory.create(product=ProductFactory( content_object=run)) resp = client.get(reverse("programs_api-list")) assert resp.status_code == status.HTTP_200_OK assert_drf_json_equal(resp.json()[0]["courses"][0]["courseruns"], [CourseRunSerializer(run).data])
def test_start_date(): """ start_date should return a start next_run_date of the Product """ program = ProgramFactory.create() course = CourseFactory.create() run = CourseRunFactory.create(course=course) program_product = ProductFactory.create(content_object=program) assert program_product.start_date == program.next_run_date run_product = ProductFactory.create(content_object=run) assert run_product.start_date == course.next_run_date
def test_thumbnail_url(): """ thumbnail_url should return a url of the Product's thumbnail """ program = ProgramFactory.create() program_product = ProductFactory.create(content_object=program) run = CourseRunFactory.create() run_product = ProductFactory.create(content_object=run) assert (program_product.thumbnail_url == program.page.thumbnail_image. get_rendition(CATALOG_COURSE_IMG_WAGTAIL_FILL).url) assert (run_product.thumbnail_url == run.course.page.thumbnail_image. get_rendition(CATALOG_COURSE_IMG_WAGTAIL_FILL).url)
def test_serialize_basket_product_version_program(mock_context): """Test ProductVersion serialization for a Program""" program = ProgramFactory.create() courses = CourseFactory.create_batch(3, program=program) product_version = ProductVersionFactory.create( product=ProductFactory(content_object=program) ) data = FullProductVersionSerializer( instance=product_version, context=mock_context ).data assert data == { "id": product_version.id, "description": product_version.description, "content_title": product_version.product.content_object.title, "price": str(round_half_up(product_version.price)), "type": product_version.product.content_type.model, "courses": [ CourseSerializer(instance=course, context=mock_context).data for course in courses ], "thumbnail_url": program.catalog_image_url, "object_id": product_version.product.object_id, "product_id": product_version.product.id, "readable_id": get_readable_id(product_version.product.content_object), "run_tag": None, "created_on": product_version.created_on.strftime(datetime_millis_format), "start_date": product_version.product.content_object.next_run_date.isoformat() if product_version.product.content_object.next_run_date else None, }
def programs(): """Fixture for a set of Programs in the database""" programs = ProgramFactory.create_batch(3) for program in programs: ProductVersionFactory.create(product=ProductFactory( content_object=program)) return programs
def voucher_and_partial_matches_with_coupons(voucher_and_partial_matches): """ Returns a voucher with partial matching CourseRuns and valid coupons """ context = voucher_and_partial_matches products = [ ProductFactory(content_object=course_run) for course_run in context.partial_matches ] coupon_eligibility_list = [ CouponEligibilityFactory(product=product) for product in products ] payment_versions = [ CouponPaymentVersionFactory(amount=1, company=context.company) for _ in coupon_eligibility_list ] coupon_versions = [ CouponVersionFactory( coupon=coupon_eligibility_list[i].coupon, payment_version=payment_versions[i], ) for i in range(len(coupon_eligibility_list)) ] return SimpleNamespace( **vars(voucher_and_partial_matches), products=products, coupon_eligibility_list=coupon_eligibility_list, coupon_versions=coupon_versions, payment_versions=payment_versions, )
def test_course_runs_not_live_in_courses_api(client, live): """Course runs should be filtered out of the courses API if not live""" run = CourseRunFactory.create(live=live, course__live=True) ProductVersionFactory.create(product=ProductFactory(content_object=run)) resp = client.get(reverse("courses_api-list")) assert resp.status_code == status.HTTP_200_OK assert_drf_json_equal(resp.json()[0]["courseruns"], [CourseRunSerializer(run).data] if live else [])
def test_programs_not_live(client, live): """Programs should be filtered out if live=False""" program = ProgramFactory.create(live=live) ProductVersionFactory.create(product=ProductFactory( content_object=program)) resp = client.get(reverse("programs_api-list")) assert resp.status_code == status.HTTP_200_OK assert_drf_json_equal(resp.json(), [ProgramSerializer(program).data] if live else [])
def test_courses_not_live_in_programs_api(client, live): """Courses should be filtered out of the programs API if not live""" course = CourseFactory.create(live=live, program__live=True) ProductVersionFactory.create(product=ProductFactory( content_object=course.program)) resp = client.get(reverse("programs_api-list")) assert resp.status_code == status.HTTP_200_OK assert_drf_json_equal(resp.json()[0]["courses"], [CourseSerializer(course).data] if live else [])
def test_product_version_save_text_id_badproduct(mocker): """ProductVersion.text_id should None if ProductVersion.product is invalid""" mock_log = mocker.patch("ecommerce.models.log") product_version = ProductVersionFactory.create( product=ProductFactory.create(content_object=LineFactory())) assert product_version.text_id is None assert mock_log.called_once_with( f"The content object for this ProductVersion ({product_version.id}) does not have a `text_id` property" )
def test_program_view(client, user, home_page, is_enrolled, has_product, has_unexpired_run, is_anonymous): """ Test that the program detail view has the right context and shows the right HTML for the enroll/view button """ program = ProgramFactory.create(live=True, page__parent=home_page) if has_unexpired_run: now = now_in_utc() CourseRunFactory.create_batch( 3, course=CourseFactory.create(program=program, live=True, position_in_program=1), live=True, start_date=now + timedelta(hours=2), ) if has_product: product_id = ProductVersionFactory.create(product=ProductFactory( content_object=program)).product.id else: product_id = None if is_enrolled: ProgramEnrollmentFactory.create(user=user, program=program) if not is_anonymous: client.force_login(user) resp = client.get(program.page.get_url()) assert resp.context["user"] == user if not is_anonymous else AnonymousUser( ) assert resp.context["product_id"] == product_id assert resp.context["enrolled"] == (is_enrolled and not is_anonymous) # Anynoymous users don't see the enrolled/enroll-now button. # For logged in users: # a) product should exist, next courserun should be there, user not enrolled (enroll now button) # b) user is enrolled (enrolled button) has_button = ((has_product and has_unexpired_run and not is_enrolled) or is_enrolled) and not is_anonymous url = "" # make linter happy class_name = "" if not is_anonymous: if not is_enrolled and has_product and has_unexpired_run: url = f'{reverse("checkout-page")}?product={product_id}' class_name = "enroll-now" if is_enrolled: url = reverse("user-dashboard") class_name = "enrolled" assert ( f'<a class="enroll-button {class_name}" href="{url}">'.encode("utf-8") in resp.content) is has_button assert ("Please Sign In to MITx PRO to enroll in a course".encode("utf-8") in resp.content) is (is_anonymous and has_product and has_unexpired_run)
def test_program_without_product_in_programs_api(client, has_product): """Programs should be filtered out of the programs API if they don't have an associated product""" program = ProgramFactory.create(live=True) if has_product: ProductVersionFactory.create(product=ProductFactory( content_object=program)) resp = client.get(reverse("programs_api-list")) assert resp.status_code == status.HTTP_200_OK assert_drf_json_equal( resp.json(), [ProgramSerializer(program).data] if has_product else [])
def test_program_current_price(): """ current_price should return the price of the latest product version if it exists """ program = ProgramFactory.create() assert program.current_price is None price = 10 ProductVersionFactory.create( product=ProductFactory(content_object=program), price=price) assert program.current_price == price
def test_course_run_current_price(): """ current_price should return the price of the latest product version if it exists """ run = CourseRunFactory.create() assert run.current_price is None price = 10 ProductVersionFactory.create(product=ProductFactory(content_object=run), price=price) assert run.current_price == price
def test_sync_product_with_hubspot(mock_hubspot_request): """Test that send_hubspot_request is called properly for a PRODUCT sync""" product = ProductFactory.create() sync_product_with_hubspot(product.id) body = make_product_sync_message(product.id) body[0]["changeOccurredTimestamp"] = ANY mock_hubspot_request.assert_called_once_with("PRODUCT", HUBSPOT_SYNC_URL, "PUT", body=body)
def test_serialize_basket_product_version_programrun(mock_context): """Test ProductVersion serialization for a Program with an associated ProgramRun""" program_run = ProgramRunFactory() product_version = ProductVersionFactory.create( product=ProductFactory(content_object=program_run.program) ) context = {**mock_context, **{"program_run": program_run}} data = FullProductVersionSerializer(instance=product_version, context=context).data assert data["object_id"] == program_run.program.id assert data["run_tag"] == program_run.run_tag
def test_course_runs_without_product_in_courses_api(client, has_product): """Course runs should be filtered out of the courses API if they don't have an associated product""" run = CourseRunFactory.create(live=True, course__live=True) if has_product: ProductVersionFactory.create(product=ProductFactory( content_object=run)) resp = client.get(reverse("courses_api-list")) assert resp.status_code == status.HTTP_200_OK assert_drf_json_equal( resp.json()[0]["courseruns"], [CourseRunSerializer(run).data] if has_product else [], )
def test_course_view(client, user, home_page, is_enrolled, has_unexpired_run, has_product, is_anonymous): """ Test that the course detail view has the right context and shows the right HTML for the enroll/view button """ course = CourseFactory.create(live=True, page__parent=home_page) if has_unexpired_run: run = CourseRunFactory.create(course=course, live=True) else: run = None if has_product and has_unexpired_run: product_id = ProductVersionFactory.create(product=ProductFactory( content_object=run)).product.id else: product_id = None if is_enrolled and has_unexpired_run: CourseRunEnrollmentFactory.create(user=user, run=run) if not is_anonymous: client.force_login(user) resp = client.get(course.page.get_url()) assert resp.context["user"] == user if not is_anonymous else AnonymousUser( ) assert resp.context["product_id"] == product_id assert resp.context["enrolled"] == (is_enrolled and has_unexpired_run and not is_anonymous) # Anynoymous users don't see the enrolled/enroll-now button. # For logged in users: # a) product should exist, next courserun should be there, user not enrolled (enroll now button) # b) user is enrolled (enrolled button) # NOTE: added `has_unexpired_run` to test for case (b) only because of the way the test is written, # enrollment isn't actually created unless the course has an unexpired run. has_button = ((has_product and has_unexpired_run and not is_enrolled) or (is_enrolled and has_unexpired_run)) and not is_anonymous url = "" # make linter happy class_name = "" if not is_anonymous: if not is_enrolled and has_product and has_unexpired_run: url = f'{reverse("checkout-page")}?product={product_id}' class_name = "enroll-now" if is_enrolled and has_unexpired_run: url = reverse("user-dashboard") class_name = "enrolled" assert ( f'<a class="enroll-button {class_name}" href="{url}">'.encode("utf-8") in resp.content) is has_button assert ("Please Sign In to MITx PRO to enroll in a course".encode("utf-8") in resp.content) is (is_anonymous and has_product and has_unexpired_run)
def test_latest_version(): """ The latest_version property should return the latest product version """ versions_to_create = 4 product = ProductFactory.create() versions = ProductVersionFactory.create_batch(versions_to_create, product=product) assert str(product) == "Product for {}".format(str(product.content_object)) assert str(versions[0]) == "ProductVersion for {}, ${}".format( versions[0].description, versions[0].price) # Latest version should be the most recently created assert product.latest_version == versions[versions_to_create - 1]
def test_apply_coupon_on_all_runs(include_future_runs): """ Test that coupons added to all future course runs of a course, only if `include_future_runs = True` """ course = CourseFactory.create() run = CourseRunFactory.create(course=course) coupon = CouponFactory.create(include_future_runs=include_future_runs) product = ProductFactory.create(content_object=run) CouponEligibilityFactory.create(coupon=coupon, product=product) # create another run with same course new_run = CourseRunFactory.create(course=course) new_product = ProductFactory.create(content_object=new_run) if include_future_runs: assert CouponEligibility.objects.filter( coupon=coupon, product=new_product ).exists() else: assert not CouponEligibility.objects.filter( coupon=coupon, product=new_product ).exists()
def test_serialize_product(text_id, expected): """ Test that ProductSerializer has correct data """ product_version = ProductVersionFactory.create( product=ProductFactory.create( content_object=CourseRunFactory.create(courseware_id=text_id) ) ) product = Product.objects.get(id=product_version.product.id) run = product.content_object serialized_data = ProductSerializer(instance=product).data assert serialized_data.get("title") == f"{run.title}: {expected}" assert serialized_data.get("product_type") == "courserun" assert serialized_data.get("id") == product.id assert serialized_data.get("price") == product.latest_version.price.to_eng_string() assert serialized_data.get("description") == product.latest_version.description
def test_run_queryset(is_program): """ run_queryset should return all runs related to the product """ program = ProgramFactory.create() runs = [CourseRunFactory.create(course__program=program) for _ in range(4)] run = runs[2] obj = program if is_program else run product = ProductFactory.create(content_object=obj) def key_func(_run): return _run.id assert sorted(product.run_queryset, key=key_func) == sorted(runs if is_program else [run], key=key_func)
def test_make_product_sync_message(): """Test make_deal_sync_message serializes a deal and returns a properly formatted sync message""" product = ProductFactory() contact_sync_message = api.make_product_sync_message(product.id) serialized_product = ProductSerializer(product).data assert contact_sync_message == [ { "integratorObjectId": "{}-{}".format( settings.HUBSPOT_ID_PREFIX, product.id ), "action": "UPSERT", "changeOccurredTimestamp": any_instance_of(int), "propertyNameToValues": serialized_product, } ]
def voucher_and_exact_match_with_coupon(voucher_and_exact_match): """ Returns a voucher with exact matching and partial matching CourseRuns and valid coupons """ context = voucher_and_exact_match company = context.company exact_match = context.exact_match product = ProductFactory(content_object=exact_match) coupon_eligibility = CouponEligibilityFactory(product=product) payment_version = CouponPaymentVersionFactory(amount=1, company=company) coupon_version = CouponVersionFactory(coupon=coupon_eligibility.coupon, payment_version=payment_version) return SimpleNamespace( **vars(voucher_and_exact_match), product=product, coupon_eligibility=coupon_eligibility, coupon_version=coupon_version, payment_version=payment_version, )
def test_send_enrollment_failure_message(mocker, is_program): """Test that send_enrollment_failure_message sends a message with proper formatting""" patched_django_mail = mocker.patch("ecommerce.mail_api.mail") product_object = (ProgramFactory.create() if is_program else CourseRunFactory.create()) product_version = ProductVersionFactory.create( product=ProductFactory.create(content_object=product_object)) order = LineFactory.create(product_version=product_version).order details = "TestException on line 21" expected_message = "{name}({email}): Order #{order_id}, {error_obj} #{obj_id} ({obj_title})\n\n{details}".format( name=order.purchaser.username, email=order.purchaser.email, order_id=order.id, error_obj=("Program" if is_program else "Run"), obj_id=product_object.id, obj_title=product_object.title, details=details, ) send_enrollment_failure_message(order, product_object, details) patched_django_mail.send_mail.assert_called_once() send_mail_args = patched_django_mail.send_mail.call_args[0] assert send_mail_args[0] == ENROLL_ERROR_EMAIL_SUBJECT assert send_mail_args[1] == expected_message
def test_product_version_save_text_id_courserun(): """ProductVersion.text_id should be set to CourseRun.courseware_id on save""" run = CourseRunFactory.create() product_version = ProductVersionFactory.create( product=ProductFactory.create(content_object=run)) assert product_version.text_id == run.courseware_id
def test_product_version_save_text_id_program(): """ProductVersion.text_id should be set to Program.readable_id on save""" program = ProgramFactory.create() product_version = ProductVersionFactory.create( product=ProductFactory.create(content_object=program)) assert product_version.text_id == program.readable_id