Repository: pactumjs/pactum
Pull request #383 (2024-10-26 08:40:18)

chore: spec store options


Patch:
diff --git a/package-lock.json b/package-lock.json
index d986bd0..b2b4b83 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "pactum",
-  "version": "3.7.2",
+  "version": "3.7.3",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "pactum",
-      "version": "3.7.2",
+      "version": "3.7.3",
       "license": "MIT",
       "dependencies": {
         "@exodus/schemasafe": "^1.3.0",
diff --git a/package.json b/package.json
index 301ec59..d07c042 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.7.2",
+  "version": "3.7.3",
   "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/helpers/toss.helper.js b/src/helpers/toss.helper.js
index ccbc575..d8d062f 100644
--- a/src/helpers/toss.helper.js
+++ b/src/helpers/toss.helper.js
@@ -51,22 +51,34 @@ function getPathValueFromRequestResponse(path, request, response) {
   return jqy(path, { data }).value;
 }
 
+/**
+ *
+ * @param {*} spec
+ * @param {import('../internal.types').ISpecStore[]} stores
+ */
 function storeSpecData(spec, stores) {
   const ctx = { req: spec._request, res: spec._response, store: stash.getDataStore() };
   for (let i = 0; i < stores.length; i++) {
     const store = stores[i];
-    if (typeof store === 'function') {
-      const specData = store(spec._request, spec._response);
-      stash.addDataStore(specData);
+    if (store.cb) {
+      stash.addDataStore(store.cb(spec._request, spec._response));
       continue;
     }
-    const specData = {};
+    let data_to_store;
+
     const captureHandler = getCaptureHandlerName(store.path);
     if (captureHandler) {
-      specData[store.name] = hr.capture(captureHandler, ctx);
+      data_to_store = hr.capture(captureHandler, ctx);
     } else {
-      specData[store.name] = getPathValueFromRequestResponse(store.path, spec._request, spec._response);
+      data_to_store = getPathValueFromRequestResponse(store.path, spec._request, spec._response);
     }
+    if(store.options && store.options.append) {
+      ctx.store[store.name] = ctx.store[store.name] || [];
+      ctx.store[store.name].push(data_to_store);
+      continue;
+    }
+    const specData = {};
+    specData[store.name] = data_to_store;
     stash.addDataStore(specData);
   }
 }
diff --git a/src/internal.types.d.ts b/src/internal.types.d.ts
new file mode 100644
index 0000000..53b0280
--- /dev/null
+++ b/src/internal.types.d.ts
@@ -0,0 +1,11 @@
+export type ISpecStore = {
+  name?: string
+  path?: string
+  options?: ISpecStoreOptions
+  cb?: Function
+}
+
+export type ISpecStoreOptions = {
+  status?: 'PASSED' | 'FAILED'
+  append?: boolean
+}
\ No newline at end of file
diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts
index ff01c43..0d757f0 100644
--- a/src/models/Spec.d.ts
+++ b/src/models/Spec.d.ts
@@ -4,6 +4,7 @@ import { Expect } from '../exports/expect';
 import { CaptureHandlerFunction, ExpectHandlerFunction, RetryHandlerFunction } from '../exports/handler';
 import { Interaction } from '../exports/mock';
 import { LogLevel } from '../exports/settings';
+import { ISpecStoreOptions } from '../internal.types';
 
 declare interface RetryOptions {
   /** maximum number of retries - defaults to 3 */
@@ -382,8 +383,8 @@ declare class Spec {
    * stores spec request & response data
    * @see https://pactumjs.github.io/api/requests/stores.html
    */
-  stores(name: string, path: string): Spec;
-  stores(name: string, handlerName: string): Spec;
+  stores(name: string, path: string, options?: ISpecStoreOptions): Spec;
+  stores(name: string, handlerName: string, options?: ISpecStoreOptions): Spec;
   stores<T>(cb: (request: Request, response: IncomingMessage & {body: Record<string, any>, json: Record<string, any>}) => T): Spec;
 
   /**
diff --git a/src/models/Spec.js b/src/models/Spec.js
index 094fa32..5e9d2da 100644
--- a/src/models/Spec.js
+++ b/src/models/Spec.js
@@ -483,20 +483,27 @@ class Spec {
   }
 
   stores(...args) {
-    if (args.length === 1 && typeof args[0] === 'function') {
-      const fn = args[0];
-      if (this._response) {
-        th.storeSpecData(this, [fn]);
-      } else {
-        this._stores.push(fn);
-      }
-    } else if (args.length === 2) {
-      const [name, path] = args;
-      if (this._response) {
-        th.storeSpecData(this, [{ name, path }]);
-      } else {
-        this._stores.push({ name, path });
-      }
+    if (args.length === 0) {
+      return this;
+    }
+
+    /**
+     * @type {import('../internal.types').ISpecStore}
+     */
+    const spec_store = {};
+
+    if (typeof args[0] === 'function') {
+      spec_store.cb = args[0];
+    } else {
+      spec_store.name = args[0];
+      spec_store.path = args[1];
+      spec_store.options = args[2];
+    }
+
+    if (this._response) {
+      th.storeSpecData(this, [spec_store]);
+    } else {
+      this._stores.push(spec_store);
     }
     return this;
   }
diff --git a/src/models/Tosser.js b/src/models/Tosser.js
index a25f832..2c3284b 100644
--- a/src/models/Tosser.js
+++ b/src/models/Tosser.js
@@ -42,7 +42,7 @@ class Tosser {
       await this.getInteractionsFromServer();
       this.saveDataInFileSystem();
       this.recordData();
-      th.storeSpecData(this.spec, this.spec._stores);
+      this.storeSpecData();
       await this.validate();
       if (hasBackgroundInteractions(this.interactions) || (this.spec._wait && typeof this.spec._wait.arg1 === 'string')) {
         await this.dynamicWait();
@@ -209,9 +209,11 @@ class Tosser {
       this.validateNonBackgroundInteractions();
       await this.validateResponse();
       this.spec.status = 'PASSED';
+      this.storeSpecData();
       this.runReport();
     } catch (error) {
       this.spec.status = 'FAILED';
+      this.storeSpecData();
       this.spec.failure = error.toString();
       this.runReport();
       this.printRequestAndResponse();
@@ -255,6 +257,36 @@ class Tosser {
     }
   }
 
+  storeSpecData() {
+    const stores = [];
+    switch (this.spec.status) {
+      case 'PASSED':
+        for (const store of this.spec._stores) {
+          if (store && store.options && store.options.status === 'PASSED') {
+            stores.push(store);
+          }
+        }
+        break;
+      case 'FAILED':
+        for (const store of this.spec._stores) {
+          if (store && store.options && store.options.status === 'FAILED') {
+            stores.push(store);
+          }
+        }
+        break;
+      default:
+        for (const store of this.spec._stores) {
+          if (store && (!store.options || !store.options.status)) {
+            stores.push(store);
+          }
+        }
+        break;
+    }
+    if (stores.length > 0) {
+      th.storeSpecData(this.spec, stores);
+    }
+  }
+
 }
 
 async function getResponse(tosser) {


Test patch:
diff --git a/test/component/stores.spec.js b/test/component/stores.spec.js
index 45b7098..597abfe 100644
--- a/test/component/stores.spec.js
+++ b/test/component/stores.spec.js
@@ -1,3 +1,4 @@
+const expect = require('chai').expect;
 const pactum = require('../../src/index');
 const stash = require('../../src/exports/stash');
 const { addInteractionHandler } = pactum.handler;
@@ -111,7 +112,7 @@ describe('Stores', () => {
         }
       })
       .post('http://localhost:9393/api/stores')
-      .withCookies('token','$S{token}')
+      .withCookies('token', '$S{token}')
       .withJson({
         UserId: '$S{FirstUser.id}'
       })
@@ -177,7 +178,7 @@ describe('Stores', () => {
       .expectStatus(200);
   });
 
-  it ('store single value by custom function', async () => {
+  it('store single value by custom function', async () => {
     await pactum.spec()
       .useInteraction({
         request: {
@@ -195,10 +196,10 @@ describe('Stores', () => {
       .expectStatus(200)
       .stores((request, response) => {
         return {
-            custom_func_id: response.body.id
+          custom_func_id: response.body.id
         };
       });
-      await pactum.spec()
+    await pactum.spec()
       .useInteraction({
         request: {
           method: 'POST',
@@ -218,7 +219,7 @@ describe('Stores', () => {
       .expectStatus(200);
   });
 
-  it ('store multiple value by custom function', async () => {
+  it('store multiple value by custom function', async () => {
     await pactum.spec()
       .useInteraction({
         request: {
@@ -237,11 +238,11 @@ describe('Stores', () => {
       .expectStatus(200)
       .stores((request, response) => {
         return {
-            custom_func_id: response.body.id,
-            custom_func_email: response.body.email
+          custom_func_id: response.body.id,
+          custom_func_email: response.body.email
         };
       });
-      await pactum.spec()
+    await pactum.spec()
       .useInteraction({
         request: {
           method: 'POST',
@@ -290,9 +291,9 @@ describe('Stores', () => {
       .get('http://localhost:9393/api/stores')
       .expectStatus(200);
     spec.stores((request, response) => {
-        return {
-            UserId: response.body.id
-        };
+      return {
+        UserId: response.body.id
+      };
     });
     await pactum.spec()
       .useInteraction('post stores')
@@ -307,6 +308,60 @@ describe('Stores', () => {
       .expectStatus(200);
   });
 
+  it('store on failure', async () => {
+    await pactum.spec()
+      .useInteraction('get stores')
+      .get('http://localhost:9393/api/stores')
+      .expectStatus(200)
+      .stores('FailedUserId', 'id', { status: 'FAILED' });
+    expect(stash.getDataStore('FailedUserId')).to.be.undefined;
+    try {
+      await pactum.spec()
+        .useInteraction('get stores')
+        .get('http://localhost:9393/api/stores')
+        .expectStatus(500)
+        .stores('FailedUserId', 'id', { status: 'FAILED' })
+        .inspect(false);
+    } catch (error) {
+
+    }
+    expect(stash.getDataStore('FailedUserId')).equals(1);
+  });
+
+  it('store on success', async () => {
+    try {
+      await pactum.spec()
+        .useInteraction('get stores')
+        .get('http://localhost:9393/api/stores')
+        .expectStatus(500)
+        .stores('FailedUserId', 'id', { status: 'PASSED' })
+        .inspect(false);
+    } catch (error) {
+
+    }
+    expect(stash.getDataStore('FailedUserId')).to.be.undefined;
+    await pactum.spec()
+      .useInteraction('get stores')
+      .get('http://localhost:9393/api/stores')
+      .expectStatus(200)
+      .stores('FailedUserId', 'id', { status: 'PASSED' });
+    expect(stash.getDataStore('FailedUserId')).equals(1);
+  });
+
+  it('store append', async () => {
+    await pactum.spec()
+      .useInteraction('get stores')
+      .get('http://localhost:9393/api/stores')
+      .expectStatus(200)
+      .stores('UserId', 'id', { append: true });
+    await pactum.spec()
+      .useInteraction('get stores')
+      .get('http://localhost:9393/api/stores')
+      .expectStatus(200)
+      .stores('UserId', 'id', { append: true });
+    expect(stash.getDataStore('UserId')).deep.equals([1,1]);
+  })
+
   afterEach(() => {
     stash.clearDataStores();
   });