def test_results_filtered_by_default_partner(self): """ Verify the search results only include items related to the default partner if no partner is specified on the request. If a partner is included, the data should be filtered to the requested partner. """ course_run = CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Published) program = ProgramFactory(partner=self.partner, status=ProgramStatus.Active) # This data should NOT be in the results other_partner = PartnerFactory() other_course_run = CourseRunFactory(course__partner=other_partner, status=CourseRunStatus.Published) other_program = ProgramFactory(partner=other_partner, status=ProgramStatus.Active) assert other_program.partner.short_code != self.partner.short_code assert other_course_run.course.partner.short_code != self.partner.short_code response = self.get_response() assert response.status_code == 200 response_data = response.json() assert response_data['objects']['results'] == \ [self.serialize_program_search(program), self.serialize_course_run_search(course_run)] # Filter results by partner response = self.get_response({'partner': other_partner.short_code}) assert response.status_code == 200 response_data = response.json() assert response_data['objects']['results'] == \ [self.serialize_program_search(other_program), self.serialize_course_run_search(other_course_run)]
def test_results_filtered_by_default_partner(self): """ Verify the search results only include items related to the default partner if no partner is specified on the request. If a partner is included, the data should be filtered to the requested partner. """ course_run = CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Published) program = ProgramFactory(partner=self.partner, status=ProgramStatus.Active) # This data should NOT be in the results other_partner = PartnerFactory() other_course_run = CourseRunFactory(course__partner=other_partner, status=CourseRunStatus.Published) other_program = ProgramFactory(partner=other_partner, status=ProgramStatus.Active) self.assertNotEqual(other_program.partner.short_code, self.partner.short_code) self.assertNotEqual(other_course_run.course.partner.short_code, self.partner.short_code) response = self.get_response() self.assertEqual(response.status_code, 200) response_data = json.loads(response.content.decode('utf-8')) self.assertListEqual(response_data['objects']['results'], [ self.serialize_program(program), self.serialize_course_run(course_run) ]) # Filter results by partner response = self.get_response({'partner': other_partner.short_code}) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content.decode('utf-8')) self.assertListEqual(response_data['objects']['results'], [ self.serialize_program(other_program), self.serialize_course_run(other_course_run) ])
def setUp(self): super().setUp() self.user = UserFactory() self.client.force_authenticate(self.user) self.catalog = CatalogFactory(query='*:*', program_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.course = self.course_run.course # Generate test programs self.test_image = make_image_file('test_banner.jpg') self.masters_program_type = ProgramType.objects.get(slug=ProgramType.MASTERS) self.microbachelors_program_type = ProgramType.objects.get(slug=ProgramType.MICROBACHELORS) self.ms_program = ProgramFactory( type=self.masters_program_type, courses=[self.course], banner_image=self.test_image, ) self.program = ProgramFactory( type=self.microbachelors_program_type, courses=[self.course], banner_image=self.test_image, ) self.affiliate_url = reverse('api:v1:partners:programs_affiliate_window-detail', kwargs={'pk': self.catalog.id})
def test_exclude_unavailable_program_types(self, path, serializer, result_location_keys, program_status, expected_queries): """ Verify that unavailable programs do not show in the program_types representation. """ course_run = CourseRunFactory(course__partner=self.partner, course__title='Software Testing', status=CourseRunStatus.Published) active_program = ProgramFactory(courses=[course_run.course], status=ProgramStatus.Active) ProgramFactory(courses=[course_run.course], status=program_status) self.reindex_courses(active_program) with self.assertNumQueries(expected_queries): response = self.get_response('software', path=path) assert response.status_code == 200 response_data = response.json() # Validate the search results expected = { 'count': 1, 'results': [ self.serialize_course_run_search(course_run, serializer=serializer) ] } self.assertDictContainsSubset(expected, response_data) # Check that the program is indeed the active one. for key in result_location_keys: response_data = response_data[key] assert response_data == active_program.type.name
def test_typeahead_org_course_runs_come_up_first(self): """ Test typeahead response to ensure org is taken into account. """ MITx = OrganizationFactory(key='MITx') HarvardX = OrganizationFactory(key='HarvardX') mit_run = CourseRunFactory(authoring_organizations=[MITx, HarvardX], title='MIT Testing1', course__partner=self.partner) harvard_run = CourseRunFactory(authoring_organizations=[HarvardX], title='MIT Testing2', course__partner=self.partner) mit_program = ProgramFactory(authoring_organizations=[MITx, HarvardX], title='MIT Testing1', partner=self.partner) harvard_program = ProgramFactory(authoring_organizations=[HarvardX], title='MIT Testing2', partner=self.partner) response = self.get_response({'q': 'mit'}) self.assertEqual(response.status_code, 200) expected = { 'course_runs': [ self.serialize_course_run_search(mit_run), self.serialize_course_run_search(harvard_run) ], 'programs': [ self.serialize_program_search(mit_program), self.serialize_program_search(harvard_program) ] } self.assertDictEqual(response.data, expected)
def test_unpublished_and_hidden_courses(self): """ Verify that typeahead does not return unpublished or hidden courses or programs that are not active. """ title = "supply" course_run = CourseRunFactory(title=title, course__partner=self.partner) CourseRunFactory(title=title + "unpublished", status=CourseRunStatus.Unpublished, course__partner=self.partner) CourseRunFactory(title=title + "hidden", hidden=True, course__partner=self.partner) program = ProgramFactory(title=title, status=ProgramStatus.Active, partner=self.partner) ProgramFactory(title=title + "unpublished", status=ProgramStatus.Unpublished, partner=self.partner) query = "suppl" response = self.get_response({'q': query}) self.assertEqual(response.status_code, 200) response_data = response.json() expected_response_data = { 'course_runs': [self.serialize_course_run_search(course_run)], 'programs': [self.serialize_program_search(program)] } self.assertDictEqual(response_data, expected_response_data)
def test_get_choices_cutoff(self): """ We should slice the queryset if provided a cutoff parameter """ ProgramFactory() ProgramFactory() serializer = SlugRelatedFieldWithReadSerializer(slug_field='uuid', queryset=Program.objects.all(), read_serializer=ProgramSerializer()) self.assertEqual(len(serializer.get_choices()), 2) self.assertEqual(len(serializer.get_choices(cutoff=1)), 1)
def test_program_type_boosting(self, program_type): """Verify MicroMasters and Professional Certificate are boosted over XSeries.""" ProgramFactory(type=ProgramType.objects.get(translations__name_t='XSeries')) test_record = ProgramFactory(type=ProgramType.objects.get(translations__name_t=program_type)) search_results = SearchQuerySet().models(Program).all() assert len(search_results) == 2 assert search_results[0].score > search_results[1].score assert str(test_record.type) == str(search_results[0].type)
def test_only_matching_partner(self): pathway = PathwayFactory(partner=self.partner) pathway.programs.add(ProgramFactory(partner=pathway.partner)) non_partner_pathway = PathwayFactory() non_partner_pathway.programs.add(ProgramFactory(partner=non_partner_pathway.partner)) response = self.client.get(self.list_path) assert response.status_code == 200 assert response.data['results'] == self.serialize_pathway([pathway], many=True)
def test_program_type_boosting(self, program_type): """ Verify MicroMasters and Professional Certificate are boosted over XSeries.""" ProgramFactory(type=ProgramType.objects.get(name='XSeries')) test_record = ProgramFactory(type=ProgramType.objects.get( name=program_type)) search_results = SearchQuerySet().models(Program).all() self.assertEqual(2, len(search_results)) self.assertGreater(search_results[0].score, search_results[1].score) self.assertEqual(str(test_record.type), str(search_results[0].type))
def test_program_type_boosting(self, program_type): """Verify MicroMasters and Professional Certificate are boosted over XSeries.""" ProgramFactory(type=ProgramType.objects.get( translations__name_t='XSeries')) test_record = ProgramFactory(type=ProgramType.objects.get( translations__name_t=program_type)) search_results = ProgramDocument.search().query( ESDSLQ('match_all')).execute() assert len(search_results) == 2 assert search_results[0].meta['score'] > search_results[1].meta['score'] assert str(test_record.type) == str(search_results[0].type)
def setUp(self): super(TestLoadProgramFixture, self).setUp() self.pk_generator = itertools.count(1) stored_site, created = Site.objects.get_or_create( # pylint: disable=unused-variable domain='example.com') self.default_partner = Partner.objects.create(site=stored_site, name='edX', short_code='edx') SeatType.objects.all().delete() ProgramType.objects.all().delete() self.partner = PartnerFactory(name='Test') self.organization = OrganizationFactory(partner=self.partner) self.seat_type_verified = SeatTypeFactory(name='Verified', slug='verified') self.program_type_masters = ProgramTypeFactory( name='Masters', slug='masters', applicable_seat_types=[self.seat_type_verified]) self.program_type_mm = ProgramTypeFactory( name='MicroMasters', slug='micromasters', applicable_seat_types=[self.seat_type_verified]) self.course = CourseFactory( partner=self.partner, authoring_organizations=[self.organization]) self.course_run = CourseRunFactory(course=self.course) self.program = ProgramFactory( type=self.program_type_masters, partner=self.partner, authoring_organizations=[self.organization]) self.course_mm = CourseFactory( partner=self.partner, authoring_organizations=[self.organization]) self.course_run_mm = CourseRunFactory(course=self.course) self.program_mm = ProgramFactory( type=self.program_type_mm, partner=self.partner, authoring_organizations=[self.organization], courses=[self.course_mm]) self.curriculum = CurriculumFactory(program=self.program) self.curriculum_course_membership = CurriculumCourseMembershipFactory( course=self.course, curriculum=self.curriculum) self.curriculum_program_membership = CurriculumProgramMembershipFactory( program=self.program_mm, curriculum=self.curriculum) self.program_2 = ProgramFactory( type=self.program_type_masters, partner=self.partner, authoring_organizations=[self.organization]) self._mock_oauth_request()
def testNormalRun(self): program = ProgramFactory() program1 = ProgramFactory() self.config.program_hooks = '''{uuid}:Bananas in pajamas {uuid1}:Are coming down the stairs'''.format(uuid=program.uuid, uuid1=program1.uuid) self.config.save() call_command('modify_program_hooks') program.refresh_from_db() program1.refresh_from_db() self.assertEqual(program.marketing_hook, 'Bananas in pajamas') self.assertEqual(program1.marketing_hook, 'Are coming down the stairs')
def test_typeahead_hidden_programs(self): """ Verify that typeahead does not return hidden programs. """ title = "hiddenprogram" program = ProgramFactory(title=title, hidden=False, status=ProgramStatus.Active, partner=self.partner) ProgramFactory(title=program.title + 'hidden', hidden=True, status=ProgramStatus.Active, partner=self.partner) response = self.get_response({'q': program.title}) self.assertEqual(response.status_code, 200) response_data = response.json() expected_response_data = { 'course_runs': [], 'programs': [self.serialize_program_search(program)] } self.assertDictEqual(response_data, expected_response_data)
def test_filter_by_status(self): """ Verify the endpoint allows programs to filtered by one, or more, statuses. """ active = ProgramFactory(status=ProgramStatus.Active, partner=self.partner) retired = ProgramFactory(status=ProgramStatus.Retired, partner=self.partner) url = self.list_path + '?status=active' self.assert_list_results(url, [active], 10) url = self.list_path + '?status=retired' self.assert_list_results(url, [retired], 10) url = self.list_path + '?status=active&status=retired' self.assert_list_results(url, [retired, active], 10)
def test_results_only_include_published_objects(self): """ Verify the search results only include items with status set to 'Published'. """ # These items should NOT be in the results CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Unpublished) ProgramFactory(partner=self.partner, status=ProgramStatus.Unpublished) course_run = CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Published) program = ProgramFactory(partner=self.partner, status=ProgramStatus.Active) response = self.get_response() assert response.status_code == 200 response_data = response.json() assert response_data['objects']['results'] == \ [self.serialize_program_search(program), self.serialize_course_run_search(course_run)]
def test_query_count_exclude_expired_course_run(self, exclude_expired, expected_queries): """ Verify that there is no query explosion when excluding expired course runs. """ program = ProgramFactory(partner=self.partner, status=ProgramStatus.Active) course_run = CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Published) course_run2 = CourseRunFactory(course=course_run.course, status=CourseRunStatus.Published) course_run3 = CourseRunFactory(course=course_run.course, status=CourseRunStatus.Published) course_run4 = CourseRunFactory(course=course_run.course, status=CourseRunStatus.Published) self.reindex_courses(program) query = {'partner': self.partner.short_code} if exclude_expired: query['exclude_expired_course_run'] = 'True' # Filter results by partner with self.assertNumQueries(expected_queries): response = self.get_response( query, endpoint='api:v1:search-all-list' ) assert response.status_code == 200 response_data = response.json() expected = [ self.serialize_course_run_search(run) for run in (course_run, course_run2, course_run3, course_run4) ] + [ self.serialize_program_search(program), # We need to render the json, and then parse it again, to get all of the formatted # data the same as the data coming out of search. json.loads(JSONRenderer().render(self.serialize_course_search(course_run.course)).decode('utf-8')), ] self.assertCountEqual(response_data['results'], expected)
def test_filter_by_hidden(self): """ Endpoint should filter programs by their hidden attribute value. """ hidden = ProgramFactory(hidden=True, partner=self.partner) not_hidden = ProgramFactory(hidden=False, partner=self.partner) url = self.list_path + '?hidden=True' self.assert_list_results(url, [hidden], 11) url = self.list_path + '?hidden=False' self.assert_list_results(url, [not_hidden], 11) url = self.list_path + '?hidden=1' self.assert_list_results(url, [hidden], 11) url = self.list_path + '?hidden=0' self.assert_list_results(url, [not_hidden], 11)
def test_uuids_only(self): """ Verify that the list view returns a simply list of UUIDs when the uuids_only query parameter is passed. """ active = ProgramFactory.create_batch(3, partner=self.partner) retired = [ ProgramFactory(status=ProgramStatus.Retired, partner=self.partner) ] programs = active + retired querystring = {'uuids_only': 1} url = '{base}?{query}'.format( base=self.list_path, query=urllib.parse.urlencode(querystring)) response = self.client.get(url) assert set(response.data) == {program.uuid for program in programs} # Verify that filtering (e.g., by status) is still supported. querystring['status'] = ProgramStatus.Retired url = '{base}?{query}'.format( base=self.list_path, query=urllib.parse.urlencode(querystring)) response = self.client.get(url) assert set(response.data) == {program.uuid for program in retired}
def test_retrieve_without_course_runs(self, django_assert_num_queries): """ Verify the endpoint returns data for a program even if the program's courses have no course runs. """ course = CourseFactory(partner=self.partner) program = ProgramFactory(courses=[course], partner=self.partner) with django_assert_num_queries(FuzzyInt(27, 2)): response = self.assert_retrieve_success(program) assert response.data == self.serialize_program(program)
def test_retrieve_without_course_runs(self): """ Verify the endpoint returns data for a program even if the program's courses have no course runs. """ course = CourseFactory() program = ProgramFactory(courses=[course]) with self.assertNumQueries(19): response = self.assert_retrieve_success(program) assert response.data == self.serialize_program(program)
def _define_course_metadata(self): # Add course runs, courses, and programs to DB to copy data into courses = {} # Course runs map to courses in the way opaque keys would (without actually using opaque keys code) course_to_run_mapping = { '00test/00test/00test': '00test/00test', '00test/00test/01test': '00test/00test', '00test/01test/00test': '00test/01test', '00test/01test/01test': '00test/01test', '00test/01test/02test': '00test/01test', '00test/02test/00test': '00test/02test' } for course_summary in self.mocked_data: course_run_key = course_summary['course_id'] course_key = course_to_run_mapping[course_run_key] if course_key in courses: course = courses[course_key] course_run = CourseRunFactory(key=course_summary['course_id'], course=course) course_run.save() else: course = CourseFactory(key=course_key) course.save() course_run = CourseRunFactory(key=course_summary['course_id'], course=course) course_run.save() courses[course_key] = course_run.course # Create a program with all of the courses we created program = ProgramFactory() program.courses.set(courses.values()) # pylint: disable=no-member
def test_stemmed_synonyms(self): """ Test that synonyms work with stemming from the snowball analyzer """ title = 'Running' ProgramFactory(title=title, partner=self.partner) response1 = self.process_response({'q': 'running'}) response2 = self.process_response({'q': 'jogging'}) self.assertDictEqual(response1, response2)
def test_title_synonyms(self): """ Test that synonyms work for terms in the title """ CourseRunFactory(title='HTML', course__partner=self.partner) ProgramFactory(title='HTML', partner=self.partner) response1 = self.process_response({'q': 'HTML5'}) response2 = self.process_response({'q': 'HTML'}) self.assertDictEqual(response1, response2)
def testWeirdCharactersInHookText(self): program = ProgramFactory() self.config.program_hooks = '%s:+:[{])(%%' % program.uuid self.config.save() call_command('modify_program_hooks') program.refresh_from_db() self.assertEqual(program.marketing_hook, '+:[{])(%')
def test_with_exclusions(self): """ Test serializer with course_run exclusions within program """ request = make_request() course = CourseFactory() excluded_runs = [] course_runs = CourseRunFactory.create_batch(2, course=course) excluded_runs.append(course_runs[0]) program = ProgramFactory(courses=[course], excluded_course_runs=excluded_runs) serializer_context = { 'request': request, 'program': program, 'course_runs': list(program.course_runs) } serializer = MinimalProgramCourseSerializer(course, context=serializer_context) expected = MinimalCourseSerializer(course, context=serializer_context).data expected['course_runs'] = MinimalCourseRunSerializer([course_runs[1]], many=True, context={ 'request': request }).data self.assertDictEqual(serializer.data, expected)
def test_with_published_course_runs_only_context(self): """ Verify setting the published_course_runs_only context value excludes unpublished course runs. """ # Create a program and course. The course should have both published and un-published course runs. request = make_request() course = CourseFactory() program = ProgramFactory(courses=[course]) unpublished_course_run = CourseRunFactory( status=CourseRunStatus.Unpublished, course=course) CourseRunFactory(status=CourseRunStatus.Published, course=course) # We do NOT expect the results to included the unpublished data expected = MinimalCourseSerializer(course, context={ 'request': request }).data expected['course_runs'] = [ course_run for course_run in expected['course_runs'] if course_run['key'] != str(unpublished_course_run.key) ] self.assertEqual(len(expected['course_runs']), 1) serializer = MinimalProgramCourseSerializer( course, context={ 'request': request, 'program': program, 'published_course_runs_only': True, 'course_runs': list(program.course_runs), }) self.assertSequenceEqual(serializer.data, expected)
def test_to_representation(self): """ Should be using provided serializer, rather than the slug """ program = ProgramFactory() serializer = SlugRelatedFieldWithReadSerializer( slug_field='uuid', queryset=Program.objects.all(), read_serializer=ProgramSerializer()) assert isinstance(serializer.to_representation(program), dict)
def test_publish_modified_program(self): self.mock_api_client(200) self.mock_node_retrieval(self.program.uuid) self.mock_node_edit(200) program_before = ProgramFactory() publisher = MarketingSitePublisher(program_before) publisher.publish_program(self.program) self.assert_responses_call_count(6)
def test_get_exclude_deleted_programs(self): """ Verify the endpoint returns no deleted associated programs """ ProgramFactory(courses=[self.course], status=ProgramStatus.Deleted) url = reverse('api:v1:course-detail', kwargs={'key': self.course.key}) with self.assertNumQueries(18): response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.data.get('programs'), [])