Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2714 list cases #2733

Merged
merged 5 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,32 @@ class to actually work with the cases collection, so that different
All methods return a tuple of (response, HTTP status code)"""

def __init__(self, store):
self._store = store
self.store = store

def get_case(self, id: str):
"""Implements get /cases/:id. Interpretation of ID is dependent
on the store implementation but here it is an opaque token that
should be unique to each case."""
case = self._store.case_by_id(id)
case = self.store.case_by_id(id)
if case is None:
return f"No case with ID {id}", 404
return jsonify(case), 200

def list_cases(self):
def list_cases(self, page: int = None, limit: int = None):
"""Implements get /cases."""
cases = self._store.all_cases()
return jsonify(cases), 200
page = 1 if page is None else page
limit = 10 if limit is None else limit
validation_error = None
if page <= 0:
validation_error = {"message": "page must be >0"}
if limit <= 0:
validation_error = {"message": "limit must be >0"}
if validation_error is not None:
return jsonify(validation_error), 422
cases = self.store.fetch_cases(page, limit)
count = self.store.count_cases()
response = {"cases": cases, "total": count}
if count > page * limit:
response["nextPage"] = page + 1

return jsonify(response), 200
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ def get_case(id):

@app.route("/api/cases")
def list_cases():
return case_controller.list_cases()
page = request.args.get("page", type=int)
limit = request.args.get("limit", type=int)
return case_controller.list_cases(page=page, limit=limit)


def set_up_controllers():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ def case_by_id(self, id: str):
except InvalidId:
return None

def all_cases(self):
cases = self.get_case_collection().find({})
def fetch_cases(self, page: int, limit: int):
cases = self.get_case_collection().find(
{}, skip=(page - 1) * limit, limit=limit
)
return [Case.from_json(dumps(c)) for c in cases]

def count_cases(self) -> int:
return self.get_case_collection().count_documents({})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might use https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.estimated_document_count -- not an issue in small collections, but maybe a bottleneck later on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. I will make that change when I add query filters because the estimated count can't be used then so I'll have to split between that and count_documents()


@staticmethod
def setup():
"""Configure a store instance from the environment."""
Expand Down
83 changes: 58 additions & 25 deletions data-serving/reusable-data-service/tests/test_case_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,83 @@ def case_by_id(self, id: str):
def put_case(self, id: str, case: Case):
self.cases[id] = case

def all_cases(self):
return list(self.cases.values())
def fetch_cases(self, page: int, limit: int):
return list(self.cases.values())[(page - 1) * limit : page * limit]

def count_cases(self):
return len(self.cases)


@pytest.fixture
def app_context():
def case_controller():
with app.app_context():
yield
store = MemoryStore()
controller = CaseController(store)
yield controller


def test_one_present_item_should_return_200_OK(app_context):
store = MemoryStore()
def test_one_present_item_should_return_200_OK(case_controller):
with open("./tests/data/case.minimal.json", "r") as minimal_file:
case = Case.from_json(minimal_file.read())
store.put_case("foo", case)
controller = CaseController(store)
(response, status) = controller.get_case("foo")
case_controller.store.put_case("foo", case)
(response, status) = case_controller.get_case("foo")
assert status == 200
assert response is not None


def test_one_absent_item_should_return_400_not_found(app_context):
store = MemoryStore()
controller = CaseController(store)
(response, status) = controller.get_case("foo")
def test_one_absent_item_should_return_400_not_found(case_controller):
(response, status) = case_controller.get_case("foo")
assert status == 404
assert response == "No case with ID foo"


def test_list_cases_should_return_200_OK(app_context):
store = MemoryStore()
controller = CaseController(store)
(response, status) = controller.list_cases()
def test_list_cases_should_return_200_OK(case_controller):
(response, status) = case_controller.list_cases()
assert status == 200
assert response.json["cases"] == []


def test_list_cases_should_list_the_cases(case_controller):
with open("./tests/data/case.minimal.json", "r") as minimal_file:
case = Case.from_json(minimal_file.read())
case_controller.store.put_case("foo", case)
case_controller.store.put_case("bar", case)
(response, status) = case_controller.list_cases()
assert status == 200
assert len(response.json["cases"]) == 2


def test_list_cases_should_paginate(case_controller):
with open("./tests/data/case.minimal.json", "r") as minimal_file:
case = Case.from_json(minimal_file.read())
for i in range(15):
case_controller.store.put_case(f"case_{i}", case)
(response, status) = case_controller.list_cases(page=1, limit=10)
assert status == 200
assert len(response.json["cases"]) == 10
assert response.json["nextPage"] == 2
assert response.json["total"] == 15


def test_list_cases_last_page(case_controller):
with open("./tests/data/case.minimal.json", "r") as minimal_file:
case = Case.from_json(minimal_file.read())
for i in range(15):
case_controller.store.put_case(f"case_{i}", case)
(response, status) = case_controller.list_cases(page=2, limit=10)
assert status == 200
assert response.json == []
assert len(response.json["cases"]) == 5
assert response.json["total"] == 15
assert "nextPage" not in response.json


def test_list_cases_should_list_the_cases(app_context):
store = MemoryStore()
controller = CaseController(store)
def test_list_cases_nonexistent_page(case_controller):
with open("./tests/data/case.minimal.json", "r") as minimal_file:
case = Case.from_json(minimal_file.read())
store.put_case("foo", case)
store.put_case("bar", case)
(response, status) = controller.list_cases()
for i in range(15):
case_controller.store.put_case(f"case_{i}", case)
(response, status) = case_controller.list_cases(page=43, limit=10)
assert status == 200
assert len(response.json) == 2
assert len(response.json["cases"]) == 0
assert response.json["total"] == 15
assert "nextPage" not in response.json
14 changes: 13 additions & 1 deletion data-serving/reusable-data-service/tests/test_case_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,16 @@ def test_get_case_with_valid_absent_id(client_with_patched_mongo):
def test_list_cases_when_none_present_is_empty_list(client_with_patched_mongo):
response = client_with_patched_mongo.get(f"/api/cases")
assert response.status_code == 200
assert response.json == []
assert response.json["cases"] == []


def test_list_cases_with_pagination_query(client_with_patched_mongo):
db = pymongo.MongoClient("mongodb://localhost:27017/outbreak")
db["outbreak"]["cases"].insert_many(
[{"confirmation_date": datetime(2020, 12, 24)} for i in range(25)]
)
response = client_with_patched_mongo.get(f"/api/cases?page=2&limit=10")
assert response.status_code == 200
assert len(response.json["cases"]) == 10
assert response.json["total"] == 25
assert response.json["nextPage"] == 3