Testing a Simple Rails XML API

Mar 24, 2009


I have a very simple xml API for adding users to our system at work. It adds a user to our front end database, and also adds that same user to our back end billing system. It needs a refactor quite badly, so testing needs to be created to make sure that the refactor works as it should.

The xml that it accepts looks like this:

<auth_request>
<vendor_id>1</vendor_id>
<vendor_password>password</vendor_password>
<lead_source_id>217</lead_source_id>
<products>
<id>A-123</id>
</products>
<firstname>Ted</firstname>
<lastname>Tester</lastname>
<address1>123 East 123 South</address1>
<address2>Apartment 6</address2>
<city>mytown</city>
<state>ST</state>
<zip>84033</zip>
<remote_host>remotesite.com</remote_host>
<email>tedtester@isp.com</email>
<billing>
<name></name>
<address></address>
<zip></zip>
<cc_num></cc_num>
<exp></exp>
</billing>
</auth_request>

It gets submitted to the api controller of my rails app, using the index action. Here is what it returned when it’s successful:

<auth_response>
<error>0</error>
<registration_code>XBZ-T-123:61111-1329</registration_code>
<debug>
</debug>
</auth_response>

Because I generated the api_controller, it provided me with a proper functional test. This is where we’ll test the api.

To start, lets put together a simple static test:

api_controller_test.rb:

include REXML

def test_index
myxml = "<auth_request>
<vendor_id>vendoruser1</vendor_id>
<vendor_password>myebiz</vendor_password>
<lead_source_id>217</lead_source_id>
<products>
<id>A-123</id>
</products>
<firstname>Ted</firstname>
<lastname>Tester</lastname>
<address1>123 East 123 South</address1>
<address2>Apartment 6</address2>
<city>mytown</city>
<state>ST</state>
<zip>84033</zip>
<remote_host>ProcessAuctions.com</remote_host>
<email>tedtester@myebiz.com</email>
<billing>
<name></name>
<address></address>
<zip></zip>
<cc_num></cc_num>
<exp></exp>
</billing>
</auth_request>"

post :index, :user=>myxml
assert_response :success
xml_response = Document.new( @response.body)

puts xml_response.elements["auth_response/registration_code"].text
end

Obviously, not the complete file, just the bits I created. The generator script started me off with much more.

If I test this script:

ruby api_controller_test.rb

I get

dmoulton@xerxes:~/dev/auth/test/functional$ ruby api_controller_test.rb
Loaded suite api_controller_test
Started
EBX-T-123:45555-1425
.
Finished in 1.120767 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Obviously, the puts statement is not that useful. Let’s replace it with a test:

assert_not_equal(0, xml_response.elements["auth_response/registration_code"].text.length,

					"A proper activation code was not returned" )

This will tell us if the api doesn’t return a valid activation code.

Next we need to get rid of that static xml and build it on the fly from some fixtures. I’d like to be able to test a large number of scenarios that might happen, including failures and malicious usage.

Here’s what my yaml file looks like:

1:

vendor_id: vendoruser1

vendor_password: vpw

lead_source_id: 217

products: 123,67

firstname: Ted

lastname: Tester

address1: 123 East 456 South

address2: Apt 2

city: slc

state: UT

zip: 84043

remote_host: myhost

email: tedtester@company.com

billing_name: ted tester

billing_address: 123 East 456 South Lehi UT

billing_zip: 84043

cc_num: 4111111111111111

cc_exp: 10/2010


2:

vendor_id: vendoruser2

vendor_password: mpw

lead_source_id: 219

products: 123

firstname: Teddy

lastname: Tester2

address1: 123 East 456 South

address2: Apt 5

city: Lehi

state: UT

zip: 84043

remote_host: myhost

email: tedtester@company.com

billing_name: ted tester2

billing_address: 123 East 456 South Lehi UT

billing_zip: 84043

cc_num: 4111111111111111

cc_exp: 10/2010

To read this in, I’ll create a method in test_helper.rb:

def make_api_xml_request(info)
	userxml = REXML::Document.new
	products = info['products'].split(",")
	userxml = REXML::Document.new
	root = REXML::Element.new('auth_request')
	userxml.add_element(root)
	root.add_element('vendor_id').add_text(info['vendor_id'])
	root.add_element('vendor_password').add_text(info['vendor_password'])
	root.add_element('lead_source_id').add_text(info['lead_source_id'].to_s)

	prod = REXML::Element.new('products')

	products.each do |p|
		prod.add_element('id').add_text(p)
	end

	root.add_element(prod)
	root.add_element('firstname').add_text(info['firstname'])
	root.add_element('lastname').add_text(info['lastname'])
	root.add_element('address1').add_text(info['address1'])
	root.add_element('address2').add_text(info['address2'])
	root.add_element('city').add_text(info['city'])
	root.add_element('state').add_text(info['state'])
	root.add_element('zip').add_text(info['zip'].to_s)
	root.add_element('remote_host').add_text(info['remote_host'])
	root.add_element('email').add_text(info['email'])
	billing = REXML::Element.new('billing')
	billing.add_element('name').add_text(info['billing_name'])
	billing.add_element('address').add_text(info['billing_address'])
	billing.add_element('zip').add_text(info['billing_zip'].to_s)
	billing.add_element('cc_num').add_text(info['cc_num'].to_s)
	billing.add_element('exp').add_text(info['cc_exp'])

	root.add_element(billing)
	userxml
end

Then, back in api_controller_test.rb, we can call the helper app for each user we find in the yaml file. Make sure that you are requiring the yaml library.

require 'yaml'

Here’s the loop to read in users from the yaml and test them:

user = load_from_yaml(RAILS_ROOT + '/test/fixtures/xml_api_users_should_pass.yml')
user.each do |tmp,info|
	userxml = make_api_xml_request(info)
	post :index, :user=>userxml.to_s
	assert_response :success
	xml_response = Document.new( @response.body)
	assert_equal('0',xml_response.elements["auth_response/error"].text)
	assert_not_equal(0, xml_response.elements["auth_response/registration_code"].text.length, "A proper activation code was not returned" )
end

Now our output is:

dmoulton@xerxes:~/dev/auth/test/functional$ ruby api_controller_test.rb
Loaded suite api_controller_test
Started
..
Finished in 611.141555 seconds.

2 tests, 12 assertions, 0 failures, 0 errors

We can then create multiple yaml files, each containing users that will test the api in one way or another. One might make sure that vendors with invalid usernames or passwords can’t log in. Another might try to use sql injection to trash the database. Use your imagination.