Mholloway has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/315996

Change subject: Add response schema validation
......................................................................

Add response schema validation

Checks responses systematically against the updated schema definitions.

Provides for schema validation checks beyond checking checking the public
x-amples.

Made a couple updates to featured-image since a failure scenario we want
to test is testable now.

Bug: T145075
Change-Id: I0d3f20cd40f18661a135b6999dff47b0888e0cea
---
M lib/feed/featured-image.js
M package.json
M spec.yaml
M test/features/app/spec.js
M test/features/featured-image/pagecontent.js
M test/utils/assert.js
6 files changed, 234 insertions(+), 10 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/mobileapps 
refs/changes/96/315996/1

diff --git a/lib/feed/featured-image.js b/lib/feed/featured-image.js
index f6bc226..0c04409 100644
--- a/lib/feed/featured-image.js
+++ b/lib/feed/featured-image.js
@@ -107,7 +107,7 @@
 
     if (!dateUtil.validate(dateUtil.hyphenDelimitedDateString(req))) {
         if (aggregated) {
-            return BBPromise.resolve({});
+            return BBPromise.resolve({ empty: true });
         }
         dateUtil.throwDateError();
     }
diff --git a/package.json b/package.json
index 00ea2eb..8dc74dc 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,8 @@
     "underscore": "^1.8.3"
   },
   "devDependencies": {
-    "csv-parse": "1.1.7",
+    "ajv": "^4.7.7",
+    "csv-parse": "^1.1.7",
     "extend": "^3.0.0",
     "grunt": "^1.0.1",
     "grunt-jscs": "^3.0.1",
diff --git a/spec.yaml b/spec.yaml
index aa9ef92..da9148b 100644
--- a/spec.yaml
+++ b/spec.yaml
@@ -98,6 +98,12 @@
             $ref: '#/definitions/article_title'
         '204':
           description: Empty response (for feed content aggregation requests 
from RESTBase)
+          schema:
+            $ref: '#/definitions/empty'
+        '501':
+          description: Unsupported language
+          schema:
+            $ref: '#/definitions/problem'
         default:
           description: Error
           schema:
@@ -513,13 +519,17 @@
     required:
       - type
     properties:
+      status:
+        type: integer
       type:
         type: string
       title:
         type: string
       detail:
         type: string
-      instance:
+      method:
+        type: string
+      uri:
         type: string
 
   article_title:
@@ -632,9 +642,20 @@
         description: Full-size image
         $ref: '#/definitions/thumbnail'
       description:
-        descrtiption: Description of an image
+        description: Description of an image
         $ref: '#/definitions/image_description'
     required:
       - title
       - thumbnail
       - image
+
+  empty:
+    type: object
+    properties:
+      type:
+        type: string
+        description: description of original content type (buffer)
+      data:
+        type: array
+        description: Buffer contents
+    additionalProperties: false
diff --git a/test/features/app/spec.js b/test/features/app/spec.js
index 570fb40..635f588 100644
--- a/test/features/app/spec.js
+++ b/test/features/app/spec.js
@@ -2,11 +2,17 @@
 
 
 var preq   = require('preq');
-var assert = require('../../utils/assert.js');
-var server = require('../../utils/server.js');
+var assert = require('../../utils/assert');
+var server = require('../../utils/server');
+var dateUtil = require('../../../lib/dateUtil');
 var URI    = require('swagger-router').URI;
 var yaml   = require('js-yaml');
 var fs     = require('fs');
+var Ajv = require('ajv');
+var date = new Date();
+var yesterday = new Date(Date.now() - dateUtil.ONE_DAY);
+var dateString = date.getUTCFullYear() + '/' + dateUtil.pad(date.getUTCMonth() 
+ 1) + '/' + dateUtil.pad(date.getUTCDate());
+var yesterdayString = yesterday.getUTCFullYear() + '/' + 
dateUtil.pad(yesterday.getUTCMonth() + 1) + '/' + 
dateUtil.pad(yesterday.getUTCDate());
 
 
 function staticSpecLoad() {
@@ -266,7 +272,202 @@
         });
     });
 
-    describe('routes', function() {
+    describe('validate responses against schema', function() {
+        var ajv = new Ajv({});
+
+        Object.keys(spec.definitions).forEach(function(defName) {
+            ajv.addSchema(spec.definitions[defName], '#/definitions/' + 
defName);
+        });
+
+        it('featured article response should conform to schema', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/page/featured/' + dateString })
+            .then(function(res) {
+                if (!ajv.validate('#/definitions/article_title', res.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('featured article request should fail for invalid language when not 
in aggregated request', function() {
+            return preq.get({ uri: server.config.uri + 
'is.wikipedia.org/v1/page/featured/' + dateString })
+            .then(function(res) {
+                assert.fail("This request should fail!");
+            })
+            .catch(function(err) {
+                if (!ajv.validate('#/definitions/problem', err.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('featured article response should conform to schema (invalid 
language, aggregated=true)', function() {
+            return preq.get({ uri: server.config.uri + 
'is.wikipedia.org/v1/page/featured/' + dateString,
+                            query: { aggregated: true } })
+            .then(function(res) {
+                var response = res.body.toJSON();
+                if (!ajv.validate('#/definitions/empty', response)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+
+                if (!!response.data[0]) {
+                    throw new assert.AssertionError({
+                        message: 'Response data should be empty!'
+                    });
+                }
+            });
+        });
+
+        it('featured image response should conform to schema', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/media/image/featured/' + dateString })
+            .then(function(res) {
+                if (!ajv.validate('#/definitions/image', res.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('featured image request should fail for invalid date when not in 
aggregated request', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/media/image/featured/2004/01/01' })
+            .then(function(res) {
+                assert.fail("This request should fail!");
+            })
+            .catch(function(err) {
+                if (!ajv.validate('#/definitions/problem', err.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('featured image response should conform to schema (invalid date, 
aggregated=true)', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/media/image/featured/2004/01/01',
+                            query: { aggregated: true } })
+            .then(function(res) {
+                var response = res.body.toJSON();
+                if (!ajv.validate('#/definitions/empty', response)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+
+                if (!!response.data[0]) {
+                    throw new assert.AssertionError({
+                        message: 'Response data should be empty!'
+                    });
+                }
+            });
+        });
+
+        it('most-read response should conform to schema', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/page/most-read/' + yesterdayString })
+            .then(function(res) {
+                if (!ajv.validate('#/definitions/mostread', res.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('most-read request should fail for invalid date when not in 
aggregated request', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/page/most-read/2004/01/01' })
+            .then(function(res) {
+                assert.fail("This request should fail!");
+            })
+            .catch(function(err) {
+                if (!ajv.validate('#/definitions/problem', err.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('most-read response should conform to schema (invalid date, 
aggregated=true)', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/page/most-read/2004/01/01',
+                            query: { aggregated: true } })
+            .then(function(res) {
+                var response = res.body.toJSON();
+                if (!ajv.validate('#/definitions/empty', response)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+
+                if (!!response.data[0]) {
+                    throw new assert.AssertionError({
+                        message: 'Response data should be empty!'
+                    });
+                }
+            });
+        });
+
+        it('news response should conform to schema', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/page/news' })
+            .then(function(res) {
+                if (!ajv.validate('#/definitions/news', res.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('news request should fail for invalid language when not in 
aggregated request', function() {
+            return preq.get({ uri: server.config.uri + 
'is.wikipedia.org/v1/page/news' })
+            .then(function(res) {
+                assert.fail("This request should fail!");
+            })
+            .catch(function(err) {
+                if (!ajv.validate('#/definitions/problem', err.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+
+        it('news response should conform to schema (invalid language, 
aggregated=true)', function() {
+            return preq.get({ uri: server.config.uri + 
'is.wikipedia.org/v1/page/news',
+                            query: { aggregated: true } })
+            .then(function(res) {
+                var response = res.body.toJSON();
+                if (!ajv.validate('#/definitions/empty', response)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+
+                if (!!response.data[0]) {
+                    throw new assert.AssertionError({
+                        message: 'Response data should be empty!'
+                    });
+                }
+            });
+        });
+
+        it('random response should conform to schema', function() {
+            return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/page/random/title' })
+            .then(function(res) {
+                if (!ajv.validate('#/definitions/random', res.body)) {
+                    throw new assert.AssertionError({
+                        message: ajv.errorsText()
+                    });
+                }
+            });
+        });
+    });
+
+    describe('validate spec examples', function() {
 
         constructTests(spec.paths, defParams).forEach(function(testCase) {
             it(testCase.title, function() {
diff --git a/test/features/featured-image/pagecontent.js 
b/test/features/featured-image/pagecontent.js
index 6bb07e9..de8d79f 100644
--- a/test/features/featured-image/pagecontent.js
+++ b/test/features/featured-image/pagecontent.js
@@ -83,12 +83,12 @@
             });
     });
 
-    it('404 for date with no featured image should be suppressed when 
aggregated flag is set', function() {
+    it('Should return 204 for date with no featured image when aggregated flag 
is set', function() {
         return preq.get({ uri: server.config.uri + 
'en.wikipedia.org/v1/media/image/featured/2002/09/12',
                         query: { aggregated: true }})
           .then(function(res) {
-            assert.status(res, 200);
-            assert.deepEqual(!!res.body, false, 'Expected the body to be 
empty');
+            assert.status(res, 204);
+            assert.deepEqual(!!res.body.data, false, 'Expected the body to be 
empty');
         });
     });
 });
diff --git a/test/utils/assert.js b/test/utils/assert.js
index 7771eeb..c3c19f7 100644
--- a/test/utils/assert.js
+++ b/test/utils/assert.js
@@ -172,4 +172,5 @@
 module.exports.selectorExistsOnce = selectorExistsOnce;
 module.exports.selectorHasValue = selectorHasValue;
 module.exports.selectorContainsValue = selectorContainsValue;
+module.exports.AssertionError = assert.AssertionError;
 

-- 
To view, visit https://gerrit.wikimedia.org/r/315996
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I0d3f20cd40f18661a135b6999dff47b0888e0cea
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/services/mobileapps
Gerrit-Branch: master
Gerrit-Owner: Mholloway <mhollo...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to