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.
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.
The token introspection endpoint consumes the body in the form of token=
- 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.
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
, andrecipient
, although the client does not use them, are set to values from the request - Field
status
is statically set to valuesuccess
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.
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.
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'
}
}
Example 4. Simulate underperforming service (Proxy Mock)¶
The following example presents how to use proxy mock to simulate underperforming service having that service already implemented and available.
The problem¶
A company selling some physical goods has split the order handling process into multiple services.
Two of these services are Order Service and Delivery Service. Order Service is responsible for handling the procedure of
ordering goods, whereas the Delivery Service manages and keeps track of the delivery process. The user journey allows
delayed deliveries, i.e., customers may pay for goods and request delivery at any random time after the payment has been completed.
When the customer requests a delivery, the order should change its status to IN_DELIVERY
, and there should be exactly
one shipment record created in the Delivery Service. In case of connectivity problems, the Order Service is supposed
to return PENDING_DELIVERY
status and later synchronize with the Delivery Service automatically.
QA engineers would like to test some unhappy cases of the delayed delivery scenario, including a timeout between the Order Service and the Delivery Service. The following diagram presents the test scenario:
Unfortunately, the real Delivery Service's response time is generally low, so it's impossible to execute deterministic tests.
One of the approaches to enable the QA team to implement deterministic tests is to implement delay logic in the Delivery Service itself. The significant disadvantage of that way is that test and production code is mixed. The more of such cases covered in the production code, the largest its pollution. Hosted HTTP mock solution (like SmartMock.io) helps to separate test code from production one.
The solution¶
SmartMock.io is a hosted HTTP mock solution that provides a feature of proxy mocks. SmartMock.io's HTTP proxy mocks with a configured
response delays may be especially useful in addressing situations when a delay needs to be included in service-to-service calls.
Order Service sends HTTP requests to proxy mocks, which forward them to the real Delivery Service.
After the Delivery Service replies, SmartMock.io applies a constant delay before sending back a response to Order Service.
Besides the mock introducing a delay, there's another mock to be created. It only forwards requests to the target service,
so the response time is comparable with the direct Delivery Service call. The content of the Order Service request determines which
mock matches: for the ones containing "zipCode": "00001"
in the payload - delaying mock, all other requests - non-delaying mock.
Mock of delayed Create Delivery¶
The delaying mock that forwards requests to the real Delivery Service should have the priority 0
. Its responsibility is to
simulate underperforming/unreachable Delivery Service.
It should match the following fields:
- HTTP Method: POST
- Path: /api/v1/delivery
- Body contains:
"zipCode": "00001"
It should also apply a constant response delay of 10 seconds
, which is larger than the request timeout between the Order Service and the Delivery Service:
- Delay type: Constant
- Delay value (ms): 10000
The mock should be of the Proxy Mock type and should point to the real Delivery Service:
- Response type: Proxy Mock
- Target URL: the address of the real Delivery Service
Mock of non-delayed Create Delivery¶
The non-delaying mock that forwards requests to the real Delivery Service should have the priority 1
. Its responsibility
is to forward all other Create Delivery requests to the Delivery Service without a delay so other tests may check
other scenarios.
It should match the following fields:
- HTTP Method: POST
- Path: /api/v1/delivery
The mock should be of the Proxy Mock type and should point to the real Delivery Service:
- Response type: Proxy Mock
- Target URL: the address of the real Delivery Service
Mock of Synchronize Delivery¶
Used by the Synchronization Job (see the diagram above). It does not delay the response but just forwards request to the Delivery Service.
- HTTP Method: PUT
- Path: /api/v1/delivery
The mock should be of the Proxy Mock type and should point to the real Delivery Service:
- Response type: Proxy Mock
- Target URL: the address of the real Delivery Service