From a15143e6a3e264fcd5f61341967d0190412b2edd Mon Sep 17 00:00:00 2001
From: Benjamin Bertrand <benjamin.bertrand@esss.se>
Date: Thu, 7 Sep 2017 15:23:40 +0200
Subject: [PATCH] Add basic filtering to retrieve items via the API

Can use:

  -  /api/items?serial_number=123456
  -  /api/items?location_id=1
---
 app/api/main.py              |  3 ++-
 app/utils.py                 | 17 +++++++++++++++++
 tests/functional/test_api.py | 37 ++++++++++++++++++++++++++++++++++++
 3 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/app/api/main.py b/app/api/main.py
index 316a011..e2c3f31 100644
--- a/app/api/main.py
+++ b/app/api/main.py
@@ -89,7 +89,8 @@ def login():
 @jwt_required
 def get_items():
     # TODO: add pagination
-    items = Item.query.order_by(Item._created)
+    query = utils.get_query(Item.query, request.args)
+    items = query.order_by(Item._created)
     data = [item.to_dict() for item in items]
     return jsonify(data)
 
diff --git a/app/utils.py b/app/utils.py
index 8365bcf..a369fbf 100644
--- a/app/utils.py
+++ b/app/utils.py
@@ -12,6 +12,7 @@ This module implements utility functions.
 import base64
 import datetime
 import io
+import sqlalchemy as sa
 
 
 class InventoryError(Exception):
@@ -103,3 +104,19 @@ def get_choices(iterable, allow_blank=False, allow_null=False):
         choices.append(('null', 'not set'))
     choices.extend([(val, val) for val in iterable])
     return choices
+
+
+def get_query(query, args):
+    """Retrieve the query from the arguments
+
+    :param query: sqlalchemy base query
+    :param MultiDict args: args from a request
+    :returns: query filtered by the arguments
+    """
+    if args:
+        kwargs = args.to_dict()
+        try:
+            query = query.filter_by(**kwargs)
+        except (sa.exc.InvalidRequestError, AttributeError) as e:
+            raise InventoryError('Invalid query arguments', status_code=422)
+    return query
diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py
index 088eece..396dc55 100644
--- a/tests/functional/test_api.py
+++ b/tests/functional/test_api.py
@@ -357,3 +357,40 @@ def test_patch_item_parent(client, session, user_token):
     assert response.json['manufacturer'] is None
     response = get(client, f'/api/items/{item3.ics_id}', token=user_token)
     assert response.json['manufacturer'] == 'Dell'
+
+
+def test_get_items(client, session, readonly_token):
+    # Create some items
+    session.add(models.Manufacturer(name='Dell'))
+    session.add(models.Status(name='Stock'))
+    session.add(models.Status(name='In service'))
+    session.add(models.Location(name='ESS'))
+    session.add(models.Location(name='ICS lab'))
+    item1 = models.Item(serial_number='123456', ics_id='AAA001', location='ESS', status='In service',
+                        manufacturer='Dell')
+    item2 = models.Item(serial_number='234567', ics_id='AAA002', status='Stock')
+    item3 = models.Item(serial_number='345678', ics_id='AAA003', status='Stock', manufacturer='Dell')
+    for item in (item1, item2, item3):
+        session.add(item)
+    session.commit()
+
+    response = get(client, '/api/items', token=readonly_token)
+    assert response.status_code == 200
+    assert len(response.json) == 3
+    check_items(response, (item1.to_dict(), item2.to_dict(), item3.to_dict()))
+
+    # test filtering
+    response = get(client, '/api/items?serial_number=234567', token=readonly_token)
+    assert response.status_code == 200
+    assert len(response.json) == 1
+    check_items(response, (item2.to_dict(),))
+    # filtering on location_id works but not location (might want to change that)
+    response = get(client, f'/api/items?location_id={item1.location_id}', token=readonly_token)
+    assert response.status_code == 200
+    assert len(response.json) == 1
+    check_items(response, (item1.to_dict(),))
+    response = get(client, '/api/items?location=ESS', token=readonly_token)
+    check_response_message(response, 'Invalid query arguments', 422)
+    # using an unknown key raises a 422
+    response = get(client, '/api/items?foo=bar', token=readonly_token)
+    check_response_message(response, 'Invalid query arguments', 422)
-- 
GitLab