Saturday, March 17, 2018

Testing PATCH Requests

Like PUT requests, PATCH requests modify an existing record.  But PATCH requests are much tricker to test!  This is because a PUT request modifies an entire record, whereas a PATCH request modifies only one part of the record.  There are many different operations that you can do within a PATCH request: you can add, replace, remove, copy, and move a value in your record.  I'll describe some examples of each and discuss the various ways you can test them.  

Let's use a very simple example of a data record for patching:


The JSON description of the first record would look like this:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551000",
    "workPhone": null
}

Notice that our first record does not have a work phone.  Let's do an add operation with our PATCH request in order to add one:

The URL for the request would look something like: https://app/customer/1, where the "1" is representing the id of the customer that you want to patch, and of course the http verb used should be PATCH.  The body of the request would look like this:
[
    {"op":"add","path":"/workPhone","value":"8005551001"}
]
The "op" in this case is referring to the operation that you want to do for your PATCH.  Here we are using an add operation.  The "path" is showing which field you would like to add a value to, in this case the work phone field; the "value" is value that you want to add to that path, in this case the phone number itself.  

When this operation is completed, the JSON description will look like this:

{
    "id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551000",
    "workPhone": "8005551001"
}

Here are some ways to test a PATCH add operation:
  • Happy Path- patching where there is currently a null value
  • patching over the null value with an empty value of ""- this should add an empty string
  • patching over an empty value of ""- this should replace the existing empty string
  • patching over an existing value- this will replace the existing value, but ideally the replace operation should be used here instead
  • adding a value with too many or too fewer characters than allowed- an appropriate error message should be returned
  • adding a value with characters that are not allowed- an appropriate error message should be returned
  • adding a value of the wrong type, such as adding an integer when a string is expected- an appropriate error message should be returned
Next, let's take a look at the replace operation.  Let's use the second record this time, and we'll replace the home phone.  This is what the record currently looks like:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": "8005551002",
    "workPhone": "8005551003"
}

The URL for the request will be https://app/customer/2, because we are patching the second record, and the body of the request will be:
[
    {"op":"replace","path":"/homePhone","value":"8005551111"}
]
This operation will replace the original home phone of 8005551002 with a phone number of 8005551111, so the record will now look like this:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": "8005551111",
    "workPhone": "8005551003"
}

To test a PATCH replace operation, here are some things that you can try:
  • Happy Path- replacing one value with another
  • replacing the value with null- a remove operation would be better for this, but this should still work
  • replacing the value with the empty string ""- this should work
  • replacing a null value with a value- this will probably work, but it would be better to use the add operation
  • replacing with a value with too many or too fewer characters than allowed- an appropriate error message should be returned
  • replacing with a value with characters that are not allowed- an appropriate error message should be returned
  • replacing with a value of the wrong type, such as adding an integer when a string is expected- an appropriate error message should be returned
  • replacing where the existing value is bad in some way, such as having too many characters or having the wrong format- in this case the new good value should be allowed to replace the bad old value
Now let's look at the remove operation.  We'll start with this record:

{
    "id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551000",
    "workPhone": "8005551001"
}

To remove the home phone of this customer, we would again use this URL: app/customer/1, and we would use this body:
[
    {"op":"remove", "path":"/homePhone"}
]
We will get this as a result:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": null,
    "workPhone": "8005551001"
}

A remove operation is fairly easy to test.  We just need to verify that the value has indeed been removed and replaced with null.  It's also good to verify that no other value was removed by mistake, such as removing both phone numbers instead of just the home phone. We can also test that it's not possible to remove a required field- this should return an appropriate error message.  

Now let's look at the move operation.  Looking at the previous state of John's record, let's imagine that we have it wrong, and that what we have for his work phone is actually his home phone.  To move the phone number, we would use this request body:
[
    {"op":"move","from":"/workPhone","path":"/homePhone"}
]
In this example, the "from" indicates where the current value is, and the "path" indicates where you would like the value to move to.  After this operation, our record will look like this:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551001",
    "workPhone": null
}

Here are some ideas for testing a move operation:
  • Happy Path- moving an existing value to a location where the value is currently null
  • moving an existing value from location A to location B, where there is already a value in location B- in this case, the value in location B will be replaced with the value that was in location A
  • doing a move from A to B where the value of A is null- this should return an appropriate error message, because there's nothing to move
  • doing a move from A to B where the validation constraints are different in location B, so A's value should not be allowed- this should return an appropriate error message
  • doing a move from A to B where the value in A is bad- this should return an appropriate error message
  • moving a value from one location to a location that does not exist: this should return an appropriate error message
Finally, we'll look at the copy operation.  This will copy an existing value and put it in a different location.  Let's take this example:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551001",
    "workPhone": null
}

If we want to copy the home phone so that the work phone will be the same number, we can do this request:
[
    {"op":"copy","from":"/homePhone","path":"/workPhone"}
]

The "from" field shows us the location of the value we want to copy, and the "path" field shows us the location we'd like to copy to.  Our record should now look like this:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551001",
    "workPhone": "8005551001"
}

Testing a copy operation is very similar to testing a move operation:
  • Happy Path: copying a value from one location to another where the value is currently null
  • copying a value from location A to location B, where location B currently has a value; this value will be replaced by the value in location A
  • copying from location A where the value of A is null- this should return an error message
  • copying from location A to a location that does not exist- this should return an error message
  • copying from A to B where the validation constraints are different in location B- this should return an error message
  • copying a bad value from location A to location B- this should return an error message
An important thing to note when patching is that PATCH requests can be chained together.  For example, if we start with this scenario:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": "8005551002",
    "workPhone": "8005551003"
}

And we run this PATCH request:
[
    {"op":"remove","path":"/homePhone"},
    {"op":"replace","path":"/workPhone","value":"8005551111"}
]
We will wind up with this result:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": null,
    "workPhone": "8005551111"
}

When we chain together requests in a PATCH, we need to test that any invalid operation will result in the entire request being invalid.  For example, if we had instead tried this request:
[
    {"op":"remove","path":"/homePhone"},
    {"op":"replace","path":"/workPhone","value":"NOTAPHONENUMBER"}
]
we should receive an error message, and the first part of the PATCH should NOT have been executed.  In other words, we should still have a value in for the home phone.  

As you can see, PATCH requests are almost a set of HTTP verbs in themselves!  They should be used very carefully in an application, because there are so many different ways they can go wrong.  If your application is using them, be sure to test them alone and in combination.  Be sure to test both the Happy Path and the many ways they can fail validation.  If you are able to put bad data into your database, be sure to test patching over bad data.  

In next week's post, I'll discuss the DELETE request, and also how to create a Postman collection!  










Saturday, March 10, 2018

Testing PUT Requests

In last week's blog post, we discussed how to create and test POST requests.  This week, we will tackle testing PUT requests.  A PUT request is actually very similar to a POST request; the major difference is that POST requests are intended to create a new record, and PUT requests are intended to replace an existing record. 

Let's return to the Swagger Pet Store to learn how to create a PUT request.  Click on the PUT /pet request to open it:


As you can see, the endpoint and the body of the request are exactly the same as that of the POST request.  The only difference is the http verb used: PUT instead of POST.  To see how the PUT request works, let's first do a POST with these values:
{
  "id": 1,
  "category": {
    "id": 1,
    "name": "Cat"
  },
  "name": "Grumpy Cat",
  "photoUrls": [
    "https://pbs.twimg.com/profile_images/948294484596375552/RyGNqDEM_400x400.jpg"
  ],
  "tags": [
    {
      "id": 1,
      "name": "Mixed breed"
    }
  ],
  "status": "available"
}
Then do a GET to make sure that the pet was added correctly.  Now, let's do a PUT with these values:
{
  "id": 1,
  "category": {
    "id": 2,
    "name": "Dog"
  },
  "name": "Droopy Dog",
  "photoUrls": [
    "https://upload.wikimedia.org/wikipedia/en/thumb/f/fd/Droopy_dog.png/150px-Droopy_dog.png"
  ],
  "tags": [
    {
      "id": 2,
      "name": "Beagle"
    }
  ],
  "status": "pending"
}
Notice that all of the values in the body of the request have been changed except for the pet id. After you have submitted the request, do a GET to confirm that all of the values have been replaced.

A PUT request will always replace ALL of the values in the entire record, which means that if any of the values are missing, they will be removed.  For example, if we did a PUT request on that same pet id, and only included a name and a photo URL, that will be all that will be saved. Any tag or status that was present in the record before will now be gone.  

To talk about testing PUT requests, I'm going to move away from the Swagger Pet Store and use a different example. Let's consider an application that stores a list of employees.  The application writes to a table that has these values:
The PUT request that will be used to update a record in the table will look like this:
{
  "firstName": "Amy",
  "lastName": "Miller"
}
And the URL of the request will look like this: app/employee/1.  In this application, the employee id is passed in through the URL, rather than the body of the request, which is a common practice.  

The first thing we will test in our imaginary application is the Happy Path: if we send this request, does employee 1 now have the name Amy Miller instead of Fred Smith?  You could check this by querying the database directly, and by doing a GET request.  

Next, we'll see what happens when trying to do a PUT request on a record that doesn't exist.  Imagine doing the same request, but instead of using the URL app/employee/1, we'll use app/employee/3.  As you can see in the data table, there is no record 3.  So this request should return an error, such as a 404 Not Found response.  You can also try passing in ids that make no sense, such as letters, words, or symbols, and passing in no id at all.  All these requests should return an appropriate error message.

Now let's turn to the body of our request.  Let's imagine that both the firstName and the lastName are required for the PUT request.  We should get an appropriate error message if we send in an empty body, or just the firstName, or just the lastName.  In a scenario where there are many more fields than we have here, you'll want to test lots of different combinations where required and non-required fields are missing.  

As I mentioned above, a PUT request should replace ALL the values in a record; it's like the record was completely removed and replaced with a brand new one.  So if we consider for a moment a scenario where the firstName is NOT required, and record 1 is currently Amy Miller, and we do a test where we send in only a lastName of "Brown", record 1 should now have a NULL value for the firstName, and Brown should be the lastName.  

You could also see what happens when you try to pass in a request with fields that aren't in the table at all!  In this example, you could try sending in 
{
  "firstName": "Amy",
  "middleName": "Jo"
  "lastName": "Miller"
}
and verify that the field is either ignored or an appropriate message is returned.  

Now we can go on to the individual fields themselves. What sort of values are allowed? What are the character limits?  You'll want to test each individual field to make sure that you receive an appropriate error message when the limits are violated, and that you receive an appropriate error message when you try to pass in characters or values that are not allowed.  You'll also want to try sending in values that could be used for cross-site scripting or SQL injection; these should either be rejected outright or sanitized in such a way that a malicious attack would not work.  

Finally, you can return to the http verb and the general request.  What happens if you change the PUT request to a POST request?  In our hypothetical application, a POST request with a URL of app/employee/1 will return a 409 error, because record 1 already exists.  You can also see what happens if you remove or change required headers on the request.  

Hopefully this post has given you a clear indication of how PUT requests behave, and the best ways to test them.  Next week, we'll be on to the lesser-used PATCH request!  



Saturday, March 3, 2018

Testing POST Requests

Last week, I introduced the concept of the GET request and how to test it.  This week we'll move to POST requests.  POST requests are perhaps the most important of the RESTful requests, because they are what adds new records to your application's database.  It's very important to test your POST requests well, because they will have a direct impact on the quality of data in your database.

To learn about POST requests, we'll once again use the Swagger Pet Store and Postman.  Navigate to the Pet Store (http://petstore.swagger.io) and click on the first POST request listed: "/pet".  This POST request will add a pet to the pet store.  Take a look at the Example Value shown:
This is the body of the POST request.  Unlike GET requests, which usually don't have a body, you will usually find some json or xml in a POST request.  The body represents the data that you are adding to the database.  Now click on the Model link:
This describes a bit about what all of the values in the body are.  You can click on the ">" icons to open up each section of the model.  I find this model to be a bit vague in terms of defining what a category is and what tags are, so I'm going to do a little guesswork.  Let's walk through each section of the Pet model:
id: this is the id of the pet, which can be used in the GET request we tested last week
category: this represents what kind of animal the pet is.  The id is the unique identifier for the category, and the name is the word describing the animal.
name: this is the pet's name
photoUrls: these are strings that link to pictures of the pet
tags: these are descriptive phrases that can be added to the pet.  The id is the unique identifier for the tag, and the name is the descriptive word describing something about the pet
status: this is the status of the pet in the store.  The status can be available, pending, or sold.

We can use the information in the model to create a POST to make a new pet.  Click on the "Try it out" link, and replace the body of the request with this information:
{
  "id": 102,
  "category": {
    "id": 1,
    "name": "cat"
  },
  "name": "Grumpy Cat",
  "photoUrls": [
    "https://pbs.twimg.com/profile_images/948294484596375552/RyGNqDEM_400x400.jpg"
  ],
  "tags": [
    {
      "id": 1,
      "name": "blue eyes"
    }
  ],
  "status": "sold"
}


Before you click the Execute button, I do want to point out something in this POST request that is different from what you will most likely experience in the applications you test.  Generally, when you post an object with an id and that id already exists in the database, you'll get a message that the record already exists.  In the case of the Pet Store, if that id already exists in the database, it will get overwritten with the object that you have posted.  

Now click the "Execute" button.  Take a look at what was returned in the body of the response.  You should see all the data you added.  It may be in a different order than it was in the body of the request, but that's not important.

Let's test to make sure that the new pet was really added!  Return to the GET pet/{petId} request, open it up, and click "Try it out".  Enter "100" into the id field, and click "Execute".  You should see the pet you added in the body of the response.  

Now that you have created a successful request in Swagger, let's try one in Postman.  Open up the Postman application, and click on the plus button (+) to create a new request.  Click on the dropdown icon next to the word "GET" and choose "POST" instead.  Enter in the request URL: http://petstore.swagger.io/v2/pet. Click on the "Body" tab underneath the URL and select the "Raw" option.  In the body section just underneath, paste the request that you used earlier in Swagger.  You may want to vary it slightly by changing the id or the pet's name.  

There's one more step we need to do before we can send this request in Postman, and that is to add a header.  Headers are used in HTTP requests and responses to pass additional information to the server.  In this case, we need to tell the server what content-type to expect.  Click on the "Header" tab underneath the URL.  In the space underneath "Key", add "Content-Type".  In the space underneath "Value", add "application/json".  In this way, we are telling the server that it should look for a body in json format when we send our request.  Now click the "Send" button.  In the bottom half of the Postman window, you should see a 200 response code, and a response body that has all of the data you added.  Now you can use a GET request in Postman to check to make sure that your pet was added.  Return to last week's blog post if you need help setting that up.  

Just as we found when we discussed testing forms, there are many different scenarios to test when testing POST requests.  First, we'll want to test many Happy Path scenarios.  Try a number of different POSTs where you vary the id, the category of the pet and its id, the pet's name, the photo URL, the tag and the tag's id, and the status of the pet. You'll want to make sure to test all three statuses of the pet: sold, available, and pending.  Also note that it's possible to pass in more than one photo URL, and more than one tag.  Here's an example of a body of a POST request where three photos and two tags are passed in:

{
  "id": 102,
  "category": {
    "id": 2,
    "name": "dog"
  },
  "name": "Snoopy",
  "photoUrls": [
    "https://schulzmuseum.org/wp-content/uploads/2017/06/920608_FlyingAce-200.jpg",
    "https://www.calmuseums.org/images/snoopy/snoopy.png",
    "https://vignette.wikia.nocookie.net/peanuts/images/2/28/AmigosperdemoSono.png/revision/latest?cb=20110823202539"
  ],
  "tags": [
    {
      "id": 2,
      "name": "beagle"
    },
    {
      "id": 3,
      "name": "flying ace"
    }
  ],
  "status": "available"
}

Now that you have tested a number of Happy Path scenarios, it's time to think about breaking the request.  First, you may have noticed in the Swagger Model that only two fields are required: the name of the pet, and one photo URL.  What happens if you pass in just those two fields?  
{
  "name": "Snoopy",
  "photoUrls": [
    "https://schulzmuseum.org/wp-content/uploads/2017/06/920608_FlyingAce-200.jpg"
  ]
}

We get a 200 response, and the pet has been added with an id that we can use to retrieve it.  What happens if there are no values in the body of the request?  What happens if one of the required fields is missing?  Now is a good time to experiment by passing in a variety of combinations of fields.  

It's worth noting at this point that you may get a 500 response for nearly every error condition you provoke.  In a real-world application, you'd get more appropriate response codes with more appropriate error messages. 

Next, it's time to take another look at passing in multiple URLs and tags.  How many URLs can you pass in before provoking an error?  How many tags can you pass in before you get an error?  

Now we'll look at the limits of the values we are passing in.  For example, what happens if you create a pet with an id of 0?  What happens if you create one with the id of -100000?  What happens if you send in an id of "FOO" or $%^?  You can test this on all of the ids: the pet's id, the category id, and the tag id.  How long are the text strings allowed to be?  How short are they allowed to be?  Are there any characters that are not allowed?  You'll want to test the upper and lower limits of the strings, and verify that any forbidden characters are not allowed.  This is also a good time to test for SQL injection and cross-site scripting.  For example, you could try passing in 
<script>alert('XSS')</script> 
or 
' or 1 = 1-- 
and see if those are allowed.  (In the case of the Pet Store, these will probably be allowed, but a real application should forbid these or at least render them ineffective.)  

Another thing to test is the enum field for the pet's status.  The pet's status should be limited to available, pending, and sold.  What happens if you pass in another word or number or symbol?  What happens if you pass in a valid status with all caps, or with some of the letters capitalized?  A POST request should generally be case-insensitive.  You will also want to test the photo URL, making sure that you can't pass in malicious scripts, and making sure that you can't pass in something that isn't a URL.

Once you have finished testing the body of the request, you can test the headers of the request.  This particular POST request requires a Content-Type header, and it will allow either an application/json header or an application/xml header.  If the header is missing, you'll get a 415 error.  If you use an application/xml header, you'll get a 400 error, unless you first change the body of the request to be in XML format.  You could also test to see what happens when you send an xml body with an application/json header.  

Finally, you can test the URL of the request itself, and the request type.  What happens if you make the request an https request?  What happens if you change the POST request to a GET?  

In summary, there are many ways to test POST requests, many of which would not be possible if you were focusing only on the UI.  In addition to testing required and non-required fields and field validation, you can also manipulate the object passed into the database and the headers and URL used to make the request. 

Next week, we'll move on to PUT requests!