Repository: Kinto/kinto

Issue:
Kinto creates blank record on Content-Type:text/plain
As Kinto is JSON Datastore and whenever we POST text/plain instead of JSON it creates a blank record in DB instead of rejecting text/plain requests. 

Request: 

```
echo '{"data": {"description": "xyz"}}' | http POST http://localhost:8080/v1/buckets/default/collections/test-collection/records Content-Type:text/plain  -v --auth 'token:my-secret'
```

```
POST /v1/buckets/default/collections/test-collection/records HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Authorization: Basic dG9rZW46bXktc2VjcmV0
Connection: keep-alive
Content-Length: 33
Content-Type: text/plain
Host: localhost:8080
User-Agent: HTTPie/0.9.2

{"data": {"description": "xyz"}}

```

Response :

```
HTTP/1.1 201 Created
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Connection: keep-alive
Content-Length: 187
Content-Type: application/json; charset=UTF-8
Date: Thu, 25 Feb 2016 10:46:57 GMT
Server: nginx/1.8.0

{
    "data": {
        "id": "1227cf0c-66a5-4620-a178-255e7f479f28",
        "last_modified": 1456417017195
    },
    "permissions": {
        "write": [
            "basicauth:157beded4ea96ad4a21ed7fd91fff2a92a5daf47f054877d2e9768259e61600c"
        ]
    }
}
```



Discussion:
You are right we should return a [`415 Unsupported Media Type`](http://www.checkupdown.com/status/E415.html) error

Surprisingly, there are tests about that in cornice: https://github.com/mozilla-services/cornice/blob/master/cornice/tests/test_validation.py#L192-L228

I have added failing tests so that we can investigate why cornice doesn't catch the matter.

Tests are passing with this branch: https://github.com/mozilla-services/cliquet/pull/667/files

I got it working. 

If we define the content_type while registering a service in cornice, the corince would take it as a default validator.  link : http://cornice.readthedocs.org/en/latest/validation.html#content-type-validation

so I have changed  code in   https://github.com/mozilla-services/cliquet/blob/master/cliquet/resource/__init__.py#L70-L79

```

service = Service(name, path, depth=depth,content_type="application/json",
                          **viewset.get_service_arguments())

```

and it gives the response on content-type: text/plain as :

```

{"errno":107,"message":"Content-Type header should be one of ['application\/json']","code":400,"details":[{"location":"header","name":"Content-Type","description":"Content-Type header should be one of ['application\/json']"}],"error":"Invalid parameters"}

```

I came across this conversation https://github.com/mozilla-services/cornice/pull/192

Waiting on https://github.com/mozilla-services/cliquet/pull/667 to get merged

mozilla-services/cliquet#667 has been merged.

Test are passing on cliquet master: https://travis-ci.org/Kinto/kinto/jobs/112033653

We need a release before merging this.


Resolving patch:
diff --git a/loadtests/loadtest/simulation.py b/loadtests/loadtest/simulation.py
--- a/loadtests/loadtest/simulation.py
+++ b/loadtests/loadtest/simulation.py
@@ -108,7 +108,7 @@ def create(self):
             self.collection_url(),
             data=json.dumps({'data': article}),
             headers={'Content-Type': 'application/json'})
-        self.incr_counter(resp.status_code)
+        self.incr_counter('status-%s' % resp.status_code)
         self.assertEqual(resp.status_code, 201)
 
     def create_put(self):
@@ -117,7 +117,7 @@ def create_put(self):
             self.record_url(uuid.uuid4()),
             data=json.dumps({'data': article}),
             headers={'Content-Type': 'application/json'})
-        self.incr_counter(resp.status_code)
+        self.incr_counter('status-%s' % resp.status_code)
         self.assertEqual(resp.status_code, 201)
 
     def batch_create(self):
@@ -158,13 +158,13 @@ def filter_sort(self):
         query_url = '&'.join(['='.join(param) for param in queryparams])
         url = self.collection_url() + '?' + query_url
         resp = self.session.get(url)
-        self.incr_counter(resp.status_code)
+        self.incr_counter('status-%s' % resp.status_code)
         self.assertEqual(resp.status_code, 200)
 
     def _patch(self, url, data, status=200):
         data = json.dumps(data)
         resp = self.session.patch(url, data)
-        self.incr_counter(resp.status_code)
+        self.incr_counter('status-%s' % resp.status_code)
         self.assertEqual(resp.status_code, status)
 
     def _run_batch(self, data):
@@ -217,7 +217,7 @@ def batch_update(self):
 
     def delete(self):
         resp = self.session.delete(self.random_url)
-        self.incr_counter(resp.status_code)
+        self.incr_counter('status-%s' % resp.status_code)
         self.assertEqual(resp.status_code, 200)
 
     def batch_delete(self):
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -18,16 +18,16 @@ def read_file(filename):
 
 REQUIREMENTS = [
     'waitress',
-    'cliquet>=2.15,<3',
+    'cliquet>=3,<4',
     'jsonschema',
 ]
 
 POSTGRESQL_REQUIREMENTS = REQUIREMENTS + [
-    'cliquet[postgresql]>=2.15,<3'
+    'cliquet[postgresql]>=3,<4'
 ]
 
 MONITORING_REQUIREMENTS = REQUIREMENTS + [
-    'cliquet[monitoring]>=2.15,<3'
+    'cliquet[monitoring]>=3,<4'
 ]
 
 FXA_REQUIREMENTS = REQUIREMENTS + [


Test patch:
diff --git a/loadtests/config/test.ini b/loadtests/config/test.ini
--- a/loadtests/config/test.ini
+++ b/loadtests/config/test.ini
@@ -1,6 +1,6 @@
 [loads]
 hits = 30
-users = 3
+users = 1
 max_initial_records = 25
 no-dns-resolve = true
 include_file = loadtest