Repository: BRIKEV/express-jsdoc-swagger (js)

Issue:
[CodeClimate] `expressJSDocSwagger` has 33 lines of code (exceeds 25 allowed)
There is one issue in our code climate that should be fixed.

Here is the [link](https://codeclimate.com/github/BRIKEV/express-jsdoc-swagger/index.js/source#issue-1322e54317da66dfb1aa0469a432dbc7) of this problem.

## Tasks

- [x] Separate in different file this block of code

```js
globFilesMatches(options.baseDir, options.file)
      .then(readFiles)
      .then(getOnlyComments)
      .then(jsdocInfo())
      .then(data => {
        swaggerObject = getPaths(swaggerObject, data);
        swaggerObject = getComponents(swaggerObject, data);
        swaggerObject = getTags(swaggerObject, data);
      })
      .catch(console.log);
```

- [x] Add test to the separated function



Pull request description:
Refactor main function
### What kind of change does this PR introduce? (check at least one)
 - [ ] Bugfix
 - [ ] Feature
 - [X] Test
 - [ ] Docs
 - [X] Refactor
 - [ ] Build-related changes
 - [ ] Other, please describe:

### Description:
This PR adds a refator of the main function to separate all the swagger parse process in another file so that it will be easier to tests all the e2e features. This also should close #29.

It also includes a fix when we create the paths as there wasn't a validation if it was an endpoint or not.

I added some e2e tests but it still needs more, but I will address those in another PR.


Patch:
diff --git a/index.js b/index.js
index 8fadcd6..56da09b 100644
--- a/index.js
+++ b/index.js
@@ -1,59 +1,19 @@
 const swaggerUi = require('swagger-ui-express');
 const debug = require('debug')('express-jsdoc-swagger');
+const processSwagger = require('./processSwagger');
 
-const readFiles = require('./consumers/readFiles');
-const globFilesMatches = require('./consumers/globFilesMatches');
-const getOnlyComments = require('./consumers/getOnlyComments');
-const jsdocInfo = require('./consumers/jsdocInfo');
-const {
-  getBasicInfo,
-  getSecuritySchemes,
-  getPaths,
-  getComponents,
-  getTags,
-} = require('./transforms');
-
-/**
- * Generator options
- * @typedef {Object} Options
- * @property {string} title - The title
- */
-
-/**
- * @param  {instance} Express instance
- * @return {function} A constructor of expressJSDocSwagger
- */
-// eslint-disable-next-line
-const expressJSDocSwagger = app => {
-  /**
-   * @param  {object} options - Generator options
-   * @return {object} swaggerInfo - Swagger compiled options
-   */
-  return options => {
+const expressJSDocSwagger = app => (
+  options => {
     debug(`Retrieving ${JSON.stringify(options)}`);
-    let swaggerObject = {
-      openapi: '3.0.0',
-      info: options.info,
-      servers: options.servers,
-      security: options.security,
-    };
-
-    debug('Getting basic swagger info');
-    swaggerObject = getBasicInfo(swaggerObject);
-    swaggerObject = getSecuritySchemes(swaggerObject);
-    debug(`SwaggerObject with basic info ${JSON.stringify(swaggerObject)}`);
-
-    globFilesMatches(options.baseDir, options.file)
-      .then(readFiles)
-      .then(getOnlyComments)
-      .then(jsdocInfo())
-      .then(data => {
-        swaggerObject = getPaths(swaggerObject, data);
-        swaggerObject = getComponents(swaggerObject, data);
-        swaggerObject = getTags(swaggerObject, data);
-      })
-      .catch(console.log);
+    let swaggerObject = {};
 
+    processSwagger(options)
+      .then(result => {
+        swaggerObject = {
+          ...swaggerObject,
+          ...result,
+        };
+      });
 
     app.use('/api-docs', (req, res, next) => {
       debug(`Render express endpoint with SwaggerObject: ${JSON.stringify(swaggerObject)}`);
@@ -66,7 +26,7 @@ const expressJSDocSwagger = app => {
     }, swaggerUi.serve, swaggerUi.setup());
 
     return swaggerObject;
-  };
-};
+  }
+);
 
 module.exports = expressJSDocSwagger;
diff --git a/processSwagger.js b/processSwagger.js
new file mode 100644
index 0000000..2427058
--- /dev/null
+++ b/processSwagger.js
@@ -0,0 +1,39 @@
+const debug = require('debug')('express-jsdoc-swagger:processSwagger');
+const readFiles = require('./consumers/readFiles');
+const globFilesMatches = require('./consumers/globFilesMatches');
+const getOnlyComments = require('./consumers/getOnlyComments');
+const jsdocInfo = require('./consumers/jsdocInfo');
+const {
+  getBasicInfo,
+  getSecuritySchemes,
+  getPaths,
+  getComponents,
+  getTags,
+} = require('./transforms');
+
+const processSwagger = options => {
+  let swaggerObject = {
+    openapi: '3.0.0',
+    info: options.info,
+    servers: options.servers,
+    security: options.security,
+  };
+
+  debug('Getting basic swagger info');
+  swaggerObject = getBasicInfo(swaggerObject);
+  swaggerObject = getSecuritySchemes(swaggerObject);
+  debug(`SwaggerObject with basic info ${JSON.stringify(swaggerObject)}`);
+
+  return globFilesMatches(options.baseDir, options.file)
+    .then(readFiles)
+    .then(getOnlyComments)
+    .then(jsdocInfo())
+    .then(data => {
+      swaggerObject = getPaths(swaggerObject, data);
+      swaggerObject = getComponents(swaggerObject, data);
+      swaggerObject = getTags(swaggerObject, data);
+      return swaggerObject;
+    });
+};
+
+module.exports = processSwagger;
diff --git a/transforms/paths/index.js b/transforms/paths/index.js
index 20ca49d..5bb6d9a 100644
--- a/transforms/paths/index.js
+++ b/transforms/paths/index.js
@@ -3,7 +3,10 @@ const responsesGenerator = require('./responses');
 const parametersGenerator = require('./parameters');
 const requestBodyGenerator = require('./requestBody');
 const { getTagInfo, getTagsInfo, formatDescriptionTag } = require('../utils/tags');
-const bodyMethods = require('./validRequestBodyMethods');
+const {
+  validRequestBodyMethods: bodyMethods,
+  validHTTPMethod,
+} = require('../utils/httpMethods');
 
 const formatTags = (tags = []) => tags.map(({ description }) => {
   const { name } = formatDescriptionTag(description);
@@ -41,8 +44,8 @@ const parsePath = (path, state) => {
   if (!path.description || !path.tags) return {};
   const [method, endpoint] = path.description.split(' ');
   // if jsdoc comment des not contain structure <Method> - <Endpoint> is not valid path
-  if (!method) return {};
   const lowerCaseMethod = method.toLowerCase();
+  if (!validHTTPMethod(lowerCaseMethod)) return {};
   const { tags } = path;
   const {
     summary, bodyValues, isDeprecated, responses, parameters, tagsValues,
diff --git a/transforms/paths/validRequestBodyMethods.js b/transforms/utils/httpMethods.js
similarity index 52%
rename from transforms/paths/validRequestBodyMethods.js
rename to transforms/utils/httpMethods.js
index b54a64f..9a82bb2 100644
--- a/transforms/paths/validRequestBodyMethods.js
+++ b/transforms/utils/httpMethods.js
@@ -11,4 +11,9 @@ const validRequestBodyMethods = {
   patch: true,
 };
 
-module.exports = validRequestBodyMethods;
+const validHTTPMethod = method => Object.keys(validRequestBodyMethods).includes(method);
+
+module.exports = {
+  validRequestBodyMethods,
+  validHTTPMethod,
+};


Test patch:
diff --git a/test/e2e/components/components.test.js b/test/e2e/components/components.test.js
new file mode 100644
index 0000000..b4a688e
--- /dev/null
+++ b/test/e2e/components/components.test.js
@@ -0,0 +1,92 @@
+const processSwagger = require('../../../processSwagger');
+
+const options = {
+  info: {
+    version: '1.0.0',
+    title: 'Albums store',
+    license: {
+      name: 'MIT',
+    },
+  },
+  file: './jsdoc-example.js',
+  baseDir: __dirname,
+};
+
+test('should parse components jsdoc from jsdoc-example', async () => {
+  const expected = {
+    openapi: '3.0.0',
+    info: {
+      title: 'Albums store',
+      description: 'Add your description',
+      contact: {},
+      license: { name: 'MIT', url: '' },
+      termsOfService: '',
+      version: '1.0.0',
+    },
+    servers: [],
+    security: undefined,
+    paths: {},
+    tags: [],
+    components: {
+      schemas: {
+        Song: {
+          description: 'A song',
+          required: [
+            'title',
+          ],
+          type: 'object',
+          properties: {
+            title: {
+              description: 'The title',
+              type: 'string',
+            },
+            artist: {
+              description: 'The artist',
+              type: 'string',
+            },
+            year: {
+              description: 'The year',
+              type: 'number',
+              format: 'int64',
+            },
+          },
+        },
+        Author: {
+          description: 'Author model',
+          required: [
+            'name',
+          ],
+          type: 'object',
+          properties: {
+            name: {
+              description: 'Author name',
+              type: 'string',
+            },
+            age: {
+              description: 'Author age',
+              type: 'number',
+              format: 'int64',
+            },
+          },
+        },
+        Album: {
+          description: 'Album',
+          required: [],
+          type: 'object',
+          properties: {
+            firstSong: {
+              description: '',
+              $ref: '#/components/schemas/Song',
+            },
+            author: {
+              description: '',
+              $ref: '#/components/schemas/Author',
+            },
+          },
+        },
+      },
+    },
+  };
+  const result = await processSwagger(options);
+  expect(result).toEqual(expected);
+});
diff --git a/test/e2e/components/jsdoc-example.js b/test/e2e/components/jsdoc-example.js
new file mode 100644
index 0000000..6a645e3
--- /dev/null
+++ b/test/e2e/components/jsdoc-example.js
@@ -0,0 +1,22 @@
+
+/**
+ * A song
+ * @typedef {object} Song
+ * @property {string} title.required - The title
+ * @property {string} artist - The artist
+ * @property {number} year - The year - int64
+ */
+
+/**
+ * Author model
+ * @typedef {object} Author
+ * @property {string} name.required - Author name
+ * @property {number} age - Author age - int64
+ */
+
+/**
+ * Album
+ * @typedef {object} Album
+ * @property {Song} firstSong
+ * @property {Author} author
+ */
diff --git a/test/e2e/configuration/configuration.test.js b/test/e2e/configuration/configuration.test.js
new file mode 100644
index 0000000..5a893c1
--- /dev/null
+++ b/test/e2e/configuration/configuration.test.js
@@ -0,0 +1,94 @@
+const processSwagger = require('../../../processSwagger');
+
+const options = {
+  info: {
+    version: '1.0.0',
+    title: 'Albums store',
+    license: {
+      name: 'MIT',
+      url: 'http://example.com',
+    },
+    description: 'API desctiption',
+    contact: {
+      name: 'contact name',
+      url: 'http://example.com',
+      email: 'test@test.com',
+    },
+    termsOfService: 'http://example.com',
+  },
+  servers: [
+    {
+      url: 'https://{username}.gigantic-server.com:{port}/{basePath}',
+      description: 'The production API server',
+      variables: {
+        username: {
+          default: 'demo',
+          description: 'this value is assigned by the service provider, in this example `gigantic-server.com`',
+        },
+        port: {
+          enum: [
+            '8443',
+            '443',
+          ],
+          default: '8443',
+        },
+        basePath: {
+          default: 'v2',
+        },
+      },
+    },
+  ],
+  file: './main.js',
+  baseDir: __dirname,
+};
+
+test('should parse basic info', async () => {
+  const expected = {
+    openapi: '3.0.0',
+    info: {
+      version: '1.0.0',
+      title: 'Albums store',
+      license: {
+        name: 'MIT',
+        url: 'http://example.com',
+      },
+      description: 'API desctiption',
+      contact: {
+        name: 'contact name',
+        url: 'http://example.com',
+        email: 'test@test.com',
+      },
+      termsOfService: 'http://example.com',
+    },
+    servers: [
+      {
+        url: 'https://{username}.gigantic-server.com:{port}/{basePath}',
+        description: 'The production API server',
+        variables: {
+          username: {
+            default: 'demo',
+            description: 'this value is assigned by the service provider, in this example `gigantic-server.com`',
+          },
+          port: {
+            enum: [
+              '8443',
+              '443',
+            ],
+            default: '8443',
+          },
+          basePath: {
+            default: 'v2',
+          },
+        },
+      },
+    ],
+    security: undefined,
+    paths: {},
+    tags: [],
+    components: {
+      schemas: {},
+    },
+  };
+  const result = await processSwagger(options);
+  expect(result).toEqual(expected);
+});
diff --git a/test/e2e/parameters/jsdoc-example.js b/test/e2e/parameters/jsdoc-example.js
new file mode 100644
index 0000000..319c0fe
--- /dev/null
+++ b/test/e2e/parameters/jsdoc-example.js
@@ -0,0 +1,15 @@
+/**
+ * GET /api/v1
+ * @summary This is the summary or description of the endpoint
+ * @param {string} name.query.required - name param description
+ * @param {number} phone.param - phone number
+ * @return {string} 200 - success response
+ */
+
+/**
+ * GET /api/v1/albums
+ * @summary This is the summary or description of the endpoint
+ * @param {array<string>} name.query.required.deprecated - name param description
+ * @param {number} phone.param
+ * @return {object} 200 - success response - application/json
+ */
diff --git a/test/e2e/parameters/parameters.test.js b/test/e2e/parameters/parameters.test.js
new file mode 100644
index 0000000..26ff8ac
--- /dev/null
+++ b/test/e2e/parameters/parameters.test.js
@@ -0,0 +1,126 @@
+const processSwagger = require('../../../processSwagger');
+
+const options = {
+  info: {
+    version: '1.0.0',
+    title: 'Albums store',
+    license: {
+      name: 'MIT',
+    },
+  },
+  file: './jsdoc-example.js',
+  baseDir: __dirname,
+};
+
+test('should parse components jsdoc from jsdoc-example', async () => {
+  const expected = {
+    openapi: '3.0.0',
+    info: {
+      title: 'Albums store',
+      description: 'Add your description',
+      contact: {},
+      license: { name: 'MIT', url: '' },
+      termsOfService: '',
+      version: '1.0.0',
+    },
+    servers: [],
+    security: undefined,
+    paths: {
+      '/api/v1': {
+        get: {
+          deprecated: false,
+          summary: 'This is the summary or description of the endpoint',
+          responses: {
+            200: {
+              description: 'success response',
+              content: {
+                'application/json': {
+                  schema: {
+                    type: 'string',
+                  },
+                },
+              },
+            },
+          },
+          parameters: [
+            {
+              name: 'name',
+              in: 'query',
+              description: 'name param description',
+              required: true,
+              deprecated: false,
+              allowEmptyValue: false,
+              schema: {
+                type: 'string',
+              },
+            },
+            {
+              name: 'phone',
+              in: 'param',
+              description: 'phone number',
+              required: false,
+              deprecated: false,
+              allowEmptyValue: false,
+              schema: {
+                type: 'number',
+              },
+            },
+          ],
+          tags: [],
+        },
+      },
+      '/api/v1/albums': {
+        get: {
+          deprecated: false,
+          summary: 'This is the summary or description of the endpoint',
+          responses: {
+            200: {
+              description: 'success response',
+              content: {
+                'application/json': {
+                  schema: {
+                    type: 'object',
+                  },
+                },
+              },
+            },
+          },
+          parameters: [
+            {
+              name: 'name',
+              in: 'query',
+              description: 'name param description',
+              required: true,
+              deprecated: true,
+              allowEmptyValue: false,
+              schema: {
+                type: 'array',
+                items: {
+                  type: 'string',
+                },
+              },
+            },
+            {
+              name: 'phone',
+              in: 'param',
+              description: '',
+              required: false,
+              deprecated: false,
+              allowEmptyValue: false,
+              schema: {
+                type: 'number',
+              },
+            },
+          ],
+          tags: [],
+        },
+      },
+    },
+    tags: [],
+    components: {
+      schemas: {},
+    },
+  };
+  const result = await processSwagger(options);
+  expect(result).toEqual(expected);
+});