Repository: Kinto/kinto

Issue:
Filtering history with ?collection_id=0 returns an empty list
- Create a collection whose name is `0`
- Filter the history with `?collection_id=0`
- Instead of obtaining the history entry, the obtained list is empty

Filtering history with ?collection_id=0 returns an empty list
- Create a collection whose name is `0`
- Filter the history with `?collection_id=0`
- Instead of obtaining the history entry, the obtained list is empty



Discussion:

Resolving patch:
diff --git a/kinto/core/storage/memory.py b/kinto/core/storage/memory.py
--- a/kinto/core/storage/memory.py
+++ b/kinto/core/storage/memory.py
@@ -319,6 +319,10 @@ def apply_filters(records, filters):
         matches = True
         for f in filters:
             right = f.value
+            if f.field == DEFAULT_ID_FIELD:
+                if isinstance(right, int):
+                    right = str(right)
+
             left = record
             subfields = f.field.split('.')
             for subfield in subfields:
diff --git a/kinto/core/storage/postgresql/__init__.py b/kinto/core/storage/postgresql/__init__.py
--- a/kinto/core/storage/postgresql/__init__.py
+++ b/kinto/core/storage/postgresql/__init__.py
@@ -641,6 +641,8 @@ def _format_conditions(self, filters, id_field, modified_field,
 
             if filtr.field == id_field:
                 sql_field = 'id'
+                if isinstance(value, int):
+                    value = str(value)
             elif filtr.field == modified_field:
                 sql_field = 'as_epoch(last_modified)'
             else:
diff --git a/kinto/plugins/history/views.py b/kinto/plugins/history/views.py
--- a/kinto/plugins/history/views.py
+++ b/kinto/plugins/history/views.py
@@ -2,6 +2,7 @@
 
 from kinto.core import resource
 from kinto.core.utils import instance_uri
+from kinto.core.storage import Filter
 
 
 class HistorySchema(resource.ResourceSchema):
@@ -31,3 +32,14 @@ class History(resource.ShareableResource):
     def get_parent_id(self, request):
         self.bucket_id = request.matchdict['bucket_id']
         return instance_uri(request, 'bucket', id=self.bucket_id)
+
+    def _extract_filters(self, queryparams=None):
+        filters = super(History, self)._extract_filters(queryparams)
+        filters_str_id = []
+        for filt in filters:
+            if filt.field in ('record_id', 'collection_id', 'bucket_id'):
+                if isinstance(filt.value, int):
+                    filt = Filter(filt.field, str(filt.value), filt.operator)
+            filters_str_id.append(filt)
+
+        return filters_str_id


Test patch:
diff --git a/kinto/core/storage/testing.py b/kinto/core/storage/testing.py
--- a/kinto/core/storage/testing.py
+++ b/kinto/core/storage/testing.py
@@ -358,6 +358,20 @@ def test_get_all_can_filter_with_numeric_values(self):
         self.assertEqual(records[1]['code'], 6)
         self.assertEqual(len(records), 2)
 
+    def test_get_all_can_filter_with_numeric_id(self):
+        for l in [0, 42]:
+            self.create_record({'id': str(l)})
+
+        filters = [Filter('id', 0, utils.COMPARISON.EQ)]
+        records, _ = self.storage.get_all(filters=filters,
+                                          **self.storage_kw)
+        self.assertEqual(len(records), 1)
+
+        filters = [Filter('id', 42, utils.COMPARISON.EQ)]
+        records, _ = self.storage.get_all(filters=filters,
+                                          **self.storage_kw)
+        self.assertEqual(len(records), 1)
+
     def test_get_all_can_filter_with_numeric_strings(self):
         for l in ["0566199093", "0781566199"]:
             self.create_record({'phone': l})
diff --git a/tests/plugins/test_history.py b/tests/plugins/test_history.py
--- a/tests/plugins/test_history.py
+++ b/tests/plugins/test_history.py
@@ -320,12 +320,19 @@ class FilteringTest(HistoryWebTest):
 
     def setUp(self):
         self.app.put('/buckets/bid', headers=self.headers)
+        self.app.put('/buckets/0', headers=self.headers)
         self.app.put('/buckets/bid/collections/cid',
                      headers=self.headers)
+        self.app.put('/buckets/0/collections/1',
+                     headers=self.headers)
         body = {'data': {'foo': 42}}
         self.app.put_json('/buckets/bid/collections/cid/records/rid',
                           body,
                           headers=self.headers)
+        body = {'data': {'foo': 0}}
+        self.app.put_json('/buckets/0/collections/1/records/2',
+                          body,
+                          headers=self.headers)
         body = {'data': {'foo': 'bar'}}
         self.app.patch_json('/buckets/bid/collections/cid/records/rid',
                             body,
@@ -381,6 +388,24 @@ def test_filter_by_collection(self):
                             headers=self.headers)
         assert len(resp.json['data']) == 4
 
+    def test_filter_by_numeric_bucket(self):
+        uri = '/buckets/0/history?bucket_id=0'
+        resp = self.app.get(uri,
+                            headers=self.headers)
+        assert len(resp.json['data']) == 1
+
+    def test_filter_by_numeric_collection(self):
+        uri = '/buckets/0/history?collection_id=1'
+        resp = self.app.get(uri,
+                            headers=self.headers)
+        assert len(resp.json['data']) == 2
+
+    def test_filter_by_numeric_record(self):
+        uri = '/buckets/0/history?record_id=2'
+        resp = self.app.get(uri,
+                            headers=self.headers)
+        assert len(resp.json['data']) == 1
+
     def test_filter_by_target_fields(self):
         uri = '/buckets/bid/history?target.data.id=rid'
         resp = self.app.get(uri,