Ejemplo n.º 1
0
    def test_search_single(self):
        org = OrganizationFactory()
        course = CourseFactory(authoring_organizations=[org])
        person1 = PersonFactory(partner=self.partner)
        person2 = PersonFactory(partner=self.partner)
        PersonFactory(partner=self.partner)
        CourseRunFactory(staff=[person1, person2], course=course)

        facet_name = 'organizations_exact:{org_key}'.format(org_key=org.key)
        self.reindex_people(person1)
        self.reindex_people(person2)

        query = {'selected_facets': facet_name}
        qs = urllib.parse.urlencode(query)
        url = '{path}?{qs}'.format(path=self.path, qs=qs)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        self.assertEqual(response_data['objects']['count'], 2)

        query = {'selected_facets': facet_name, 'q': person1.uuid}
        qs = urllib.parse.urlencode(query)
        url = '{path}?{qs}'.format(path=self.path, qs=qs)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        self.assertEqual(response_data['objects']['count'], 1)
        self.assertEqual(response_data['objects']['results'][0]['uuid'],
                         str(person1.uuid))
        self.assertEqual(response_data['objects']['results'][0]['full_name'],
                         person1.full_name)
Ejemplo n.º 2
0
    def test_run_handles_pagination(self):
        """ Verify that run supports paginated queries. """
        course_1 = CourseFactory()
        for _ in range(5):
            CourseRunFactory(title='foo', course=course_1)

        query = DistinctCountsSearchQuery()
        query.aggregation_key = 'aggregation_key'
        query.add_filter(SQ(title='foo'))
        query.add_model(CourseRun)

        query.run()
        all_results = query._results
        assert len(all_results) == 5

        query._reset()
        query.set_limits(low=1, high=3)
        query.run()

        paginated_results = query._results
        assert len(paginated_results) == 2

        expected = sorted([run.key for run in all_results[1:3]])
        actual = sorted([run.key for run in paginated_results])
        assert expected == actual
Ejemplo n.º 3
0
    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,
                threshold=1):  # travis sometimes adds a query
            response = self.get_response('software', path=path)
        assert response.status_code == 200
        response_data = response.data

        # 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
Ejemplo n.º 4
0
    def assert_successful_search(self, path=None, serializer=None):
        """ Asserts the search functionality returns results for a generated query. """
        # Generate data that should be indexed and returned by the query
        course_run = CourseRunFactory(course__partner=self.partner,
                                      course__title='Software Testing',
                                      status=CourseRunStatus.Published)
        response = self.get_response('software', path=path)

        assert response.status_code == 200
        response_data = response.data

        # Validate the search results
        expected = {
            'count':
            1,
            'results': [
                self.serialize_course_run_search(course_run,
                                                 serializer=serializer)
            ]
        }
        actual = response_data[
            'objects'] if path == self.faceted_path else response_data
        self.assertDictContainsSubset(expected, actual)

        return course_run, response_data
Ejemplo n.º 5
0
 def create_program(self, orgs):
     program = ProgramFactory(
         authoring_organizations=orgs, type=self.program_type
     )
     curr = CurriculumFactory(program=program)
     course1_draft = CourseFactory(draft=True)
     course1 = CourseFactory(draft_version=course1_draft)
     _run1a = CourseRunFactory(course=course1)
     _run1b = CourseRunFactory(course=course1)
     course2 = CourseFactory()
     _run2a = CourseRunFactory(course=course2)
     run2b = CourseRunFactory(course=course2)
     _mem1 = CurriculumCourseMembershipFactory(curriculum=curr, course=course1)
     mem2 = CurriculumCourseMembershipFactory(curriculum=curr, course=course2)
     _ex = CurriculumCourseRunExclusionFactory(course_membership=mem2, course_run=run2b)
     return program
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
    def test_from_queryset(self):
        """ Verify that a DistinctCountsSearchQuerySet can be built from an existing SearchQuerySet."""
        course_1 = CourseFactory()
        CourseRunFactory(title='foo', course=course_1)
        CourseRunFactory(title='foo', course=course_1)

        course_2 = CourseFactory()
        CourseRunFactory(title='foo', course=course_2)
        CourseRunFactory(title='bar', course=course_2)
        queryset = DSLFacetedSearch(
            index=CourseRunDocument._index._name).filter('term', title='foo')
        dc_queryset = DistinctCountsSearchQuerySet.from_queryset(queryset)

        expected = sorted([run.key for run in queryset])
        actual = sorted([run.key for run in dc_queryset])
        assert expected == actual
Ejemplo n.º 8
0
    def test_create_run(self):
        run = CourseRunFactory()
        self.api.create_course_run_in_studio(run)

        expected_data = self.make_studio_data(run)
        self.assertEqual(self.client.course_runs.post.call_args_list[0][0][0],
                         expected_data)
Ejemplo n.º 9
0
    def test_create_run(self):
        run = CourseRunFactory()

        expected_data = self.make_studio_data(run)
        responses.add(responses.POST, f'{self.studio_url}api/v1/course_runs/',
                      match=[responses.matchers.json_params_matcher(expected_data)])

        self.api.create_course_run_in_studio(run)
Ejemplo n.º 10
0
    def test_update_run(self):
        run = CourseRunFactory()

        expected_data = self.make_studio_data(run, add_pacing=False, add_schedule=False)
        responses.add(responses.PATCH, f'{self.studio_url}api/v1/course_runs/{run.key}/',
                      match=[responses.matchers.json_params_matcher(expected_data)])

        self.api.update_course_run_details_in_studio(run)
Ejemplo n.º 11
0
 def setUp(self):
     super().setUp()
     self.user = UserFactory(is_staff=True, is_superuser=True)
     self.client.force_authenticate(self.user)
     self.course = CourseFactory(partner=self.partner, key='simple_key')
     self.course_run = CourseRunFactory(course=self.course)
     self.url_base = reverse('api:v1:catalog-query_contains')
     self.error_message = 'CatalogQueryContains endpoint requires query and identifiers list(s)'
Ejemplo n.º 12
0
 def test_update_course_run_image_in_studio_without_course_image(self):
     run = CourseRunFactory(course__image=None)
     with mock.patch(
             'course_discovery.apps.api.utils.logger') as mock_logger:
         self.api.update_course_run_image_in_studio(run)
         mock_logger.warning.assert_called_with(
             'Card image for course run [%d] cannot be updated. The related course [%d] has no image defined.',
             run.id, run.course.id)
Ejemplo n.º 13
0
    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)]
Ejemplo n.º 14
0
    def test_course_availability_empty_if_no_published_runs(self):
        course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner)
        CourseRunFactory(
            course=course,
            status=CourseRunStatus.Unpublished,
        )

        assert course.availability_level == []
Ejemplo n.º 15
0
    def test_distinct_count_runs_query_when_cache_is_empty(self):
        """ Verify that distinct_count runs the query, caches, and returns the distinct_count when cache is empty."""
        course_1 = CourseFactory()
        CourseRunFactory(title='foo', course=course_1)
        CourseRunFactory(title='foo', course=course_1)

        course_2 = CourseFactory()
        CourseRunFactory(title='foo', course=course_2)
        CourseRunFactory(title='bar', course=course_2)

        queryset = SearchQuerySet().filter(title='foo').models(CourseRun)
        dc_queryset = DistinctCountsSearchQuerySet.from_queryset(
            queryset).with_distinct_counts('aggregation_key')

        assert dc_queryset._distinct_result_count is None  # pylint: disable=protected-access
        assert dc_queryset.distinct_count() == 2
        assert dc_queryset._distinct_result_count == 2  # pylint: disable=protected-access
Ejemplo n.º 16
0
    def test_get_draft(self):
        extra = CourseRunFactory(course=self.draft.course)

        with pytest.raises(CourseRun.DoesNotExist):
            CourseRun.objects.get_draft(hidden=True)
        with pytest.raises(CourseRun.MultipleObjectsReturned):
            CourseRun.objects.get_draft(course=extra.course)
        assert CourseRun.objects.get_draft(uuid=self.draft.uuid) == self.draft
Ejemplo n.º 17
0
    def test_ensure_draft_world_draft_obj_given(self):
        course_run = CourseRunFactory(draft=True)
        ensured_draft_course_run = utils.ensure_draft_world(course_run)

        self.assertEqual(ensured_draft_course_run, course_run)
        self.assertEqual(ensured_draft_course_run.id, course_run.id)
        self.assertEqual(ensured_draft_course_run.uuid, course_run.uuid)
        self.assertEqual(ensured_draft_course_run.draft, course_run.draft)
Ejemplo n.º 18
0
    def test_results_ordered_by_start_date(self, ordering):
        """ Verify the search results can be ordered by start date """
        now = datetime.datetime.now(pytz.UTC)
        archived = CourseRunFactory(course__partner=self.partner, start=now - datetime.timedelta(weeks=2))
        current = CourseRunFactory(course__partner=self.partner, start=now - datetime.timedelta(weeks=1))
        starting_soon = CourseRunFactory(course__partner=self.partner, start=now + datetime.timedelta(weeks=3))
        upcoming = CourseRunFactory(course__partner=self.partner, start=now + datetime.timedelta(weeks=4))
        course_run_keys = [course_run.key for course_run in [archived, current, starting_soon, upcoming]]

        with self.assertNumQueries(6):
            response = self.get_response({"ordering": ordering})
        assert response.status_code == 200
        assert response.data['objects']['count'] == 4

        course_runs = CourseRun.objects.filter(key__in=course_run_keys).order_by(ordering)
        expected = [self.serialize_course_run_search(course_run) for course_run in course_runs]
        assert response.data['objects']['results'] == expected
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
    def test_facet_counts_caches_results(self):
        """ Verify that facet_counts cache results when it is forced to run the query."""
        course = CourseFactory()
        runs = [
            CourseRunFactory(title='foo',
                             pacing_type='self_paced',
                             hidden=True,
                             course=course),
            CourseRunFactory(title='foo',
                             pacing_type='self_paced',
                             hidden=True,
                             course=course),
            CourseRunFactory(title='foo',
                             pacing_type='instructor_paced',
                             hidden=False,
                             course=course),
        ]

        queryset = SearchQuerySet().filter(title='foo').models(CourseRun)
        queryset = queryset.facet('pacing_type').query_facet(
            'hidden', 'hidden:true')
        dc_queryset = DistinctCountsSearchQuerySet.from_queryset(
            queryset).with_distinct_counts('aggregation_key')

        # This should force the query to run and the results to be cached
        facet_counts = dc_queryset.facet_counts()

        with mock.patch.object(DistinctCountsSearchQuery, 'run') as mock_run:
            # Calling facet_counts again shouldn't result in an additional query
            cached_facet_counts = dc_queryset.facet_counts()
            assert not mock_run.called
            assert facet_counts == cached_facet_counts

            # Calling count shouldn't result in another query, as we should have already cached it with the
            # first request.
            count = dc_queryset.count()
            assert not mock_run.called
            assert count == len(runs)

            # Fetching the results shouldn't result in another query, as we should have already cached them
            # with the initial request.
            results = dc_queryset[:]
            assert not mock_run.called
            expected = {run.key for run in runs}
            actual = {run.key for run in results}
            assert expected == actual
Ejemplo n.º 21
0
    def test_availability_faceting(self):
        """ Verify the endpoint returns availability facets with the results. """
        now = datetime.datetime.now(pytz.UTC)
        archived = CourseRunFactory(course__partner=self.partner,
                                    start=now - datetime.timedelta(weeks=2),
                                    end=now - datetime.timedelta(weeks=1),
                                    status=CourseRunStatus.Published)
        current = CourseRunFactory(course__partner=self.partner,
                                   start=now - datetime.timedelta(weeks=2),
                                   end=now + datetime.timedelta(weeks=1),
                                   status=CourseRunStatus.Published)
        starting_soon = CourseRunFactory(course__partner=self.partner,
                                         start=now +
                                         datetime.timedelta(days=10),
                                         end=now + datetime.timedelta(days=90),
                                         status=CourseRunStatus.Published)
        upcoming = CourseRunFactory(course__partner=self.partner,
                                    start=now + datetime.timedelta(days=61),
                                    end=now + datetime.timedelta(days=90),
                                    status=CourseRunStatus.Published)

        response = self.get_response(path=self.faceted_path)
        assert response.status_code == 200
        response_data = response.json()

        # Verify all course runs are returned
        assert response_data['objects']['count'] == 4

        for run in [archived, current, starting_soon, upcoming]:
            serialized = self.serialize_course_run_search(run)
            # Force execution of lazy function.
            serialized['availability'] = serialized['availability'].strip()
            assert serialized in response_data['objects']['results']

        self.assert_response_includes_availability_facets(response_data)

        # Verify the results can be filtered based on availability
        url = '{path}?page=1&selected_query_facets={facet}'.format(
            path=self.faceted_path, facet='availability_archived')
        response = self.client.get(url)
        assert response.status_code == 200
        response_data = response.json()
        assert response_data['objects']['results'] == [
            self.serialize_course_run_search(archived)
        ]
Ejemplo n.º 22
0
    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)
Ejemplo n.º 23
0
    def test_delete_orphans(self):
        """ Verify the delete_orphans method deletes orphaned instances. """
        orphan = VideoFactory()
        used = CourseRunFactory().video

        delete_orphans(Video)

        assert used.__class__.objects.filter(pk=used.pk).exists()
        assert not orphan.__class__.objects.filter(pk=orphan.pk).exists()
Ejemplo n.º 24
0
    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})
        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)
        CourseRunFactory(enrollment_end=enrollment_end,
                         course__title='ABC Test Course 2')
        CourseRunFactory(enrollment_end=enrollment_end, course=self.course)

        with self.assertNumQueries(42):
            response = self.client.get(url)
            self.assertEqual(response.status_code, 200)
            self.assertListEqual(
                response.data['results'],
                self.serialize_catalog_course(courses, many=True))
Ejemplo n.º 25
0
 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)
Ejemplo n.º 26
0
 def test_generate_data_for_studio_api_without_team(self):
     run = CourseRunFactory()
     with mock.patch('course_discovery.apps.api.utils.logger.warning') as mock_logger:
         assert StudioAPI.generate_data_for_studio_api(run, True) == self.make_studio_data(run)
     mock_logger.assert_called_with(
         'No course team admin specified for course [%s]. This may result in a Studio course run '
         'being created without a course team.',
         run.key.split('/')[1]
     )
Ejemplo n.º 27
0
 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)
Ejemplo n.º 28
0
    def test_course_ordering(self):
        """
        Verify that courses in a program are ordered by ascending run start date,
        with ties broken by earliest run enrollment start date.
        """
        request = make_request()
        course_list = CourseFactory.create_batch(3)

        # Create a course run with arbitrary start and empty enrollment_start.
        CourseRunFactory(
            course=course_list[2],
            enrollment_start=None,
            start=datetime(2014, 2, 1),
        )

        # Create a second run with matching start, but later enrollment_start.
        CourseRunFactory(
            course=course_list[1],
            enrollment_start=datetime(2014, 1, 2),
            start=datetime(2014, 2, 1),
        )

        # Create a third run with later start and enrollment_start.
        CourseRunFactory(
            course=course_list[0],
            enrollment_start=datetime(2014, 2, 1),
            start=datetime(2014, 3, 1),
        )

        program = ProgramFactory(courses=course_list)
        serializer = self.serializer_class(program,
                                           context={'request': request})

        expected = MinimalProgramCourseSerializer(
            # The expected ordering is the reverse of course_list.
            course_list[::-1],
            many=True,
            context={
                'request': request,
                'program': program,
                'course_runs': list(program.course_runs)
            }).data

        self.assertEqual(serializer.data['courses'], expected)
    def test_courses(self):
        """
        Verify the endpoint returns the list of available courses contained in
        the catalog, and that courses appearing in the response always have at
        least one serialized run.
        """
        url = reverse('api:v1:catalog-courses', kwargs={'id': self.catalog.id})

        for state in self.states():
            Course.objects.all().delete()

            course_run = CourseRunFactory(course__title='ABC Test Course')
            for function in state:
                function(course_run)

            course_run.save()

            if state in self.available_states:
                course = course_run.course

                # This run has no seats, but we still expect its parent course
                # to be included.
                filtered_course_run = CourseRunFactory(course=course)

                with self.assertNumQueries(19):
                    response = self.client.get(url)

                assert response.status_code == 200

                # Emulate prefetching behavior.
                filtered_course_run.delete()

                assert response.data[
                    'results'] == self.serialize_catalog_course([course],
                                                                many=True)

                # Any course appearing in the response must have at least one serialized run.
                assert len(response.data['results'][0]['course_runs']) > 0
            else:
                with self.assertNumQueries(3):
                    response = self.client.get(url)

                assert response.status_code == 200
                assert response.data['results'] == []
Ejemplo n.º 30
0
    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)