From 58ca5932e9d884aecd3c78cc6b51183115c82f49 Mon Sep 17 00:00:00 2001 From: Brandon Byars Date: Mon, 27 Mar 2017 12:49:56 -0500 Subject: [PATCH] Renamed to defacto; added Gruntfile and linting --- .eslintrc | 143 ++++++++++++++++++++++++++++++++++++++++ Gruntfile.js | 47 +++++++++++++ README.MD | 32 ++++----- package.json | 8 +-- src/defacto.js | 3 +- src/httpInterceptor.js | 8 +-- src/templatizer.js | 2 +- src/type.js | 8 +-- test/contractTest.js | 28 ++++---- test/templatizerTest.js | 2 +- 10 files changed, 235 insertions(+), 46 deletions(-) create mode 100644 .eslintrc create mode 100644 Gruntfile.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..f15a419 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,143 @@ +{ + "env": { + "node": true, + "browser": true, + "jquery": true, + "mocha": true + }, + "rules": { + "accessor-pairs": 2, + "array-bracket-spacing": [2, "never"], + "arrow-body-style": [2, "as-needed"], + "arrow-parens": [2, "as-needed"], + "arrow-spacing": [2, { "before": true, "after": true }], + "block-spacing": [2, "always"], + "brace-style": [2, "stroustrup", { "allowSingleLine": true }], + "camelcase": [2, {"properties": "always"}], + "comma-dangle": [2, "never"], + "comma-spacing": [2, {"before": false, "after": true}], + "comma-style": [2, "last"], + "complexity": [2, 7], + "computed-property-spacing": [2, "never"], + "consistent-return": 2, + "curly": 2, + "dot-location": [2, "property"], + "dot-notation": 2, + "eqeqeq": 2, + "generator-star-spacing": [2, {"before": true, "after": false}], + "handle-callback-err": [2, "error"], + "indent": [2, 4, {"SwitchCase": 1}], + "key-spacing": 2, + "keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}], + "linebreak-style": 2, + "max-depth": [2, 3], + "max-nested-callbacks": [2, 6], + "max-params": [2, 5], + "new-parens": 2, + "no-alert": 2, + "no-array-constructor": 2, + "no-bitwise": 2, + "no-caller": 2, + "no-case-declarations": 2, + "no-class-assign": 2, + "no-cond-assign": 2, + "no-confusing-arrow": 2, + "no-constant-condition": 2, + "no-control-regex": 2, + "no-const-assign": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-dupe-args": 2, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty": 2, + "no-empty-character-class": 2, + "no-empty-pattern": 2, + "no-eq-null": 2, + //"no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-semi": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + //"no-inner-declarations": [2, "both"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 2, + "no-loop-func": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 1}], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-nested-ternary": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + //"no-param-reassign": 2, + "no-path-concat": 2, + "no-plusplus": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-return-assign": [2, "always"], + "no-script-url": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-undef-init": 2, + "no-underscore-dangle": [2, { "allow": ["_links", "_behaviors", "_mode"] }], + "no-unexpected-multiline": 2, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-expressions": 2, + "no-unused-vars": 2, + // "no-use-before-define": 2, + "no-useless-call": 2, + "no-useless-concat": 2, + "no-void": 2, + "no-warning-comments": 1, + "no-with": 2, + "object-curly-spacing": [2, "always"], + //"one-var": [2, "always"], + "operator-assignment": [2, "always"], + "operator-linebreak": [2, "after"], + "quote-props": [2, "as-needed"], + "quotes": [2, "single", "avoid-escape"], + "require-yield": 2, + "semi": [2, "always"], + "semi-spacing": [2, {"before": false, "after": true}], + "space-before-blocks": 2, + "space-before-function-paren": 2, + "space-in-parens": [2, "never"], + "space-infix-ops": [2, {"int32Hint": false}], + "space-unary-ops": [1, { "words": true, "nonwords": false }], + "spaced-comment": [2, "always"], + "strict": [2, "global"], + "use-isnan": 2, + "valid-jsdoc": [2, {"requireReturn": false, "requireReturnDescription": false}], + "valid-typeof": 2, + "yoda": 2 + } +} diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..0d9afc9 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,47 @@ +'use strict'; + +var fs = require('fs'), + thisPackage = require('./package.json'), + version = process.env.DEFACTO_GRUNT_VERSION || thisPackage.version; + +module.exports = function (grunt) { + + require('time-grunt')(grunt); + + grunt.loadNpmTasks('grunt-mocha-test'); + grunt.loadNpmTasks('grunt-eslint'); + + grunt.initConfig({ + mochaTest: { + unit: { + options: { reporter: 'spec' }, + src: ['test/**/*.js'] + }, + functional: { + options: { reporter: 'spec' }, + src: ['functionalTest/**/*.js'] + } + }, + eslint: { + target: [ + 'Gruntfile.js', + 'src/**/*.js', + 'test/**/*.js', + 'functionalTest/**/*.js' + ] + } + }); + + grunt.registerTask('version', 'Set the version number', function () { + var newPackage = require('./package.json'); + + newPackage.version = version; + console.log('Using version ' + version); + fs.writeFileSync('./package.json', JSON.stringify(newPackage, null, 2) + '\n'); + }); + + grunt.registerTask('test:unit', 'Run the unit tests', ['mochaTest:unit']); + grunt.registerTask('test:functional', 'Run the functional tests', ['mochaTest:functional']); + grunt.registerTask('test', 'Run all non-performance tests', ['test:unit', 'test:functional']); + grunt.registerTask('default', ['eslint', 'version', 'test']); +}; diff --git a/README.MD b/README.MD index 18b1f68..8764a2a 100644 --- a/README.MD +++ b/README.MD @@ -1,26 +1,26 @@ -# node-litigate +# node-defacto -`node-litigate` asserts your API contract, represented as an OpenAPI/Swagger -specification. As the API provider testing your own service, `litigate` allows -you to make two types of assertions: +`node-defacto` discovers your _de facto_ API contract, represented as an OpenAPI/Swagger +specification. As the API provider testing your own service, `defacto` in conjunction +with a `swagger-diff` tool allows you to make two types of assertions: * The contract specification is up-to-date and correct * Everything in the contract specification has been tested -As the API consumer testing your application against a stub `litigate` allows -you to verify that the spec you're writing tests against is compatible with -the spec given by the API provider. +As the API consumer testing your application against a stub, `defacto` and +`swagger-diff` allow you to verify that the spec you're writing tests against is +compatible with the spec given by the API provider. ## How to use it -`node-litigate` needs to be initialized before any tests are run. In mocha, you can +`node-defacto` needs to be initialized before any tests are run. In mocha, you can use a [root-level before hook](https://mochajs.org/#root-level-hooks) to do the trick. This might be the initialization function for [mountebank](https://github.com/bbyars/mountebank): ```` // Outside of any describe block before(function () { - require('node-litigate').capture({ + require('node-defacto').capture({ title: 'mountebank', version: '1', baseURL: 'http://localhost:2525/', @@ -30,7 +30,7 @@ before(function () { }); ```` -Then execute your _service_ test suite against your API. `node-litigate` doesn't work +Then execute your _service_ test suite against your API. `node-defacto` doesn't work with your unit tests. It can only capture the test contract expectations for those tests that use node's `http` module to call your API over the wire. In the example above, all test traffic sent to is analyzed, which represents @@ -40,23 +40,23 @@ The complete OpenAPI specification that the tests expect is captured in ## How does it work -`node-litigate` wraps the `http` module, capturing all client requests and responses +`node-defacto` wraps the `http` module, capturing all client requests and responses that match the `host` and `basePath` given in the first parameter to the `capture` function. Each time a new OpenAPI `path` and `operation` is detected, it is added -to the spec. Each time `litigate` detects a new input `parameter`, it adds it to the +to the spec. Each time `defacto` detects a new input `parameter`, it adds it to the spec. Each time a new `response` is detected, it is added to the spec. Every request and response JSON body is captured, and all fields and types are added to the spec. ## Limitations -* `node-litigate` assumes JSON. +* `node-defacto` assumes JSON. * It assumes you're testing against an HTTP API rather than an HTTPS one * It assumes no tests are running in parallel -* It does nothing with headers, more or less assuming application/json -* It assumes you never use chunked encoding and always write the request body in one fell swoop +* It does nothing with headers, more or less assuming application/json (easy to fix) +* I think it assumes you never use chunked encoding and always write the request body in one fell swoop * It does not detect required fields. Future versions can assume required if ALL tests pass it in ## Contributing -`node-litigate` is not written in ES6 because it needs to support the oldest +`node-defacto` is not written in ES6 because it needs to support the oldest supported version of node (4.0), which does not fully support ES6. diff --git a/package.json b/package.json index 24677b0..42749d7 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "node-litigate", + "name": "node-defacto", "version": "0.0.1", "author": "Brandon Byars ", - "description": "Captures the API spec that your tests understand", + "description": "Captures the de facto API spec that your tests understand", "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/bbyars/node-litigate" + "url": "https://github.com/bbyars/node-defacto" }, "bugs": { - "url": "https://github.com/bbyars/node-litigate/issues", + "url": "https://github.com/bbyars/node-defacto/issues", "email": "brandon.byars@gmail.com" }, "keywords": [ diff --git a/src/defacto.js b/src/defacto.js index 694e1bb..ef18a7e 100644 --- a/src/defacto.js +++ b/src/defacto.js @@ -4,8 +4,7 @@ var Templatizer = require('./templatizer'), Contract = require('./contract'), interceptor = require('./httpInterceptor'), url = require('url'), - fs = require('fs'), - http = require('http'); + fs = require('fs'); function capture (options) { var host = url.parse(options.baseURL).host, diff --git a/src/httpInterceptor.js b/src/httpInterceptor.js index 32ce54b..c5289aa 100644 --- a/src/httpInterceptor.js +++ b/src/httpInterceptor.js @@ -7,13 +7,13 @@ function intercept (interceptor) { var currentRequest = {}, restores = []; - function _intercept (obj, fn, interceptor, resultProcessor) { + function interceptFunction (obj, fn, interceptorFn, resultProcessor) { var original = obj[fn]; obj[fn] = function () { var args = Array.prototype.slice.call(arguments); // Allow returning new args array to change parameters to intercepted function - var nextArgs = interceptor.apply(this, args) || args; + var nextArgs = interceptorFn.apply(this, args) || args; var result = original.apply(this, nextArgs || args); if (typeof resultProcessor !== 'undefined') { @@ -25,7 +25,7 @@ function intercept (interceptor) { restores.push(function () { obj[fn] = original; }); } - _intercept(http, 'request', function (requestOptions, callback) { + interceptFunction(http, 'request', function (requestOptions, callback) { if (typeof requestOptions === 'string') { requestOptions = url.parse(requestOptions); } @@ -68,7 +68,7 @@ function intercept (interceptor) { // Return changed callback to add our response callback return [requestOptions, callbackWithInterceptor]; }, function (request) { - _intercept(request, 'write', function (body) { + interceptFunction(request, 'write', function (body) { currentRequest.body = body; }); }); diff --git a/src/templatizer.js b/src/templatizer.js index 853943c..bc4aeaa 100644 --- a/src/templatizer.js +++ b/src/templatizer.js @@ -27,7 +27,7 @@ function create (possiblePaths) { values = patterns[template].valuePattern.exec(path), parameters = {}; - for (var i = 1; i < names.length; i++) { + for (var i = 1; i < names.length; i += 1) { var name = names[i].replace('{', '').replace('}', ''); parameters[name] = values[i]; } diff --git a/src/type.js b/src/type.js index 57a56ad..ee6a045 100644 --- a/src/type.js +++ b/src/type.js @@ -3,13 +3,13 @@ var util = require('util'); function isInteger (value) { - return (typeof value === 'number' && value.toString().indexOf('.') < 0) - || (typeof value === 'string' && /^\d+$/.test(value)); + return (typeof value === 'number' && value.toString().indexOf('.') < 0) || + (typeof value === 'string' && /^\d+$/.test(value)); } function isDouble (value) { - return (typeof value === 'number' && value.toString().indexOf('.') >= 0) - || (typeof value === 'string' && /^\d*\.\d+$/.test(value)); + return (typeof value === 'number' && value.toString().indexOf('.') >= 0) || + (typeof value === 'string' && /^\d*\.\d+$/.test(value)); } function isBoolean (value) { diff --git a/test/contractTest.js b/test/contractTest.js index 5e6459a..90e7a84 100644 --- a/test/contractTest.js +++ b/test/contractTest.js @@ -7,11 +7,11 @@ var assert = require('assert'), describe('contract', function () { describe('#merge', function () { it('should add path if it does not already exist', function () { - var templatizer = Templatizer.create(['/']), - contract = { paths: {} }, - request = { path: '/', method: 'GET' }, - response = { statusCode: 200 }, - spec = Contract.create(templatizer).merge(contract, request, response); + var templatizer = Templatizer.create(['/']), + contract = { paths: {} }, + request = { path: '/', method: 'GET' }, + response = { statusCode: 200 }, + spec = Contract.create(templatizer).merge(contract, request, response); assert.deepEqual(spec, { paths: { @@ -19,7 +19,7 @@ describe('contract', function () { get: { parameters: [], responses: { - '200': { schema: {}, description: '' } + 200: { schema: {}, description: '' } } } } @@ -41,7 +41,7 @@ describe('contract', function () { get: { parameters: [], responses: { - '200': { schema: {}, description: '' } + 200: { schema: {}, description: '' } } } } @@ -72,7 +72,7 @@ describe('contract', function () { type: 'string' } ], - responses: { '200': { schema: {}, description: '' } } + responses: { 200: { schema: {}, description: '' } } } } } @@ -102,7 +102,7 @@ describe('contract', function () { type: 'string' } ], - responses: { '200': { schema: {}, description: '' } } + responses: { 200: { schema: {}, description: '' } } } } } @@ -137,7 +137,7 @@ describe('contract', function () { } } ], - responses: { '200': { schema: {}, description: '' } } + responses: { 200: { schema: {}, description: '' } } } } } @@ -184,7 +184,7 @@ describe('contract', function () { } } ], - responses: { '200': { schema: {}, description: '' } } + responses: { 200: { schema: {}, description: '' } } } } } @@ -223,7 +223,7 @@ describe('contract', function () { items: { type: 'object', properties: { - type: { type: 'string' }, + type: { type: 'string' }, name: { type: 'string' } } } @@ -232,7 +232,7 @@ describe('contract', function () { } } ], - responses: { '200': { schema: {}, description: '' } } + responses: { 200: { schema: {}, description: '' } } } } } @@ -255,7 +255,7 @@ describe('contract', function () { get: { parameters: [], responses: { - '200': { + 200: { description: '', schema: { type: 'object', diff --git a/test/templatizerTest.js b/test/templatizerTest.js index a01c7a1..ed879fb 100644 --- a/test/templatizerTest.js +++ b/test/templatizerTest.js @@ -18,7 +18,7 @@ describe('templatizer', function () { it('should return false if does not match resource template', function () { var templatizer = Templatizer.create(['/resource/{id}']); assert.ok(!templatizer.test('/resource')); - }) + }); }); describe('#parse', function () {