├── .gitignore ├── package.json ├── readme.md ├── spec ├── index-spec.js ├── mocks │ ├── bareSwagger.json │ └── invalidSwagger.json ├── ngApiResponseMock-spec.js └── support │ └── jasmine.json └── src ├── generateRequestMocks.js ├── generateResponseMocks.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | .idea 3 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-api-test-generator", 3 | "version": "0.1.4", 4 | "description": "Generate mockdata and tests based on an OpenApi specification (fka Swagger Specification)", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+ssh://git@github.com:Remco75/openapi-test-generator.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/Remco75/openapi-test-generator/issues" 11 | }, 12 | "scripts": { 13 | "test": "node node_modules/jasmine/bin/jasmine" 14 | }, 15 | "homepage": "https://github.com/Remco75/openapi-test-generator#readme", 16 | "dependencies": { 17 | "asyncawait": "^1.0.6", 18 | "json-schema-deref-sync": "^0.3.2", 19 | "json-schema-test-data-generator": "^0.1.1", 20 | "mkdirp": "^0.5.1", 21 | "swagger-schema-official": "^2.0.0-bab6bed", 22 | "swagger-test-templates": "1.3.0", 23 | "swagmock": "https://github.com/Remco75/swagmock", 24 | "z-schema": "^3.18.0" 25 | }, 26 | "devDependencies": { 27 | "chai": "^3.5.0", 28 | "file-exists": "^2.0.0", 29 | "jasmine": "^2.5.2", 30 | "request": "^2.75.0", 31 | "rimraf": "^2.5.4" 32 | }, 33 | "main": "src/index.js", 34 | "keywords": [ 35 | "Swagger", 36 | "OpenApi", 37 | "specification", 38 | "test", 39 | "generator", 40 | "mock", 41 | "ngApiMock" 42 | ], 43 | "author": { 44 | "name": "Remco Vlierman", 45 | "email": "rcvlierman@gmail.com" 46 | }, 47 | "license": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # OpenAPI test and mock data generator 2 | This small module makes it easy to generate *tests* and *request* and *response* data for you openAPI spec (f.k.a. Swagger-spec). 3 | It makes use of some great modules out there to generate request mocks, response mocks, and then ties 'em together. 4 | 5 | The generated response data is great for communication with the Frontend devvers in your team: tell 'em to use these files as mocks. 6 | As time and your API progresses they can just generate the most up date mocks. 7 | 8 | If you would like to use the seperate functionality, (ie just generate mocks, or only the test templates) check these dependencies we use: 9 | 10 | - [swagger-test-templates](https://www.npmjs.com/package/swagger-test-templates) 11 | - [swagmock](https://www.npmjs.com/package/swagmock) 12 | - [json-schema-test-data-generator](https://www.npmjs.com/package/json-schema-test-data-generator) 13 | 14 | 15 | 16 | ## Installing 17 | `npm install open-api-test-generator` 18 | 19 | ## Usage 20 | ```javascript 21 | //Simply import the module with 22 | var OpenApiGenerator = require('open-api-test-generator'); 23 | //Import your openAPI spec: 24 | var spec = require('path/to/swagger.json'); 25 | 26 | //Construct the generator object: 27 | var generator = OpenApiGenerator(spec, 'path/to/write/tests/and/mocks'); 28 | 29 | //Then use it: 30 | generator.generate(); 31 | ``` 32 | 33 | ### options 34 | The generate function can also be called with 2 optional parameters 35 | - writeRequestMocks: should we write request mocks to file? default false 36 | - templatesPath: path to overwrite the templates used to generate tests 37 | 38 | ## ng-apimock 39 | To generate mocks for the [ng-api-mock](https://www.npmjs.com/package/ng-apimock) module: 40 | 41 | ```javascript 42 | generator.generateNgApiMockData().then(function(mockData) { 43 | // do something with mocks 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /spec/index-spec.js: -------------------------------------------------------------------------------- 1 | describe('OpenApiTestGenerator', function() { 2 | var OpenApiGenerator = require('../src/index'), 3 | // responseMockgenerator = require('../src/generateResponseMocks'), 4 | fs = require('fs'), 5 | fileExists = require('file-exists'), 6 | path = require('path'), 7 | rimraf = require('rimraf'), 8 | outputTestPath = path.join('.tmp/generated'), 9 | spec = require('../spec/mocks/bareSwagger.json'); 10 | 11 | describe('when the building the generator object though the constructor', function() { 12 | beforeEach(function() { 13 | rimraf.sync(path.join(outputTestPath,'/**')); 14 | }); 15 | 16 | it('should return an object with a generator property', function() { 17 | var myGenerator = OpenApiGenerator(spec, outputTestPath); 18 | expect(myGenerator.generate).toBeTruthy(); 19 | expect(myGenerator.generateNgApiMockdata).toBeTruthy(); 20 | }); 21 | 22 | it('should fail when we do not supply a open-api json', function() { 23 | expect(OpenApiGenerator).toThrow(new Error('please provide a openAPI json to start')); 24 | }); 25 | 26 | it('should fail when we supply INVALID a open-api json', function() { 27 | expect(function() { OpenApiGenerator(require('../spec/mocks/invalidSwagger.json'))}).toThrow(); 28 | }); 29 | 30 | it('should build an valid directory structure', function() { 31 | var myGenerator = OpenApiGenerator(spec, outputTestPath); 32 | expect(fs.statSync(path.join(outputTestPath, 'get/path1/{orderId}')).isDirectory()).toBe(true); 33 | expect(fs.statSync(path.join(outputTestPath, 'post/path2')).isDirectory()).toBe(true); 34 | }); 35 | }); 36 | 37 | describe('when calling "generate"', function() { 38 | var myGenerator; 39 | 40 | beforeEach(function() { 41 | rimraf.sync(path.join(outputTestPath,'/**')); 42 | myGenerator = OpenApiGenerator(spec, outputTestPath); 43 | }); 44 | 45 | it('should create testfiles for each path, in the output dir', function() { 46 | myGenerator.generate(); 47 | expect(fileExists(path.join(outputTestPath, 'path1-{orderId}-spec.js'))).toBe(true); 48 | expect(fileExists(path.join(outputTestPath, 'path2-spec.js'))).toBe(true); 49 | }); 50 | 51 | it('should NOT create testfiles for each path, in the output dir', function() { 52 | myGenerator.generate(); 53 | expect(fileExists(path.join(outputTestPath, 'get/path1/{orderId}/request-array.json'))).toBe(false); 54 | expect(fileExists(path.join(outputTestPath, 'post/path2/request-array.json'))).toBe(false); 55 | }); 56 | 57 | it('should create requestMocks for each path / operation', function() { 58 | myGenerator.generate(true); 59 | expect(fileExists(path.join(outputTestPath, 'get/path1/{orderId}/request-array.json'))).toBe(true); 60 | expect(fileExists(path.join(outputTestPath, 'post/path2/request-array.json'))).toBe(true); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /spec/mocks/bareSwagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger" : "2.0", 3 | "info" : { 4 | "title": "A very minimal Valid definition", 5 | "version": "1.0" 6 | }, 7 | "host" : "localhost:8080", 8 | "basePath" : "/somebase", 9 | "tags" : [{ 10 | "name" : "Calculation", 11 | "description" : "calculation" 12 | }], 13 | "paths" : { 14 | "/path1/{orderId}" : { 15 | "get" : { 16 | "operationId" : "path1", 17 | "consumes" : ["application/json"], 18 | "produces" : ["application/json"], 19 | 20 | "responses" : { 21 | "200" : { 22 | "description" : "successful operation", 23 | "schema": { 24 | "$ref": "#/definitions/User" 25 | } 26 | }, 27 | "400": { 28 | "description": "validation errors" 29 | } 30 | }, 31 | "parameters": [ 32 | { 33 | "name": "orderId", 34 | "in": "path", 35 | "description": "ID of pet that needs to be fetched", 36 | "required": true, 37 | "type": "integer", 38 | "maximum": 10.0, 39 | "minimum": 1.0 40 | } 41 | ] 42 | } 43 | }, 44 | "/path2" : { 45 | "post" : { 46 | "operationId" : "path2", 47 | "consumes" : ["application/json"], 48 | "produces" : ["application/json"], 49 | "parameters": [ 50 | { 51 | "in": "body", 52 | "name": "body", 53 | "description": "Created user object", 54 | "required": true, 55 | "schema": { 56 | "$ref": "#/definitions/User" 57 | } 58 | } 59 | ], 60 | "responses" : { 61 | "200" : { 62 | "description" : "successful operation", 63 | "schema": { 64 | "$ref": "#/definitions/User" 65 | } 66 | } 67 | } 68 | } 69 | } 70 | }, 71 | "definitions": { 72 | "User": { 73 | "type": "object", 74 | "properties": { 75 | "id": { 76 | "type": "integer", 77 | "format": "int64" 78 | }, 79 | "username": { 80 | "type": "string", 81 | "example": "remco75" 82 | }, 83 | "firstName": { 84 | "type": "string", 85 | "example": "Remco" 86 | }, 87 | "lastName": { 88 | "type": "string", 89 | "example": "Vlierman" 90 | }, 91 | "email": { 92 | "type": "string" 93 | }, 94 | "password": { 95 | "type": "string" 96 | }, 97 | "phone": { 98 | "type": "string" 99 | }, 100 | "userStatus": { 101 | "type": "integer", 102 | "format": "int32", 103 | "description": "User Status", 104 | "minimum": 0 105 | } 106 | }, 107 | "xml": { 108 | "name": "User" 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /spec/mocks/invalidSwagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger" : "2.0", 3 | "host" : "localhost:8080", 4 | "basePath" : "/online/rest/mortgage", 5 | "tags" : [{ 6 | "name" : "Calculation", 7 | "description" : "Mortgage calculation" 8 | }, { 9 | "name" : "Mail", 10 | "description" : "Mail sending" 11 | } 12 | ], 13 | "paths" : { 14 | "/calculation/anonymousdata/findby/{guid}/{code}" : { 15 | "get" : { 16 | "tags" : ["Calculation"], 17 | "operationId" : "getMortgageDetailsByGuidAndCode", 18 | "consumes" : ["application/json"], 19 | "produces" : ["application/json"], 20 | "parameters" : [{ 21 | "name" : "guid", 22 | "in" : "path", 23 | "required" : true, 24 | "type" : "string" 25 | }, { 26 | "name" : "code", 27 | "in" : "path", 28 | "required" : true, 29 | "type" : "string" 30 | } 31 | ], 32 | "responses" : { 33 | "200" : { 34 | "description" : "successful operation", 35 | "schema" : { 36 | "$ref" : "#/definitions/AnonymousDataResponse" 37 | }, 38 | "headers" : { 39 | } 40 | } 41 | } 42 | } 43 | }, 44 | "/calculation/anonymousdata/store" : { 45 | "post" : { 46 | "tags" : ["Calculation"], 47 | "operationId" : "storeMortgageDetails", 48 | "consumes" : ["application/json"], 49 | "produces" : ["application/json"], 50 | "parameters" : [{ 51 | "in" : "body", 52 | "name" : "body", 53 | "required" : false, 54 | "schema" : { 55 | "$ref" : "#/definitions/MaxMortgageCalculationInput" 56 | } 57 | } 58 | ], 59 | "responses" : { 60 | "200" : { 61 | "description" : "successful operation", 62 | "schema" : { 63 | "$ref" : "#/definitions/AnonymousDataStorageResponse" 64 | }, 65 | "headers" : { 66 | } 67 | } 68 | } 69 | } 70 | }, 71 | "/calculation/calculate/all" : { 72 | "post" : { 73 | "tags" : ["Calculation"], 74 | "summary" : "Calculate possible mortgage", 75 | "description" : "From simple income to the total picture.", 76 | "operationId" : "getMortgageAllDetails", 77 | "consumes" : ["application/json"], 78 | "produces" : ["application/json"], 79 | "parameters" : [{ 80 | "in" : "body", 81 | "name" : "body", 82 | "required" : false, 83 | "schema" : { 84 | "$ref" : "#/definitions/MaxMortgageCalculationInput" 85 | } 86 | } 87 | ], 88 | "responses" : { 89 | "200" : { 90 | "description" : "successful operation", 91 | "schema" : { 92 | "$ref" : "#/definitions/MortgageResponse" 93 | } 94 | } 95 | } 96 | } 97 | }, 98 | "/calculation/calculate/monthly-costs" : { 99 | "post" : { 100 | "tags" : ["Calculation"], 101 | "operationId" : "calculateMonthlyCosts", 102 | "consumes" : ["application/json"], 103 | "produces" : ["application/json"], 104 | "parameters" : [{ 105 | "in" : "body", 106 | "name" : "body", 107 | "required" : false, 108 | "schema" : { 109 | "$ref" : "#/definitions/CalculateMonthlyCostsRequest" 110 | } 111 | } 112 | ], 113 | "responses" : { 114 | "200" : { 115 | "description" : "successful operation", 116 | "schema" : { 117 | "$ref" : "#/definitions/MonthlyCostsResponse" 118 | }, 119 | "headers" : { 120 | } 121 | } 122 | } 123 | } 124 | }, 125 | "/calculation/interest-discounts" : { 126 | "get" : { 127 | "tags" : ["Calculation"], 128 | "operationId" : "getInterestDiscounts", 129 | "consumes" : ["application/json"], 130 | "produces" : ["application/json"], 131 | "parameters" : [], 132 | "responses" : { 133 | "200" : { 134 | "description" : "successful operation", 135 | "schema" : { 136 | "$ref" : "#/definitions/InterestDiscountResponse" 137 | }, 138 | "headers" : { 139 | } 140 | } 141 | } 142 | } 143 | }, 144 | "/calculation/sendmail/calculation" : { 145 | "post" : { 146 | "tags" : ["Mail"], 147 | "summary" : "Sending email containing mortgage details", 148 | "description" : "", 149 | "operationId" : "emailMortgageCalculation", 150 | "consumes" : ["application/json"], 151 | "produces" : ["application/json"], 152 | "parameters" : [{ 153 | "in" : "body", 154 | "name" : "body", 155 | "required" : false, 156 | "schema" : { 157 | "$ref" : "#/definitions/EmailMortgageRequest" 158 | } 159 | } 160 | ], 161 | "responses" : { 162 | "default" : { 163 | "description" : "successful operation" 164 | } 165 | } 166 | } 167 | } 168 | }, 169 | "definitions" : { 170 | "InterestDiscount" : { 171 | "type" : "object", 172 | "properties" : { 173 | "type" : { 174 | "type" : "string", 175 | "enum" : ["NEW_CHECKING_ACCOUNT", "QUICK_CLOSING_MORTGAGE", "QUOTE_DISCOUNT"] 176 | }, 177 | "value" : { 178 | "type" : "number" 179 | }, 180 | "useInMaxMortgageCalculation" : { 181 | "type" : "boolean", 182 | "default" : false 183 | }, 184 | "alwaysUseInMaxMortgageCalculation" : { 185 | "type" : "boolean", 186 | "default" : false 187 | }, 188 | "useInMonthlyCostsCalculation" : { 189 | "type" : "boolean", 190 | "default" : false 191 | } 192 | } 193 | }, 194 | "MaxMortgageCalculationInput" : { 195 | "type" : "object", 196 | "required" : ["yearlyIncome"], 197 | "properties" : { 198 | "yearlyIncome" : { 199 | "type" : "number", 200 | "example" : 22500.0, 201 | "minimum" : 0.0, 202 | "maximum" : 500000.0 203 | }, 204 | "yearlyIncomePartner" : { 205 | "type" : "number", 206 | "minimum" : 0.0, 207 | "maximum" : 500000.0 208 | }, 209 | "monthlyAlimony" : { 210 | "type" : "number", 211 | "minimum" : 0.0, 212 | "maximum" : 10000.0 213 | }, 214 | "liabilities" : { 215 | "type" : "number", 216 | "minimum" : 0.0, 217 | "maximum" : 100000.0 218 | }, 219 | "askingPrice" : { 220 | "type" : "number", 221 | "example" : 210000.0, 222 | "minimum" : 0.0, 223 | "maximum" : 9999999.0 224 | }, 225 | "renovationCosts" : { 226 | "type" : "number" 227 | }, 228 | "ownMoney" : { 229 | "type" : "number" 230 | }, 231 | "sellingRequest" : { 232 | "$ref" : "#/definitions/MortgageSellingRequest" 233 | }, 234 | "interestDiscounts" : { 235 | "type" : "array", 236 | "items" : { 237 | "type" : "string", 238 | "enum" : ["NEW_CHECKING_ACCOUNT", "QUICK_CLOSING_MORTGAGE", "QUOTE_DISCOUNT"] 239 | } 240 | } 241 | } 242 | }, 243 | "LoanPart" : { 244 | "type" : "object", 245 | "required" : ["share", "type"], 246 | "properties" : { 247 | "type" : { 248 | "type" : "string", 249 | "enum" : ["NOT_APPLICABLE", "INTEREST_ONLY", "SAVING", "ANNUITY", "INVESTMENT"] 250 | }, 251 | "share" : { 252 | "type" : "number" 253 | } 254 | } 255 | }, 256 | "MortgageSellingRequest" : { 257 | "type" : "object", 258 | "properties" : { 259 | "sellingPriceCurrentProperty" : { 260 | "type" : "number" 261 | }, 262 | "currentMortgage" : { 263 | "type" : "number" 264 | } 265 | } 266 | }, 267 | "MonthlyPayments" : { 268 | "type" : "object", 269 | "properties" : { 270 | "grossMortgageCosts" : { 271 | "type" : "number" 272 | }, 273 | "netMortgageCosts" : { 274 | "type" : "number" 275 | }, 276 | "cityTax" : { 277 | "type" : "number" 278 | }, 279 | "cableAndInternetCosts" : { 280 | "type" : "number" 281 | }, 282 | "publicUtilityCosts" : { 283 | "type" : "number" 284 | }, 285 | "serviceAndMaintenanceCosts" : { 286 | "type" : "number" 287 | }, 288 | "insuranceCosts" : { 289 | "type" : "number" 290 | }, 291 | "totalCosts" : { 292 | "type" : "number" 293 | }, 294 | "grandTotalNetCosts" : { 295 | "type" : "number" 296 | } 297 | } 298 | }, 299 | "MortgageSellingResponse" : { 300 | "type" : "object", 301 | "properties" : { 302 | "sellingResult" : { 303 | "type" : "number" 304 | }, 305 | "sellingCosts" : { 306 | "type" : "number" 307 | }, 308 | "sellingCostsPercentage" : { 309 | "type" : "number" 310 | }, 311 | "sellingTotal" : { 312 | "type" : "number" 313 | } 314 | } 315 | }, 316 | "EmailMortgageRequest" : { 317 | "type" : "object", 318 | "required" : ["emailAddress", "mortgageRequest"], 319 | "properties" : { 320 | "mortgageRequest" : { 321 | "$ref" : "#/definitions/MaxMortgageCalculationInput" 322 | }, 323 | "emailAddress" : { 324 | "type" : "string" 325 | } 326 | } 327 | }, 328 | "AnonymousDataStorageResponse" : { 329 | "type" : "object", 330 | "properties" : { 331 | "guid" : { 332 | "type" : "string", 333 | "format" : "uuid" 334 | }, 335 | "code" : { 336 | "type" : "string" 337 | } 338 | } 339 | }, 340 | "AnonymousDataResponse" : { 341 | "type" : "object", 342 | "properties" : { 343 | "mortgageRequest" : { 344 | "$ref" : "#/definitions/MaxMortgageCalculationInput" 345 | } 346 | } 347 | }, 348 | "BuyersCosts" : { 349 | "type" : "object", 350 | "properties" : { 351 | "nhgCosts" : { 352 | "type" : "number" 353 | }, 354 | "capitalTransferTax" : { 355 | "type" : "number" 356 | }, 357 | "notaryFees" : { 358 | "type" : "number" 359 | }, 360 | "advisoryFees" : { 361 | "type" : "number" 362 | }, 363 | "brokerageFees" : { 364 | "type" : "number" 365 | }, 366 | "valuationFees" : { 367 | "type" : "number" 368 | }, 369 | "totalCosts" : { 370 | "type" : "number" 371 | } 372 | } 373 | }, 374 | "MortgageResponse" : { 375 | "type" : "object", 376 | "properties" : { 377 | "maximumMortgage" : { 378 | "type" : "number" 379 | }, 380 | "maximumMortgageForIncome" : { 381 | "type" : "number" 382 | }, 383 | "maximumMortgageForProperty" : { 384 | "type" : "number" 385 | }, 386 | "requiredToBuyProperty" : { 387 | "type" : "number" 388 | }, 389 | "financingRequirement" : { 390 | "type" : "number" 391 | }, 392 | "requiredDeposit" : { 393 | "type" : "number" 394 | }, 395 | "totalDeposit" : { 396 | "type" : "number" 397 | }, 398 | "mortgage" : { 399 | "type" : "number" 400 | }, 401 | "interestRate" : { 402 | "type" : "number" 403 | }, 404 | "nhgPossible" : { 405 | "type" : "boolean", 406 | "default" : false 407 | }, 408 | "redemptionFrequency" : { 409 | "type" : "string", 410 | "enum" : ["VARIABLE", "ONE_YEAR_FIXED", "THREE_YEARS_FIXED", "FIVE_YEARS_FIXED", "SIX_YEARS_FIXED", "TEN_YEARS_FIXED", "TWELVE_YEARS_FIXED", "FIFTEEN_YEARS_FIXED", "TWENTY_YEARS_FIXED", "THIRTY_YEARS_FIXED"] 411 | }, 412 | "redemptionType" : { 413 | "type" : "string", 414 | "enum" : ["NOT_APPLICABLE", "INTEREST_ONLY", "SAVING", "ANNUITY", "INVESTMENT"] 415 | }, 416 | "buyersCosts" : { 417 | "$ref" : "#/definitions/BuyersCosts" 418 | }, 419 | "monthlyPayments" : { 420 | "$ref" : "#/definitions/MonthlyPayments" 421 | }, 422 | "sellingResponse" : { 423 | "$ref" : "#/definitions/MortgageSellingResponse" 424 | }, 425 | "maximumLtvRatio" : { 426 | "type" : "number" 427 | } 428 | } 429 | }, 430 | "MonthlyCostsResponse" : { 431 | "type" : "object", 432 | "properties" : { 433 | "interestRatePart1" : { 434 | "type" : "number" 435 | }, 436 | "interestRatePart2" : { 437 | "type" : "number" 438 | }, 439 | "grossMortgageCosts" : { 440 | "type" : "number" 441 | }, 442 | "nghInterestRate" : { 443 | "type" : "boolean", 444 | "default" : false 445 | } 446 | } 447 | }, 448 | "InterestDiscountResponse" : { 449 | "type" : "object", 450 | "properties" : { 451 | "interestDiscountList" : { 452 | "type" : "array", 453 | "items" : { 454 | "$ref" : "#/definitions/InterestDiscount" 455 | } 456 | } 457 | } 458 | }, 459 | "CalculateMonthlyCostsRequest" : { 460 | "type" : "object", 461 | "required" : ["intendedMortgage", "loanPart1", "maxMortgage", "propertyValue", "redemptionFrequency", "yearlyIncome", "yearlyIncomePartner"], 462 | "properties" : { 463 | "yearlyIncome" : { 464 | "type" : "number", 465 | "minimum" : 0.0, 466 | "maximum" : 500000.0 467 | }, 468 | "yearlyIncomePartner" : { 469 | "type" : "number", 470 | "minimum" : 0.0, 471 | "maximum" : 500000.0 472 | }, 473 | "maxMortgage" : { 474 | "type" : "number" 475 | }, 476 | "intendedMortgage" : { 477 | "type" : "number" 478 | }, 479 | "loanPart1" : { 480 | "$ref" : "#/definitions/LoanPart" 481 | }, 482 | "loanPart2" : { 483 | "$ref" : "#/definitions/LoanPart" 484 | }, 485 | "redemptionFrequency" : { 486 | "type" : "string", 487 | "enum" : ["VARIABLE", "ONE_YEAR_FIXED", "THREE_YEARS_FIXED", "FIVE_YEARS_FIXED", "SIX_YEARS_FIXED", "TEN_YEARS_FIXED", "TWELVE_YEARS_FIXED", "FIFTEEN_YEARS_FIXED", "TWENTY_YEARS_FIXED", "THIRTY_YEARS_FIXED"] 488 | }, 489 | "propertyValue" : { 490 | "type" : "number" 491 | }, 492 | "interestDiscounts" : { 493 | "type" : "array", 494 | "items" : { 495 | "type" : "string", 496 | "enum" : ["NEW_CHECKING_ACCOUNT", "QUICK_CLOSING_MORTGAGE", "QUOTE_DISCOUNT"] 497 | } 498 | } 499 | } 500 | } 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /spec/ngApiResponseMock-spec.js: -------------------------------------------------------------------------------- 1 | fdescribe('The ngApiMock generator', function() { 2 | var responseMockgenerator = require('../src/generateResponseMocks'), 3 | fs = require('fs'), 4 | deref = require('json-schema-deref-sync'), 5 | path = require('path'), 6 | mkdirp = require('mkdirp'), 7 | rimraf = require('rimraf'), 8 | fileExists = require('file-exists'), 9 | outputTestPath = './.tmp/generated', 10 | flatPaths = [ 11 | { path: '/path1/{orderId}', operation: 'get' }, 12 | { path: '/path2', operation: 'post' } 13 | ], 14 | spec = require('../spec/mocks/bareSwagger.json'); 15 | 16 | describe('when calling ngApiMockgenerator', function() { 17 | var myGenerator; 18 | rimraf.sync(path.join(outputTestPath, '/**')); 19 | 20 | beforeEach(function() { 21 | myGenerator = responseMockgenerator(spec, flatPaths, outputTestPath); 22 | mkdirp.sync(path.join(process.cwd(), outputTestPath, '/path1/{orderId}', 'get')); 23 | mkdirp.sync(path.join(process.cwd(), outputTestPath, '/path2', 'post')); 24 | }); 25 | 26 | it('should return an array with 3 mocks', function(done) { 27 | myGenerator.generateNgApiMockData().then(function(data) { 28 | expect(Array.isArray(data)).toBe(true); 29 | expect(data.length).toBe(2); 30 | done(); 31 | }); 32 | }); 33 | 34 | fit('should add the basePath to the ngApiMock expression if present', function(done) { 35 | myGenerator.generateNgApiMockData().then(function(data) { 36 | expect(data[0].expression).toContain('somebase'); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('should should write 2 mockfiles', function(done) { 42 | myGenerator.generateNgApiMockData().then(function(data) { 43 | expect(fileExists(path.join(outputTestPath,'path2_post.json'))).toBe(true); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | -------------------------------------------------------------------------------- /src/generateRequestMocks.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | var fs = require('fs'), 4 | path = require('path'), 5 | mockRequestGenerator = require('json-schema-test-data-generator'); 6 | 7 | /** 8 | * init the module with a swagger spec, flattened paths and outputFolder 9 | * @param spec 10 | * @param flatPaths 11 | * @param outputBase 12 | * @returns {*} 13 | */ 14 | module.exports = function(spec, flatPaths, outputBase) { 15 | 16 | /** 17 | * @description generate a mock-object for a given path / operation combination based on it's parameter schema 18 | * @param apiPath {string} path to generate for 19 | * @param operation {string} operation to generate for 20 | * @param successStatusCode {number} The http status-code which is returned for succes 21 | * @param validationErrorStatusCode {number} The http status-code which is returned for request validation errors 22 | * @param writeMockToFile {boolean} Should we write the mockfile to the output path 23 | * @returns {object} the mock for the path / operation combi. 24 | * Contains 2 properties: 25 | * - [successStatusCode]: contains the mocks that should lead to a succesful result 26 | * - [validationErrorStatusCode]: contains the mocks that should lead to a unsuccesful result, with validation errors 27 | */ 28 | function generateRequestMock(apiPath, operation, successStatusCode, validationErrorStatusCode, writeMockToFile) { 29 | var pathMock = {}, getMocks = {}; 30 | 31 | pathMock[successStatusCode] = []; 32 | pathMock[validationErrorStatusCode] = []; 33 | 34 | if(spec['paths'][apiPath][operation].parameters) { 35 | 36 | spec['paths'][apiPath][operation].parameters.forEach(function(param) { 37 | getMocks[param.name] = (param.schema) ? mockRequestGenerator(param.schema) : mockRequestGenerator(param); 38 | }); 39 | } 40 | 41 | if(Object.keys(getMocks).length > 0) { 42 | Object.keys(getMocks).forEach(function(paramName) { 43 | 44 | getMocks[paramName].filter(function (fullMock) { return fullMock.valid; }).forEach(function (mock) { 45 | var validMocks = {}; 46 | validMocks[paramName] = mock.data; 47 | validMocks.description = mock.message; 48 | pathMock[successStatusCode].push(validMocks); 49 | }); 50 | 51 | getMocks[paramName].filter(function (fullMock) { return !fullMock.valid; }).forEach(function (mock) { 52 | var inValidMocks = {}; 53 | inValidMocks[paramName] = mock.data; 54 | inValidMocks.description = mock.message; 55 | pathMock[validationErrorStatusCode].push(inValidMocks); 56 | }); 57 | }); 58 | 59 | if (writeMockToFile) { 60 | fs.writeFileSync(path.join(process.cwd(), outputBase, operation, apiPath) + '/request-array.json', JSON.stringify(getMocks), {}, function (err) { 61 | if (err) { return console.log(err); } 62 | console.log("The request mock for " + apiPath + operation +" was saved"); 63 | }); 64 | } 65 | return pathMock; 66 | } 67 | } 68 | 69 | return { 70 | generateRequestMock: generateRequestMock 71 | }; 72 | } ; 73 | }()); 74 | -------------------------------------------------------------------------------- /src/generateResponseMocks.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | var fs = require('fs'), 4 | path = require('path'), 5 | Swagmock = require('swagmock'), 6 | async = require('asyncawait/async'), 7 | await = require('asyncawait/await'); 8 | 9 | /** 10 | * init the module with a swagger spec, flattened paths and outputFolder 11 | * @param spec {Object} The swagger spec 12 | * @param flatPaths {Array} Array with path / operation objects 13 | * @param outputBase {String} Where to write 14 | * @returns {*} 15 | */ 16 | module.exports = function(spec, flatPaths, outputBase) { 17 | /** 18 | * Generate mock for the given swagger specn in ngApiMock style and write to outputDir 19 | * @return {Promise} resolves with Array all the mock objects 20 | */ 21 | var collectNgApiMockData = async (function () { 22 | var ngApiMocks = []; 23 | 24 | var basePath = spec.basePath || ''; 25 | await (flatPaths.forEach(function(apiPathOp) { 26 | Swagmock(spec).responses({path: apiPathOp.path, operation: apiPathOp.operation, useExamples: true}, function(err, mock) { 27 | var responses = {}, 28 | expessionPath = path.join(basePath, apiPathOp.path.replace(/\{(.*)\}/, '*')); 29 | 30 | for (var statusCode in mock.responses) { 31 | responses['generated_'+statusCode] = { 32 | default: false, 33 | status: statusCode, 34 | data: mock.responses[statusCode] 35 | }; 36 | } 37 | 38 | var thisMock = { 39 | expression: expessionPath, 40 | method: apiPathOp.operation, 41 | name: apiPathOp.path.replace(/\//g , '_').replace(/_/, '') + '_' + apiPathOp.operation, 42 | isArray: Array.isArray(mock.responses[statusCode]), 43 | responses: responses 44 | }; 45 | 46 | ngApiMocks.push(thisMock); 47 | }); 48 | })); 49 | // due to the async nature of the mock generation we can only do this in a new loop 50 | ngApiMocks.forEach(function(ngApiMock) { 51 | fs.writeFileSync(path.join(outputBase, ngApiMock.name + '.json' ), JSON.stringify(ngApiMock)); 52 | }); 53 | return ngApiMocks; 54 | }); 55 | 56 | /** 57 | * @description generate responseMocks based on the schemes, where possible we will use the `example` properties to fill the data 58 | * @param apiPath {string} path to generate for 59 | * @param operation {string} operation to generate for 60 | * @param statusCode {number} http statusCode to generate the response for 61 | * @return {promise} the responseMock. Also written to file 62 | */ 63 | function generateResponseMock(apiPath, operation, statusCode) { 64 | return Swagmock(spec).responses({path: apiPath, operation: operation, response: statusCode}, function(error, mock) { 65 | if (error) { return console.log(error); } 66 | fs.writeFileSync(path.join(process.cwd(), outputBase, operation, apiPath)+'/response.json', JSON.stringify(mock)); 67 | return 'mock written'; 68 | }); 69 | } 70 | 71 | return { 72 | generateNgApiMockData: collectNgApiMockData, 73 | generateResponseMock: generateResponseMock 74 | }; 75 | } ; 76 | }()); 77 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | var fs = require('fs'), 4 | mkdirp = require('mkdirp'), 5 | stt = require('swagger-test-templates'), 6 | path = require('path'), 7 | deref = require('json-schema-deref-sync'), 8 | ZSchema = require('z-schema'), 9 | zSchema = new ZSchema(), 10 | swaggerSchema = require('swagger-schema-official/schema'), 11 | outputBase, spec, flatPaths, responseMockGenerator, requestMockGenerator; 12 | 13 | /** 14 | * @description Generates tests and mocks for the given openApi spec. 15 | * @param writeMocks {boolean} Whether the module should write the generated requestMocks to file seperatly 16 | * @param templatesPath {String} Folder with tests-tempates. Needed as long as swagger-test-templates does not support options 17 | * @todo remove when stt support request options 18 | */ 19 | function generate(writeMocks, templatesPath) { 20 | var requestMocks = {}, 21 | basePath = spec.basePath || ''; 22 | 23 | flatPaths.forEach(function(apiPath) { 24 | requestMocks[path.join(basePath, apiPath.path)] = {}; 25 | requestMocks[path.join(basePath, apiPath.path)][apiPath.operation] = requestMockGenerator(apiPath.path, apiPath.operation, 200, 400, writeMocks); 26 | responseMockGenerator.generateResponseMock(apiPath.path, apiPath.operation, 200); 27 | }); 28 | generateTests(requestMocks, templatesPath); 29 | } 30 | 31 | /** 32 | * @description Generates tests for the given openApi spec. 33 | * @param mocks {object} Mock data ordered by endpoint / operation / responseCode 34 | * @param templatesPath {String} Folder with tests-tempates. Needed as long as swagger-test-templates does not support options 35 | */ 36 | function generateTests(mocks, templatesPath) { 37 | var testConfig = { 38 | assertionFormat: 'should', testModule: 'request', pathName: [], requestData: mocks, maxLen: -1, templatesPath: templatesPath 39 | }; 40 | stt.testGen(spec, testConfig).forEach(function(file) { 41 | //we want spec in the name, not test 42 | fs.writeFileSync(path.join(outputBase, file.name.replace('test', 'spec')), file.test); 43 | }); 44 | } 45 | 46 | /** 47 | * @description Smash nested path / operation info from swaggerJson for easier handling 48 | * @param apiPaths {object} the endpoint for the api 49 | * @param base {string} baseDirectory if filled with a path we will create a directory structure to store mockdata 50 | * @return {array} paths / operations combinations 51 | */ 52 | function flattenAndCreateDirectories(apiPaths, base) { 53 | var responsePaths = []; 54 | for (var localPath in apiPaths) { 55 | for ( var operation in apiPaths[localPath]) { 56 | responsePaths.push({ path: localPath, operation: operation }); 57 | if (base) { 58 | mkdirp.sync(path.join(process.cwd(), outputBase, operation, localPath)); 59 | } 60 | } 61 | } 62 | return responsePaths; 63 | } 64 | 65 | /** 66 | * @constructor 67 | * @param openApiSpec {object} A valid openApi Specification json describing your api 68 | * @param outputPath {string} The base where to write the files to 69 | * @returns {{generate: generate}} 70 | */ 71 | module.exports = function(openApiSpec, outputPath) { 72 | if (!openApiSpec) throw new Error('please provide a openAPI json to start'); 73 | if (!zSchema.validate(openApiSpec, swaggerSchema)) { 74 | var errors = zSchema.getLastErrors(); 75 | throw new Error('openAPI spec not valid\n' + JSON.stringify(errors, null, 2)); 76 | } 77 | if (!outputPath) { throw new Error('please supply an output path'); } 78 | 79 | outputBase = outputPath; 80 | spec = deref(openApiSpec); 81 | flatPaths = flattenAndCreateDirectories(spec.paths, outputBase); 82 | 83 | responseMockGenerator = require('./generateResponseMocks')(spec, flatPaths, outputBase); 84 | requestMockGenerator = require('./generateRequestMocks')(spec, flatPaths, outputBase).generateRequestMock; 85 | 86 | return { 87 | generate: generate, 88 | generateNgApiMockdata: responseMockGenerator.generateNgApiMockData 89 | } 90 | }; 91 | }()); 92 | --------------------------------------------------------------------------------