gears

For Ruby projects, a common solution for simulating web API transactions is to use the VCR gem. VCR can record a request to the API, and the response from it, and these recordings can then be played back when performing automated tests. This means no actual requests to, and responses from, the API are needed for ongoing automated testing.

VCR alone can be insufficient when an API interaction goes beyond a simple request and response. With Cybersource transactions, the interactions are more complex, and the final response is sent to a pre-determined fixed URL, not the requester. Cybersource calls this URL the “Customer Response Page.” It is saved on the Cybersource server as part of the merchant profile, so it cannot be updated dynamically. If you are a developer attempting to test Cybersource transactions on your own computer, this diagram illustrates the scenario:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
                    +                    +                   +
                    |     Developer's    |    Cybersource    |  "Customer Response"
    User's browser  |     test server    |    test server    |         server
+------------------+--------------------+-------------------+---------------------+
  Request credit
    card form
        +
        |
        +----------->   Respond with
                      credit card form
                              +
                              |
    Submit form <-------------+
        +
        |
        +------------------------------>   Process and log
                                            transaction;
                                          generate "Customer
                                            Response" form
                                                  +
                                                  |
  Hidden "Customer <------------------------------+
  Response" form is
    automatically
    submitted by JS
        +
        |
        +--------------------------------------------------->   Process submission;
                                                                generate response page
                                                                           +
                                                                           |
          Display  <-------------------------------------------------------+
       response page

There are work-arounds that can facilitate testing in this environment, but they have drawbacks. You could make the “Developer’s test server” and the “‘Customer Response’ server” the same machine, or make manual changes to the “Customer Response Page” URL in the Cybersource test server profile as different developers work on the project. These work-arounds require you to develop and test on a publicly available address, and they preclude simultaneous development and testing on more than one server.

In developing the Cybersourcery gem, we needed a more robust approach. So we wrote the Cybersourcery Testing gem. Understanding how it works can help guide automated testing efforts for similarly complex API environments.

The gem provides a “translating proxy” server, running on Sinatra, which has middleware to manage the requests and responses between your test server, the Cybersource server, and the “Customer Response” server. Middleware provides an excellent solution, as it allows us to modify requests and responses before they reach the application’s code. In order to explain how the gem works, we need to first take a look at its dependencies:

  • The Rack Proxy gem is a Rack app which allows you to rewrite requests and responses. It’s very open-ended and is designed to be subclassed, so you can implement rewrite rules appropriate to your project.
  • The Rack::Translating Proxy gem inherits from the Rack Proxy gem, and provides an implementation that’s suitable for the Cybersource use case. We need to provide it with a target_host URL, which indicates where the proxy server should redirect requests. We also need to provide it with a request_mapping, which indicates what strings to look for and change in the requests and responses, and what to change them to. It uses a hash format, so that on requests, the keys are translated to the values, and on responses, the values are translated to the keys.

The Cybersourcery Testing gem inherits from the Rack::Translating Proxy gem, and implements the methods described above. Specifically:

For the target_host, we provide the URL of the Cybersource testing site. So if the proxy server is running at http://localhost:5556, and the target_host is https://testsecureacceptance.cybersource.com, requests to http://localhost:5556/some/path will be redirected to https://testsecureacceptance.cybersource.com/some/path. The gem also hooks into VCR, allowing us to record transactions that pass through the proxy server, for use in subsequent automated tests.

This is a simplified version of the request_mapping implementation, using hard-coded values for clarity:

1
2
3
4
5
6
def request_mapping
  {
    # local test server                Cybersource's "Customer Response Page" URL
    'http://localhost:1234/confirm' => 'http://your-site.com/confirm'
  }
end

A Cybersource transaction in this environment looks like this:

  1. The credit card form is submitted to the proxy server (if you’re using the Cybersourcery gem, it dynamically determines the URL to use for the form submission).
  2. Based on the target_host, the proxy server passes the request through to the actual Cybersource test server. If the transaction was previously recorded with VCR, VCR will instead play back the recording of the transaction.
  3. Cybersource (or VCR) sends a hidden form back to the browser, which is automatically submitted via JavaScript to the “Customer Response Page” URL. The request_mapping will rewrite the URL of the form’s action, causing the form to instead submit to the local test server.

The Cybersourcery Testing gem automatically handles the possibility of the port number changing on the test server between sessions. It also reports several known but undocumented form submission requests that can trigger unhelpful, generic 500 server error messages from Cybersource.