What does/can fake: do? - ruby-on-rails

I've inherited this code and I'm aware that this is creating a stub for fake api calls. I don't understand how it is working. Can it return only JSON? Can I set a response to simply a 200 success? Is there any documentation on this?
class GuessTransaction < ActiveRestClient::Base
request_body_type :json
get :all, '/transaction', fake: [{last_name:"Durdan", first_name:"Tyler"}]
get :find, '/transaction/:id', fake: {id: "1", first_name:"Tyler", last_name: "Durdan"}
post :create, '/transaction', fake:->(request) { {id: 12345 } }
end

If you read the documentation for active-rest-client
you can find this:
Faking Calls
There are times when an API hasn't been developed yet, so you want to
fake the API call response. To do this, you can simply pass a fake
option when mapping the call containing the response.
class Person < ActiveRestClient::Base
get :all, '/people', fake: [{first_name:"Johnny"}, {first_name:"Bob"}]
end
You may want to run a proc when faking data (to put information from
the parameters in to the response or return different responses
depending on the parameters). To do this just pass a proc to :fake:
class Person < ActiveRestClient::Base
get :all, '/people', fake: ->(request) { {result: request.get_params[:id]} }
end

Based on the Source for active-rest-client.
Excerpt
return handle_response(
OpenStruct.new(
status:200,
body:fake,
response_headers:{
"X-ARC-Faked-Response" => "true",
"Content-Type" => content_type
}
)
)
It appears it will always respond with 200 so you can just do something like fake:{}
This will respond with 200 and an empty body for the response.
Even fake: true should work.

Related

What's the easiest way to make HTTParty return an object/class rather than a JSON response?

Right now I call:
def child(parent_id, child_id, params = {})
if #api_token
self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params,
:headers=>{"Authorization"=>"Token token=#{#api_token}"})
else
self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params)
end
end
It returns the JSON response directly from the API as a hash. Is there an easy way for me to standardize the response so that it parses the JSON and generates a class?
Example:
Original Response
--------
{'child' : { 'name' : 'John Doe', 'age' : 23 } }
Desired Response
--------
res.name # John Doe
res.age # 23
res.class # APIClient::Child
It can be achieved via custom parser passed to request call (however I would strongly advise not to do it and leave it as it is now)
an example of parser you could pass is
class InstanceParser < HTTParty::Parser
def parse
#assuming you always have a json in format { 'one_key_mapping_to_model' => data }
body_as_json = JSON.parse(body) #string parsed to json
model_class_name = body_as_json.keys.first # == 'one_key_mapping'
model_class_data = body_as_json[model_class_name] # == data
class_instance = model_class_name.camelize.constantize.new(model_class_data) # will create new instance of OneKeyMapping
class_instance
end
end
and then in your api call pass self.class.get("/parents/#{parent_id}/children/#{child_id}", query: params, parser: InstanceParser)
Pass the hash to an initializer.
class APIClient::Child
attr_accessor :foo, :bar
def initialize(hash = {})
hash.each do |k,v|
public_send("#{k}=", v)
end
end
end
Then in your API client you would map between responses and objects:
def child(parent_id, child_id, params = {})
opts = { query: params }
opts.merge!(:headers=>{"Authorization"=>"Token token=#{#api_token}"}) if #api_token
begin
res = self.class.get("/parents/#{parent_id}/children/#{child_id}", opts)
case response.code
when 200
APIClient::Child.new(res[:child])
when 404
# not found - raise some sort of error that the API client consumer
# can catch maybe?
else
# probably just log it since there is little we can do here.
end
rescue HTTParty::Error
# probaly just log it. Could be a connection error or something else.
end
end
This is probably a lot less magical than what you have hoped for but what is the role of a API Client if not to map between HTTP requests and objects suitable for consumption. Most of the boooring boilerplate code here when it comes to passing tokens and handling errors can be farmed out parent classes or modules.

Getting 2 returns instead of 1 in tests

I'm writing test using double/stub, and i want to return me a fake value, but instead i'm getting and fake value and real value. Here is my controller and spec files, model has nothing in it. Any ideas what am i doing wrong? My guess is i'm doing it completely wrong. P.s. i'm kinda new to spec and mock/stub things
controller:
def destroy
uri = URI('example.com')
request_params = {'requestId' => #session.id,
'merchantId' => MERCHANTID,
'locale' => LOCALE,
'partnerId' => PARTNERID,
'sessionId' => params[:id]
}
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
request = Net::HTTP::Post.new uri.request_uri
request.set_form_data(request_params)
#request.content_type = 'application/x-www-form-urlencoded'
response = http.request request # Net::HTTPResponse object
json = ActiveSupport::JSON.decode(response.body)
puts '<<<<<<<<<'
puts json
if json['expireSessionRes']['isError'] == false
render :json => {:status => 'ok', :message => 'logged out'}
else
render :json => {:status => 'failed', :message => 'logout failed'}
end
end
spec file:
it "should correctly return isError false if logout was sucessful" do
SID = "123"
#data3 = {:id => SID}
delete :destroy, default_rest_params.merge(#data3)
response = double('http.request')
response.stub!(:body => '{"expireSessionRes"=>{"requestId"=>"72", "sessionId"=>"123", "isError"=>false, "desc"=>""}}')
puts response.body
end
receiving:
{"expireSessionRes"=>{"requestId"=>"94", "isError"=>true, "desc"=>"Session exipred", "errorCode"=>"-10011"}}
{"expireSessionRes"=>{"requestId"=>"72", "sessionId"=>"123", "isError"=>false, "desc"=>""}}
first comes from controller
second from stub
So the thing with mocking and stubbing is, you have to hook your mock objects up to your real code somehow... the double('http.request') bit just creates a mock object called "http.request", it doesn't make http.request return this object.
What you need to do is stub the request method to return the mock response. But it's going to be pretty difficult to inject a mock, because you don't have access to the http object in the block from your tests.
Difficulty testing code is often a good sign that you need to refactor your code.
The simplest thing would be to replace the whole Net::HTTP.start thing with a single line
response = Net::HTTP.post_form(uri, request_params)
and then you can test it by doing
remote_response = double('remote_response', body: '...')
Net::HTTP.stub(:post_form).and_return(remote_response)
delete :destroy, ...
response.should whatever
(I assume you're in a controller spec and you're going to need the response object that RSpec provides, so your mock can't be stored in a variable called response).
But that is just glossing over the more important point that you should be extracting your remote service interaction out into a dedicated class and testing that separately, it doesn't belong in the controller.
Hope that helps!

Is there a way to set the default namespace with Savon::Model?

Savon is stubborn in generating SOAP envelopes from WSDL's. It does it improperly and I see no way to fix it. It also takes the liberty of inserting the wsdl: namespace on everything for whatever reason.
The request I am building uses the tns: namespace. I'd love to be able to use Savon::Model, but right now I have to do:
client.request :tns, :function_name do
soap.body = { params }
end
Instead of something like:
super(params)
Making the request block in every function is tedious, and I have to define the function name every time instead of Savon automatically calling the correct function like what would happen in the ideal case. Right now my functions are looking like
def foo
client.request :tns, :foo do
...
end
Having to say "foo" twice seems ridiculous. Is there a way to set the default namespace for every request in a class that extends Savon::Model?
client = Savon.client do
wsdl "blah blah"
element_form_default :qualified
namespace_identifier :tem
env_namespace :soapenv
end
I am not sure if I understand your questions. I assume you are asking how to set the default namespace and wrap the request body in a function, so you don't need to write the request body every time. This code works for me, but I removed some irrelevant parts
class ExampleWS
EXAMPLE_WS_DEFAULT_NAMESPACE = "urn:example:request:1.0.0"
......
def getStockPrice( locale, stockId )
response = $client.request :get_stock_price do
soap.input = [
"ns1:getStockPrice",
{
"xmlns:ns1" => EXAMPLE_WS_DEFAULT_NAMESPACE #set default namespace here
}
]
soap.body = {
"locale" => locale,
"stockId" => stockId
}
end
end
......
end
......
# call the function
getStockPrice("en_US", 123 )
This works for me. It uses Savon 2, though:
class Soapservice
extend Savon::Model
client wsdl: "http://example.com?wsdl", env_namespace: :tns,
operations :get_resource, :put_resource
def :get_resource(id)
super(message: { id: id })
end
end
service = Soapservice.new
response = service.get_resource(1) #overwriting get_resource
# or
response = service.put_resource(message: { username: "luke", secret: "secret" })
(My example builds on the one from the official savon homepage)

RSpec Google Contacts Connection

I'm trying to test out a controller action on Rails 2.3.10 that connect to Google to retrieve contacts. I'm using Rspec and Mocha for testing. I want to stub out the actual call to Google since this is a unit test. I want to verify that the authsub_url method is called with the correct parameters. Stubbing the method out causes the expectation to fail.
Any advice would be appreciated.
Thanks!
My method that sets up the client to google is below:
def setup_client
#client = GData::Client::DocList.new(:authsub_scope => CONTACTS_SCOPE, :source => 'google-DocListManager-v1.1', :version => '3.0')
if params[:token].nil? && session[:google_token].nil?
#authsub_link = #client.authsub_url(import_method_gmail_url, false, true)
render :action => :index, :layout => "empty"
elsif params[:token] && session[:google_token].nil?
#client.authsub_token = params[:token]
session[:google_token] = #client.auth_handler.upgrade
end
#client.authsub_token = session[:google_token] if session[:google_token]
end
Here is my test:
describe "setup_client" do
it "has a authsub_link if there is no token parameter and the google token is not present in the session" do
GData::Client::DocList.any_instance.stubs(:authsub_url).returns("http://test.google.com/contacts")
user = Factory(:subscriber_user)
profile = Factory(:profile, :user => user)
login_as user
controller.instance_variable_get(:#client).expects(:authsub_url).with(import_method_gmail_url, false, true).once
get :index
assigns(:authsub_link).should == "http://test.google.com/contacts"
end
end
I would recommend FakeWeb. It allows you to fake web requests. Simple define the URL you're going to call and prepare the response(s). Makes your life very easy.
It looks like you are stubbing out the DocList#authsub_url method in two places :-
The first stub is on any instance of DocList and returns a URL :-
GData::Client::DocList.any_instance.stubs(:authsub_url).returns("http://test.google.com/contacts")
The second stub is on the actual instance of DocList but this returns nil because no there is no returns clause :-
controller.instance_variable_get(:#client).expects(:authsub_url).with(import_method_gmail_url, false, true).once
I think you can achieve what you want by combining them something like this :-
controller.instance_variable_get(:#client).expects(:authsub_url).with(import_method_gmail_url, false, true).returns("http://test.google.com/contacts")
Note that once is the default so is not needed unless you want to emphasise that the method should only be called once.

Disabled/Custom params_parser per action

I have a create action that handles XML requests. Rather than using the built in params hash, I use Nokogiri to validate the XML against an XML schema. If this validation passes, the raw XML is stored for later processing.
As far as I understand, the XML is parsed twice: First the Rails creates the params hash, then the Nokogiri parsing happens. I've been looking for ways to disable the params parsing to speed things up but have found nothing.
ActionController::Base.param_parsers[Mime::XML] = lambda do |body|
# something
end
I know it's possible to customize the XML params parsing in general using something like the above, but I depend on the default behaviour in other controllers.
Is it possible to bypass the params parsing on a per-action basis? What options do I have?
Thank you for your help!
I've managed to solve the problem using Rails Metal. The relevant part looks something like this:
class ReportMetal
def self.call(env)
if env["PATH_INFO"] =~ /^\/reports/
request = Rack::Request.new(env)
if request.post?
report = Report.new(:raw_xml => request.body.string)
if report.save # this triggers the nokogiri validation on raw_xml
return [201, { 'Content-Type' => 'application/xml' }, report.to_xml]
else
return [422, { 'Content-Type' => 'application/xml' }, report.errors.to_xml]
end
end
end
[404, { "Content-Type" => "text/html" }, "Not Found."]
ensure
ActiveRecord::Base.clear_active_connections!
end
end
Thanks!
PS: Naive benchmarking with Apache Bench in development shows 22.62 Requests per second for standard Rails vs. 57.60 Requests per second for the Metal version.

Resources