def mock_courses_api(self): # Create existing seats to be removed by ingest audit_run_type = CourseRunType.objects.get(slug=CourseRunType.AUDIT) credit_run_type = CourseRunType.objects.get(slug=CourseRunType.CREDIT_VERIFIED_AUDIT) verified_run_type = CourseRunType.objects.get(slug=CourseRunType.VERIFIED_AUDIT) audit_run = CourseRunFactory(title_override='audit', key='audit/course/run', type=audit_run_type) verified_run = CourseRunFactory(title_override='verified', key='verified/course/run', type=verified_run_type) credit_run = CourseRunFactory(title_override='credit', key='credit/course/run', type=credit_run_type) no_currency_run = CourseRunFactory(title_override='no currency', key='nocurrency/course/run', type=verified_run_type) professional_type = SeatTypeFactory.professional() SeatFactory(course_run=audit_run, type=professional_type) SeatFactory(course_run=verified_run, type=professional_type) SeatFactory(course_run=credit_run, type=professional_type) SeatFactory(course_run=no_currency_run, type=professional_type) bodies = mock_data.ECOMMERCE_API_BODIES url = self.api_url + 'courses/' responses.add_callback( responses.GET, url, callback=mock_api_callback(url, bodies), content_type=JSON ) return bodies
def test_ingest_verified_deadline(self, mock_push_to_ecomm): """ Verify the method ingests data from the Courses API. """ api_data = self.mock_api() self.assertEqual(Course.objects.count(), 0) self.assertEqual(CourseRun.objects.count(), 0) # Assume that while we are relying on ORGS_ON_OLD_PUBLISHER it will never be empty with self.settings(ORGS_ON_OLD_PUBLISHER='OTHER'): self.loader.ingest() # Verify the API was called with the correct authorization header self.assert_api_called(4) runs = CourseRun.objects.all() # Run with a verified entitlement, but no change in end date run1 = runs[0] run1.seats.add( SeatFactory(course_run=run1, type=SeatTypeFactory.verified())) run1.save() # Run with a verified entitlement, and the end date has changed run2 = runs[1] run2.seats.add( SeatFactory( course_run=run2, type=SeatTypeFactory.verified(), upgrade_deadline=datetime.datetime.now(pytz.UTC), )) original_run2_deadline = run2.seats.first().upgrade_deadline run2.end = datetime.datetime.now(pytz.UTC) run2.save() # Run with a credit entitlement, and the end date has changed should not run3 = runs[2] run3.seats.add( SeatFactory( course_run=run3, type=SeatTypeFactory.credit(), upgrade_deadline=None, )) run3.end = datetime.datetime.now(pytz.UTC) run3.save() # Verify the CourseRuns were created correctly expected_num_course_runs = len(api_data) self.assertEqual(CourseRun.objects.count(), expected_num_course_runs) # Verify multiple calls to ingest data do NOT result in data integrity errors. self.loader.ingest() calls = [ mock.call(run2), mock.call(run3), ] mock_push_to_ecomm.assert_has_calls(calls) # Make sure the verified seat with a course run end date is changed self.assertNotEqual(original_run2_deadline, run2.seats.first().upgrade_deadline) # Make sure the credit seat with a course run end date is unchanged self.assertIsNone(run3.seats.first().upgrade_deadline)
def test_courses_with_include_archived(self): """ Verify the endpoint returns the list of available and archived courses if include archived is True in catalog. """ url = reverse('api:v1:catalog-courses', kwargs={'id': self.catalog.id}) Course.objects.all().delete() now = datetime.datetime.now(pytz.UTC) future = now + datetime.timedelta(days=30) past = now - datetime.timedelta(days=30) course_run = CourseRunFactory.create( course__title='ABC Test Course With Archived', end=future, enrollment_end=future ) SeatFactory.create(course_run=course_run) # Create an archived course run CourseRunFactory.create(course=course_run.course, end=past) response = self.client.get(url) assert response.status_code == 200 # The course appearing in response should have on 1 course run assert len(response.data['results'][0]['course_runs']) == 1 # Mark include archived True in catalog self.catalog.include_archived = True self.catalog.save() response = self.client.get(url) assert response.status_code == 200 # The course appearing in response should include archived course run assert len(response.data['results'][0]['course_runs']) == 2
def test_ensure_draft_world_not_draft_course_run_given(self): course = CourseFactory() course_run = CourseRunFactory(course=course) verified_seat = SeatFactory(type='verified', course_run=course_run) audit_seat = SeatFactory(type='audit', course_run=course_run) course_run.seats.add(verified_seat, audit_seat) ensured_draft_course_run = utils.ensure_draft_world(course_run) not_draft_course_run = CourseRun.objects.get(uuid=course_run.uuid) self.assertNotEqual(ensured_draft_course_run, not_draft_course_run) self.assertEqual(ensured_draft_course_run.uuid, not_draft_course_run.uuid) self.assertTrue(ensured_draft_course_run.draft) self.assertNotEqual(ensured_draft_course_run.course, not_draft_course_run.course) self.assertEqual(ensured_draft_course_run.course.uuid, not_draft_course_run.course.uuid) # Check slugs are equal self.assertEqual(ensured_draft_course_run.slug, not_draft_course_run.slug) # Seat checks draft_seats = ensured_draft_course_run.seats.all() not_draft_seats = not_draft_course_run.seats.all() self.assertNotEqual(draft_seats, not_draft_seats) self.assertEqual(len(draft_seats), len(not_draft_seats)) for i, __ in enumerate(draft_seats): self.assertEqual(draft_seats[i].price, not_draft_seats[i].price) self.assertEqual(draft_seats[i].sku, not_draft_seats[i].sku) self.assertNotEqual(draft_seats[i].course_run, not_draft_seats[i].course_run) self.assertEqual(draft_seats[i].course_run.uuid, not_draft_seats[i].course_run.uuid) self.assertEqual(draft_seats[i].official_version, not_draft_seats[i]) self.assertEqual(not_draft_seats[i].draft_version, draft_seats[i]) # Check draft course is also created draft_course = ensured_draft_course_run.course not_draft_course = Course.objects.get(uuid=course.uuid) self.assertNotEqual(draft_course, not_draft_course) self.assertEqual(draft_course.uuid, not_draft_course.uuid) self.assertTrue(draft_course.draft) # Check official and draft versions match up self.assertEqual(ensured_draft_course_run.official_version, not_draft_course_run) self.assertEqual(not_draft_course_run.draft_version, ensured_draft_course_run)
def setUp(self): super(AffiliateWindowViewSetTests, self).setUp() self.user = UserFactory() self.client.force_authenticate(self.user) self.catalog = CatalogFactory(query='*:*', viewers=[self.user]) self.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30) self.course_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=60) self.course_run = CourseRunFactory(enrollment_end=self.enrollment_end, end=self.course_end) self.seat_verified = SeatFactory(course_run=self.course_run, type=Seat.VERIFIED) self.course = self.course_run.course self.affiliate_url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': self.catalog.id}) self.refresh_index()
def setUp(self): super(CreateCoursesTests, self).setUp() transcript_languages = LanguageTag.objects.all()[:2] self.subjects = SubjectFactory.create_batch(3) self.course = CourseFactory(subjects=self.subjects) self.command_name = 'import_metadata_courses' self.command_args = ['--start_id={}'.format(self.course.id), '--end_id={}'.format(self.course.id)] # create multiple course-runs against course. course_runs = CourseRunFactory.create_batch( 3, course=self.course, transcript_languages=transcript_languages, language=transcript_languages[0], short_description_override='Testing description' ) canonical_course_run = course_runs[0] for seat_type in ['honor', 'credit', 'verified']: # to avoid same type seat creation. SeatFactory(course_run=canonical_course_run, type=seat_type) staff = PersonFactory.create_batch(2) canonical_course_run.staff.add(*staff) self.course.canonical_course_run = canonical_course_run self.course.save() # create org and assign to the course-metadata self.forganization_extension = factories.OrganizationExtensionFactory() self.organization = self.forganization_extension.organization self.course.authoring_organizations.add(self.organization)
def test_data(self): user = UserFactory() CatalogFactory(query='*:*', viewers=[user]) course_run = CourseRunFactory() seat = SeatFactory(course_run=course_run) serializer = AffiliateWindowSerializer(seat) # Verify none of the course run attributes are empty; otherwise, Affiliate Window will report errors. # pylint: disable=no-member self.assertTrue( all((course_run.title, course_run.short_description, course_run.marketing_url))) expected = { 'pid': '{}-{}'.format(course_run.key, seat.type), 'name': course_run.title, 'desc': course_run.short_description, 'purl': course_run.marketing_url, 'price': { 'actualp': seat.price }, 'currency': seat.currency.code, 'imgurl': course_run.card_image_url, 'category': 'Other Experiences' } self.assertDictEqual(serializer.data, expected)
def test_marketable_enrollable_course_runs_with_archived(self, marketable_enrollable_course_runs_with_archived): """ Verify the endpoint filters course runs to those that are marketable and enrollable, including archived course runs (with an end date in the past). """ past = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=2) future = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2) course_run = CourseRunFactory(enrollment_start=None, enrollment_end=future, course=self.course) SeatFactory(course_run=course_run) filtered_course_runs = [ CourseRunFactory(enrollment_start=None, enrollment_end=None, course=self.course), CourseRunFactory( enrollment_start=past, enrollment_end=future, course=self.course ), CourseRunFactory(enrollment_start=future, course=self.course), CourseRunFactory(enrollment_end=past, course=self.course), ] url = reverse('api:v1:course-detail', kwargs={'key': self.course.key}) url = '{}?marketable_enrollable_course_runs_with_archived={}'.format( url, marketable_enrollable_course_runs_with_archived ) response = self.client.get(url) assert response.status_code == 200 if marketable_enrollable_course_runs_with_archived: # Emulate prefetching behavior. for course_run in filtered_course_runs: course_run.delete() assert response.data == self.serialize_course(self.course)
def test_affiliate_with_supported_seats(self): """ Verify that endpoint returns course runs for verified and professional seats only. """ response = self.client.get(self.affiliate_url) self.assertEqual(response.status_code, 200) root = ET.fromstring(response.content) self.assertEqual(1, len(root.findall('product'))) self.assert_product_xml( root.findall('product/[pid="{}-{}"]'.format( self.course_run.key, self.seat_verified.type.slug))[0], self.seat_verified) # Add professional seat seat_professional = SeatFactory(course_run=self.course_run, type=SeatTypeFactory.professional()) response = self.client.get(self.affiliate_url) root = ET.fromstring(response.content) self.assertEqual(2, len(root.findall('product'))) self.assert_product_xml( root.findall('product/[pid="{}-{}"]'.format( self.course_run.key, self.seat_verified.type.slug))[0], self.seat_verified) self.assert_product_xml( root.findall('product/[pid="{}-{}"]'.format( self.course_run.key, seat_professional.type.slug))[0], seat_professional)
def test_marketable_seats_exclusions(self, has_seats): """ Verify that the method excludes CourseRuns without seats. """ course_run = CourseRunFactory() if has_seats: SeatFactory(course_run=course_run) assert CourseRun.objects.marketable().exists() == has_seats
def test_data(self): request = make_request() course_run = CourseRunFactory() SeatFactory(course_run=course_run) serializer_context = {'request': request} serializer = FlattenedCourseRunWithCourseSerializer( course_run, context=serializer_context) expected = self.get_expected_data(request, course_run) self.assertDictEqual(serializer.data, expected)
def test_marketable_exclusions(self): """ Verify the method excludes CourseRuns without a slug. """ course_run = CourseRunFactory() SeatFactory(course_run=course_run) course_run.slug = '' # blank out auto-generated slug course_run.save() self.assertEqual(CourseRun.objects.marketable().exists(), False)
def test_marketable_course_runs_only(self, marketable_course_runs_only): """ Verify that a client requesting marketable_course_runs_only only receives course runs that are published, have seats, and can still be enrolled in. """ # Published course run with a seat, no enrollment start or end, and an end date in the future. enrollable_course_run = CourseRunFactory( status=CourseRunStatus.Published, end=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=10), enrollment_start=None, enrollment_end=None, course=self.course) SeatFactory(course_run=enrollable_course_run) # Unpublished course run with a seat. unpublished_course_run = CourseRunFactory( status=CourseRunStatus.Unpublished, course=self.course) SeatFactory(course_run=unpublished_course_run) # Published course run with no seats. no_seats_course_run = CourseRunFactory( status=CourseRunStatus.Published, course=self.course) # Published course run with a seat and an end date in the past. closed_course_run = CourseRunFactory( status=CourseRunStatus.Published, end=datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=10), course=self.course) SeatFactory(course_run=closed_course_run) url = reverse('api:v1:course-detail', kwargs={'key': self.course.key}) url = '{}?marketable_course_runs_only={}'.format( url, marketable_course_runs_only) response = self.client.get(url) assert response.status_code == 200 if marketable_course_runs_only: # Emulate prefetching behavior. for course_run in (unpublished_course_run, no_seats_course_run, closed_course_run): course_run.delete() assert response.data == self.serialize_course(self.course)
def test_marketable_unpublished_exclusions(self, is_published): """ Verify the method excludes CourseRuns with Unpublished status. """ course_run = CourseRunFactory(status=CourseRunStatus.Unpublished) SeatFactory(course_run=course_run) if is_published: course_run.status = CourseRunStatus.Published course_run.save() assert CourseRun.objects.marketable().exists() == is_published
def test_data_without_level_type(self): """ Verify the serializer handles courses with no level type set. """ request = make_request() course_run = CourseRunFactory(course__level_type=None) SeatFactory(course_run=course_run) serializer_context = {'request': request} serializer = FlattenedCourseRunWithCourseSerializer( course_run, context=serializer_context) expected = self.get_expected_data(request, course_run) self.assertDictEqual(serializer.data, expected)
def test_courses(self): """ Verify the endpoint returns the list of courses contained in the catalog. """ url = reverse('api:v1:catalog-courses', kwargs={'id': self.catalog.id}) SeatFactory(course_run=self.course_run) courses = [self.course] # These courses/course runs should not be returned because they are no longer open for enrollment. enrollment_end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=30) excluded_runs = [ CourseRunFactory(enrollment_end=enrollment_end, course__title='ABC Test Course 2'), CourseRunFactory(enrollment_end=enrollment_end, course=self.course), ] for course_run in excluded_runs: SeatFactory(course_run=course_run) with self.assertNumQueries(27): response = self.client.get(url) assert response.status_code == 200 assert response.data['results'] == self.serialize_catalog_course(courses, many=True)
def mock_courses_api(self): # Create existing seats to be removed by ingest audit_run = CourseRunFactory(title_override='audit', key='audit/course/run') verified_run = CourseRunFactory(title_override='verified', key='verified/course/run') credit_run = CourseRunFactory(title_override='credit', key='credit/course/run') no_currency_run = CourseRunFactory(title_override='no currency', key='nocurrency/course/run') SeatFactory(course_run=audit_run, type=Seat.PROFESSIONAL) SeatFactory(course_run=verified_run, type=Seat.PROFESSIONAL) SeatFactory(course_run=credit_run, type=Seat.PROFESSIONAL) SeatFactory(course_run=no_currency_run, type=Seat.PROFESSIONAL) bodies = mock_data.ECOMMERCE_API_BODIES url = self.api_url + 'courses/' responses.add_callback( responses.GET, url, callback=mock_api_callback(url, bodies), content_type=JSON ) return bodies
def test_filter_by_marketable(self): """ Verify the endpoint filters course runs to those that are marketable. """ CourseRun.objects.all().delete() expected = CourseRunFactory.create_batch(3, course__partner=self.partner) for course_run in expected: SeatFactory(course_run=course_run) CourseRunFactory.create_batch(3, slug=None, course__partner=self.partner) CourseRunFactory.create_batch(3, slug='', course__partner=self.partner) url = reverse('api:v1:course_run-list') + '?marketable=1' self.assert_list_results(url, expected)
def create_course_with_basic_active_course_run(self): course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner) course_run = CourseRunFactory(course=course, start=self.YESTERDAY, end=self.YESTERDAY, status=CourseRunStatus.Published) SeatFactory( course_run=course_run, type=SeatTypeFactory.audit(), ) return course
def test_courses_with_time_range_query(self): catalog = CatalogFactory(query='start:[2015-01-01 TO 2015-12-01]') course_run_1 = CourseRunFactory( start=datetime.datetime(2015, 9, 1, tzinfo=pytz.UTC), status=CourseRunStatus.Published, type__is_marketable=True, ) course_run_2 = CourseRunFactory( start=datetime.datetime(2015, 10, 13, tzinfo=pytz.UTC), status=CourseRunStatus.Published, type__is_marketable=True, ) SeatFactory.create(course_run=course_run_1) SeatFactory.create(course_run=course_run_2) call_command('search_index', '--rebuild', '-f') url = reverse('api:v1:catalog-courses', kwargs={'id': catalog.id}) response = self.client.get(url) assert response.status_code == 200 assert response.data['results'] == self.serialize_catalog_course( [course_run_1.course, course_run_2.course], many=True)
def test_courses_with_different_catalog_queries_but_the_same_meaning( self, query): catalog = CatalogFactory(query=query) course_run_1 = CourseRunFactory( start=datetime.datetime(2015, 9, 1, tzinfo=pytz.UTC), course__title='Science at the Polls: Biology for Voters, Part 1', status=CourseRunStatus.Published, type__is_marketable=True, ) course_run_2 = CourseRunFactory( start=datetime.datetime(2015, 10, 13, tzinfo=pytz.UTC), course__title="DNA: Biology's Genetic Code", status=CourseRunStatus.Published, type__is_marketable=True, ) course_run_3 = CourseRunFactory( status=CourseRunStatus.Published, start=datetime.datetime(2015, 1, 1, tzinfo=pytz.UTC), course__title="AP Biology", type__is_marketable=True, ) SeatFactory.create(course_run=course_run_1) SeatFactory.create(course_run=course_run_2) SeatFactory.create(course_run=course_run_3) url = reverse('api:v1:catalog-courses', kwargs={'id': catalog.id}) response = self.client.get(url) assert response.status_code == 200 assert response.data['results'] == self.serialize_catalog_course( [course_run_1.course, course_run_2.course, course_run_3.course], many=True)
def create_upgradeable_course_starting_soon(self): course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner) upgradeable_course_run = CourseRunFactory( course=course, start=self.TOMORROW, end=self.IN_FIFTEEN_DAYS, enrollment_end=self.IN_FIFTEEN_DAYS, status=CourseRunStatus.Published) SeatFactory(course_run=upgradeable_course_run, type=SeatTypeFactory.verified(), upgrade_deadline=self.IN_FIFTEEN_DAYS, price=10) return course
def create_current_upgradeable_course(self, **kwargs): course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner) current_upgradeable_course_run = CourseRunFactory( course=course, start=self.YESTERDAY, end=self.IN_FIFTEEN_DAYS, enrollment_end=self.IN_FIFTEEN_DAYS, status=CourseRunStatus.Published, **kwargs) SeatFactory(course_run=current_upgradeable_course_run, type=SeatTypeFactory.verified(), upgrade_deadline=self.TOMORROW, price=10) return course
def create_current_non_upgradeable_course(self): course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner) non_upgradeable_course_run = CourseRunFactory( course=course, start=self.YESTERDAY, end=self.IN_FIFTEEN_DAYS, enrollment_end=self.IN_FIFTEEN_DAYS, status=CourseRunStatus.Published) # not upgradeable because upgrade_deadline has passed SeatFactory(course_run=non_upgradeable_course_run, type=SeatTypeFactory.verified(), upgrade_deadline=self.YESTERDAY, price=10) return course
def test_data(self): course_run = CourseRunFactory() seat = SeatFactory(course_run=course_run) serializer = SeatSerializer(seat) expected = { 'type': seat.type, 'price': str(seat.price), 'currency': seat.currency.code, 'upgrade_deadline': json_date_format(seat.upgrade_deadline), 'credit_provider': seat.credit_provider, # pylint: disable=no-member 'credit_hours': seat.credit_hours # pylint: disable=no-member } self.assertDictEqual(serializer.data, expected)
def create_upcoming_non_upgradeable_course(self, additional_days=0): course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner) future_course_run = CourseRunFactory( course=course, start=self.IN_THREE_DAYS + datetime.timedelta(days=additional_days), end=self.IN_FIFTEEN_DAYS + datetime.timedelta(days=additional_days), enrollment_end=self.IN_THREE_DAYS + datetime.timedelta(days=additional_days), status=CourseRunStatus.Published ) SeatFactory( course_run=future_course_run, type=SeatTypeFactory.verified(), upgrade_deadline=self.YESTERDAY, price=10 ) return course
def test_index_if_non_active_course_run_is_hidden(self): course = self.create_course_with_basic_active_course_run() course.authoring_organizations.add(OrganizationFactory()) non_upgradeable_course_run = CourseRunFactory( course=course, start=self.YESTERDAY, end=self.IN_FIFTEEN_DAYS, enrollment_end=self.IN_FIFTEEN_DAYS, status=CourseRunStatus.Published, hidden=True) # not upgradeable because upgrade_deadline has passed SeatFactory(course_run=non_upgradeable_course_run, type=SeatTypeFactory.verified(), upgrade_deadline=self.YESTERDAY, price=10) assert course.should_index
class AffiliateWindowViewSetTests(ElasticsearchTestMixin, SerializationMixin, APITestCase): """ Tests for the AffiliateWindowViewSet. """ def setUp(self): super(AffiliateWindowViewSetTests, self).setUp() self.user = UserFactory() self.client.force_authenticate(self.user) self.catalog = CatalogFactory(query='*:*', viewers=[self.user]) self.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30) self.course_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=60) self.course_run = CourseRunFactory(enrollment_end=self.enrollment_end, end=self.course_end) self.seat_verified = SeatFactory(course_run=self.course_run, type=Seat.VERIFIED) self.course = self.course_run.course self.affiliate_url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': self.catalog.id}) self.refresh_index() def test_without_authentication(self): """ Verify authentication is required when accessing the endpoint. """ self.client.logout() response = self.client.get(self.affiliate_url) self.assertEqual(response.status_code, 403) def test_affiliate_with_supported_seats(self): """ Verify that endpoint returns course runs for verified and professional seats only. """ response = self.client.get(self.affiliate_url) self.assertEqual(response.status_code, 200) root = ET.fromstring(response.content) self.assertEqual(1, len(root.findall('product'))) self.assert_product_xml( root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, self.seat_verified.type))[0], self.seat_verified ) # Add professional seat. seat_professional = SeatFactory(course_run=self.course_run, type=Seat.PROFESSIONAL) response = self.client.get(self.affiliate_url) root = ET.fromstring(response.content) self.assertEqual(2, len(root.findall('product'))) self.assert_product_xml( root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, self.seat_verified.type))[0], self.seat_verified ) self.assert_product_xml( root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, seat_professional.type))[0], seat_professional ) @ddt.data(Seat.CREDIT, Seat.HONOR, Seat.AUDIT) def test_with_non_supported_seats(self, non_supporting_seat): """ Verify that endpoint returns no data for honor, credit and audit seats. """ self.seat_verified.type = non_supporting_seat self.seat_verified.save() response = self.client.get(self.affiliate_url) self.assertEqual(response.status_code, 200) root = ET.fromstring(response.content) self.assertEqual(0, len(root.findall('product'))) def test_with_closed_enrollment(self): """ Verify that endpoint returns no data if enrollment is close. """ self.course_run.enrollment_end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=100) self.course_run.end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=100) self.course_run.save() # new course run with future end date and no enrollment_date. CourseRunFactory(end=self.course_end, course=self.course, enrollment_end=None) response = self.client.get(self.affiliate_url) self.assertEqual(response.status_code, 200) root = ET.fromstring(response.content) self.assertEqual(0, len(root.findall('product'))) def assert_product_xml(self, content, seat): """ Helper method to verify product data in xml format. """ self.assertEqual(content.find('pid').text, '{}-{}'.format(self.course_run.key, seat.type)) self.assertEqual(content.find('name').text, self.course_run.title) self.assertEqual(content.find('desc').text, self.course_run.short_description) self.assertEqual(content.find('purl').text, self.course_run.marketing_url) self.assertEqual(content.find('imgurl').text, self.course_run.image.src) self.assertEqual(content.find('price/actualp').text, str(seat.price)) self.assertEqual(content.find('currency').text, seat.currency.code) self.assertEqual(content.find('category').text, AffiliateWindowSerializer.CATEGORY) def test_dtd_with_valid_data(self): """ Verify the XML data produced by the endpoint conforms to the DTD file. """ response = self.client.get(self.affiliate_url) self.assertEqual(response.status_code, 200) filename = abspath(join(dirname(dirname(__file__)), 'affiliate_window_product_feed.1.4.dtd')) dtd = etree.DTD(open(filename)) root = etree.XML(response.content) self.assertTrue(dtd.validate(root)) def test_permissions(self): """ Verify only users with the appropriate permissions can access the endpoint. """ catalog = CatalogFactory() superuser = UserFactory(is_superuser=True) url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': catalog.id}) # Superusers can view all catalogs self.client.force_authenticate(superuser) response = self.client.get(url) self.assertEqual(response.status_code, 200) # Regular users can only view catalogs belonging to them self.client.force_authenticate(self.user) response = self.client.get(url) self.assertEqual(response.status_code, 403) catalog.viewers = [self.user] response = self.client.get(url) self.assertEqual(response.status_code, 200)
def test_marketable(self): """ Verify the method filters CourseRuns to those with slugs. """ course_run = CourseRunFactory() SeatFactory(course_run=course_run) assert list(CourseRun.objects.marketable()) == [course_run]
def test_csv(self): SeatFactory(type='audit', course_run=self.course_run) SeatFactory(type='verified', course_run=self.course_run) SeatFactory(type='credit', course_run=self.course_run, credit_provider='ASU', credit_hours=9) SeatFactory(type='credit', course_run=self.course_run, credit_provider='Hogwarts', credit_hours=4) url = reverse('api:v1:catalog-csv', kwargs={'id': self.catalog.id}) with self.assertNumQueries(20): response = self.client.get(url) course_run = self.serialize_catalog_flat_course_run(self.course_run) expected = [ course_run['key'], course_run['title'], course_run['pacing_type'], course_run['start'], course_run['end'], course_run['enrollment_start'], course_run['enrollment_end'], course_run['announcement'], course_run['full_description'], course_run['short_description'], course_run['marketing_url'], course_run['image']['src'], '', '', '', course_run['video']['src'], course_run['video']['description'], course_run['video']['image']['src'], course_run['video']['image']['description'], str(course_run['video']['image']['height']), str(course_run['video']['image']['width']), course_run['content_language'], str(course_run['level_type']), str(course_run['max_effort']), str(course_run['min_effort']), course_run['subjects'], course_run['expected_learning_items'], course_run['prerequisites'], course_run['owners'], course_run['sponsors'], course_run['seats']['audit']['type'], course_run['seats']['honor']['type'], course_run['seats']['professional']['type'], str(course_run['seats']['professional']['price']), course_run['seats']['professional']['currency'], course_run['seats']['professional']['upgrade_deadline'], course_run['seats']['verified']['type'], str(course_run['seats']['verified']['price']), course_run['seats']['verified']['currency'], course_run['seats']['verified']['upgrade_deadline'], '{}'.format(course_run['seats']['credit']['type']), '{}'.format(str(course_run['seats']['credit']['price'])), '{}'.format(course_run['seats']['credit']['currency']), '{}'.format(course_run['seats']['credit']['upgrade_deadline']), '{}'.format(course_run['seats']['credit']['credit_provider']), '{}'.format(course_run['seats']['credit']['credit_hours']), course_run['modified'], course_run['course_key'], ] # collect streamed content received_content = b'' for item in response.streaming_content: received_content += item # convert received content to csv for comparison f = StringIO(received_content.decode('utf-8')) reader = csv.reader(f) content = list(reader) self.assertEqual(response.status_code, 200) self.assertEqual(set(expected), set(content[1]))
def test_marketable_exclusions(self, slug): """ Verify the method excludes CourseRuns without a slug. """ course_run = CourseRunFactory(slug=slug) SeatFactory(course_run=course_run) self.assertEqual(CourseRun.objects.marketable().exists(), False)