Why "assert_response" ignores the custom message in Rails? - ruby-on-rails

In my functional test I verify the response status like that:
assert_response :error, "My error message"
However, Rails ignores my custom message and reports:
Expected response to be a <:error>, but was <201>
Any ideas why?
I use Rails 3.2.6.

I was wondering the same thing. Turns out it's been fixed on Github: https://github.com/rails/rails/commit/d28a15ede59f6434f1b7a8d01be060fa73b4746c
For me, updating rails/the actionpack gem to the latest version hadn't included that fix, but I was able to update response.rb manually. It was located at:
/Users/alex/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.8/lib/action_dispatch/testing/assertions/response.rb

In your test you have mentioned the response type as :error which matches to status code 500-599. But as your test is returning status code 201 it is not able to match that and showing the message according to the definition of assert_response which is
def assert_response(type, message = nil)
clean_backtrace do
if [ :success, :missing, :redirect, :error ].include?(type) && #response.send("#{type}?")
assert_block("") { true } # to count the assertion
elsif type.is_a?(Fixnum) && #response.response_code == type
assert_block("") { true } # to count the assertion
else
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, #response.response_code)) { false }
end
end
end
it goes into else due to the type and status code mismatch and gives that message.
Here are the type of response and their matching codes:
:success: Status code was 200
:redirect: Status code was in the 300-399 range
:missing: Status code was 404
:error: Status code was in the 500-599 range

Related

How to catch a Rack RangeError in Rails 6

I have a Rails 6 app to which users can upload CSV files. Rails/Rack imposes a limit in the number of params that can be included in a request, and I've set this to a size larger than likely submissions to my app. However, I would like to return a friendly response if a too-large file is uploaded.
It looks like I need to add some custom middleware, to catch and rescue the error, but I can't get the code to work - the basic error is still raised without my rescue block being called.
The error from the server is:
Rack app error handling request { POST /[PATH_TO]/datasets }
#<RangeError: exceeded available parameter key space>
The code in my app/middleware/catch_errors.rb file is basically taken from a previous SO answer, where someone was catching ActionDispatch::ParamsParser::ParseError in JSON, but with my own code in the rescue block (which I realise may not work properly in this context, but that's not the issue right now):
class CatchErrors
def initialize(_app)
#app = _app
end
def call(_env)
begin
#app.call(_env)
rescue RangeError => _error
_error_output = "There were too many fields in the data you submitted: #{_error}"
if env['HTTP_ACCEPT'] =~ /application\/html/
Rails.logger.error("Caught RangeError: #{_error}")
flash[:error_title] = 'Too many fields in your data'
flash[:error_detail1] = _error_output
render 'static_pages/error', status: :bad_request
elsif env['HTTP_ACCEPT'] =~ /application\/json/
return [
:bad_request, { "Content-Type" => "application/json" },
[ { status: :bad_request, error: _error_output }.to_json ]
]
else
raise _error
end
end
end
end
I'm loading it in config.application.rb like this:
require_relative '../app/middleware/catch_errors'
...
config.middleware.use CatchErrors
I'm resetting the size limit for testing in app/initializers/rack.rb like this:
if Rack::Utils.respond_to?("key_space_limit=")
Rack::Utils.key_space_limit = 1
end
Any help gratefully received!
First, execute command to see all middlewares:
bin/rails middleware
config.middleware.use place your middleware at the bottom of the middleware stack. Because of that it can not catch error. Try to place it at the top:
config.middleware.insert_before 0, CatchErrors
Another point to mention, may be you will need to config.middleware.move_after or even config.middleware.delete some middleware. For instance, while tinkering I needed to place:
config.middleware.move_after CatchErrors, Rack::MiniProfiler

app signal gem send error to app signal without a crash request

I am using appsignal gem to track if there is an error processing in my app.
This case i do call external API using faraday.
def truck_information(req_params)
response = #conn.post('truck/info') do |req|
req.headers['Content-Type'] = 'application/json'
req.body = req_params
end
return JSON.parse(response.body) if response_successful?(response)
response_error(response)
end
def response_successful?(response)
response.status == 200
end
def response_error(response)
err = NctError, "Code: #{response.status}, response: #{response.body}"
Appsignal.set_error(err)
raise NctError, I18n.t('error_messages.ppob.server_error')
end
my truck_information is used to call external api. and if success i will parse it to json. but if error i will call response_error method parser to create custom class error (NctError) and i want to send to appsignal to show the error without breaking the application process.
But when i was tested it, it doesn't send to appsignal. How to do send error to appsignal, even if it doesn't crash a request? because i need to track the error.
Thank you
You could try Appsignal.send_error
Appsignal.send_error(err)
If above doesn't work either, then set_error and send_error may only work with Exception:
def response_error(response)
raise NctError, I18n.t('error_messages.ppob.server_error')
rescue => e
Appsignal.send_error(e) do |transaction|
transaction.params = { code: response.status, response: response.body }
end
end

Server replies with default debug response instead of raised exception response

Within an engine w/ an API, when querying the API an exception is thrown, but the server response is not using the response specified, and instead rendering a default debug response (in production).
I can confirm that the exception is caught by the controller:
Started GET "/api_v2/admin/submissions?system_id=123&spt_id=123" for
127.0.0.1 at 2019-03-15 10:04:37 -0400
Processing by ApiV2::Admin::SubmissionsController#show as JSON
Parameters: {"system_id"=>"123", "spt_id"=>"123"}
[3, 12] in /....../emp_api_v2/app/controllers/emp_api_v2/application_controller.rb
3:
4: before_action :doorkeeper_authorize!
5:
6: rescue_from ::ActiveRecord::RecordNotFound do |e|
7: byebug
=> 8: response(:standard_error, 500, "Hello World")
9: end
10:
11: def doorkeeper_unauthorized_render_options(error: nil)
12: { json: { message: "Not authorized" }, status: 403 }
(byebug) cont
Completed 500 Internal Server Error in 5220ms (ActiveRecord: 0.0ms)
ActiveRecord::RecordNotFound - Couldn't find Emp::System with 'id'=123:
The server is expected to respond with 500 Server Error not the debug error stacktrace.
Why are there two responses, even though the controller catches the exception and runs a response method.
NOTE: This happens in dev and prod ! Server responds with 500 first (my catch response) but then with a stacktrace and 404 (As this is the source of the error and correct exception). It breaks my tests as the past response was 500. I was not able to revert my server to the old behavior by: reinstalling ruby, reinstalling rails + all gems + rolling back all changes throughout the repo. This behavior seems to be externally set by a ENV variable or something else.
I'd be grateful for any insight.
Edit: The (api) app controller looks like this:
module ApiV2
class ApplicationController < ActionController::API
before_action :doorkeeper_authorize!
rescue_from ::ActiveRecord::RecordNotFound do |e|
response(:standard_error, 500, "Hello World")
end
def doorkeeper_unauthorized_render_options(error: nil)
{ json: { message: "Not authorized" }, status: 403 }
end
end
end
Edit 2: I was able to get the correct response by wrapping the call in a rescue block. That code will result in a lot of begin/rescue blocks though as each of them has a specific error message.
begin
system = Emp::System.find(sys_id)
rescue
render json: { status: 500, content: "Specific Error msg" } and return
end
Originally i had a method as follows:
def handle_exception(message, &block)
begin
block.call
rescue Exception => e
render json: { message: message }, status: 500 and return
end
end
This will result in double render error as it's not returning from the top-level method but from the block.

Rescue not capturing failure

I'm missing something here, but for some reason my begin then rescue Ruby code isn't capturing this error:
#<ActiveResource::ResourceInvalid: Failed. Response code = 422. Response message = Unprocessable Entity.>
This is my code:
begin
ShopifyAPI::CarrierService.create(with some arguments)
rescue StandardError => e
pp e
end
It doesn't ever capture it. In my rescue section I've tried the above but also:
rescue Exception => e
rescue ActiveResource::Errors => e
All with no luck. Where did I go astray?
Thanks
EDIT:
This is the full error, it not really anymore info, but here goes:
#<ShopifyAPI::CarrierService:0x0000000357a0a0
#attributes=
{"name"=>"XXXX",
"callback_url"=>
"https://XX-XX-XX-XX.c9users.io/receive_rate_request",
"format"=>"json",
"service_discovery"=>"true",
"carrier_service_type"=>"api"},
#errors=
#<ActiveResource::Errors:0x00000003578930
#base=#<ShopifyAPI::CarrierService:0x0000000357a0a0 ...>,
#messages={:base=>["you already have XXX set up for this shop"]}>,
#persisted=false,
#prefix_options={},
#remote_errors=
#<ActiveResource::ResourceInvalid: Failed. Response code = 422. Response message = Unprocessable Entity.>,
#validation_context=nil>
That's it!
Because it is not raising an exception, If you want to raise the exception when the response is false, you may have to use create with bang
begin
ShopifyAPI::CarrierService.create!(with some arguments)
rescue StandardError => e
pp e
end
According to the ActiveResource code (lib/active_resource/base.rb):
# <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
# following HTTP response codes will also result in these exceptions:
#
# * 200..399 - Valid response. No exceptions, other than these redirects:
# * 301, 302, 303, 307 - ActiveResource::Redirection
# * 400 - ActiveResource::BadRequest
# * 401 - ActiveResource::UnauthorizedAccess
# * 403 - ActiveResource::ForbiddenAccess
# * 404 - ActiveResource::ResourceNotFound
...
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
So it indicates that 422's are rescued by save on validation, which happens when .create is fired, and are bubbled up as validation errors instead.
Looking at lib/active_resource/validations.rb, you can see the ResourceInvalid exception is gobbled:
# Validate a resource and save (POST) it to the remote web service.
# If any local validations fail - the save (POST) will not be attempted.
def save_with_validation(options={})
perform_validation = options[:validate] != false
# clear the remote validations so they don't interfere with the local
# ones. Otherwise we get an endless loop and can never change the
# fields so as to make the resource valid.
#remote_errors = nil
if perform_validation && valid? || !perform_validation
save_without_validation
true
else
false
end
rescue ResourceInvalid => error
# cache the remote errors because every call to <tt>valid?</tt> clears
# all errors. We must keep a copy to add these back after local
# validations.
#remote_errors = error
load_remote_errors(#remote_errors, true)
false
end
So I wonder if it's logging that an exception happened, but is not actually raising an exception because it turns it in to a return false. It does say "local validations" in the comment, but sets remote_errors, so it's not perfectly clear where this code path is executed.

How to test the response code with Capybara + Selenium

I have the following spec:
it "deletes post", :js => true do
...
...
page.status_code.should = '404'
end
The page.status_code line is giving me this error:
Capybara::NotSupportedByDriverError
How do I check the page's status code?
As an aside. This line
page.status_code.should = '404'
Should be
page.status_code.should == 404
This worked for me with capybara-webkit.
status_code is not currently supported by the Selenium driver. You will need to write a different test to check the response status code.
Either switch to another driver (like rack-test) for that test, or test that the displayed page is the 404 page (should have content 'Not Found' in h1).
As #eugen said, Selenium doesn't support status codes.
Selenium web driver doest not implement status_code and there is no direct way to test response_code with selenium (developer's choice).
To test it I added in my layout/application.html.erb:
<html code="<%= response.try(:code) if defined?(response) %>">[...]</html>
And then in my test:
def no_error?
response_code = page.first('html')[:code]
assert (response_code == '200'), "Response code should be 200 : got #{response_code}"
end
Try it out
expect(page).to have_http_status(200)
Use js to make a request and get status as below:
js_script = <<JSS
xhr = new XMLHttpRequest();
xhr.open('GET', '#{src}', true);
xhr.send();
JSS
actual.execute_script(js_script)
status = actual.evaluate_script('xhr.status') # get js variable value
Check https://gist.github.com/yovasx2/1c767114f2e003474a546c89ab4f90db for more details

Resources