Repository: Kinto/kinto

Issue:
503 when using an integer ID
```
echo '{"data": {"id": 2} }' | http POST localhost:8888/v1/buckets/default/collections/foo/records -a test:test
```

produces

```
2016-06-21 13:41:49,701 DEBUG [kinto.core.storage.postgresql.pool.QueuePoolWithMaxBacklog][waitress] Connection <connection object at 0x7f1f396ad6e0; dsn: 'dbname=postgres user=postgres password=xxxxxxxx host=localhost', closed: 0> checked out from pool
2016-06-21 13:41:49,711 ERROR [kinto.core.storage.postgresql.client][waitress] "POST  /v1/buckets/3f2e55d2-25ad-5fa9-3dcb-c0f29235627f/collections/foo/records" ? (? ms) (psycopg2.ProgrammingError) operator does not exist: text = integer
LINE 4:          WHERE id = 2
                          ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
 [SQL: '\n        SELECT as_epoch(last_modified) AS last_modified, data\n          FROM records\n         WHERE id = %(object_id)s\n           AND parent_id = %(parent_id)s\n           AND collection_id = %(collection_id)s;\n        '] [parameters: {'parent_id': u'/buckets/3f2e55d2-25ad-5fa9-3dcb-c0f29235627f/collections/foo', 'object_id': 2, 'collection_id': 'record'}] agent=HTTPie/0.9.2 authn_type=basicauth collection_id=record collection_timestamp=1466530900543 errno=None lang=None uid=44ac0e1157457b4ff75a88b490cd735f73a27d5c8ac5b3fb4672d449969d88ea
2016-06-21 13:41:49,712 DEBUG [kinto.core.storage.postgresql.pool.QueuePoolWithMaxBacklog][waitress] Connection <connection object at 0x7f1f396ad6e0; dsn: 'dbname=postgres user=postgres password=xxxxxxxx host=localhost', closed: 0> being returned to pool
2016-06-21 13:41:49,712 DEBUG [kinto.core.storage.postgresql.pool.QueuePoolWithMaxBacklog][waitress] Connection <connection object at 0x7f1f396ad6e0; dsn: 'dbname=postgres user=postgres password=xxxxxxxx host=localhost', closed: 0> rollback-on-return, via agent
2016-06-21 13:41:49,713 CRITI [kinto.core.views.errors][waitress] "POST  /v1/buckets/3f2e55d2-25ad-5fa9-3dcb-c0f29235627f/collections/foo/records" ? (? ms) (psycopg2.ProgrammingError) operator does not exist: text = integer
LINE 4:          WHERE id = 2
                          ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
 [SQL: '\n        SELECT as_epoch(last_modified) AS last_modified, data\n          FROM records\n         WHERE id = %(object_id)s\n           AND parent_id = %(parent_id)s\n           AND collection_id = %(collection_id)s;\n        '] [parameters: {'parent_id': u'/buckets/3f2e55d2-25ad-5fa9-3dcb-c0f29235627f/collections/foo', 'object_id': 2, 'collection_id': 'record'}] agent=HTTPie/0.9.2 authn_type=basicauth collection_id=record collection_timestamp=1466530900543 errno=None exception=Traceback (most recent call last):
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 101, in tm_tween
    reraise(*exc_info)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 83, in tm_tween
    response = handler(request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/router.py", line 158, in handle_request
    view_name
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/view.py", line 547, in _call_view
    response = view_callable(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 413, in viewresult_to_response
    result = view(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 147, in _requestonly_view
    response = view(request)
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/plugins/default_bucket/__init__.py", line 150, in default_bucket
    response = request.invoke_subrequest(subrequest)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/router.py", line 211, in invoke_subrequest
    response = handle_request(request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/router.py", line 158, in handle_request
    view_name
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/view.py", line 547, in _call_view
    response = view_callable(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/config/views.py", line 182, in __call__
    return view(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 393, in attr_view
    return view(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 371, in predicate_wrapper
    return view(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 302, in _secured_view
    return view(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 442, in rendered_view
    result = view(context, request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/pyramid/viewderivers.py", line 147, in _requestonly_view
    response = view(request)
  File "/home/ethan/Jobs/Mozilla/kinto/.venv/lib/python2.7/site-packages/cornice/service.py", line 571, in wrapper
    response = view_()
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/core/resource/__init__.py", line 303, in collection_post
    existing = self._get_record_or_404(_id)
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/core/resource/__init__.py", line 670, in _get_record_or_404
    return self.model.get_record(record_id)
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/core/resource/model.py", line 274, in get_record
    record = super(ShareableModel, self).get_record(record_id, parent_id)
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/core/resource/model.py", line 142, in get_record
    auth=self.auth)
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/core/storage/postgresql/__init__.py", line 261, in get
    existing = result.fetchone()
  File "/usr/lib64/python2.7/contextlib.py", line 35, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/ethan/Jobs/Mozilla/kinto/kinto/core/storage/postgresql/client.py", line 53, in connect
    raise exceptions.BackendError(original=e)
BackendError: ProgrammingError: (psycopg2.ProgrammingError) operator does not exist: text = integer
LINE 4:          WHERE id = 2
                          ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
 [SQL: '\n        SELECT as_epoch(last_modified) AS last_modified, data\n          FROM records\n         WHERE id = %(object_id)s\n           AND parent_id = %(parent_id)s\n           AND collection_id = %(collection_id)s;\n        '] [parameters: {'parent_id': u'/buckets/3f2e55d2-25ad-5fa9-3dcb-c0f29235627f/collections/foo', 'object_id': 2, 'collection_id': 'record'}] lang=None uid=44ac0e1157457b4ff75a88b490cd735f73a27d5c8ac5b3fb4672d449969d88ea
2016-06-21 13:41:49,713 INFO  [kinto.core.initialization][waitress] "POST  /v1/buckets/3f2e55d2-25ad-5fa9-3dcb-c0f29235627f/collections/foo/records" 503 (13 ms) request.summary agent=HTTPie/0.9.2 authn_type=basicauth collection_id=record collection_timestamp=1466530900543 errno=201 lang=None time=2016-06-21T13:41:49 uid=44ac0e1157457b4ff75a88b490cd735f73a27d5c8ac5b3fb4672d449969d88ea
```

I uncovered this when exploring some kinto.js tests. We may not need to really support this.



Discussion:
nice one!


Resolving patch:
diff --git a/kinto/core/resource/__init__.py b/kinto/core/resource/__init__.py
--- a/kinto/core/resource/__init__.py
+++ b/kinto/core/resource/__init__.py
@@ -717,7 +717,8 @@ def _raise_400_if_invalid_id(self, record_id):
 
         :raises: :class:`pyramid.httpexceptions.HTTPBadRequest`
         """
-        if not self.model.id_generator.match(six.text_type(record_id)):
+        is_string = isinstance(record_id, six.string_types)
+        if not is_string or not self.model.id_generator.match(record_id):
             error_details = {
                 'location': 'path',
                 'description': "Invalid record id"


Test patch:
diff --git a/kinto/tests/core/resource/test_views.py b/kinto/tests/core/resource/test_views.py
--- a/kinto/tests/core/resource/test_views.py
+++ b/kinto/tests/core/resource/test_views.py
@@ -405,6 +405,13 @@ def test_id_is_validated_on_post(self):
                            headers=self.headers,
                            status=400)
 
+        with mock.patch.object(self.app.app.registry.id_generator, 'match',
+                               return_value=True):
+            self.app.post_json(self.collection_url,
+                               {'data': record},
+                               headers=self.headers,
+                               status=400)
+
     def test_id_is_preserved_on_post(self):
         record = MINIMALIST_RECORD.copy()
         record_id = record['id'] = '472be9ec-26fe-461b-8282-9c4e4b207ab3'