Repository: pactumjs/pactum
Pull request #42 (2021-01-09 14:43:00)

3.0.4
- returns, records, stores now work after calling .toss


Patch:
diff --git a/README.md b/README.md
index 7eb46ce..e150741 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,10 @@
 
 **pactum** is a REST API Testing Tool used to automate e2e, integration, contract & component (*or service level*) tests. It comes with a powerful *mock server* which can control the state of external dependencies.
 
-- 🐦 Lightweight
+- 🎈 Lightweight
 - 🚀 Simple & Powerful
+- 🛠️ Compelling Mock Server
+- 💎 Elegant Data Management
 - 🔧 Extendable & Customizable
 - 📚 Clear & Comprehensive Testing Style
 - 🔗 Component, Contract & E2E testing of APIs
diff --git a/package-lock.json b/package-lock.json
index 464f2f1..b8ae524 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.0.3",
+  "version": "3.0.4",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 3e4c573..d3313b4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "pactum",
-  "version": "3.0.3",
-  "description": "REST API Testing Tool for all levels of testing in a Test Pyramid",
+  "version": "3.0.4",
+  "description": "REST API Testing Tool for all levels in a Test Pyramid",
   "main": "./src/index.js",
   "types": "./src/index.d.ts",
   "files": [
diff --git a/src/helpers/helper.js b/src/helpers/helper.js
index c50870b..e337227 100644
--- a/src/helpers/helper.js
+++ b/src/helpers/helper.js
@@ -19,10 +19,6 @@ const helper = {
     return Date.now().toString();
   },
 
-  /**
-   * validates if the value is string or not
-   * @param {string} value - value to be validated
-   */
   isValidString(value) {
     return (typeof value === 'string' && value);
   },
@@ -44,10 +40,6 @@ const helper = {
     return value !== null && typeof value === 'object' && !Array.isArray(value);
   },
 
-  /**
-   * returns stringified object with 2 spaces
-   * @param {object} obj - object
-   */
   stringify(obj) {
     if (this.isValidObject(obj)) {
       try {
diff --git a/src/helpers/toss.helper.js b/src/helpers/toss.helper.js
new file mode 100644
index 0000000..3530198
--- /dev/null
+++ b/src/helpers/toss.helper.js
@@ -0,0 +1,91 @@
+const jqy = require('json-query');
+
+const config = require('../config');
+const helper = require('./helper');
+const hr = require('./handler.runner');
+
+const stash = require('../exports/stash');
+
+function getCaptureHandlerName(name) {
+  if (helper.matchesStrategy(name, config.strategy.capture.handler)) {
+    return helper.sliceStrategy(name, config.strategy.capture.handler);
+  }
+}
+
+function getPathValueFromSpec(path, spec) {
+  let data;
+  if (path.startsWith('req.headers')) {
+    path = path.replace('req.headers', '');
+    data = spec._request.headers;
+  } else if (path.startsWith('req.body')) {
+    path = path.replace('req.body', '');
+    data = spec._request.body;
+  } else if (path.startsWith('res.headers')) {
+    path = path.replace('res.headers', '');
+    data = spec._response.headers;
+  } else {
+    path = path.replace('res.body', '');
+    data = spec._response.json;
+  }
+  return jqy(path, { data }).value;
+}
+
+function storeSpecData(spec, stores) {
+  const ctx = { req: spec._request, res: spec._response };
+  for (let i = 0; i < stores.length; i++) {
+    const store = stores[i];
+    const specData = {};
+    const captureHandler = getCaptureHandlerName(store.path);
+    if (captureHandler) {
+      specData[store.name] = hr.capture(captureHandler, ctx);
+    } else {
+      specData[store.name] = getPathValueFromSpec(store.path, spec);
+    }
+    stash.addDataStore(specData);
+  }
+}
+
+function recordSpecData(spec, recorders) {
+  const ctx = { req: spec._request, res: spec._response };
+  recorders.forEach(recorder => {
+    const { name, path } = recorder;
+    const captureHandler = getCaptureHandlerName(path);
+    if (captureHandler) {
+      spec.recorded[name] = hr.capture(captureHandler, ctx);
+    } else {
+      spec.recorded[name] = getPathValueFromSpec(path, spec);
+    }
+  });
+}
+
+function getOutput(spec, returns) {
+  const outputs = [];
+  const ctx = { req: spec._request, res: spec._response };
+  for (let i = 0; i < returns.length; i++) {
+    const _return = returns[i];
+    if (typeof _return === 'function') {
+      outputs.push(_return(ctx));
+    }
+    if (typeof _return === 'string') {
+      const captureHandler = getCaptureHandlerName(_return);
+      if (captureHandler) {
+        outputs.push(hr.capture(captureHandler, ctx));
+      } else {
+        outputs.push(getPathValueFromSpec(_return, spec));
+      }
+    }
+  }
+  if (outputs.length === 1) {
+    return outputs[0];
+  } else if (outputs.length > 1) {
+    return outputs;
+  } else {
+    return spec._response;
+  }
+}
+
+module.exports = {
+  storeSpecData,
+  recordSpecData,
+  getOutput
+};
\ No newline at end of file
diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts
index ad6ef6e..fc2fa2e 100644
--- a/src/models/Spec.d.ts
+++ b/src/models/Spec.d.ts
@@ -490,42 +490,27 @@ declare class Spec {
   expectResponseTime(value: number): Spec;
 
   /**
-   * stores spec response data 
+   * stores spec request & response data 
    */
   stores(name: string, path: string): Spec;
 
+  /**
+   * stores spec request & response data 
+   */
+  stores(name: string, handlerName: string): Spec;
+
   /**
    * returns custom response from json response using custom handler
-   * @example
-   * const id = await pactum.spec()
-   *  .get('some-url')
-   *  .expectStatus(200)
-   *  .returns('GetUserId');
    */
   returns(handlerName: string): Spec;
 
   /**
    * returns custom response from json response using json-query
-   * @example
-   * const id = await pactum.spec()
-   *  .get('some-url')
-   *  .expectStatus(200)
-   *  .returns('user.id') // json query
-   * // 'id' will be equal to '123' if response is { user: { id: 123 }}
-   * 
    */
-  returns(query: string): Spec;
+  returns(path: string): Spec;
 
   /**
    * returns custom response from json response using custom function
-   * @example
-   * const resp = await pactum.spec()
-   *  .get('some-url')
-   *  .expectStatus(200)
-   *  .returns('[0].name')
-   *  .returns((req, res) => { return res.json[0].id }) // custom function
-   * // 'resp' will be an array containing ['name', 'id']
-   * 
    */
   returns(handler: ReturnHandlerFunction): Spec;
 
diff --git a/src/models/Spec.js b/src/models/Spec.js
index 647928c..356dbff 100644
--- a/src/models/Spec.js
+++ b/src/models/Spec.js
@@ -4,6 +4,7 @@ const Expect = require('./expect');
 const State = require('./State');
 const helper = require('../helpers/helper');
 const log = require('../helpers/logger');
+const th = require('../helpers/toss.helper');
 const { PactumRequestError } = require('../helpers/errors');
 const responseExpect = require('../exports/expect');
 const handler = require('../exports/handler');
@@ -356,17 +357,29 @@ class Spec {
   }
 
   returns(handler) {
-    this._returns.push(handler);
+    if (this._response) {
+      return th.getOutput(this, [handler]);
+    } else {
+      this._returns.push(handler);
+    }
     return this;
   }
 
   stores(name, path) {
-    this._stores.push({ name, path });
+    if (this._response) {
+      th.storeSpecData(this, [{ name, path }]);
+    } else {
+      this._stores.push({ name, path });
+    }
     return this;
   }
 
   records(name, path) {
-    this._recorders.push({ name, path });
+    if (this._response) {
+      th.recordSpecData(this, [{ name, path }]);
+    } else {
+      this._recorders.push({ name, path });
+    }
     return this;
   }
 
diff --git a/src/models/Tosser.js b/src/models/Tosser.js
index 994013f..013fd8e 100644
--- a/src/models/Tosser.js
+++ b/src/models/Tosser.js
@@ -1,14 +1,11 @@
 const phin = require('phin');
-const jqy = require('json-query');
-const config = require('../config');
-const hr = require('../helpers/handler.runner');
 const helper = require('../helpers/helper');
 const log = require('../helpers/logger');
 const rlc = require('../helpers/reporter.lifeCycle');
 const requestProcessor = require('../helpers/requestProcessor');
+const th = require('../helpers/toss.helper');
 const mock = require('../exports/mock');
 const handler = require('../exports/handler');
-const stash = require('../exports/stash');
 const request = require('../exports/request');
 
 class Tosser {
@@ -18,9 +15,6 @@ class Tosser {
     this.request = spec._request;
     this.state = spec._state;
     this.expect = spec._expect;
-    this.stores = spec._stores;
-    this.returns = spec._returns;
-    this.recorders = spec._recorders;
     this.interactions = spec.interactions;
     this.previousLogLevel = spec.previousLogLevel;
     this.response = {};
@@ -39,9 +33,9 @@ class Tosser {
     await this.getInteractionsFromServer();
     await this.removeInteractionsFromServer();
     this.recordData();
-    this.storeSpecData();
+    th.storeSpecData(this.spec, this.spec._stores);
     this.validate();
-    return this.getOutput();
+    return th.getOutput(this.spec, this.spec._returns);
   }
 
   setState() {
@@ -99,8 +93,8 @@ class Tosser {
 
   recordData() {
     const defaultRecorders = request.getDefaultRecorders();
-    defaultRecorders.forEach(recorder => { recordData(recorder, this.spec); });
-    this.recorders.forEach(recorder => { recordData(recorder, this.spec); });
+    th.recordSpecData(this.spec, defaultRecorders);
+    th.recordSpecData(this.spec, this.spec._recorders);
   }
 
   async getInteractionsFromServer() {
@@ -155,53 +149,12 @@ class Tosser {
     log.warn('Response', helper.getTrimResponse(this.response));
   }
 
-  storeSpecData() {
-    const ctx = { req: this.request, res: this.response };
-    for (let i = 0; i < this.stores.length; i++) {
-      const store = this.stores[i];
-      const specData = {};
-      const captureHandler = getCaptureHandlerName(store.path);
-      if (captureHandler) {
-        specData[store.name] = hr.capture(captureHandler, ctx);
-      } else {
-        specData[store.name] = getPathValueFromSpec(store.path, this.spec);
-      }
-      stash.addDataStore(specData);
-    }
-  }
-
   sleep(ms) {
     if (typeof ms === 'number') {
       return helper.sleep(ms);
     }
   }
 
-  getOutput() {
-    const outputs = [];
-    const ctx = { req: this.request, res: this.response };
-    for (let i = 0; i < this.returns.length; i++) {
-      const _return = this.returns[i];
-      if (typeof _return === 'function') {
-        outputs.push(_return(ctx));
-      }
-      if (typeof _return === 'string') {
-        const captureHandler = getCaptureHandlerName(_return);
-        if (captureHandler) {
-          outputs.push(hr.capture(captureHandler, ctx));
-        } else {
-          outputs.push(getPathValueFromSpec(_return, this.spec));
-        }
-      }
-    }
-    if (outputs.length === 1) {
-      return outputs[0];
-    } else if (outputs.length > 1) {
-      return outputs;
-    } else {
-      return this.response;
-    }
-  }
-
 }
 
 async function getResponse(req) {
@@ -219,43 +172,4 @@ async function getResponse(req) {
   return res;
 }
 
-function recordData(recorder, spec) {
-  try {
-    const { name, path } = recorder;
-    const captureHandler = getCaptureHandlerName(path);
-    if (captureHandler) {
-      spec.recorded[name] = hr.capture(captureHandler, { req: spec._request, res: spec._response });
-    } else {
-      spec.recorded[name] = getPathValueFromSpec(path, spec);
-    }
-  } catch (error) {
-    log.warn('Unable to record data');
-    log.warn(error.toString());
-  }
-}
-
-function getPathValueFromSpec(path, spec) {
-  let data;
-  if (path.startsWith('req.headers')) {
-    path = path.replace('req.headers', '');
-    data = spec._request.headers;
-  } else if (path.startsWith('req.body')) {
-    path = path.replace('req.body', '');
-    data = spec._request.body;
-  } else if (path.startsWith('res.headers')) {
-    path = path.replace('res.headers', '');
-    data = spec._response.headers;
-  } else {
-    path = path.replace('res.body', '');
-    data = spec._response.json;
-  }
-  return jqy(path, { data }).value;
-}
-
-function getCaptureHandlerName(name) {
-  if (helper.matchesStrategy(name, config.strategy.capture.handler)) {
-    return helper.sliceStrategy(name, config.strategy.capture.handler);
-  }
-}
-
 module.exports = Tosser;
\ No newline at end of file


Test patch:
diff --git a/test/component/chai.spec.js b/test/component/chai.spec.js
index 9e6d3ec..e08fd42 100644
--- a/test/component/chai.spec.js
+++ b/test/component/chai.spec.js
@@ -2,6 +2,7 @@ const { like } = require('pactum-matchers');
 const pactum = require('../../src/index');
 const handler = pactum.handler;
 const expect = pactum.expect;
+const ce = require('chai').expect;
 
 describe('Chai Like Assertions', () => {
 
@@ -80,4 +81,8 @@ describe('Chai Like Assertions', () => {
     expect(response).to.have._('a user');
   });
 
+  it('should return custom data', async () => {
+    ce(spec.returns('name')).equals('snow');
+  });
+
 });
\ No newline at end of file
diff --git a/test/component/fuzz.swagger.v2.spec.js b/test/component/fuzz.swagger.v2.spec.js
index a36d93b..b14f8c9 100644
--- a/test/component/fuzz.swagger.v2.spec.js
+++ b/test/component/fuzz.swagger.v2.spec.js
@@ -130,6 +130,8 @@ describe('Fuzz', () => {
   it('swagger - with batch size & inspect', async () => {
     await pactum.fuzz()
       .onSwagger('http://localhost:9393/swagger.json')
+      .withHeaders('x', 'y')
+      .withHeaders({'x': 'z'})
       .withBatchSize(5)
       .inspect();
   });
diff --git a/test/component/recorder.spec.js b/test/component/recorder.spec.js
index 9cc2921..7554f61 100644
--- a/test/component/recorder.spec.js
+++ b/test/component/recorder.spec.js
@@ -55,12 +55,13 @@ describe('Recorder', () => {
 
   it('res header data to recorder using capture handler', async () => {
     pactum.handler.addCaptureHandler('GetMethod', ({ res }) => res.json.method);
-    await pactum.spec()
+    const spec = pactum.spec();
+    await spec
       .useInteraction('default get')
       .get('http://localhost:9393/default/get')
       .records('Method', '#GetMethod')
-      .records('Path', 'res.body.path')
       .expectStatus(200);
+    spec.records('Path', 'res.body.path');
   });
 
   after(() => {
diff --git a/test/component/stores.spec.js b/test/component/stores.spec.js
index 6180e93..59de148 100644
--- a/test/component/stores.spec.js
+++ b/test/component/stores.spec.js
@@ -1,11 +1,12 @@
 const pactum = require('../../src/index');
 const stash = require('../../src/exports/stash');
+const { addInteractionHandler } = pactum.handler;
 
 describe('Stores', () => {
 
-  it('store single value', async () => {
-    await pactum.spec()
-      .useInteraction({
+  before(() => {
+    addInteractionHandler('get stores', () => {
+      return {
         request: {
           method: 'GET',
           path: '/api/stores'
@@ -16,12 +17,10 @@ describe('Stores', () => {
             id: 1
           }
         }
-      })
-      .get('http://localhost:9393/api/stores')
-      .expectStatus(200)
-      .stores('UserId', 'id');
-    await pactum.spec()
-      .useInteraction({
+      };
+    });
+    addInteractionHandler('post stores', () => {
+      return {
         request: {
           method: 'POST',
           path: '/api/stores',
@@ -32,14 +31,10 @@ describe('Stores', () => {
         response: {
           status: 200
         }
-      })
-      .post('http://localhost:9393/api/stores')
-      .withJson({
-        UserId: '$S{UserId}'
-      })
-      .expectStatus(200);
-    await pactum.spec()
-      .useInteraction({
+      };
+    });
+    addInteractionHandler('post stores with id', () => {
+      return {
         request: {
           method: 'POST',
           path: '/api/stores/1'
@@ -47,7 +42,25 @@ describe('Stores', () => {
         response: {
           status: 200
         }
+      };
+    });
+  });
+
+  it('store single value', async () => {
+    await pactum.spec()
+      .useInteraction('get stores')
+      .get('http://localhost:9393/api/stores')
+      .expectStatus(200)
+      .stores('UserId', 'id');
+    await pactum.spec()
+      .useInteraction('post stores')
+      .post('http://localhost:9393/api/stores')
+      .withJson({
+        UserId: '$S{UserId}'
       })
+      .expectStatus(200);
+    await pactum.spec()
+      .useInteraction('post stores with id')
       .post('http://localhost:9393/api/stores/$S{UserId}')
       .expectStatus(200);
   });
@@ -156,6 +169,26 @@ describe('Stores', () => {
       .expectStatus(200);
   });
 
+  it('store single value after response', async () => {
+    const spec = pactum.spec();
+    await spec
+      .useInteraction('get stores')
+      .get('http://localhost:9393/api/stores')
+      .expectStatus(200);
+    spec.stores('UserId', 'id');
+    await pactum.spec()
+      .useInteraction('post stores')
+      .post('http://localhost:9393/api/stores')
+      .withJson({
+        UserId: '$S{UserId}'
+      })
+      .expectStatus(200);
+    await pactum.spec()
+      .useInteraction('post stores with id')
+      .post('http://localhost:9393/api/stores/$S{UserId}')
+      .expectStatus(200);
+  });
+
   afterEach(() => {
     stash.clearDataStores();
   });