Repository: pactumjs/pactum
Pull request #51 (2021-04-01 07:00:23)

#47 support | add trace and options request-methods
Support for OPTIONS and TRACE request-methods added.
Also, withPath and withMethod methods have been added.

(a) Files Updated :
- models/spec.js
- models/spec.d.ts

(b) Methods implemented in spec.js:
- options(url)
- trace(url)
- withPath(url)
- withMethod(method)

(c) Tests added in request.spec.js

(d) Interaction.model.js 
- "OPTIONS" and "TRACE" added in ALLOWED_REQUEST_METHODS 
  



Patch:
diff --git a/src/models/Interaction.model.js b/src/models/Interaction.model.js
index 22232e2..f19249d 100644
--- a/src/models/Interaction.model.js
+++ b/src/models/Interaction.model.js
@@ -2,7 +2,16 @@ const { setMatchingRules, getValue } = require('pactum-matchers').utils;
 const processor = require('../helpers/dataProcessor');
 const helper = require('../helpers/helper');
 const { PactumInteractionError } = require('../helpers/errors');
-const ALLOWED_REQUEST_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']);
+const ALLOWED_REQUEST_METHODS = new Set([
+  'GET',
+  'POST',
+  'PUT',
+  'DELETE',
+  'PATCH',
+  'HEAD',
+  'OPTIONS',
+  'TRACE',
+]);
 
 function process(raw) {
   processor.processMaps();
@@ -49,7 +58,9 @@ function validate(raw) {
   }
   if (typeof request.queryParams !== 'undefined') {
     if (!helper.isValidObject(request.queryParams)) {
-      throw new PactumInteractionError('`request.queryParams` should be object');
+      throw new PactumInteractionError(
+        '`request.queryParams` should be object'
+      );
     }
   }
   if (helper.isValidObject(response)) {
@@ -78,16 +89,13 @@ function setResponse(response) {
 }
 
 class InteractionRequestGraphQL {
-
   constructor(graphQL) {
     this.query = graphQL.query;
     this.variables = graphQL.variables;
   }
-
 }
 
 class InteractionRequest {
-
   constructor(request) {
     this.matchingRules = {};
     this.method = request.method;
@@ -125,11 +133,9 @@ class InteractionRequest {
       };
     }
   }
-
 }
 
 class InteractionResponse {
-
   constructor(response) {
     this.matchingRules = {};
     this.status = response.status;
@@ -143,11 +149,9 @@ class InteractionResponse {
       this.delay = new InteractionResponseDelay('RANDOM', response.randomDelay);
     }
   }
-
 }
 
 class InteractionResponseDelay {
-
   constructor(type, props) {
     this.type = type;
     if (type === 'RANDOM') {
@@ -158,20 +162,16 @@ class InteractionResponseDelay {
       this.value = props;
     }
   }
-
 }
 
 class InteractionExpectations {
-
   constructor(expects) {
     this.exercised = expects.exercised;
     this.callCount = expects.callCount;
   }
-
 }
 
 class Interaction {
-
   constructor(raw) {
     let unprocessedResponse;
     if (raw && raw.response) {
@@ -187,7 +187,16 @@ class Interaction {
     this.callCount = 0;
     this.exercised = false;
     this.calls = [];
-    const { id, provider, flow, strict, request, response, expects, stores } = raw;
+    const {
+      id,
+      provider,
+      flow,
+      strict,
+      request,
+      response,
+      expects,
+      stores,
+    } = raw;
     this.id = id || helper.getRandomId();
     if (flow) this.flow = flow;
     if (provider) this.provider = provider;
@@ -199,7 +208,6 @@ class Interaction {
       this.stores = stores;
     }
   }
-
 }
 
-module.exports = Interaction;
\ No newline at end of file
+module.exports = Interaction;
diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts
index 0047a93..98e6475 100644
--- a/src/models/Spec.d.ts
+++ b/src/models/Spec.d.ts
@@ -139,6 +139,39 @@ declare class Spec {
    */
   delete(url: string): Spec;
 
+  /**
+   * The OPTIONS method asks for request-methods supported by the request.
+   * @example
+   * await pactum.spec()
+   *  .options('https://jsonplaceholder.typicode.com/posts')
+   *  .expectStatus(204)
+   *  .expectHeader('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
+   */
+   options(url: string): Spec;
+  
+   /**
+    * The TRACE method echos the contents of an HTTP Request back to the requester
+    * @example
+    * await pactum.spec()
+    *  .trace('http://localhost:9393/projects/1')
+    *  .expectStatus(200);
+    */
+   trace(url: string): Spec;
+ 
+   /**
+    * The WITHMETHOD method extends the support for the request-methods apart from 
+    * (GET, POST, DELETE, PATCH, PUT, HEAD)
+    * 
+    * The WITHPATH method triggers the request passed through withMethod()
+    * @example
+    * await pactum.spec()
+    *  .withMethod('HEAD')
+    *  .withPath('https://jsonplaceholder.typicode.com/posts')
+    *  .expectStatus(200);
+    */
+   withMethod(method: string): Spec;
+   withPath(url: string): Spec;
+   
   /**
    * replaces path params in the request url - /api/users/mike
    * @example
diff --git a/src/models/Spec.js b/src/models/Spec.js
index f39af91..4ad8c0b 100644
--- a/src/models/Spec.js
+++ b/src/models/Spec.js
@@ -13,7 +13,6 @@ const hr = require('../helpers/handler.runner');
 const rlc = require('../helpers/reporter.lifeCycle');
 
 class Spec {
-
   constructor(name, data) {
     this.id = helper.getRandomId();
     this.flow = '';
@@ -97,6 +96,31 @@ class Spec {
     return this;
   }
 
+  options(url) {
+    validateRequestUrl(this._request, url);
+    this._request.url = url;
+    this._request.method = 'OPTIONS';
+    return this;
+  }
+
+  trace(url) {
+    validateRequestUrl(this._request, url);
+    this._request.url = url;
+    this._request.method = 'TRACE';
+    return this;
+  }
+
+  withMethod(method) {
+    this._request.method = method;
+    return this;
+  }
+
+  withPath(url) {
+    validateRequestUrl(this._request, url);
+    this._request.url = url;
+    return this;
+  }
+
   withPathParams(key, value) {
     if (!this._request.pathParams) {
       this._request.pathParams = {};
@@ -177,7 +201,9 @@ class Spec {
 
   withBody(body) {
     if (typeof this._request.data !== 'undefined') {
-      throw new PactumRequestError(`Duplicate body in request - ${this._request.data}`);
+      throw new PactumRequestError(
+        `Duplicate body in request - ${this._request.data}`
+      );
     }
     this._request.data = body;
     return this;
@@ -219,7 +245,9 @@ class Spec {
     if (!options) {
       options = {};
     }
-    options.filename = options.filename ? options.filename : path.basename(filePath);
+    options.filename = options.filename
+      ? options.filename
+      : path.basename(filePath);
     return this.withMultiPartFormData(key, fs.readFileSync(filePath), options);
   }
 
@@ -423,7 +451,9 @@ class Spec {
 
   response() {
     if (!this._response) {
-      throw new PactumRequestError(`'response()' should be called after resolving 'toss()'`);
+      throw new PactumRequestError(
+        `'response()' should be called after resolving 'toss()'`
+      );
     }
     return responseExpect(this._response, this);
   }
@@ -432,12 +462,13 @@ class Spec {
     rlc.afterSpecReport(this);
     return this;
   }
-
 }
 
 function validateRequestUrl(request, url) {
   if (request.url && request.method) {
-    throw new PactumRequestError(`Duplicate request initiated. Existing request - ${request.method} ${request.url}`);
+    throw new PactumRequestError(
+      `Duplicate request initiated. Existing request - ${request.method} ${request.url}`
+    );
   }
   if (!helper.isValidString(url)) {
     throw new PactumRequestError(`Invalid request url - ${url}`);


Test patch:
diff --git a/test/component/request.spec.js b/test/component/request.spec.js
index 734d743..f154e5e 100644
--- a/test/component/request.spec.js
+++ b/test/component/request.spec.js
@@ -3,10 +3,10 @@ const request = pactum.request;
 const config = require('../../src/config');
 
 describe('Request', () => {
-
-  it('with baseurl', async () => {
+  it('GET with baseurl', async () => {
     request.setBaseUrl('http://localhost:9393');
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         request: {
           method: 'GET',
@@ -21,9 +21,69 @@ describe('Request', () => {
       .inspect();
   });
 
+  it('OPTIONS with baseurl', async () => {
+    request.setBaseUrl('http://localhost:9393');
+    await pactum
+      .spec()
+      .useInteraction({
+        request: {
+          method: 'OPTIONS',
+          path: '/users'
+        },
+        response: {
+          status: 204,
+          headers: {
+            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE'
+          }
+        }
+      })
+      .options('/users')
+      .expectStatus(204)
+      .expectHeader(
+        'access-control-allow-methods',
+        'GET,HEAD,PUT,PATCH,POST,DELETE'
+      );
+  });
+
+  it('TRACE with baseurl', async () => {
+    request.setBaseUrl('http://localhost:9393');
+    await pactum
+      .spec()
+      .useInteraction({
+        request: {
+          method: 'TRACE',
+          path: '/users'
+        },
+        response: {
+          status: 200
+        }
+      })
+      .trace('/users')
+      .expectStatus(200);
+  });
+
+  it('HEAD with baseurl', async () => {
+    request.setBaseUrl('http://localhost:9393');
+    await pactum
+      .spec()
+      .useInteraction({
+        request: {
+          method: 'HEAD',
+          path: '/users'
+        },
+        response: {
+          status: 200
+        }
+      })
+      .withMethod('HEAD')
+      .withPath('/users')
+      .expectStatus(200);
+  });
+
   it('with baseurl override', async () => {
     request.setBaseUrl('http://localhost:9392');
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         request: {
           method: 'GET',
@@ -40,7 +100,8 @@ describe('Request', () => {
   it('with default header', async () => {
     request.setBaseUrl('http://localhost:9393');
     request.setDefaultHeaders('x', 'a');
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         request: {
           method: 'GET',
@@ -60,7 +121,8 @@ describe('Request', () => {
   it('with override default header to empty value', async () => {
     request.setBaseUrl('http://localhost:9393');
     request.setDefaultHeaders('x', 'a');
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         request: {
           method: 'GET',
@@ -81,7 +143,8 @@ describe('Request', () => {
   it('with override default header', async () => {
     request.setBaseUrl('http://localhost:9393');
     request.setDefaultHeaders('x', 'a');
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         request: {
           method: 'GET',
@@ -100,7 +163,8 @@ describe('Request', () => {
   });
 
   it('with file - just path', async () => {
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         strict: false,
         request: {
@@ -117,7 +181,8 @@ describe('Request', () => {
   });
 
   it('with file - path & options', async () => {
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         strict: false,
         request: {
@@ -134,7 +199,8 @@ describe('Request', () => {
   });
 
   it('with file - key & path', async () => {
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         strict: false,
         request: {
@@ -151,7 +217,8 @@ describe('Request', () => {
   });
 
   it('with file - key, path & options', async () => {
-    await pactum.spec()
+    await pactum
+      .spec()
       .useInteraction({
         strict: false,
         request: {
@@ -171,5 +238,4 @@ describe('Request', () => {
     config.request.baseUrl = '';
     config.request.headers = {};
   });
-
-});
\ No newline at end of file
+});