Question

I've implemented a custom Puppet function that queries a Keystone server for information. The module that defines this function includes some helper methods that perform the actual work of querying keystone. Broadly, the structure looks like this:

def authenticate(auth_url, username, password)
...
end

def list_tenants(auth_url, token)
...
end

module Puppet::Parser::Functions
    newfunction(:lookup_tenant, :type => :rvalue) do |args|
    ...
    end
end

I would like to mock out the authenticate and list_tenants methods during testing so that I can test the rest of the Puppet module in the absence of an actual Keystone server.

I haven't previously worked with either Ruby or Rpsec before, and I'm having a hard time finding examples of how to provide stubs for these internal methods.

So far I have a stub rspec file that simply verified the existence of the function:

require 'spec_helper'

describe 'lookup_tenant' do
    it "should exist" do
        Puppet::Parser::Functions.function("lookup_tenant").should == "function_lookup_tenant"
    end

    # This will fail because there is no keystone server.
    it "should fail" do
        should run.with_params(
            'http://127.0.0.1:35357/v2.0',
            'admin_user',
            'admin_password',
            'admin_tenant_name',
            'target_tenant_name'
       ).and_raise_error(KeystoneError)
    end
end

I would like to be able to provide custom returns from the authenticate and list_tenants methods (or even raise exceptions from inside these methods) so that I can test the behavior of the lookup_tenant function in different failure scenarios.

Was it helpful?

Solution

WebMock could be used for simulating the http requests as stubs. Here is the link to the github repo: https://github.com/bblimke/webmock

OTHER TIPS

For folks who haven't seen webmock before, I wanted to leave some information here about why it's particularly awesome.

So, I have in my module some code that makes an http request:

url = URI.parse("#{auth_url}/tokens")
req = Net::HTTP::Post.new url.path
req['content-type'] = 'application/json'
req.body = JSON.generate(post_args)

begin
    res = Net::HTTP.start(url.host, url.port) {|http|
        http.request(req)
    }

    if res.code != '200'
        raise KeystoneError, "Failed to authenticate to Keystone server at #{auth_url} as user #{username}."
    end
rescue Errno::ECONNREFUSED
    raise KeystoneError, "Failed to connect to Keystone server at #{auth_url}."
end

By simply adding a require to the start of the spec file:

require `webmock`

Attempts to open a connection will result in:

 WebMock::NetConnectNotAllowedError:
   Real HTTP connections are disabled. Unregistered request: POST http://127.0.0.1:35357/v2.0/tokens with body '{"auth":{"passwordCredentials":{"username":"admin_user","password":"admin_password"},"tenantName":"admin_tenant"}}' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}

   You can stub this request with the following snippet:

   stub_request(:post, "http://127.0.0.1:35357/v2.0/tokens").
     with(:body => "{\"auth\":{\"passwordCredentials\":{\"username\":\"admin_user\",\"password\":\"admin_password\"},\"tenantName\":\"admin_tenant\"}}",
          :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}).
     to_return(:status => 200, :body => "", :headers => {})

And that's just about all the information you need to stub out the call. You can make the stubs as granular as necessary; I ended up using something like:

good_auth_request = { 
    'auth' => {
        'passwordCredentials' => {
            'username' => 'admin_user',
            'password' => 'admin_password',
        },
        'tenantName' => 'admin_tenant',
    }
}

auth_response = {
    'access' => {
        'token' => {
            'id' => 'TOKEN',
        }
    }
}

stub_request(:post, "http://127.0.0.1:35357/v2.0/tokens").
    with(:body => good_auth_request.to_json).
    to_return(:status => 200, :body => auth_response.to_json, :headers => {})

And now I can test my module when there is no Keystone server available.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top