From a3e9c955b7a3138b2571d18681c47a23ff723dd8 Mon Sep 17 00:00:00 2001 From: Richard Taylor Date: Fri, 20 Nov 2015 15:32:01 +0000 Subject: [PATCH 1/2] Get mock values from `schema.example` (if no `default` value) In many cases you don't want to define a `default` value for a schema object, but you still want to provide representative examples, particularly in mock responses. This is particularly important where `string` type uses a `pattern` that doesn't match the hard-coded mock values, which in turns causes the mocked response to fail the response validator. This change allows mock values to be pulled from the `example` field to solve this problem. `example` is inserted between `default` and `enum`, leaving the the mock value source precedence as: 1. `1.2` `defaultValue` 2. `2.0` `default` 3. `2.0` `example` 4. `enum` (first value) 5. hardcoded value (or *now* for date/date-time) Test plan: * Added unit tests to cover the appropriate selection of `default` and `example` as the mock value for string, number, integer, and boolean types. * Used gulp to run lint and all unit tests and ensured everything passes. * Used coverage report to verify all new lines were tested at least once. --- middleware/swagger-router.js | 8 + test/2.0/test-middleware-swagger-router.js | 168 +++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/middleware/swagger-router.js b/middleware/swagger-router.js index 5429760ab8..6bbebe9917 100644 --- a/middleware/swagger-router.js +++ b/middleware/swagger-router.js @@ -123,6 +123,8 @@ var getMockValue = function (version, schema) { value = schema.defaultValue; } else if (version === '2.0' && !_.isUndefined(schema.default)) { value = schema.default; + } else if (version === '2.0' && !_.isUndefined(schema.example)) { + value = schema.example; } else if (_.isArray(schema.enum)) { value = schema.enum[0]; } else { @@ -145,6 +147,8 @@ var getMockValue = function (version, schema) { value = schema.defaultValue; } else if (version === '2.0' && !_.isUndefined(schema.default)) { value = schema.default; + } else if (version === '2.0' && !_.isUndefined(schema.example)) { + value = schema.example; } else if (_.isArray(schema.enum)) { value = schema.enum[0]; } else { @@ -180,6 +184,8 @@ var getMockValue = function (version, schema) { value = schema.defaultValue; } else if (version === '2.0' && !_.isUndefined(schema.default)) { value = schema.default; + } else if (version === '2.0' && !_.isUndefined(schema.example)) { + value = schema.example; } else if (_.isArray(schema.enum)) { value = schema.enum[0]; } else { @@ -200,6 +206,8 @@ var getMockValue = function (version, schema) { value = schema.defaultValue; } else if (version === '2.0' && !_.isUndefined(schema.default)) { value = schema.default; + } else if (version === '2.0' && !_.isUndefined(schema.example)) { + value = schema.example; } else if (_.isArray(schema.enum)) { value = schema.enum[0]; } else { diff --git a/test/2.0/test-middleware-swagger-router.js b/test/2.0/test-middleware-swagger-router.js index 16f8da3175..1c12f3a81e 100644 --- a/test/2.0/test-middleware-swagger-router.js +++ b/test/2.0/test-middleware-swagger-router.js @@ -293,6 +293,174 @@ describe('Swagger Router Middleware v2.0', function () { done(); }); }); + + describe('get mock values from default then example', function () { + it('should return mock string from default over example', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'string', + pattern: '^([A-Za-z0-9]*)$', + example: 'exampleString', + default: 'defaultString' + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify('defaultString'), done)); + }); + }); + + it('should return mock string from example if no default', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'string', + pattern: '^([A-Za-z0-9]*)$', + example: 'exampleString' + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify('exampleString'), done)); + }); + }); + + it('should return mock number from default over example', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'number', + example: 1.01, + default: 2.02 + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify(2.02), done)); + }); + }); + + it('should return mock number from example if no default', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'number', + example: 1.01 + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify(1.01), done)); + }); + }); + + it('should return mock integer from default over example', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'integer', + example: 1, + default: 2 + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify(2), done)); + }); + }); + + it('should return mock integer from example if no default', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'integer', + example: 1 + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify(1), done)); + }); + }); + + it('should return mock boolean from default over example', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'boolean', + example: false, + default: true + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify(true), done)); + }); + }); + + it('should return mock boolean from example if no default', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'boolean', + example: true + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify(true), done)); + }); + }); + }); describe('issues', function () { it('should handle uncaught exceptions (Issue 123)', function (done) { From 570edef50be401869cc0d564fa5045dac40e225a Mon Sep 17 00:00:00 2001 From: Richard Taylor Date: Mon, 23 Nov 2015 16:49:31 +0000 Subject: [PATCH 2/2] Generate mock arrays with minimum number of items The previous code always generated 1 item in the array. If the property has a minItems value that is >1 then the mock will fail response validation. This change fixes it by generating the minimum number of items in the array to match minItems. If minItems is not defined, then it will continue to generate 1 item as before. Test plan: * Added unit tests to cover minItems set and not set * Used gulp to run lint and all unit tests and ensured everything passes. * Used coverage report to verify all new lines were tested at least once. --- middleware/swagger-router.js | 7 +++- test/2.0/test-middleware-swagger-router.js | 47 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/middleware/swagger-router.js b/middleware/swagger-router.js index 6bbebe9917..6c385ce915 100644 --- a/middleware/swagger-router.js +++ b/middleware/swagger-router.js @@ -114,8 +114,11 @@ var getMockValue = function (version, schema) { switch (type) { case 'array': - value = [getMockValue(version, _.isArray(schema.items) ? schema.items[0] : schema.items)]; - + var minItems = schema.minItems || 1; + value = []; + for (var i = 0; i < minItems; ++i) { + value.push(getMockValue(version, _.isArray(schema.items) ? schema.items[0] : schema.items)); + } break; case 'boolean': diff --git a/test/2.0/test-middleware-swagger-router.js b/test/2.0/test-middleware-swagger-router.js index 1c12f3a81e..22299ce02d 100644 --- a/test/2.0/test-middleware-swagger-router.js +++ b/test/2.0/test-middleware-swagger-router.js @@ -461,6 +461,53 @@ describe('Swagger Router Middleware v2.0', function () { }); }); }); + + describe('mock arrays', function () { + it('should return array of specified min length', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'array', + items: [{type: 'number'}], + minItems: 2, + maxItems: 3 + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify([1,1]), done)); + }); + }); + + it('should return array of length 1 if not specified', function (done) { + var cPetStoreJson = _.cloneDeep(petStoreJson); + + cPetStoreJson.paths['/pets/{id}'].get.responses['200'].schema = { + type: 'array', + items: [{type: 'number'}], + maxItems: 3 + }; + + helpers.createServer([cPetStoreJson], { + swaggerRouterOptions: { + useStubs: true + } + }, function (app) { + request(app) + .get('/api/pets/1') + .expect(200) + .end(helpers.expectContent(JSON.stringify([1]), done)); + }); + }); + + + }); describe('issues', function () { it('should handle uncaught exceptions (Issue 123)', function (done) {