Repository: pactumjs/pactum
Pull request #67 (2021-05-23 11:31:56)

3.0.15
- **Bug Fix** => Support string type in request body for interactions
- **Improved Logs** => Printing failure messages while interaction matching
- **Logo** => Added new logo.
- **withJson** => Supports file paths
- **res** => Now includes `text`, `buffer`, `body` & `json` properties


Patch:
diff --git a/README.md b/README.md
index a5b6473..841dc6a 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,8 @@
 <span align="center">
 
-<p><a href="https://pactumjs.github.io"><img src="https://raw.githubusercontent.com/pactumjs/pactum/v3/assets/logo.png" alt="PactumJS Logo" height="100"/></a>
-</p>
+![logo](https://pactumjs.github.io/_media/logo-icon-small.svg)
 
-<div>
- <h2>REST API Testing Tool for all levels in a Test Pyramid</h2>
-</div>
+# PactumJS
 
 ![Build](https://github.com/pactumjs/pactum/workflows/Build/badge.svg?branch=master)
 ![Coverage](https://img.shields.io/codeclimate/coverage/ASaiAnudeep/pactum)
@@ -13,10 +10,12 @@
 ![Size](https://img.shields.io/bundlephobia/minzip/pactum)
 ![Platform](https://img.shields.io/node/v/pactum)
 
+#### REST API Testing Tool for all levels in a Test Pyramid
+
 </span>
 
 <br />
-<p align="center"><a href="https://pactumjs.github.io"><img src="https://raw.githubusercontent.com/pactumjs/pactum/v3/assets/demo.gif" alt="PactumJS Demo"/></a>
+<p align="center"><a href="https://pactumjs.github.io"><img src="https://raw.githubusercontent.com/pactumjs/pactum/master/assets/demo.gif" alt="PactumJS Demo"/></a>
 </p>
 <br />
 
@@ -39,7 +38,7 @@
 </tr>
 </table>
 
-![----------](https://raw.githubusercontent.com/pactumjs/pactum/v3/assets/rainbow.png)
+![----------](https://raw.githubusercontent.com/pactumjs/pactum/master/assets/rainbow.png)
 
 ## Documentation
 
@@ -66,7 +65,7 @@ npm install --save-dev pactum
 npm install --save-dev mocha
 ```
 
-![----------](https://raw.githubusercontent.com/pactumjs/pactum/v3/assets/rainbow.png)
+![----------](https://raw.githubusercontent.com/pactumjs/pactum/master/assets/rainbow.png)
 
 # Usage
 
@@ -169,7 +168,7 @@ mock.addInteraction({
 mock.start(3000);
 ```
 
-![----------](https://raw.githubusercontent.com/pactumjs/pactum/v3/assets/rainbow.png)
+![----------](https://raw.githubusercontent.com/pactumjs/pactum/master/assets/rainbow.png)
 
 # Notes
 
diff --git a/package-lock.json b/package-lock.json
index 36925f8..2e7616f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.0.14",
+  "version": "3.0.15",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index d4304f7..3d18782 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.0.14",
+  "version": "3.0.15",
   "description": "REST API Testing Tool for all levels in a Test Pyramid",
   "main": "./src/index.js",
   "types": "./src/index.d.ts",
diff --git a/src/exports/handler.d.ts b/src/exports/handler.d.ts
index 69461d9..9bd5cb6 100644
--- a/src/exports/handler.d.ts
+++ b/src/exports/handler.d.ts
@@ -11,6 +11,9 @@ interface Request {
 
 interface Response extends IncomingMessage {
   json?: object;
+  body?: any;
+  text?: string;
+  buffer?: Buffer;
 }
 
 interface StateHandlerContext {
diff --git a/src/helpers/graphQL.js b/src/helpers/graphQL.js
index 77b1914..e7eec5e 100644
--- a/src/helpers/graphQL.js
+++ b/src/helpers/graphQL.js
@@ -10,17 +10,17 @@ const graphql = {
         const expectedQuery = parse(expected.query);
         removeLoc(actualQuery);
         removeLoc(expectedQuery);
-        const result = compare(actualQuery, expectedQuery, {}, '$.graphQL.query', strict).equal;
+        const result = compare(actualQuery, expectedQuery, {}, '$.graphQL.query', strict);
         const actualVariables = typeof actual.variables === 'string' ? JSON.parse(actual.variables) : actual.variables;
-        if (result && (actualVariables || expected.variables)) {
-          return compare(actualVariables, expected.variables, {}, '$.graphQL.variables', strict).equal;
+        if (result.equal && (actualVariables || expected.variables)) {
+          return compare(actualVariables, expected.variables, {}, '$.graphQL.variables', strict);
         }
         return result;
       }
     } catch (error) {
-      return false;
+      return { message: error.toString(), equal: false };
     }
-    return false;
+    return { message: 'Not a graphql query', equal: false };
   }
 
 };
diff --git a/src/helpers/utils.js b/src/helpers/utils.js
index 6c10e51..46792c4 100644
--- a/src/helpers/utils.js
+++ b/src/helpers/utils.js
@@ -15,33 +15,33 @@ const utils = {
     const ids = Array.from(interactions.keys());
     for (let i = ids.length - 1; i >= 0; i--) {
       const interactionId = ids[i];
-      log.debug(`Comparing interaction with id ${interactionId}`);
+      log.debug(`Comparing interaction with id "${interactionId}"`);
       const interaction = interactions.get(interactionId);
       const isValidMethod = (interaction.request.method === req.method);
       if (!isValidMethod) {
-        log.debug(`Interaction with id ${interactionId} failed to match - HTTP Method`);
+        log.debug(`Interaction with id "${interactionId}" failed to match - HTTP Method`);
         continue;
       }
       const isValidPath = validatePath(req, interaction);
-      if (!isValidPath) {
-        log.debug(`Interaction with id ${interactionId} failed to match - HTTP Path`);
+      if (!isValidPath.equal) {
+        log.debug(`Interaction with id "${interactionId}" failed to match - HTTP Path - ${isValidPath.message}`);
         continue;
       }
       const isValidQuery = validateQuery(req, interaction);
-      if (!isValidQuery) {
-        log.debug(`Interaction with id ${interactionId} failed to match - HTTP Query Params`);
+      if (!isValidQuery.equal) {
+        log.debug(`Interaction with id "${interactionId}" failed to match - HTTP Query Params - ${isValidQuery.message}`);
         continue;
       }
       const isValidHeaders = validateHeaders(req, interaction);
-      if (!isValidHeaders) {
-        log.debug(`Interaction with id ${interactionId} failed to match - HTTP Headers`);
+      if (!isValidHeaders.equal) {
+        log.debug(`Interaction with id "${interactionId}" failed to match - HTTP Headers - ${isValidHeaders.message}`);
         continue;
       }
       const isValidBody = validateBody(req, interaction);
-      if (isValidMethod && isValidPath && isValidQuery && isValidHeaders && isValidBody) {
+      if (isValidMethod && isValidPath.equal && isValidQuery.equal && isValidHeaders.equal && isValidBody.equal) {
         return interaction;
       }
-      log.debug(`Interaction with id ${interactionId} failed to match - HTTP Body`);
+      log.debug(`Interaction with id "${interactionId}" failed to match - HTTP Body - ${isValidBody.message}`);
     }
     return null;
   },
@@ -70,7 +70,10 @@ function validatePath(req, interaction) {
     const actualParts = actualPath.split('/');
     const expectedParts = expectedPath.split('/');
     if (actualParts.length !== expectedParts.length) {
-      return false;
+      return {
+        message: `Path parts length not equal "${actualParts.length}" !== "${expectedParts.length}"`,
+        equal: false
+      };
     }
     const actual = {};
     const expected = {};
@@ -84,14 +87,17 @@ function validatePath(req, interaction) {
         actual[param] = actualPart;
       } else {
         if (actualPart !== expectedPart) {
-          return false;
+          return {
+            message: `Path part not equal "${actualPart}" !== "${expectedPart}"`,
+            equal: false
+          };
         }
       }
     }
     req.pathParams = actual;
-    return compare(actual, expected, matchingRules, '$.path').equal;
+    return compare(actual, expected, matchingRules, '$.path');
   } else {
-    return compare(actualPath, expectedPath, matchingRules, '$.path').equal;
+    return compare(actualPath, expectedPath, matchingRules, '$.path');
   }
 }
 
@@ -100,7 +106,7 @@ function validateQuery(req, interaction) {
   if (req.method === 'GET' && request.graphQL) {
     return graphQL.compare(req.query, request.queryParams, strict);
   }
-  return compare(req.query, request.queryParams, request.matchingRules, '$.query', strict).equal;
+  return compare(req.query, request.queryParams, request.matchingRules, '$.query', strict);
 }
 
 function validateHeaders(req, interaction) {
@@ -114,10 +120,9 @@ function validateHeaders(req, interaction) {
     for (const prop in request.headers) {
       lowerCaseExpected[prop.toLowerCase()] = request.headers[prop];
     }
-    const response = compare(lowerCaseActual, lowerCaseExpected, request.matchingRules, '$.headers');
-    return response.equal;
+    return compare(lowerCaseActual, lowerCaseExpected, request.matchingRules, '$.headers');
   }
-  return true;
+  return { message: '', equal: true };
 }
 
 function validateBody(req, interaction) {
@@ -127,14 +132,14 @@ function validateBody(req, interaction) {
   }
   if (strict) {
     if (req.body || request.body) {
-      return compare(req.body, request.body, request.matchingRules, '$.body', strict).equal;
+      return compare(req.body, request.body, request.matchingRules, '$.body', strict);
     }
   } else {
     if (request.body) {
-      return compare(req.body, request.body, request.matchingRules, '$.body', strict).equal;
+      return compare(req.body, request.body, request.matchingRules, '$.body', strict);
     }
   }
-  return true;
+  return { message: '', equal: true };
 }
 
 module.exports = utils;
\ No newline at end of file
diff --git a/src/models/Interaction.model.js b/src/models/Interaction.model.js
index e1ed0ae..f1380c3 100644
--- a/src/models/Interaction.model.js
+++ b/src/models/Interaction.model.js
@@ -121,8 +121,10 @@ class InteractionRequest {
     } else {
       this.queryParams = {};
     }
-    if (request.body && typeof request.body === 'object') {
-      setMatchingRules(this.matchingRules, request.body, '$.body');
+    if (typeof request.body !== 'undefined') {
+      if (typeof request.body === 'object') {
+        setMatchingRules(this.matchingRules, request.body, '$.body');
+      }
       this.body = getValue(request.body);
     }
     if (request.graphQL) {
diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts
index 1bf6b39..86e2305 100644
--- a/src/models/Spec.d.ts
+++ b/src/models/Spec.d.ts
@@ -235,6 +235,7 @@ declare class Spec {
    *  .expectStatus(201);
    */
   withJson(json: object): Spec;
+  withJson(filePath: string): Spec;
 
   /**
    * attaches headers to the request
diff --git a/src/models/Spec.js b/src/models/Spec.js
index cc6d657..4cb1c37 100644
--- a/src/models/Spec.js
+++ b/src/models/Spec.js
@@ -179,7 +179,9 @@ class Spec {
   }
 
   withJson(json) {
-    if (typeof json !== 'object') {
+    if (typeof json === 'string') {
+      json = JSON.parse(fs.readFileSync(json));
+    } else if (typeof json !== 'object') {
       throw new PactumRequestError(`Invalid json in request - ${json}`);
     }
     this._request.data = json;
diff --git a/src/models/Tosser.js b/src/models/Tosser.js
index 3867c5e..81368e1 100644
--- a/src/models/Tosser.js
+++ b/src/models/Tosser.js
@@ -185,6 +185,8 @@ async function getResponse(tosser) {
   try {
     log.debug(`${request.method} ${request.url}`);
     res = await phin(request);
+    res.buffer = res.body;
+    res.text = helper.bufferToString(res.body) || '';
     res.body = helper.bufferToString(res.body);
     res.json = helper.getJson(res.body);
     if (helper.isContentJson(res)) {
diff --git a/src/models/server.js b/src/models/server.js
index 8acd110..05f67da 100644
--- a/src/models/server.js
+++ b/src/models/server.js
@@ -87,6 +87,7 @@ class Server {
  */
 function registerAllRoutes(server, app) {
   app.all('/*', (req, response) => {
+    log.debug(`Request received - ${req.method} ${req.path}`);
     const res = new ExpressResponse(response);
     const interaction = utils.getMatchingInteraction(req, server.interactions);
     if (interaction) {


Test patch:
diff --git a/test/component/interactions.spec.js b/test/component/interactions.spec.js
index 954167d..1c86f20 100644
--- a/test/component/interactions.spec.js
+++ b/test/component/interactions.spec.js
@@ -368,6 +368,23 @@ describe('Mock', () => {
       .toss();
   });
 
+  it('POST - one interaction - with string as body', async () => {
+    await pactum.spec()
+      .useInteraction({
+        request: {
+          method: 'POST',
+          path: '/api/hello',
+          body: 'Hello World!'
+        },
+        response: {
+          status: 200
+        }
+      })
+      .post('http://localhost:9393/api/hello')
+      .withBody('Hello World!')
+      .expectStatus(200);
+  });
+
   it('POST - one interaction - with form data', async () => {
     await pactum.spec()
       .useInteraction({
diff --git a/test/component/request.spec.js b/test/component/request.spec.js
index cc5d0b1..0521046 100644
--- a/test/component/request.spec.js
+++ b/test/component/request.spec.js
@@ -3,6 +3,7 @@ const request = pactum.request;
 const config = require('../../src/config');
 
 describe('Request', () => {
+
   it('GET with baseurl', async () => {
     request.setBaseUrl('http://localhost:9393');
     await pactum
@@ -234,8 +235,30 @@ describe('Request', () => {
       .expectStatus(200);
   });
 
+  it('with json - path', async () => {
+    await pactum
+      .spec()
+      .useInteraction({
+        strict: false,
+        request: {
+          method: 'POST',
+          path: '/api/file',
+          body: {
+            "name": "pactum"
+          }
+        },
+        response: {
+          status: 200
+        }
+      })
+      .post('http://localhost:9393/api/file')
+      .withJson('./package.json')
+      .expectStatus(200);
+  });
+
   afterEach(() => {
     config.request.baseUrl = '';
     config.request.headers = {};
   });
+
 });
diff --git a/test/unit/graphQL.spec.js b/test/unit/graphQL.spec.js
index 1643bdb..b2a07c8 100644
--- a/test/unit/graphQL.spec.js
+++ b/test/unit/graphQL.spec.js
@@ -6,13 +6,13 @@ describe('GraphQL', () => {
   it('null objects', () => {
     const actual = null;
     const expected = null;
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('empty objects', () => {
     const actual = {};
     const expected = {};
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('plain query', () => {
@@ -22,7 +22,7 @@ describe('GraphQL', () => {
     const expected = {
       query: `{ hello }`
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('plain query with different indentation', () => {
@@ -36,7 +36,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('plain query invalid', () => {
@@ -46,7 +46,7 @@ describe('GraphQL', () => {
     const expected = {
       query: `{ hello }`
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('multi line query', () => {
@@ -68,7 +68,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('multi line query with different indentation', () => {
@@ -84,7 +84,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('multi line query invalid', () => {
@@ -101,7 +101,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('nested query', () => {
@@ -131,7 +131,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('nested query with different indentation', () => {
@@ -156,7 +156,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('nested query invalid', () => {
@@ -182,7 +182,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('query with arguments', () => {
@@ -206,7 +206,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('query with arguments & different indentation', () => {
@@ -228,7 +228,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('invalid query with arguments', () => {
@@ -250,7 +250,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('query with enum arguments', () => {
@@ -274,7 +274,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('query with enum arguments & different indentation', () => {
@@ -296,7 +296,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('invalid query with enum arguments', () => {
@@ -318,7 +318,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('query with alias', () => {
@@ -346,7 +346,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('query with alias & different indentation', () => {
@@ -368,7 +368,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('invalid query with alias', () => {
@@ -394,7 +394,7 @@ describe('GraphQL', () => {
       }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('query with fragments', () => {
@@ -437,7 +437,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('query with fragments & different indentation', () => {
@@ -478,7 +478,7 @@ describe('GraphQL', () => {
       }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('invalid query with fragments', () => {
@@ -519,7 +519,7 @@ describe('GraphQL', () => {
       }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('query with operation name', () => {
@@ -547,7 +547,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('query with operation name & different indentation', () => {
@@ -573,7 +573,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('invalid query with operation name', () => {
@@ -599,7 +599,7 @@ describe('GraphQL', () => {
         }
       `
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
   it('query with variables', () => {
@@ -633,7 +633,7 @@ describe('GraphQL', () => {
         "episode": "JEDI"
       }
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('query with variables & different indentation', () => {
@@ -665,7 +665,7 @@ describe('GraphQL', () => {
         "episode": "JEDI"
       }
     };
-    expect(graphQL.compare(actual, expected)).to.be.true;
+    expect(graphQL.compare(actual, expected).equal).to.be.true;
   });
 
   it('invalid query with variables', () => {
@@ -699,7 +699,7 @@ describe('GraphQL', () => {
         "episode": "MODI"
       }
     };
-    expect(graphQL.compare(actual, expected)).to.be.false;
+    expect(graphQL.compare(actual, expected).equal).to.be.false;
   });
 
 });
\ No newline at end of file