Mocking a Network Resource with Camping

Tammer Saleh

Names have been changed to protect the innocent

One of our projects involves interacting with an internal credit card processing server. We wrote a small middle-layer library that will be talking to the server, and which will be shared among other applications that do credit card processing for this client. Basically, we post XML to the server (which contains the CC info), and get back XML holding the response code.

With payment processing you usually test against the live server, using pre-determined credit card numbers like 4111111111111111—supplied by Visa or Master Card or what not. Unfortunately, this server is hosted inside our client’s well-protected network, and is unaccessible from our development machines. I also don’t like the idea of depending on some other dude’s service for my tests to pass, and I’d like to have more control over what results the tests can expect.

Camping to the rescue

We decided that best solution was to run a small web server that mocks the real server’s functionality. The only issue was in figuring out how to write this server in as little time as possible (it is only for testing, after all). Camping was perfect for the job. Here’s the code:

#!/bin/env ruby

require 'rubygems'
$LOAD_PATH.unshift(".")
require 'camping'
require 'rexml/document'

Camping.goes :PaymentProcessor

silence_warnings do
  Responses = {
    "4111111111111111" => { :response_code => 101 },
    "4111111111111112" => { :response_code => 102 },
    "Missing"          => { :response_code => 103 },
    "Malformed"        => { :response_code => 200 },
  }
  Responses.default = { :response_code => 100 }
end

module PaymentProcessor::Controllers
  class Index < R '/'
    def get
      render :index
    end
  end

  class Purchase < R '/purchase'
    def get
      render :index
    end

    def post
      @headers['Content-Type'] = 'text/xml charset=utf8'
      begin
        $logger.info "\nPOST: \n#{@raw_post_data}"
        xml = REXML::Document.new(@raw_post_data)
        cc_number = xml.elements["//customer-cc-number"]
        @payment = Responses[cc_number ? cc_number.text : "Missing"]
      rescue Exception => e
        $logger.info e
        @payment = Responses["Malformed"]
      end
      $logger.info "\nResponding with: \n#{@payment.to_xml}"
      # $logger.info "@raw_post_data: #{@raw_post_data}"
      render :purchase
    end
  end
end

module PaymentProcessor::Views
  def index
    html do
      body do
        h1  "Payment Gateway Mock"
        div "This server is here to let thoughtbot's developers" +
              "  test their credit card processing in a controlled environment."
        div "Point your payment library at /purchase, and use the following test
          data:"
        pre Responses.to_yaml
        div "All other values for the credit card number will return successful."
      end
    end
  end

  def purchase
    @payment.to_xml(:root => "response")
  end
end

if __FILE__ == $0
  require 'mongrel'

  $logger = Logger.new('loggylogger.log')

  server = Mongrel::Camping::start("0.0.0.0", 2000, "/", PaymentProcessor)
  puts "** PaymentProcessor is running at http://localhost:3000/"
  server.run.join
end

All in under 80 lines! Fantastic. It’s even self-documenting (of a sort) for anyone browsing to http://localhost:2000/.

We now run this server on our central development box, and point our library at it when in development or testing mode. Problem solved.

There’s always a gotcha

So, I had to make the smallest of changes to the camping code in order to get to the XML in the post body…

425c425,426
<         qs.merge!(C.qsp(@in.read))
---
>         @raw_post_data = @in.read
>         qs.merge!(C.qsp(@raw_post_data))

I won’t say that the camping code was easy to read, but at under 800 lines, it wasn’t too hard to figure out the patch above. And the folk over at #camping were all incredibly helpful.