I have existing functionality, where specs calls HTTP PUT method of a controller, which in-turn calls model's method to get some JSON data via. API.
class SomeModel
def GetData()
uri = URI.parse('http://api.mydomain.com/getmydata/123/ohmyurl')
Net::HTTP.start(uri.host, uri.port, :read_timeout => 200) do |http|
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
unless response.body == "null"
return JSON.parse(response.body)
end
#......
end
end
end
class SomeController < ApplicationController
def update
#...
#model.GetData()
#...
end
end
#......specs............
put :update
I need to mock the API in SomeModel. So far I have tried:
#......specs............
before do
stub_request(:get, /api.mydomain.com/)
.with(headers: {'Accept'=>'application/json' })
.to_return(status: 200, body: {id: 1, secondParam: '324'}.to_json, headers: {})
end
#.......
put :update
result = JSON.load response.body
expect(result['secondParam']).to eq("324")
Which is not able to mock the API call and actual API is called.
kindly ignore syntax errors
Related
I'm using Rubys rest-client gem to make a call to Google API and want to shorten the url part.
Current code:
class GoogleTimezoneGetter
def initialize(lat:, lon:)
#lat = lat
#lon = lon
#time_stamp = Time.new.to_i
end
def response
response = RestClient.get "https://maps.googleapis.com/maps/api/timezone/json?location=#{#lat},#{#lon}×tamp=#{#time_stamp}&key=#{GOOGLE_TIME_ZONE_KEY}"
JSON.parse(response)
end
def time_zone
response["timeZoneId"]
end
end
I would like to be able to do something like:
def response
response = RestClient.get (uri, params)
JSON.parse(response)
end
But I'm struggling to find out how to do so.
To make the class a bit tidier, I'd like to break the url down into 'uri' and 'params'. I think the rest-client gem allows you to do this but I can't find specific examples.
I want to put the
{#lat},#{#lon}×tamp=#{#time_stamp}&key=#{GOOGLE_TIME_ZONE_KEY}"
in to a 'params' method and pass that to the RestClient.get method.
Have you check the rest-client gem readme?
They did give a specific example on this (below example quoted from the readme)
RestClient.get 'http://example.com/resource', {params: {id: 50, 'foo' => 'bar'}}
In your case, it should be something like this
def url
"https://maps.googleapis.com/maps/api/timezone/json"
end
def params
{
locations: "#{#lat},#{#lon}",
timestamp: #time_stamp,
key: GOOGLE_TIME_ZONE_KEY
}
end
def response
response = RestClient.get(url, params: params)
JSON.parse(response)
end
rest-client already accepts a hash for params. If you prefer a bunch of little methods on your class, you can divide out each step to a method and keep everything readable.
class GoogleTimezoneGetter
def initialize(lat:, lon:)
#lat = lat
#lon = lon
#time_stamp = Time.new.to_i
end
def response
response = RestClient.get gtz_url, params: { gtz_params }
JSON.parse(response)
end
def time_zone
response["timeZoneId"]
end
def gtz_url
"https://maps.googleapis.com/maps/api/timezone/json"
end
def gtz_params
return {location: "#{#lat},#{#lon}", timestamp: #time_stamp, key: GOOGLE_TIME_ZONE_KEY }
end
end
i have a controller (User controller), this is used to create update and delete a user from db. Within the UserObserver class after_save is been defined as a method in UserObserver after each save in db i have to call another application through http with user_details(in hash format)
class UserObserver < ActiveRecord::Observer
def after_save user_details
begin
http Net::HTTP.new("localhost", "8000")
request = Net::HTTP::Post.new("/verification")
request.add_field('Content-Type', 'application/json')
request.body = user_details.to_json
response = http.request(request)
#http_error = false if response.code == 200
rescue Exception => error
#http_error = true
end
end
end
i need to access #http_error variable in user_controller to know status of http request been made so that furthur action can be taken according to it.
I keep getting a 422 error when testing stripe's webhook for customer.subscription.deleted
I placed this in my config routes
post 'stripewebhooks/receive'
here is my controller
class StripewebhooksController < ApplicationController
Stripe::api_key = ENV['STRIPE_SECRET_KEY']
require 'json'
def receive
data_json = JSON.parse request.body.read
p data_json['data']['object']['customer']
if data_json[:type] == "customer.subscription.deleted"
cancel_subscription(data_event)
end
end
def cancel_subscription(data_event)
#subscription = Subscription.find_by_stripe_customer_token(data['data']['object']['customer'])
#subscription.update_attribute(:subscription_status, "inactive")
end
end
I am unclear on what is suppose to go in the parenthesis after
def cancel_subscription
I am not sure that I am suppose to put data_event or what this means.
When you get a post data from stripe, you need to return a 200 status code from your application.
try this
def receive
data_json = JSON.parse request.body.read
p data_json['data']['object']['customer']
if data_json[:type] == "customer.subscription.deleted"
# Why did you send data_event? send the parsed data_json as parameter
cancel_subscription(data_json)
end
# Return a 200 status code
render :text => '{}', :status => :ok
end
I have a bit of code that checks the response code of a list of URL's and presents them back - I am having trouble with a few of URL's that are hanging which causes the application not load at all. How can I make the request to give up after 30 seconds and check the next URL marking the skipped URL as failure.
below is my current code;
(Model/status.rb)
require "net/http"
require "uri"
class Status
def initialize(url)
#url = url
end
def get_status
response.code
end
def active?
["200","203","302"].include?(get_status) ? true : false
end
private
def lookup
URI.parse(#url)
end
def http
Net::HTTP.new(lookup.host, lookup.port)
end
def request
Net::HTTP::Get.new(lookup.request_uri)
end
def response
http.request(request)
end
end
(controllers/welcome_controller.rb)
class WelcomeController < ApplicationController
def index
#syndication = [
["http://v1.syndication.u01.example.uk/organisations?apikey=bbccdd", "U.INT 01"],
["http://v1.syndication.u02.example.uk/organisations?apikey=bbccdd", "U.INT 02"],
].collect { |url| logger.info("Boom #{url[0]}"); ["#{url[1]} (#{url[0]})", Status.new(url[0]).active?] }
end
end
Got the answer..
adding the following to my "def get_status"
def get_status
begin
response.code
rescue Exception => e
Rails.logger.info("Error #{e}")
end
end
This logged the error and the went to the next URL
Rails 4 adds an exception ActionDispatch::ParamsParser::ParseError exception but since its in the middleware stack it appears it can't be rescued in the normal controller environment. In a json API application I want respond with a standard error format.
This gist shows a strategy for inserting middleware to intercept and respond. Following this pattern I have:
application.rb:
module Traphos
class Application < Rails::Application
....
config.middleware.insert_before ActionDispatch::ParamsParser, "JSONParseError"
end
end
And the middleware is:
class JSONParseError
def initialize(app)
#app = app
end
def call(env)
begin
#app.call(env)
rescue ActionDispatch::ParamsParser::ParseError => e
[422, {}, ['Parse Error']]
end
end
end
If I run my test without the middleware I get (spec):
Failures:
1) Photo update attributes with non-parseable json
Failure/Error: patch update_url, {:description => description}, "CONTENT_TYPE" => content_type, "HTTP_ACCEPT" => accepts, "HTTP_AUTHORIZATION" => #auth
ActionDispatch::ParamsParser::ParseError:
399: unexpected token at 'description=Test+New+Description]'
Which is exactly what I would expect (ParseError that I can't rescue_from).
Now with the only change to add in the middleware above:
2) Photo update attributes with non-parseable json
Failure/Error: response.status.should eql(422)
expected: 422
got: 200
And the log shows that the standard controller action is being executed and returning a normal response (although since it didn't receive any parameters it didn't update anything).
My questions:
How can rescue from ParseError and return a custom response. Feels like I'm on the right track but not quite there.
I can't work out why, when the exception is raised and rescued, that the controller action still proceeds.
Help much appreciated, --Kip
Turns out that further up the middleware stack, ActionDispatch::ShowExceptions can be configured with an exceptions app.
module Traphos
class Application < Rails::Application
# For the exceptions app
require "#{config.root}/lib/exceptions/public_exceptions"
config.exceptions_app = Traphos::PublicExceptions.new(Rails.public_path)
end
end
Based heavily on the Rails provided one I am now using:
module Traphos
class PublicExceptions
attr_accessor :public_path
def initialize(public_path)
#public_path = public_path
end
def call(env)
exception = env["action_dispatch.exception"]
status = code_from_exception(env["PATH_INFO"][1..-1], exception)
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
body = {:status => { :code => status, :exception => exception.class.name, :message => exception.message }}
render(status, content_type, body)
end
private
def render(status, content_type, body)
format = content_type && "to_#{content_type.to_sym}"
if format && body.respond_to?(format)
render_format(status, content_type, body.public_send(format))
else
render_html(status)
end
end
def render_format(status, content_type, body)
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [body]]
end
def render_html(status)
found = false
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
if found || File.exist?(path)
render_format(status, 'text/html', File.read(path))
else
[404, { "X-Cascade" => "pass" }, []]
end
end
def code_from_exception(status, exception)
case exception
when ActionDispatch::ParamsParser::ParseError
"422"
else
status
end
end
end
end
To use it in a test environment requires setting config variables (otherwise you get the standard exception handling in development and test). So to test I have (edited to just have the key parts):
describe Photo, :type => :api do
context 'update' do
it 'attributes with non-parseable json' do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
patch update_url, {:description => description}
response.status.should eql(422)
result = JSON.parse(response.body)
result['status']['exception'].should match(/ParseError/)
Rails.application.config.consider_all_requests_local = true
Rails.application.config.action_dispatch.show_exceptions = false
end
end
end
Which performs as I need in a public API way and is adaptable for any other exceptions I may choose to customise.
This article (also from 2013) thoughtbot covers also this topic. They put their response inside this middleware service only if you requested json
if env['HTTP_ACCEPT'] =~ /application\/json/
error_output = "There was a problem in the JSON you submitted: #{error}"
return [
400, { "Content-Type" => "application/json" },
[ { status: 400, error: error_output }.to_json ]
]
else
raise error
end