コード例 #1
0
class OrganizationsViewsetsTestCase(TestCase):
    """
    Test the API endpoints for organizations (list and details)
    """
    def test_viewsets_organizations_retrieve(self):
        """
        Happy path: the client requests an existing organization, gets it back
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/organizations/42")

        with mock.patch.object(
                settings.ES_CLIENT,
                "get",
                return_value={
                    "_id": 42,
                    "_source": {
                        "logo": {
                            "fr": "/logo.png"
                        },
                        "title": {
                            "fr": "Université Paris 42"
                        },
                    },
                },
        ):
            # Note: we need to use a separate argument for the ID as that is what the ViewSet uses
            response = OrganizationsViewSet.as_view({"get": "retrieve"
                                                     })(request,
                                                        42,
                                                        version="1.0")

        # The client received a proper response with the relevant organization
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.data,
            {
                "id": 42,
                "logo": "/logo.png",
                "title": "Université Paris 42"
            },
        )

    def test_viewsets_organizations_retrieve_unknown(self):
        """
        Error case: the client is asking for an organization that does not exist
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/organizations/43")

        # Act like the ES client would when we attempt to get a non-existent document
        with mock.patch.object(settings.ES_CLIENT,
                               "get",
                               side_effect=NotFoundError):
            response = OrganizationsViewSet.as_view({"get": "retrieve"
                                                     })(request,
                                                        43,
                                                        version="1.0")

        # The client received a standard NotFound response
        self.assertEqual(response.status_code, 404)

    @mock.patch(
        "richie.apps.search.indexers.organizations.OrganizationsIndexer.build_es_query",
        lambda x: (2, 0, {
            "query": "something"
        }),
    )
    @mock.patch.object(settings.ES_CLIENT, "search")
    def test_viewsets_organizations_search(self, mock_search):
        """
        Happy path: the consumer is filtering the organizations by title
        """
        factory = APIRequestFactory()
        request = factory.get(
            "/api/v1.0/organizations?query=Université&limit=2")

        mock_search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": 21,
                        "_source": {
                            "logo": {
                                "fr": "/logo_21.png"
                            },
                            "title": {
                                "fr": "Université Paris 13"
                            },
                        },
                    },
                    {
                        "_id": 61,
                        "_source": {
                            "logo": {
                                "fr": "/logo_61.png"
                            },
                            "title": {
                                "fr": "Université Paris 8"
                            },
                        },
                    },
                ],
                "total":
                32,
            }
        }

        response = OrganizationsViewSet.as_view({"get": "list"})(request,
                                                                 version="1.0")

        # The client received a properly formatted response
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.data,
            {
                "meta": {
                    "count": 2,
                    "offset": 0,
                    "total_count": 32
                },
                "objects": [
                    {
                        "id": 21,
                        "logo": "/logo_21.png",
                        "title": "Université Paris 13"
                    },
                    {
                        "id": 61,
                        "logo": "/logo_61.png",
                        "title": "Université Paris 8"
                    },
                ],
            },
        )
        # The ES connector was called with a query that matches the client's request
        mock_search.assert_called_with(
            _source=["absolute_url", "logo", "title.*"],
            body={"query": "something"},
            doc_type="organization",
            from_=0,
            index="richie_organizations",
            size=2,
        )

    @mock.patch(
        "richie.apps.search.indexers.organizations.OrganizationsIndexer.build_es_query",
        side_effect=QueryFormatException({"limit": "incorrect value"}),
    )
    def test_viewsets_organizations_search_with_invalid_params(self, _):
        """
        Error case: the client used an incorrectly formatted request
        """
        factory = APIRequestFactory()
        # The request contains incorrect params: limit should be a positive integer
        request = factory.get("/api/v1.0/organizations?title=&limit=-2")

        response = OrganizationsViewSet.as_view({"get": "list"})(request,
                                                                 version="1.0")

        # The client received a BadRequest response with the relevant data
        self.assertEqual(response.status_code, 400)
        self.assertTrue("limit" in response.data["errors"])
コード例 #2
0
class CoursesViewsetsTestCase(TestCase):
    """
    Test the API endpoints for courses (list and details)
    """
    def setUp(self):
        """
        Make sure all our tests are timezone-agnostic. Some of them parse ISO datetimes and those
        would be broken if we did not enforce timezone normalization.
        """
        timezone.activate(pytz.utc)

    def test_viewsets_courses_retrieve(self, *_):
        """
        Happy path: the client requests an existing course, gets it back
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/courses/42")

        with mock.patch.object(settings.ES_CLIENT,
                               "get",
                               return_value={"_id": 42}):
            # Note: we need to use a separate argument for the ID as that is what the ViewSet uses
            response = CoursesViewSet.as_view({"get":
                                               "retrieve"})(request,
                                                            42,
                                                            version="1.0")

        # The client received a proper response with the relevant course
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, "Course #42")

    def test_viewsets_courses_retrieve_unknown(self, *_):
        """
        Error case: the client is asking for a course that does not exist
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/courses/43")

        # Act like the ES client would when we attempt to get a non-existent document
        with mock.patch.object(settings.ES_CLIENT,
                               "get",
                               side_effect=NotFoundError):
            response = CoursesViewSet.as_view({"get":
                                               "retrieve"})(request,
                                                            43,
                                                            version="1.0")

        # The client received a standard NotFound response
        self.assertEqual(response.status_code, 404)

    @mock.patch(
        "richie.apps.search.indexers.courses.CoursesIndexer.build_es_query",
        lambda *args: (2, 77, {
            "some": "query"
        }, {
            "some": "aggs"
        }),
    )
    @mock.patch(
        "richie.apps.search.indexers.courses.CoursesIndexer.get_list_sorting_script",
        lambda *args: {"some": "sorting"},
    )
    @mock.patch.object(settings.ES_CLIENT, "search")
    @mock.patch(
        "richie.apps.search.viewsets.courses.FILTERS",
        new=[
            (
                "richie.apps.search.utils.filter_definitions.FilterDefinitionCustom",
                {
                    "name":
                    "availability",
                    "human_name":
                    "Availability",
                    "choices": [
                        ("coming_soon", "Coming soon", [{
                            "is_coming_soon": True
                        }]),
                        ("current", "Current session", [{
                            "is_current": True
                        }]),
                        ("open", "Open for enrollment", [{
                            "is_open": True
                        }]),
                    ],
                },
            ),
            (
                "richie.apps.search.utils.filter_definitions.FilterDefinitionTerms",
                {
                    "name": "organizations",
                    "human_name": "Organizations"
                },
            ),
        ],
    )
    def test_viewsets_courses_search(self, mock_search, *_):
        """
        Happy path: the consumer is filtering courses by matching text
        """
        factory = APIRequestFactory()
        request = factory.get(
            "/api/v1.0/courses?query=some%20phrase%20terms&limit=2&offset=20")

        # We use a mock implementation instead of return_value as a pragmatic way to get results
        # from the whole filters pipeline without having to mock too many things.
        # pylint: disable=inconsistent-return-statements
        def mock_search_implementation(index, **_):
            if index == "richie_courses":
                return {
                    "hits": {
                        "hits": [{
                            "_id": 523
                        }, {
                            "_id": 861
                        }],
                        "total": 35
                    },
                    "aggregations": {
                        "all_courses": {
                            "availability@coming_soon": {
                                "doc_count": 8
                            },
                            "availability@current": {
                                "doc_count": 42
                            },
                            "availability@open": {
                                "doc_count": 59
                            },
                            "organizations": {
                                "organizations": {
                                    "buckets": [
                                        {
                                            "key": "11",
                                            "doc_count": 17
                                        },
                                        {
                                            "key": "21",
                                            "doc_count": 19
                                        },
                                    ]
                                }
                            },
                        }
                    },
                }
            if index == "richie_organizations":
                return {
                    "hits": {
                        "hits": [
                            {
                                "_id": "11",
                                "_source": {
                                    "title": {
                                        "en": "Organization 11"
                                    }
                                },
                            },
                            {
                                "_id": "21",
                                "_source": {
                                    "title": {
                                        "en": "Organization 21"
                                    }
                                },
                            },
                        ]
                    }
                }

        mock_search.side_effect = mock_search_implementation

        response = CoursesViewSet.as_view({"get": "list"})(request,
                                                           version="1.0")

        # The client received a properly formatted response
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.data,
            {
                "meta": {
                    "count": 2,
                    "offset": 77,
                    "total_count": 35
                },
                "objects": ["Course #523", "Course #861"],
                "facets": {
                    "availability": {
                        "coming_soon": 8,
                        "current": 42,
                        "open": 59
                    },
                    "organizations": {
                        "11": 17,
                        "21": 19
                    },
                },
                "filters": {
                    "availability": {
                        "human_name":
                        "Availability",
                        "is_drilldown":
                        False,
                        "name":
                        "availability",
                        "values": [
                            {
                                "count": 8,
                                "human_name": "Coming soon",
                                "key": "coming_soon",
                            },
                            {
                                "count": 42,
                                "human_name": "Current session",
                                "key": "current",
                            },
                            {
                                "count": 59,
                                "human_name": "Open for enrollment",
                                "key": "open",
                            },
                        ],
                    },
                    "organizations": {
                        "human_name":
                        "Organizations",
                        "is_drilldown":
                        False,
                        "name":
                        "organizations",
                        "values": [
                            {
                                "count": 17,
                                "human_name": "Organization 11",
                                "key": "11"
                            },
                            {
                                "count": 19,
                                "human_name": "Organization 21",
                                "key": "21"
                            },
                        ],
                    },
                },
            },
        )
        # The ES connector was called with appropriate arguments for the client's request
        mock_search.assert_any_call(
            _source=[
                "start",
                "end",
                "enrollment_start",
                "enrollment_end",
                "absolute_url",
                "cover_image",
                "languages",
                "organizations",
                "categories",
                "title.*",
            ],
            body={
                "aggs": {
                    "some": "aggs"
                },
                "query": {
                    "some": "query"
                },
                "sort": {
                    "some": "sorting"
                },
            },
            doc_type="course",
            from_=77,
            index="richie_courses",
            size=2,
        )

    @mock.patch(
        "richie.apps.search.indexers.courses.CoursesIndexer.build_es_query",
        side_effect=QueryFormatException({"limit": "incorrect value"}),
    )
    def test_viewsets_courses_search_with_invalid_params(self, *_):
        """
        Error case: the query string params are not properly formatted
        """
        factory = APIRequestFactory()
        # The request contains incorrect params: limit should be an integer, not a string
        request = factory.get("/api/v1.0/courses?limit=fail")

        response = CoursesViewSet.as_view({"get": "list"})(request,
                                                           version="1.0")

        # The client received a BadRequest response with the relevant data
        self.assertEqual(response.status_code, 400)
        self.assertTrue("limit" in response.data["errors"])
コード例 #3
0
class CoursesViewsetsTestCase(TestCase):
    """
    Test the API endpoints for courses (list and details)
    """
    def setUp(self):
        """
        Make sure all our tests are timezone-agnostic. Some of them parse ISO datetimes and those
        would be broken if we did not enforce timezone normalization.
        """
        timezone.activate(pytz.utc)

    def test_viewsets_courses_retrieve(self, *_):
        """
        Happy path: the client requests an existing course, gets it back
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/courses/42")

        with mock.patch.object(settings.ES_CLIENT,
                               "get",
                               return_value={"_id": 42}):
            # Note: we need to use a separate argument for the ID as that is what the ViewSet uses
            response = CoursesViewSet.as_view({"get":
                                               "retrieve"})(request,
                                                            42,
                                                            version="1.0")

        # The client received a proper response with the relevant course
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, "Course #42")

    def test_viewsets_courses_retrieve_unknown(self, *_):
        """
        Error case: the client is asking for a course that does not exist
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/courses/43")

        # Act like the ES client would when we attempt to get a non-existent document
        with mock.patch.object(settings.ES_CLIENT,
                               "get",
                               side_effect=NotFoundError):
            response = CoursesViewSet.as_view({"get":
                                               "retrieve"})(request,
                                                            43,
                                                            version="1.0")

        # The client received a standard NotFound response
        self.assertEqual(response.status_code, 404)

    @mock.patch(
        "richie.apps.search.indexers.courses.CoursesIndexer.build_es_query",
        lambda *args: (2, 77, {
            "some": "query"
        }, {
            "some": "aggs"
        }),
    )
    @mock.patch(
        "richie.apps.search.indexers.courses.CoursesIndexer.get_list_sorting_script",
        lambda *args: {"some": "sorting"},
    )
    @mock.patch.object(settings.ES_CLIENT, "search")
    def test_viewsets_courses_search(self, mock_search, *_):
        """
        Happy path: the consumer is filtering courses by matching text
        """
        factory = APIRequestFactory()
        request = factory.get(
            "/api/v1.0/courses?query=some%20phrase%20terms&limit=2&offset=20")

        mock_search.return_value = {
            "hits": {
                "hits": [{
                    "_id": 523
                }, {
                    "_id": 861
                }],
                "total": 35
            },
            "aggregations": {
                "all_courses": {
                    "availability@coming_soon": {
                        "doc_count": 8
                    },
                    "availability@current": {
                        "doc_count": 42
                    },
                    "availability@open": {
                        "doc_count": 59
                    },
                    "language@en": {
                        "doc_count": 81
                    },
                    "language@fr": {
                        "doc_count": 23
                    },
                    "subjects": {
                        "subjects": {
                            "buckets": [
                                {
                                    "key": "11",
                                    "doc_count": 17
                                },
                                {
                                    "key": "21",
                                    "doc_count": 19
                                },
                            ]
                        }
                    },
                }
            },
        }

        response = CoursesViewSet.as_view({"get": "list"})(request,
                                                           version="1.0")

        # The client received a properly formatted response
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.data,
            {
                "meta": {
                    "count": 2,
                    "offset": 77,
                    "total_count": 35
                },
                "objects": ["Course #523", "Course #861"],
                "facets": {
                    "availability": {
                        "coming_soon": 8,
                        "current": 42,
                        "open": 59
                    },
                    "language": {
                        "en": 81,
                        "fr": 23
                    },
                    "subjects": {
                        "11": 17,
                        "21": 19
                    },
                },
            },
        )
        # The ES connector was called with appropriate arguments for the client's request
        mock_search.assert_called_with(
            body={
                "aggs": {
                    "some": "aggs"
                },
                "query": {
                    "some": "query"
                },
                "sort": {
                    "some": "sorting"
                },
            },
            doc_type="course",
            from_=77,
            index="richie_courses",
            size=2,
        )

    @mock.patch(
        "richie.apps.search.indexers.courses.CoursesIndexer.build_es_query",
        side_effect=QueryFormatException({"limit": "incorrect value"}),
    )
    def test_viewsets_courses_search_with_invalid_params(self, *_):
        """
        Error case: the query string params are not properly formatted
        """
        factory = APIRequestFactory()
        # The request contains incorrect params: limit should be an integer, not a string
        request = factory.get("/api/v1.0/courses?limit=fail")

        response = CoursesViewSet.as_view({"get": "list"})(request,
                                                           version="1.0")

        # The client received a BadRequest response with the relevant data
        self.assertEqual(response.status_code, 400)
        self.assertTrue("limit" in response.data["errors"])
コード例 #4
0
class SubjectsViewsetsTestCase(TestCase):
    """
    Test the API endpoints for subjects (list and details)
    """
    def test_viewsets_subjects_retrieve(self):
        """
        Happy path: the client requests an existing subject, gets it back
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/subjects/42")

        with mock.patch.object(
                settings.ES_CLIENT,
                "get",
                return_value={
                    "_id": 42,
                    "_source": {
                        "image": "example.com/image.png",
                        "name": {
                            "fr": "Some Subject"
                        },
                    },
                },
        ):
            # Note: we need to use a separate argument for the ID as that is what the ViewSet uses
            response = SubjectsViewSet.as_view({"get":
                                                "retrieve"})(request,
                                                             42,
                                                             version="1.0")

        # The client received a proper response with the relevant subject
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.data,
            {
                "id": 42,
                "image": "example.com/image.png",
                "name": "Some Subject"
            },
        )

    def test_viewsets_subjects_retrieve_unknown(self):
        """
        Error case: the client is asking for a subject that does not exist
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/subjects/43")

        # Act like the ES client would when we attempt to get a non-existent document
        with mock.patch.object(settings.ES_CLIENT,
                               "get",
                               side_effect=NotFoundError):
            response = SubjectsViewSet.as_view({"get":
                                                "retrieve"})(request,
                                                             43,
                                                             version="1.0")

        # The client received a standard NotFound response
        self.assertEqual(response.status_code, 404)

    @mock.patch(
        "richie.apps.search.indexers.subjects.SubjectsIndexer.build_es_query",
        lambda x: (2, 0, {
            "query": "example"
        }),
    )
    @mock.patch.object(settings.ES_CLIENT, "search")
    def test_viewsets_subjects_search(self, mock_search):
        """
        Happy path: the subject is filtering the subjects by name
        """
        factory = APIRequestFactory()
        request = factory.get("/api/v1.0/subject?query=Science&limit=2")

        mock_search.return_value = {
            "hits": {
                "hits": [
                    {
                        "_id": 21,
                        "_source": {
                            "image": "example.com/image.png",
                            "name": {
                                "fr": "Computer Science"
                            },
                        },
                    },
                    {
                        "_id": 61,
                        "_source": {
                            "image": "example.com/image.png",
                            "name": {
                                "fr": "Engineering Sciences"
                            },
                        },
                    },
                ],
                "total":
                32,
            }
        }

        response = SubjectsViewSet.as_view({"get": "list"})(request,
                                                            version="1.0")

        # The client received a properly formatted response
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.data,
            {
                "meta": {
                    "count": 2,
                    "offset": 0,
                    "total_count": 32
                },
                "objects": [
                    {
                        "id": 21,
                        "image": "example.com/image.png",
                        "name": "Computer Science",
                    },
                    {
                        "id": 61,
                        "image": "example.com/image.png",
                        "name": "Engineering Sciences",
                    },
                ],
            },
        )
        # The ES connector was called with a query that matches the client's request
        mock_search.assert_called_with(
            body={"query": "example"},
            doc_type="subject",
            from_=0,
            index="richie_subjects",
            size=2,
        )

    @mock.patch(
        "richie.apps.search.indexers.subjects.SubjectsIndexer.build_es_query",
        side_effect=QueryFormatException({"limit": "incorrect value"}),
    )
    def test_viewsets_subjects_search_with_invalid_params(self, _):
        """
        Error case: the client used an incorrectly formatted request
        """
        factory = APIRequestFactory()
        # The request contains incorrect params: limit should be a positive integer
        request = factory.get("/api/v1.0/subject?name=&limit=-2")

        response = SubjectsViewSet.as_view({"get": "list"})(request,
                                                            version="1.0")

        # The client received a BadRequest response with the relevant data
        self.assertEqual(response.status_code, 400)
        self.assertTrue("limit" in response.data["errors"])