Virtualize CRUD functionality

Wouldn't it be great if you had a virtualization solution that fully simulates Create, Read, Update and Delete functionality?
apiUi offers state-full behaviour in two flavors, a low-code way and a way with scripting.
This tutorial will demonstrate how to realize CRUD functionality with scripting.

CRUD functionality

The OpenApi specification (OAS) we use for this tutorial has one operation for Create (http POST), two for Read (http GET), one for Update (http PUT) and another one for Delete (http DELETE).
Start apiUi, copy this link and add it in the list of Wsdl and API files (Project->Maintain list of Wsdl and API files...).
After having done this, apiUi will look like below.
apiUi after opening the AOS for this tutorial Make sure this instance of apiUi listens for HTTP requests on port 7777 (Extra->Options).

In apiUi scripts, there are a number of functions that deal with environment-variables (envvars). In this tutorial we will use the envvars as a kind of an in-memory-database. Therefor we will follow a simple convention in name-giving for the variables.

Each environment-name will consist of three parts

  1. an entity name (e.g. 'contact')
  2. a unique key
  3. an attribute name (e.g. 'emailaddress')

an example: contact[janId].emailaddress.

Values will be set with the SetEnvVar function. Retrieving will be done with GetEnvVar and GetEnvVardef. In a more advanced operation we will retrieve a list of contacts using the MatchingEnvVar function. For updating we will also use ResetEnvVar to remove a property. Deletion of a contact will be done with ResetEnvVars to remove a set of envvars.

Create

The message for creation of contacts has contactId as optional field. If this field is omitted, the mock will generate an unique id. If the contactId is delivered, then the mock will first check for uniqueness of this value. If the delivered contactId is already in use, then an http-code 409 Conflict will be returned, otherwise, the delivered contactId will be used. If all is OK then we will create envvars that reflect the provided contact data. Since every field is optional, we will check for each individual field if a value was provided.

Open the script-editor by clicking on the script-button at the left of the createContact operation and enter below code there.

string ws.id;
string ws.varprfx;

with Req.createContact.body req do
{
  if Assigned (req.contactId) then
  {
    ws.id := req.contactId;
    if GetEnvVar ('contact[' + ws.id + '].contactId') <> '' then
    {
      Rpy.createContact.undefined.responseCode := '409';
      Exit ();
    }
  }
  else
    ws.id := GenerateGUID ();
  Rpy.createContact.rspns200.body.id := ws.id;
  ws.varprfx := 'contact[' + ws.id + '].';
  SetEnvVar (ws.varprfx + 'contactId', ws.id);
  if Assigned (req.contactType) then SetEnvVar (ws.varprfx + 'contactType', req.contactType);
  if Assigned (req.name) then SetEnvVar (ws.varprfx + 'name', req.name);
  if Assigned (req.phonenumber) then SetEnvVar (ws.varprfx + 'phonenumber', req.phonenumber);
  if Assigned (req.emailaddress) then SetEnvVar (ws.varprfx + 'emailaddress', req.emailaddress);
  SetEnvVar (ws.varprfx + 'created', NowAsStr ());
}

The line of code with Req.createContact.body req do sets req as a shortcut for Req.createContact.body. The envvar for contactId intentionally gets the value of contactId self in this line of code:
SetEnvVar (ws.varprfx + 'contactId', ws.id);.
This is done to make the script for queryContacts operation somewhat easier.

In this tutorial we will assume that you use another instance of apiUi to test our virtualized services. To do so, follow next steps:

  1. Activate apiUi by choosing Run->Start (F9) and then switch to a test-driver.
  2. Start another instance of apiUi.
  3. Copy this link again and add it to the list of Wsdl and API files
    (Project->Maintain list of Wsdl and API files...).
  4. Set the createContact operation to outbound with the drop-down below the list of operations
  5. Populate the request with data you like.
    Try the automatic population of data as shown below.
    auto-populate data
    which will fill this createContact request with:
    auto-populated data
  6. Activate this instance of apiUi Run->Start (F9)
  7. Send this request twice to the service-virtualization Run->Send request (Ctrl-G)
  8. The log-panel will now show a success response and a conflict response. two creates logged
  9. Uncheck the contactId in the request and send the request again. Note that a third log-line appears with a success response again and that the response contains a generated unique identifier.
  10. Switch to the instance of apiUi that acts as the service-virtualization and inspect the environment-variables (Environment->Edit...)
    environment variables after creates

Read

The getContact operation retrieves data for a single contact based on the given parameter id.
When no contact can be found, the mock will respond with an http-code 404 not found.
Default value for the response-field contactType is 'prospect'.

In the service-virtualization open the script-editor for the getContact operation and enter below code.

string ws.varprfx := 'contact[' + Req.getContact.id + '].';
string ws.value;

if GetEnvVar (ws.varprfx + 'contactId') = '' then
{
  Rpy.getContact.undefined.responseCode := '404';
  Exit ();
}

Rpy.getContact.rspns200 := nil;

with Rpy.getContact.rspns200.body rpy do
{
  rpy.contactId := Req.getContact.id;
  rpy.contactType := GetEnvVarDef (ws.varprfx + 'contactType', 'prospect');
  ws.value := GetEnvVar (ws.varprfx + 'name');
  if ws.value <> '' then rpy.name := ws.value;
  ws.value := GetEnvVar (ws.varprfx + 'phonenumber');
  if ws.value <> '' then rpy.phonenumber := ws.value;
  ws.value := GetEnvVar (ws.varprfx + 'emailaddress');
  if ws.value <> '' then rpy.emailaddress := ws.value;
  ws.value := GetEnvVar (ws.varprfx + 'created');
  if ws.value <> '' then rpy.created := ws.value;
}

To test this virtualization, switch to the other instance of apiUi again and follw next steps:

  1. Set the getContact operation to outbound.
  2. Enter 'id' as parameter.
  3. Send the request Run->Send request (Ctrl-G)
  4. Note that the virtualization responses with an http-code 404 Not Found.
  5. Enter 'contactId' or any other value you have used and send the request again.
  6. Now the virtualization will respond with an http-code 200 OK and with details of the requested contact in the body.

In the Update part of this tutorial we will also test the assignment of the default for contactType.

Update

The updateContact operation has a mandatory path-parameter id which is the key for updating contacts.
When no contact exists with a given id, the mock will respond with an http-code 404 not found.
The body consists of optional properties for a contact. When the contact can be found on the id parameter then delivered parameters will be updated. In case such a body parameter has an empty string, the property will be removed.

Open the script-editor for the updateContact operation and enter below code.

string ws.varprfx := 'contact[' + Req.updateContact.id + '].';

if GetEnvVar (ws.varprfx + 'contactId') = '' then
{
  Rpy.updateContact.undefined.responseCode := '404';
  Exit ();
}

Rpy.updateContact.rspns200 := 'OK';

with Req.updateContact.body req do
{
  if assigned (req.contactType) then
  {
    if req.contactType = '' then
      ResetEnvVar (ws.varprfx + 'contactType')
    else
      SetEnvVar (ws.varprfx + 'contactType', req.contactType);
  }
  if assigned (req.name) then
  {
    if req.name = '' then
      ResetEnvVar (ws.varprfx + 'name')
    else
      SetEnvVar (ws.varprfx + 'name', req.name);
  }
  if assigned (req.phonenumber) then
  {
    if req.phonenumber = '' then
      ResetEnvVar (ws.varprfx + 'phonenumber')
    else
      SetEnvVar (ws.varprfx + 'phonenumber', req.phonenumber);
  }
  if assigned (req.emailaddress) then
  {
    if req.emailaddress = '' then
      ResetEnvVar (ws.varprfx + 'emailaddress')
    else
      SetEnvVar (ws.varprfx + 'emailaddress', req.emailaddress);
  }
}

To test above script, switch to the instance of apiUi that acts as test-driver and follow next steps:

  1. Select operation updateContact and set it to Outbound.
  2. Populate the request and enter an id that you did not use yet.
  3. Send the request Run->Send request (Ctrl-G)
  4. Note that the response http-code is 404 Not Found
  5. Enter an id that you did use with createContact, e.g. 'contactId'
  6. Set the value for contactType to an empty string
    (Ignore the validation message and make sure contactType is checked to include it in the message)
  7. Change the value for the name property to something like 'Jan'.
  8. Also set the value for phonenumber to an empty string and make sure it is checked
  9. Uncheck the property emailaddress.
  10. The data as shown in the message-treeview should no look like this: update contact data
    This request will update the contact with id 'contactId'.
    It will remove the properties contactType and phonenumber, update property name and leave emailaddress as is.
  11. Before updating, select operation getContact, enter the same id and retrieve the data by pressing Ctrl-G.
  12. Select updateContact again and send the update-request Ctrl-G
  13. Repeat retrieval with getContact
  14. In the log panel select the last log-line which shows getContact by just clicking it.
  15. Also select the previous log-line which shows the getContact just before the update.
    (click on it two times while holding the Ctrl key down)
  16. The log panel should look like this
    selected to get log-lines
  17. Use the context-menu option Compare (requires 2 selected rows) to compare the two log-lines
  18. The form that will pop-up will show data like below: two log-lines compared
    Note the contactType, it shows the default value and also note that the phonenumber for the contact indeed has been removed.

Delete

The deleteContact operation of course has a mandatory path-parameter id which is the key for deletion.
When no contact exists with the given id, the mock will respond with an http-code 404 not found.
When the contact can be found on id then the corresponding contact data will be deleted.

Enter below code in the script-editor for the deleteContact operation.

string ws.varprfx := 'contact[' + Req.deleteContact.id + '].';

if GetEnvVar (ws.varprfx + 'contactId') = '' then
{
  Rpy.deleteContact.undefined.responseCode := '404';
  Exit ();
}

ResetEnvVars (RegExprSafeStr (ws.varprfx) + '.*');

To remove all contact data an envvar function is used that has a regular expression as argument.
The function RegExprSafeStr is used to escape the meta-characters for regular-expression that are in the envvar-names.

To test above script, switch to the instance of apiUi that acts as test-driver and follow next steps:

  1. Select operation deleteContact and set it to Outbound.
  2. Populate the request and enter an id that you used earlier with createContact.
  3. Send the request Run->Send request (Ctrl-G) twice
  4. Note that the first response returns http-code 200 OK, second one 404 Not Found

Query

The queryContacts operation just lists all the contacts currently existing in the service-virtualization.
(So indeed, it's main purpose is just this tutorial...)

Enter below code in the script-editor for the queryContacts operation.

string ws.regexp := RegExprSafeStr ('contact[') + '.*' + RegExprSafeStr ('].contactId');
string ws.varname;
string ws.varprfx;
string ws.value;

Rpy.queryContacts := nil;
for each MatchingEnvVar (ws.regexp) as ws.varname do
{
  with new Rpy.queryContacts.rspns200.body._ rpy do
  {
    rpy.contactId := GetEnvVar (ws.varname);
    ws.varprfx := 'contact[' + rpy.contactId + '].';
    rpy.contactType := GetEnvVarDef (ws.varprfx + 'contactType', 'prospect');
    ws.value := GetEnvVar (ws.varprfx + 'name');
    if ws.value <> '' then rpy.name := ws.value;
    ws.value := GetEnvVar (ws.varprfx + 'phonenumber');
    if ws.value <> '' then rpy.phonenumber := ws.value;
    ws.value := GetEnvVar (ws.varprfx + 'emailaddress');
    if ws.value <> '' then rpy.emailaddress := ws.value;
    ws.value := GetEnvVar (ws.varprfx + 'created');
    if ws.value <> '' then rpy.created := ws.value;
  }
}

Since we will use a function that has a regular expression as argument, we have to deal with meta-characters for regular expressions.

As a result of the code
string ws.regexp := RegExprSafeStr ('contact[') + '.*' + RegExprSafeStr ('].contactId');
the string-variable ws.regexp will contain contact\[.*\]\.contactId, exactly what we need for the MatchingEnvVar function.

Then the code for each MatchingEnvVar (ws.regexp) as ws.varname do will assign all the envvar-names that match this regular expression to the string field ws.varname and execute the attached code-block for each value found.
An example value for ws.varname is contact[janId].contactId.
Because in the createContact operation we assigned the id as value to this envvar, a simple GetEnvVar can assign the contactId to the response field contactId.
The remaining code implements the same logic as getContact.

To test above script, switch to the instance of apiUi that acts as test-driver and follow next steps:

  1. Select the 'createContact' operation and add some contacts
  2. Select the operation queryContacts and set it to Outbound.
  3. Send the request Run->Send request (Ctrl-G)
  4. Click on the log-line for this call on the grid icon Browse reply.
    Below form will pop-up with the data you supplied with createContact.
    queryied contacts in a table