Examples and Case Studies

Example 1. Remote Token Introspection Service (Static Mock)

A company utilizes a self-hosted OAuth 2.0 server-to-server authorization infrastructure. One of the company's security paradigms is that every call between two services must be secured by OAuth 2.0 infrastructure. Every service must check if the OAuth token provided in the request is valid. This check is done using remote token introspection.

When an OAuth 2.0 client makes a request to the resource server, the resource server needs some way to verify the access token. The OAuth 2.0 core spec does not define a specific method of how the resource server should verify access tokens, and it just mentions that it requires coordination between the resource and authorization servers. The OAuth 2.0 Token Introspection extension defines a protocol that returns information about an access token, intended to be used by resource servers or other internal servers. The token introspection endpoint needs to be able to return information about a token.

The problem

The problem with real token introspection endpoints in the development environment is that it cannot handle high traffic, is unstable, and issuing token takes a significant amount of time. This results in flaky and long-running tests.

Example 1 - Real Introspection Service

The solution

For this reason, the team developing one of the services decided to simulate the development instance of the token introspection endpoint to stabilize and speed up automatic acceptance tests.

Example 1 - Mocked Introspection Service

The token introspection endpoint consumes the body in the form of token= and should return two types of responses:

  • success for a token with value ValidToken
  • error response for tokens with different values

Sample request:

POST /token_info HTTP/1.1

token=c1MGYwsDSdqwJiYmYxNDFkZjVkOGI0MSAgLQ

Sample response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "active": true,
  "scope": "read write email",
  "client_id": "J8NFmU4tJVdfasdfaFmXTWvaHO",
  "exp": 1437275311
}

Mock of success response

A mock returning a success response should match the following fields:

  • HTTP Method: POST
  • Path: /token_info
  • Body: equals "token=ValidToken"

The mock may return a fixed body with an HTTP status of 200 OK and Content-Type application/json, so a static mock is appropriate for the given use case (Note: exp field is set to a value far in the future).

{
  "active": true,
  "scope": "read write email",
  "client_id": "Client123",
  "exp": 2000000000
}

Mock of failed response

A mock returning a failed response should match the following fields:

  • HTTP Method: POST
  • Path: /token_info
  • Body: does not equal "token=ValidToken"

The mock returns a fixed body with HTTP status of 200 OK and Content-Type application/json.

{
  "error": "invalid_client",
  "error_description": "Some error message"
}

Example 2. SMS Sending Service (Template Mock)

One of the company products uses an external service called SMS Gateway. The service is used to send out SMS notifications to customers whenever the status of their order changes.

The problem

Sending SMSes costs a few cents each, and SMS Gateway does not provide a stable test environment. The development team has built a broad set of acceptance and performance tests, and perform these tests on a daily basis. Unfortunately, the cost of sending SMSes became significant.

The solution

To limit unnecessary costs, the development team decided to simulate the SMS Gateway using SmartMock.io.

The SMS Gateway's API is straightforward. It just accepts apiKey, message, sender, and number parameters as query parameters.

Sample request:

POST https://supersmsgateway.com/send?apiKey=82aaa1fd-dbec-43d6-8d9a-a6fe5bfd47f1&message=Very%20important%20message.&sender=John%20Doe&number=12025550142

Sample response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 150
X-RequestId: 8e9f0301-a316-4140-9cb7-834cd182470f

{
   "batchId":123456789,
   "cost":2,
   "numMessages":1,
   "message":{
      "numParts":1,
      "sender":"John Doe",
      "content":"Very important message"
   },
   "messages":[{
      "id":"0de209f4-5e39-477f-a727-c93641bfe2e8",
      "recipient": "12025550142"
   },
   "result":"success"
}

From the client’s perspective, the important parts of the response are the HTTP status code (which should be 200), result field (which should be success), and pair of X-RequestId header and batchId field, which are logged for auditing purposes. Both X-RequestId and batchId are expected to be unique for each request. To achieve uniqueness, the development team used a template mock with some random helpers.

Example 2 - Mocked SMS Sending Service

Mock of the success response

A mock with a success response should match the following fields:

  • HTTP Method: POST
  • Path: /send

The mock should return an HTTP status code of 200 OK and content-type of application/json. There should also be a random value generated for header X-RequestId, and a valid body returned.

Headers:
X-RequestId: {{randomUuid}}

Body:
{
   "batchId":{{now format='yyyymmddHH'}},
   "cost":2,
   "numMessages":1,
   "message":{
      "numParts":1,
      "sender":"{{req.query.[sender]}}",
      "content":"{{req.query.[message]}}"
   },
   "messages":[{
      "id":"{{randomUuid}}",
      "recipient": "{{req.query.[number]}}"
   },
   "status":"success"
}
  • Random UUID is set as X-RequestId header
  • batchId is an incremental value that changes every hour. It's simply the concatenation of the current year, month, day, and hour. For example 2018050610
  • Fields sender, content, and recipient, although the client does not use them, are set to values from the request
  • Field status is statically set to value success which is enough for the tested use case

Example 3. Payment Service Provider Integration (Dynamic Mock)

This example presents how dynamic mock and stateful behavior may be used to simulate delay of payment processing by a Payment Service Provider (PSP).

The problem

Some PSPs may return three statuses of the payment: Ready, Pending, and Failed. Depending on the response provided, the user interface should display a message about the payment being accepted or rejected, or display a spinner indicating the payment is being processed, as well as periodically poll Backend for Front-End (BFF) for the latest payment status. The problem is that the frequency of a Pending status being returned by a real PSP in any environment is very low, and its occurrences are non-deterministic. Because of this, it was impossible to test or to demo polling behavior.

Example 3 - Real Payment Service Provider

The solution

The development team decided to mock the PSP and use dynamic mocks to return a Pending status when the payment amount is 1500 else return a Ready status.

Example 3 - Mock Payment Service Provider

BFF uses two PSP methods: Create Payment and Get Payment Details.

Create Payment request sample:

POST /psp/creditcard/payments HTTP/1.1
Content-Type: application/json    
{
  "description" : "Some product description",
  "amount": 1500,
  "currency" : "USD",
  ... other payment data
}

Create Payment response sample:

HTTP/1.1 200 OK
Content-Type: application/json
{
  "payment": {
    "id": "5adc265f-f87f-4313-577e-08d3dca1a26c",
    "status": "Pending",
    ... other payment data
  }
}

Get Payment Details request sample:

GET /psp/creditcard/payments/5adc265f-f87f-4313-577e-08d3dca1a26c HTTP/1.1
Content-Type: application/json

Get Payment Details response sample:

HTTP/1.1 200 OK
Content-Type: application/json
{
  "payment": {
    "id": "5adc265f-f87f-4313-577e-08d3dca1a26c",
    "status": "Ready",
    ... other payment data
  }
}

Mock of Create Payment

Mock of the Create Payment endpoint should match the following request fields:

  • HTTP Method: POST
  • Path: /psp/creditcard/payments
  • Content Type: application/json

A dynamic mock is a suitable choice for the given use case. It should return a randomly generated payment id, and a status of Ready or Pending depending on the request amount value. Also, it must make use of the state feature to store payment details for further query. Duration of the Pending status is random (10 - 100 seconds):

// Set response status of 200 OK
res.status=200
 //Set content type of the response
res.addHeader('Content-Type', 'application/json'); 

// Generate payment ID
const id = faker.random.uuid();

// Choose payment status depending on payment amount
let status = 'Ready';
const amount = req.jsonBody.amount; //retrieve amount value from the request
if (amount === 1500) {
    status = 'Pending';
}

const payment = {id, status};

// Set response body
res.body= {
    payment: payment,
    // HATEOAS links
    operations: {
        method: "GET",
        href: `https://${req.host}/psp/creditcard/payments/${id}`,
        rel: "view-verification",
        contentType: "application/javascript"
    }
}

// Get state
const payments = state.getOrSet('payments', {})

// Put information about the reposne to state
payments[id] = {
    // Payment will be in Pending status up to 100 seconds from its creation
    pendingUntil: status === 'Pending' ? new Date().getTime() + faker.random.number({min:10000, max:100000}) : undefined,
    payment: payment
}

Mock of Get Payment Details

A mock of the Get Payment Details endpoint should match the following fields:

  • HTTP Method: GET
  • Path: /psp/creditcard/payments/{paymentId}
  • Content Type: application/json

A dynamic mock is also an appropriate choice for this mock. It needs to retrieve payment details from the state and set a Ready status if enough time has passed since payment creation.

//Set content type of the response
res.addHeader('Content-Type', 'application/json');

// Get state
const payments = state.getOrSet('payments', {});

// Retrieve response info from state
const paymentHolder = payments[req.pathParams['paymentId']];
if (paymentHolder) {
    //If pendingUntil defined and current time is larger than pendingUntil set payment status to 'Ready'
    if (paymentHolder.pendingUntil && new Date().getTime() > paymentHolder.pendingUntil) {
        paymentHolder.payment.status = 'Ready';
    }

    // Set resposne status code 200 OK and body 
    res.status=200;
    res.body={
        payment: paymentHolder.payment
    };
} else {
    // If payment not found return HTTO 404 Not Found with error details
    res.status=404;
    res.body={
        errorCode: 'PAYMENT_NOT_FOUND',
        errorMessage: 'Payment not found'
    }
}