Commit 840f5017 by Ehtesham Committed by muzaffaryousaf

[TNL-3940] Adding pagination in stub server and updating unit tests

parent e2333214
...@@ -7,11 +7,12 @@ import re ...@@ -7,11 +7,12 @@ import re
from uuid import uuid4 from uuid import uuid4
from datetime import datetime from datetime import datetime
from copy import deepcopy from copy import deepcopy
from math import ceil
from urllib import urlencode
from .http import StubHttpRequestHandler, StubHttpService from .http import StubHttpRequestHandler, StubHttpService
# pylint: disable=invalid-name
class StubEdxNotesServiceHandler(StubHttpRequestHandler): class StubEdxNotesServiceHandler(StubHttpRequestHandler):
""" """
Handler for EdxNotes requests. Handler for EdxNotes requests.
...@@ -165,7 +166,7 @@ class StubEdxNotesServiceHandler(StubHttpRequestHandler): ...@@ -165,7 +166,7 @@ class StubEdxNotesServiceHandler(StubHttpRequestHandler):
""" """
Return the note by note id. Return the note by note id.
""" """
notes = self.server.get_notes() notes = self.server.get_all_notes()
result = self.server.filter_by_id(notes, note_id) result = self.server.filter_by_id(notes, note_id)
if result: if result:
self.respond(content=result[0]) self.respond(content=result[0])
...@@ -191,6 +192,53 @@ class StubEdxNotesServiceHandler(StubHttpRequestHandler): ...@@ -191,6 +192,53 @@ class StubEdxNotesServiceHandler(StubHttpRequestHandler):
else: else:
self.respond(404, "404 Not Found") self.respond(404, "404 Not Found")
@staticmethod
def _get_next_prev_url(url_path, query_params, page_num, page_size):
"""
makes url with the query params including pagination params
for pagination next and previous urls
"""
query_params = deepcopy(query_params)
query_params.update({
"page": page_num,
"page_size": page_size
})
return url_path + "?" + urlencode(query_params)
def _get_paginated_response(self, notes, page_num, page_size):
"""
Returns a paginated response of notes.
"""
start = (page_num - 1) * page_size
end = start + page_size
total_notes = len(notes)
url_path = "http://{server_address}:{port}{path}".format(
server_address=self.client_address[0],
port=self.server.port,
path=self.path_only
)
next_url = None if end >= total_notes else self._get_next_prev_url(
url_path, self.get_params, page_num + 1, page_size
)
prev_url = None if page_num == 1 else self._get_next_prev_url(
url_path, self.get_params, page_num - 1, page_size)
# Get notes from range
notes = deepcopy(notes[start:end])
paginated_response = {
'total': total_notes,
'num_pages': int(ceil(float(total_notes) / page_size)),
'current_page': page_num,
'rows': notes,
'next': next_url,
'start': start,
'previous': prev_url
}
return paginated_response
def _search(self): def _search(self):
""" """
Search for a notes by user id, course_id and usage_id. Search for a notes by user id, course_id and usage_id.
...@@ -199,32 +247,35 @@ class StubEdxNotesServiceHandler(StubHttpRequestHandler): ...@@ -199,32 +247,35 @@ class StubEdxNotesServiceHandler(StubHttpRequestHandler):
usage_id = self.get_params.get("usage_id", None) usage_id = self.get_params.get("usage_id", None)
course_id = self.get_params.get("course_id", None) course_id = self.get_params.get("course_id", None)
text = self.get_params.get("text", None) text = self.get_params.get("text", None)
page = int(self.get_params.get("page", 1))
page_size = int(self.get_params.get("page_size", 2))
if user is None: if user is None:
self.respond(400, "Bad Request") self.respond(400, "Bad Request")
return return
notes = self.server.get_notes() notes = self.server.get_all_notes()
if course_id is not None: if course_id is not None:
notes = self.server.filter_by_course_id(notes, course_id) notes = self.server.filter_by_course_id(notes, course_id)
if usage_id is not None: if usage_id is not None:
notes = self.server.filter_by_usage_id(notes, usage_id) notes = self.server.filter_by_usage_id(notes, usage_id)
if text: if text:
notes = self.server.search(notes, text) notes = self.server.search(notes, text)
self.respond(content={ self.respond(content=self._get_paginated_response(notes, page, page_size))
"total": len(notes),
"rows": notes,
})
def _collection(self): def _collection(self):
""" """
Return all notes for the user. Return all notes for the user.
""" """
user = self.get_params.get("user", None) user = self.get_params.get("user", None)
page = int(self.get_params.get("page", 1))
page_size = int(self.get_params.get("page_size", 2))
notes = self.server.get_all_notes()
if user is None: if user is None:
self.send_response(400, content="Bad Request") self.send_response(400, content="Bad Request")
return return
notes = self.server.get_notes() notes = self._get_paginated_response(notes, page, page_size)
self.respond(content=notes) self.respond(content=notes)
def _cleanup(self): def _cleanup(self):
...@@ -245,9 +296,9 @@ class StubEdxNotesService(StubHttpService): ...@@ -245,9 +296,9 @@ class StubEdxNotesService(StubHttpService):
super(StubEdxNotesService, self).__init__(*args, **kwargs) super(StubEdxNotesService, self).__init__(*args, **kwargs)
self.notes = list() self.notes = list()
def get_notes(self): def get_all_notes(self):
""" """
Returns a list of all notes. Returns a list of all notes without pagination
""" """
notes = deepcopy(self.notes) notes = deepcopy(self.notes)
notes.reverse() notes.reverse()
......
""" """
Unit tests for stub EdxNotes implementation. Unit tests for stub EdxNotes implementation.
""" """
import urlparse
import json import json
import unittest import unittest
import requests import requests
...@@ -19,7 +19,7 @@ class StubEdxNotesServiceTest(unittest.TestCase): ...@@ -19,7 +19,7 @@ class StubEdxNotesServiceTest(unittest.TestCase):
""" """
super(StubEdxNotesServiceTest, self).setUp() super(StubEdxNotesServiceTest, self).setUp()
self.server = StubEdxNotesService() self.server = StubEdxNotesService()
dummy_notes = self._get_dummy_notes(count=2) dummy_notes = self._get_dummy_notes(count=5)
self.server.add_notes(dummy_notes) self.server.add_notes(dummy_notes)
self.addCleanup(self.server.shutdown) self.addCleanup(self.server.shutdown)
...@@ -99,17 +99,48 @@ class StubEdxNotesServiceTest(unittest.TestCase): ...@@ -99,17 +99,48 @@ class StubEdxNotesServiceTest(unittest.TestCase):
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_search(self): def test_search(self):
# Without user
response = requests.get(self._get_url("api/v1/search"))
self.assertEqual(response.status_code, 400)
# get response with default page and page size
response = requests.get(self._get_url("api/v1/search"), params={ response = requests.get(self._get_url("api/v1/search"), params={
"user": "dummy-user-id", "user": "dummy-user-id",
"usage_id": "dummy-usage-id", "usage_id": "dummy-usage-id",
"course_id": "dummy-course-id", "course_id": "dummy-course-id",
}) })
notes = self._get_notes()
self.assertTrue(response.ok) self.assertTrue(response.ok)
self.assertDictEqual({"total": 2, "rows": notes}, response.json()) self._verify_pagination_info(
response=response.json(),
total_notes=5,
num_pages=3,
notes_per_page=2,
start=0,
current_page=1,
next_page=2,
previous_page=None
)
response = requests.get(self._get_url("api/v1/search")) # search notes with text that don't exist
self.assertEqual(response.status_code, 400) response = requests.get(self._get_url("api/v1/search"), params={
"user": "dummy-user-id",
"usage_id": "dummy-usage-id",
"course_id": "dummy-course-id",
"text": "world war 2"
})
self.assertTrue(response.ok)
self._verify_pagination_info(
response=response.json(),
total_notes=0,
num_pages=0,
notes_per_page=0,
start=0,
current_page=1,
next_page=None,
previous_page=None
)
def test_delete(self): def test_delete(self):
notes = self._get_notes() notes = self._get_notes()
...@@ -119,7 +150,7 @@ class StubEdxNotesServiceTest(unittest.TestCase): ...@@ -119,7 +150,7 @@ class StubEdxNotesServiceTest(unittest.TestCase):
for note in notes: for note in notes:
response = requests.delete(self._get_url("api/v1/annotations/" + note["id"])) response = requests.delete(self._get_url("api/v1/annotations/" + note["id"]))
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
remaining_notes = self.server.get_notes() remaining_notes = self.server.get_all_notes()
self.assertNotIn(note["id"], [note["id"] for note in remaining_notes]) self.assertNotIn(note["id"], [note["id"] for note in remaining_notes])
self.assertEqual(len(remaining_notes), 0) self.assertEqual(len(remaining_notes), 0)
...@@ -139,24 +170,149 @@ class StubEdxNotesServiceTest(unittest.TestCase): ...@@ -139,24 +170,149 @@ class StubEdxNotesServiceTest(unittest.TestCase):
response = requests.get(self._get_url("api/v1/annotations/does_not_exist")) response = requests.get(self._get_url("api/v1/annotations/does_not_exist"))
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
# pylint: disable=too-many-arguments
def _verify_pagination_info(
self,
response,
total_notes,
num_pages,
notes_per_page,
current_page,
previous_page,
next_page,
start
):
"""
Verify the pagination information.
Argument:
response: response from api
total_notes: total notes in the response
num_pages: total number of pages in response
notes_per_page: number of notes in the response
current_page: current page number
previous_page: previous page number
next_page: next page number
start: start of the current page
"""
def get_page_value(url):
"""
Return page value extracted from url.
"""
if url is None:
return None
parsed = urlparse.urlparse(url)
query_params = urlparse.parse_qs(parsed.query)
page = query_params["page"][0]
return page if page is None else int(page)
self.assertEqual(response["total"], total_notes)
self.assertEqual(response["num_pages"], num_pages)
self.assertEqual(len(response["rows"]), notes_per_page)
self.assertEqual(response["current_page"], current_page)
self.assertEqual(get_page_value(response["previous"]), previous_page)
self.assertEqual(get_page_value(response["next"]), next_page)
self.assertEqual(response["start"], start)
def test_notes_collection(self): def test_notes_collection(self):
response = requests.get(self._get_url("api/v1/annotations"), params={"user": "dummy-user-id"}) """
self.assertTrue(response.ok) Test paginated response of notes api
self.assertEqual(len(response.json()), 2) """
# Without user
response = requests.get(self._get_url("api/v1/annotations")) response = requests.get(self._get_url("api/v1/annotations"))
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
# Without any pagination parameters
response = requests.get(self._get_url("api/v1/annotations"), params={"user": "dummy-user-id"})
self.assertTrue(response.ok)
self._verify_pagination_info(
response=response.json(),
total_notes=5,
num_pages=3,
notes_per_page=2,
start=0,
current_page=1,
next_page=2,
previous_page=None
)
# With pagination parameters
response = requests.get(self._get_url("api/v1/annotations"), params={
"user": "dummy-user-id",
"page": 2,
"page_size": 3
})
self.assertTrue(response.ok)
self._verify_pagination_info(
response=response.json(),
total_notes=5,
num_pages=2,
notes_per_page=2,
start=3,
current_page=2,
next_page=None,
previous_page=1
)
def test_notes_collection_next_previous_with_one_page(self):
"""
Test next and previous urls of paginated response of notes api
when number of pages are 1
"""
response = requests.get(self._get_url("api/v1/annotations"), params={
"user": "dummy-user-id",
"page_size": 10
})
self.assertTrue(response.ok)
self._verify_pagination_info(
response=response.json(),
total_notes=5,
num_pages=1,
notes_per_page=5,
start=0,
current_page=1,
next_page=None,
previous_page=None
)
def test_notes_collection_when_no_notes(self):
"""
Test paginated response of notes api when there's no note present
"""
# Delete all notes
self.test_cleanup()
# Get default page
response = requests.get(self._get_url("api/v1/annotations"), params={"user": "dummy-user-id"})
self.assertTrue(response.ok)
self._verify_pagination_info(
response=response.json(),
total_notes=0,
num_pages=0,
notes_per_page=0,
start=0,
current_page=1,
next_page=None,
previous_page=None
)
def test_cleanup(self): def test_cleanup(self):
response = requests.put(self._get_url("cleanup")) response = requests.put(self._get_url("cleanup"))
self.assertTrue(response.ok) self.assertTrue(response.ok)
self.assertEqual(len(self.server.get_notes()), 0) self.assertEqual(len(self.server.get_all_notes()), 0)
def test_create_notes(self): def test_create_notes(self):
dummy_notes = self._get_dummy_notes(count=2) dummy_notes = self._get_dummy_notes(count=2)
response = requests.post(self._get_url("create_notes"), data=json.dumps(dummy_notes)) response = requests.post(self._get_url("create_notes"), data=json.dumps(dummy_notes))
self.assertTrue(response.ok) self.assertTrue(response.ok)
self.assertEqual(len(self._get_notes()), 4) self.assertEqual(len(self._get_notes()), 7)
response = requests.post(self._get_url("create_notes")) response = requests.post(self._get_url("create_notes"))
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
...@@ -177,7 +333,7 @@ class StubEdxNotesServiceTest(unittest.TestCase): ...@@ -177,7 +333,7 @@ class StubEdxNotesServiceTest(unittest.TestCase):
""" """
Return a list of notes from the stub EdxNotes service. Return a list of notes from the stub EdxNotes service.
""" """
notes = self.server.get_notes() notes = self.server.get_all_notes()
self.assertGreater(len(notes), 0, "Notes are empty.") self.assertGreater(len(notes), 0, "Notes are empty.")
return notes return notes
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment