Scenarios examples
A collection of examples to show how to declare Scenarios and how to use Copycat's features
HTTP examples
CRUD for User entity
Create
There are 3 Scenarios declared for the Create endpoint:
- A precondition that checks if the
username
has already been taken, if it matches, will return an error message - Checks if the body contains field
type
with value different of1
. - Success when the fields
name
,username
andtelephone
are present andtype
equals to1
. There is a command on this scenario to save the received body on the database.
If none of these matches, returns the defaultResponse
, "bad request".
This Scenario also declares the optional fallbackResponse
to be used if a failure happens while processing the received request.
{
"key": {
"protocol": "HTTP",
"method": "POST",
"pathTemplate": "/users"
},
"defaultResponse": {
"status": "400",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "bad request"
}
},
"fallbackResponse": {
"status": "500",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "Unexpected error creating a new user"
}
},
"globalVariables":{
"validations": {
"invalidAge": "${request.body.age < 18 ? 'Age must be > 18' : null}",
"invalidType": "${request.body.type != 1 ? 'Invalid user type!' : null}"
}
},
"scenarios": [
{
"name": "Use real system",
"preConditions": [],
"request": {
"headers": {
"x-real-system": true
}
},
"redirect": {
"passThrough": {
"host": "http://real-app/users"
}
}
},
{
"name": "Bad request",
"preConditions": [ "${globalVariables.validations.values().stream().anyMatch(predicate((v)-> {v != null})) }" ],
"request": {},
"response": {
"status": "400",
"delay": 0,
"headers": { "Content-Type": "application/json" },
"body": {
"message": "${globalVariables.validations.values().stream().filter(predicate((v)-> {v != null})).collect(Collectors.toList())}"
}
},
"commands": []
},
{
"name": "Fail to create user - username not available",
"preConditions": [ "${db.getBy('users', {'username': request.body.username}) != null}" ],
"request": {},
"response": {
"status": "409",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "${format.string('username %s is not available',request.body.username)}"
}
}
},
{
"name": "Create new user - Success",
"request": {
"body": {
"name": "#{.*}",
"username": "#{.*}",
"telephone": "#{.*}",
"type": "1"
}
},
"response": {
"status": "201",
"delay": 0,
"headers": { "Content-Type": "application/json" },
"body": {
"id": "${id.seqId('users')}",
"name": "${request.body.name}",
"username": "${request.body.username}",
"telephone": "${request.body.telephone}",
"type": "${request.body.type}",
"age": "${request.body.age}"
}
},
"commands": [
"${db.save('users', response.body.id, response.body)}"
]
}
]
}
Retrieve
The GET by Id endpoint is declare with only one Scenario that checks for the existence of
an User with the received Id on the database.
If the user is present, it will be returned, otherwise, returns the defaultResponse
, user not found.
{
"key": {
"protocol": "HTTP",
"method": "GET",
"pathTemplate": "/users/{idUser}"
},
"defaultResponse": {
"status": "404",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "user not found"
}
},
"globalVariables": {
"user": "${!request.pathParams.idUser != null ? db.get('users', request.pathParams.idUser) : null}"
},
"scenarios": [
{
"name": "GET user - Success",
"preConditions": ["${globalVariables.user != null}"],
"request": {
"headers": {},
"pathParams": {}
},
"response": {
"headers": { "Content-Type": "application/json" },
"status": "200",
"body": "${globalVariables.user}"
},
"commands": []
}
]
}
List
The following Scenarios were added to provide more options than only a search by Id
- If an username is provided, finds an unique user based on the
username
. This Scenario uses avariable
to avoid hitting the database twice. - Checks if there are
queryParams
and if true, lists all Users that matches those params - Lists all users
As the last Scenario does not have any restriction, it will always match, and, in this case, the defaultResponse
will not be used. An alternative to this is remove the last Scenario and declare its response on defaultResponse
.
{
"key": {
"protocol": "HTTP",
"method": "GET",
"pathTemplate": "/users"
},
"defaultResponse": {
"status": "200",
"headers": { "Content-Type": "application/json" },
"body": {
"body": "[]"
}
},
"scenarios": [
{
"name": "find one user by userName - Success/Failure",
"preConditions": [],
"request": {
"queryParams": {
"username": "${request.queryParams.username != null}"
}
},
"variables": {"user": "${db.getBy('users', request.queryParams)}"},
"response": {
"headers": { "Content-Type": "application/json" },
"body": "${variables.user}",
"status": "${variables.user != null ? 200 : 404}"
},
"commands": []
},
{
"name": "Filter by parameters - Success",
"preConditions": ["${!request.queryParams.isEmpty()}"],
"request": {},
"response": {
"headers": { "Content-Type": "application/json" },
"status": "200",
"body": "${db.listBy('users', request.queryParams)}"
},
"commands": []
},
{
"name": "List all Users - Success",
"request": {},
"response": {
"headers": { "Content-Type": "application/json" },
"status": "200",
"body": "${db.list('users')}"
},
"commands": []
}
]
}
Update
There are 3 Scenarios declared for the Update endpoint:
- A precondition that checks that the
username
is present, and in this case, the Scenario will return an error informing that this change is not allowed - Checks if the body contains field
type
with value different of1
. - Success when the fields
name
,username
andtelephone
are present andtype
equals to1
. There is a command on this scenario to update the database with the received body.
If none of these matches, returns the defaultResponse
, "bad request".
{
"key": {
"protocol": "HTTP",
"method": "PUT",
"pathTemplate": "/users/{idUser}"
},
"defaultResponse": {
"status": "404",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "user not found"
}
},
"scenarios": [
{
"name": "Fail to update user - cannot update username",
"preConditions": [ "${request.body.username != null}" ],
"request": {},
"response": {
"status": "409",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "Not allowed to update username field"
}
},
"commands": []
},
{
"name": "Bad request - Invalid type",
"request": {
"body": {
"type": "${request.body.type != 1}"
}
},
"response": {
"status": "400",
"delay": 0,
"headers": { "Content-Type": "application/json" },
"body": {
"message": "Invalid user type!",
"type": "${request.body.type}"
}
},
"commands": []
},
{
"name": "Update user - Success",
"preConditions": [ "${db.exists('users', request.pathParams.idUser)}" ],
"request": {
"body": {
"name": "#{.*}",
"telephone": "#{.*}",
"type": "1"
}
},
"response": {
"status": "200",
"delay": 0,
"headers": { "Content-Type": "application/json" },
"body": "${db.update('users', request.pathParams.idUser, request.body)}"
},
"commands": []
}
]
}
Delete
The DELETE by Id endpoint is declare with only one Scenario that checks for the existence of
an User with the received Id on the database.
If the user is present, it will be permanently deleted, otherwise, returns the defaultResponse
, user not found.
{
"key": {
"protocol": "HTTP",
"method": "DELETE",
"pathTemplate": "/users/{idUser}"
},
"defaultResponse": {
"status": "404",
"headers": { "Content-Type": "application/json" },
"body": {
"message": "user not found"
}
},
"scenarios": [
{
"name": "DELETE user - Success",
"preConditions": [
"${db.exists('users', request.pathParams.idUser)}"
],
"request": {},
"response": {
"delay": 1000,
"headers": { "Content-Type": "application/json" },
"status": "204"
},
"commands": [
"${db.delete('users', request.pathParams.idUser)}"
]
}
]
}
ISO-8583 examples
Note
Remember that ISO-8583 requires a PortMapping that explains to Copycat how to interpret the
received message. All mti
used on the Scenario must be on this mapping.
This example adds two Scenarios to the mti
0100, Authorization Request.
- Checks if the field
amount
on the received body is greater than 10000. If true, returns a 0110 message with the error code 16 (Insufficient funds) - Returns the
mti
0110 with the success code (00) if theprocessingCode
andstan
are positive values and theterminal
is T001. This Scenario also declares a callback that will send an 0422 message (Reversal advice) after 1000 ms using the same connection that received the original message.
If no Scenario matches, the defaultResponse
will be returned, a 0110 message with error code 06 (General error)
Note
This is an ISO-8583 Scenario example with an ISO-8583 callback, but remember that the callback may be of a different type, for instance, it may be an ISO-8583 Scenario with an HTTP callback.
Authorization Request
{
"key": {
"protocol": "ISO8583",
"mti": "0100",
"resourceName": "xpto"
},
"defaultResponse": {
"mti": "0110",
"body": {
"processingCode": "${request.body.processinCode}",
"stan": "${request.body.stan}",
"responseCode": "06",
"terminal": "${request.body.terminal}",
"cardAcceptor": "${request.body.cardAcceptor}"
}
},
"scenarios": [
{
"name": "Insufficient funds",
"request": {
"body": {
"amount": "${request.body.amount > 10000}"
}
},
"response": {
"mti": "0110",
"delay": 1000,
"body": {
"processingCode": "${request.body.processinCode}",
"stan": "${request.body.stan}",
"responseCode": "16",
"terminal": "${request.body.terminal}",
"cardAcceptor": "${request.body.cardAcceptor}"
}
},
"commands": []
},
{
"name": "Success - Terminal T0000001",
"request": {
"body": {
"terminal": "T0000001"
}
},
"response": {
"mti": "0110",
"body": {
"processingCode": "${request.body.processinCode}",
"stan": "${request.body.stan}",
"responseCode": "00",
"terminal": "${request.body.terminal}",
"cardAcceptor": "${request.body.cardAcceptor}"
}
},
"callback": {
"type": "ISO8583",
"typeProperties": {
"connectionId": "${request.connectionId}",
"mti": "0422"
},
"payload": {
"processingCode": "${request.body.processinCode}",
"stan": "${request.body.stan}",
"terminal": "${request.body.terminal}",
"cardAcceptor": "${request.body.cardAcceptor}"
},
"delay": 1000
},
"commands": []
}
]
}