Repository: BRIKEV/express-jsdoc-swagger
Pull request #10 (2020-05-31 18:07:44)

Multiple endpoints
This PR adds a test for multiple endpoints and multiple methods for the same endpoints. It also includes a fix for empty body params.


Patch:
diff --git a/consumers/jsdocInfo.js b/consumers/jsdocInfo.js
index f04a483..f8079a2 100644
--- a/consumers/jsdocInfo.js
+++ b/consumers/jsdocInfo.js
@@ -8,10 +8,11 @@ const jsdocInfo = (options = { unwrap: true }) => comments => {
       ...tag,
       description: tag.description ? tag.description.replace('\n/', '').replace(/\/$/, '') : tag.description,
     }));
+    const description = parsedValue.description.replace('/**\n', '').replace('\n/', '');
     return {
       ...parsedValue,
       tags,
-      description: parsedValue.description.replace('/**\n', ''),
+      description,
     };
   });
   return parsed;
diff --git a/transforms/paths/index.js b/transforms/paths/index.js
index 7156940..8ac3713 100644
--- a/transforms/paths/index.js
+++ b/transforms/paths/index.js
@@ -1,13 +1,15 @@
 const debug = require('debug')('express-jsdoc-swagger:transforms:paths');
 const responsesGenerator = require('./responses');
 const parametersGenerator = require('./parameters');
-const requestBody = require('./requestBody');
+const requestBodyGenerator = require('./requestBody');
 const { getTagInfo, getTagsInfo } = require('../utils/tags');
-const validBodyMethods = require('./validRequestBodyMethods');
+const bodyMethods = require('./validRequestBodyMethods');
 
 const formatTags = (tags = []) => tags.map(({ description: name }) => name);
 
-const parsePath = path => {
+const bodyParams = ({ name }) => name.includes('request.body');
+
+const parsePath = (path, state) => {
   debug(`Transforms path: ${JSON.stringify(path)}`);
   if (!path.description || !path.tags) return {};
   const [method, endpoint] = path.description.split(' ');
@@ -15,23 +17,33 @@ const parsePath = path => {
   if (!method) return {};
   const lowerCaseMethod = method.toLowerCase();
   const { tags } = path;
+  /* Endpoint meta info */
   const summary = getTagInfo(tags, 'summary');
   const deprecated = getTagInfo(tags, 'deprecated');
   const isDeprecated = !!deprecated;
+  /* Response info */
   const returnValues = getTagsInfo(tags, 'return');
-  const paramValues = getTagsInfo(tags, 'param');
-  const tagsValues = getTagsInfo(tags, 'tags');
   const responses = responsesGenerator(returnValues);
+  /* Parameters info */
+  const paramValues = getTagsInfo(tags, 'param');
   const parameters = parametersGenerator(paramValues);
+  /* Tags info */
+  const tagsValues = getTagsInfo(tags, 'tags');
+  /* Request body info */
+  const bodyValues = paramValues.filter(bodyParams);
+  const hasBodyValues = bodyValues.length > 0;
+  const requestBody = requestBodyGenerator(bodyValues);
   return {
+    ...state,
     [endpoint]: {
+      ...state[endpoint],
       [lowerCaseMethod]: {
         deprecated: isDeprecated,
         summary: summary && summary.description ? summary.description : '',
         responses,
         parameters,
         tags: formatTags(tagsValues),
-        ...(validBodyMethods[lowerCaseMethod] ? { requestBody: requestBody(paramValues) } : {}),
+        ...(bodyMethods[lowerCaseMethod] && hasBodyValues ? { requestBody } : {}),
       },
     },
   };
@@ -40,7 +52,7 @@ const parsePath = path => {
 const parsePaths = (swaggerObject = {}, paths = []) => {
   if (!paths || !Array.isArray(paths)) return { paths: {} };
   const pathObject = paths.reduce((acum, item) => ({
-    ...acum, ...parsePath(item),
+    ...acum, ...parsePath(item, acum),
   }), {});
   return {
     ...swaggerObject,
diff --git a/transforms/paths/requestBody.js b/transforms/paths/requestBody.js
index 8b80e99..64f8745 100644
--- a/transforms/paths/requestBody.js
+++ b/transforms/paths/requestBody.js
@@ -33,14 +33,11 @@ const parseBodyParameter = (currentState, body) => {
   };
 };
 
-const bodyParams = ({ name }) => name.includes('request.body');
-
 const INITIAL_STATE = { content: {} };
 
 const requestBodyGenerator = (params = []) => {
   if (!params || !Array.isArray(params)) return {};
-  const bodyValues = params.filter(bodyParams);
-  const requestBody = bodyValues.reduce((acc, body) => (
+  const requestBody = params.reduce((acc, body) => (
     { ...acc, ...parseBodyParameter(acc, body) }
   ), INITIAL_STATE);
   return requestBody;


Test patch:
diff --git a/test/transforms/paths/index.test.js b/test/transforms/paths/index.test.js
index 6633454..358c995 100644
--- a/test/transforms/paths/index.test.js
+++ b/test/transforms/paths/index.test.js
@@ -96,4 +96,132 @@ describe('setPaths method', () => {
     const result = setPaths({}, parsedJSDocs);
     expect(result).toEqual(expected);
   });
+
+  it('should parse jsdoc path spec with multiple endpoints', () => {
+    const jsodInput = [`
+      /**
+       * GET /api/v1
+       * @deprecated
+       * @summary This is the summary or description of the endpoint
+       * @return {object} 200 - success response - application/json
+       */
+    `,
+    `
+      /**
+       * GET /api/v1/songs
+       * @summary This is the summary or description of the endpoint
+       * @return {object} 200 - success response - application/json
+       */
+    `];
+    const expected = {
+      paths: {
+        '/api/v1': {
+          get: {
+            deprecated: true,
+            summary: 'This is the summary or description of the endpoint',
+            parameters: [],
+            tags: [],
+            responses: {
+              200: {
+                description: 'success response',
+                content: {
+                  'application/json': {
+                    schema: {
+                      type: 'object',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+        '/api/v1/songs': {
+          get: {
+            deprecated: false,
+            summary: 'This is the summary or description of the endpoint',
+            parameters: [],
+            tags: [],
+            responses: {
+              200: {
+                description: 'success response',
+                content: {
+                  'application/json': {
+                    schema: {
+                      type: 'object',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = setPaths({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
+
+  it('should parse jsdoc path spec with multiple methods for one endpoint', () => {
+    const jsodInput = [`
+      /**
+       * GET /api/v1
+       * @deprecated
+       * @summary This is the summary or description of the endpoint
+       * @return {object} 200 - success response - application/json
+       */
+    `,
+    `
+      /**
+       * POST /api/v1
+       * @summary This is the summary or description of the endpoint
+       * @return {object} 200 - success response - application/json
+       */
+    `];
+    const expected = {
+      paths: {
+        '/api/v1': {
+          get: {
+            deprecated: true,
+            summary: 'This is the summary or description of the endpoint',
+            parameters: [],
+            tags: [],
+            responses: {
+              200: {
+                description: 'success response',
+                content: {
+                  'application/json': {
+                    schema: {
+                      type: 'object',
+                    },
+                  },
+                },
+              },
+            },
+          },
+          post: {
+            deprecated: false,
+            summary: 'This is the summary or description of the endpoint',
+            parameters: [],
+            tags: [],
+            responses: {
+              200: {
+                description: 'success response',
+                content: {
+                  'application/json': {
+                    schema: {
+                      type: 'object',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = setPaths({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
 });
diff --git a/test/transforms/paths/requestBody.test.js b/test/transforms/paths/requestBody.test.js
index d24762e..f014088 100644
--- a/test/transforms/paths/requestBody.test.js
+++ b/test/transforms/paths/requestBody.test.js
@@ -2,6 +2,30 @@ const jsdocInfo = require('../../../consumers/jsdocInfo');
 const setPaths = require('../../../transforms/paths');
 
 describe('request body tests', () => {
+  it('should not add requestBody when there is no body params', () => {
+    const jsodInput = [`
+      /**
+       * POST /api/v1/
+       */
+    `];
+    const expected = {
+      paths: {
+        '/api/v1/': {
+          post: {
+            deprecated: false,
+            summary: '',
+            responses: {},
+            tags: [],
+            parameters: [],
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = setPaths({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
+
   it('should parse jsdoc request body', () => {
     const jsodInput = [`
       /**