Raising route not found error - ruby-on-rails-3

I'm writing a book on Rails 3 at the moment and past-me has written in Chapter 3 or so that when a specific feature is run that a routing error is generated. Now, it's unlike me to go writing things that aren't true, so I'm pretty sure this happened once in the past.
I haven't yet been able to duplicate the scenario myself, but I'm pretty confident it's one of the forgotten settings in the environment file.
To duplicate this issue:
Generate a new rails project
important: Remove the public/index.html file
Add cucumber-rails and capybara to the "test" group in your Gemfile
run bundle install
run rails g cucumber:skeleton
Generate a new feature, call it features/creating_projects.feature
Inside this feature put:
This:
Feature: Creating projects
In order to value
As a role
I want feature
Scenario: title
Given I am on the homepage
When you run this feature using bundle exec cucumber features/creating_projects.feature it should fail with a "No route matches /" error, because you didn't define the root route. However, what I and others are seeing is that it doesn't.
Now I've set a setting in test.rb that will get this exception page to show, but I would rather Rails did a hard-raise of the exception so that it showed up in Cucumber as a failing step, like I'm pretty sure it used to, rather than a passing step.
Does anybody know what could have changed since May-ish of last year for Rails to not do this? I'm pretty confident it's some setting in config/environments/test.rb, but for the life of me I cannot figure it out.

After I investigate the Rails source code, it seems like the ActionDispatch::ShowExceptions middleware that responsible of raising exception ActionController::RoutingError is missing in the test environment. Confirmed by running rake middleware and rake middleware RAILS_ENV=test.
You can see that in https://github.com/josh/rack-mount/blob/master/lib/rack/mount/route_set.rb#L152 it's returning X-Cascade => 'pass' header, and it's ActionDispatch::ShowExceptions's responsibility to pick it up (in https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L52)
So the reason you're seeing that your test case is passing because rack-mount is returning "Not Found" text, with status 404.
I'll git blame people and get it fix for you. It's this conditional here: https://github.com/rails/rails/blob/master/railties/lib/rails/application.rb#L159. If the setting is true, the error got translated right but we got error page output. If it's false, then this middleware doesn't get loaded at all. Hold on ...
Update: To clearify the previous block, you're hitting the dead end here. If you're setting action_dispatch.show_exceptions to false, you'll not get that middleware loaded, resulted in the 404 error from rack-mount got rendered. Whereas if you're setting action_dispatch.show_exceptions to true, that middleware will got loaded but it will rescue the error and render a nice "exception" page for you.

Related

RSpec - Expecting a 500 error after completing a JS prompt modal

I'm working on an interface where the user creates an entity and gives it a name via a simple JS alert prompt, and by design our back-end returns a 422 error if you submit a name that already exists in the database. My task is to then display a message on the front-end after this error happens.
I'm using accept_prompt(with: "NAME") to properly test the modal functionality, but I'm having a hard time telling Rspec to expect an error from the attempted POST that results from completing the prompt.
I've tried the code below and variations of it but it always seems like Rspec fails with "no error was raised" and then immediately fails with ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR(the exact error I'm trying to expect).
expect do
accept_prompt(with: "NAME") do
find(".selector .selector-item:first-child").click
end
end.to raise_exception
I've even tried the below code to no avail:
expect accept_prompt(with: "NAME") do
find(".selector .selector-item:first-child").click
end.to raise_exception
Is there some other known way to expect an error from a modal interaction in Rspec, or is it simply not possible?
We just resolved the issue, turns out the 500 error in the spec was caused by a problem with my spec configuration, unrelated to the 422 error that we expected to get and handle in the interface. Once we addressed that issue the 500 error went away and did not break the test, allowing us to continue running assertions against the frontend error validation.
So while there doesn't seem to be a way to actually catch and expect a backend error using Capybara, we didn't need to because that error wasn't supposed to be happening.

form_for doesn't save to db

I have a very basic Rails application that has some standard scaffolded CRUD resources that worked just fine. All of a sudden, and I can't figure out why, the "form_for"s are no longer saving or updating the DB. However, everything works just fine in console. I am getting no errors -- just a redirect back to the 'new' page because of an unsuccessful save attempt. I've restarted servers, restarted everything, bundled again, migrated, etc. Nothing seems to work. How can I debug this issue?
Try temporarily changing #model.save to #model.save! in one of the relevant controllers.
This tells Rails to throw an exception if saving fails, instead of it's usual approach (redirecting to new, usually). Most likely you have a validation error, but whether or not that's the case, if you do this, you'll get an actual description of the error that occurred when the controller tried to save your updated model. That should give you somewhere to start debugging.

Better logging from Capybara/RSpec?

I'm having a really tough time investigating the cause of a test failure. I'm a very experienced programmer and am well versed in general debugging techniques, but I'm new to Capybara and RSpec so I'm hoping there's some kind of facility I'm ignorant of that can help me.
In short, I have a test something like this:
expect { click('.fake_button'); sleep 1 }.to change { clicks.count }.by(1)
When the fake button is clicked, it triggers an AJAX call to the Rails app which, among other things, adds a click record to the database. I can think of dozens of things that could be causing this test to fail and have had only limited success getting information out of logs. The tests do not fail in development and it only fails sporadically in test. One of the differences of the test environment is that the tests are run on a server in our office against a server in the cloud, so there are network delays along with other possible issues.
This is very hard to diagnose because there's so little information coming out of the failed test and of course all the database information is thrown away by the time I read about the failure. I know clicks.count didn't change in the test and I can infer that click('.fake_button') succeeded, but due to server time sync issues I can't even be sure that the click happened on the right button or that the AJAX call fired.
What I'd like are some tools to help me follow this test case in the web server logs (maybe using automatic URL parameters, for example), detailed logging about what Capybara did, and a record of the web page as it was when the failure occurred, including cookie values. Can I get any of that? Anything like that?
Capybara simulates human actions. The test code does exactly what needed. It's something a real user should expect. I don't think you should complain the code.
I think it's okay to increase the wait time, say 1 to 2, due to your network latency, but it should not exceed a reasonable value otherwise the app does not work as real user expected.
To debug Capybara codes, there are three methods as I summarized:
Add "save_and_open_page" to the place you want to see result. Then a saved html page will appear during the test. (I forget if "launchy" gem should be added)
Temporarily set this test as JS to see how this test going.
scenario "a fake test", js: true do
# code here
end
By doing this a real browser will pop up and Capybara will show you step by step how it play the code.
Just run $ tail log/test.log to show what happened recently.
Building off what #Billy suggested, log/test.log was not giving me any useful information and I was already using js: true so I tried this:
begin
expect { click('.fake_button'); sleep 1 }.to change { clicks.count }.by(1)
rescue Exception => e
begin
timestamp = Time::now.strftime('%Y%m%d%H%M%S%L')
begin
screenshot_name = "tmp/capybara/capybara-screenshot-#{timestamp}.png"
$stderr.puts "Trying to save screenshot #{screenshot_name} due to test failure"
page.save_screenshot(screenshot_name)
rescue Exception => inner
$stderr.puts "Ignoring exception #{inner} while trying to save screenshot of test page"
end
begin
# Page saved by Capybara under tmp/capybara/ by default
save_page "capybara-html-#{timestamp}.html"
rescue Exception => inner
$stderr.puts "Ignoring exception #{inner} while trying to save HTML of failed test page"
end
ensure
raise e
end
end
Later I changed the test itself to take advantage of Capybara's AJAX synchronization features by doing something like this:
starting_count = clicks.count
click('.fake_button')
page.should have_css('.submitted') # Capybara is smart enough to wait for this to happen
clicks.count.should == starting_count + 1
Note that the CSS I'm looking for is something added to the page in JavaScript by the AJAX callback, so it showing up is a signal that the AJAX call completed.
The rescue blocks are important because the screenshot has a high failure rate from not having enough memory to render the full page and convert it to an image.
EDIT
Though I haven't tried it, a promising solution is Capybara::Screenshot which automatically saves the screenshot and HTML on any failure. Just reading the code it looks like it will have problems when the screenshot fails and I can't tell what state the page will be in by the time the screenshot is triggered, but it certainly looks like it's worth a try.
A nice way to debug tests is to use irb to watch what's actually happening in the browser. RSpec fails usually give decent information for simple cases, but for more complicated things I either split the case up until it is simple, or chuck it in irb for a live session to make sure its doing what it should do.
Make sure to use :selenium as your driver, and you should see firefox come up and be able to be driven by your irb session.

Ruby on Rails exception handling basics

I have a Rails 3 app that is using the exception_notification gem to send emails about exceptions.
I would also like to show users specific error messages when exceptions occur, but by catching a generic Exception because I'm not sure of all the exceptions that can occur. I'm thinking the way to do this is to rescue from Exception, and then raise a custom exception. That way I'm still getting an email about the exception, and the user can see the custom exception's error page.
Does this sound like a Rails 3 way to do things?
Thanks a lot.
I think not.
As Ryan Davis says
Don’t rescue Exception. EVER. Or I will stab you.
More info about that statement here.
Rails 3.2 does exception handling in two middlewares:
ActionDispatch::ShowExceptions
ActionDispatch::DebugExceptions
You can check that one by running
$ rake middleware
ActionDispatch::ShowExceptions [source]
Used in production to render the exception pages.
ActionDispatch::DebugExceptions [source]
Used in development environment to render detailed stack traces when exceptions occur. Stops the middleware call chain and renders the stack trace if action_dispatch.show_detailed_exceptions is true to be more precise.
So the most simple way of doing something normal with this middleware would be monkeypatching the call method of ActionDispatch::DebugExceptions, doing all you need to do and then calling the original method.
The better way of doing this is, however, including your own middleware between those two. In it, you would wrap the call inside a rescue block and do your custom processing.
I am a maintainer of Airbrake and this is exactly what we're doing right now.
You might also want to check Errbit, the self-hosted alternative.

Debugging ActiveMerchant; need full request and response. How to?

Rails 3.0.10 and activemerchant gem 1.29.3
My app works fine in sandbox, but transactions in production mode are failing with "Security header is not valid", "ErrorCode"=>"10002"
We initiated a support request with paypal, after reviewing all the configuration parameters a million times and they feel we're hitting an incorrect endpoint. They've asked for a full trace for the transaction, including headers, etc, so I'm trying to figure out how to do that. I found this article
which suggested adding this to the config block
ActiveMerchant::Billing::PaypalGateway.wiredump_device = File.new(File.join([Rails.root, "log", "paypal.log"]), "a")
But that just results in an empty log; nothing gets dumped to it.
So, how can I obtain this info from the GATEWAY object, if possible? Here's the production config, the format of which is identical to what's used in staging env.
::GATEWAY = ActiveMerchant::Billing::PaypalGateway(
:login => 'me_api1.blah...',
:password => 'string...',
:signature => 'longer string...'
)
Thanks.
Needed to add the additional line as follows:
ActiveMerchant::Billing::PaypalGateway.wiredump_device.sync = true
Within the same config block in the environment
Somewhere in the class library you're using there should be a function to output this for you (if it's a well built library, that is.)
Even without that, though, you should be able to look in that PaypalGateway function to see where/how it's setting the endpoint. It's either hard-coding the value or it'll be setting different endpoints based on some sandbox option you have configured somewhere else in the class.
It's hard to tell you more than that without getting a look a the actual class library you're using, but I can concur that it must be either incorrect credentials or an incorrect endpoint. I've never once seen that security header error when it wasn't simply invalid credentials, which means either your values are incorrect or you're hitting the wrong endpoint.
If you want to post that whole function (or maybe even the whole library as the endpoint could be getting set from some other function) I can take a look and find the problem for you.