How to test Devise Async with Sidekiq? - ruby-on-rails-4

Thanks in advance!
Sidekiq is working just fine, but I cannot manage to test it with Devise Async, or should I say that I cannot test the latter?
According to Sidekiq's documentation, when test mode is set to fake!, any job given to a worker is pushed to an array named jobs of that same worker. So it is trivial to test the increase of this array.
But, with Devise Async, it is not so trivial, although its backend includes Sidekiq::Worker. Here's a small list of things that I tried to test:
Devise::Async::Backend::Sidekiq.jobs
Devise::Mailer.deliveries
ActionMailer::Base.deliveries
Devise::Async::Backend::Worker.jobs
None of these testing subjects points an increase in size. Since Devise sends its emails as models callbacks, I tried testing both in a model and in a controller spec. Using Factory Girl and Database Cleaner, I also tried both modes: transaction and truncation. Needless to say that I also tried both modes of Sidekiq: fake! and inline!.
What am I missing?

As mentioned in the documentation, you can check the queue size as
Sidekiq::Extensions::DelayedMailer.jobs.size

Was working on this issue, chanced upon a beautiful implementation done by gitlab which I thought might be helpful in testing devise-async or email that are push via the sidekiq queue.
spec_helper.rb
email_helpers.rb
By adding these lines in spec_helper.rb
# An inline mode that runs the job immediately instead of enqueuing it
require 'sidekiq/testing/inline'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.include EmailHelpers
# other configurations line
end
And adding /spec/support/email_helpers.rb
module EmailHelpers
def sent_to_user?(user)
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
end
def should_email(user)
expect(sent_to_user?(user)).to be_truthy
end
def should_not_email(user)
expect(sent_to_user?(user)).to be_falsey
end
end
To run the test for example testing your forgot password, I am assuming you know rspec, factorygirl, capybara
/spec/features/password_reset_spec.rb
require 'rails_helper'
feature 'Password reset', js: true do
describe 'sending' do
it 'reset instructions' do
#FactoryGirl create
user = create(:user)
forgot_password(user)
expect(current_path).to eq(root_path)
expect(page).to have_content('You will receive an email in a few minutes')
should_email(user)
end
end
def forgot_password(user)
visit '/user/login'
click_on 'Forgot password?'
fill_in 'user[email]', with: user.email
click_on 'Reset my password'
user.reload
end
end
You would notice that in this test implementation
will cause sidekiq to run the job instead of enqueuing it,
The user model email attribute must be called email or you can just replace the code above.
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1 check to see ActionMailer::Base.deliveries is delivering to user.email

Related

Session variable lost after redirect for feature spec

The session object is cleared after redirect during testing.
First, my test stack. I am running a Rails 4.2.4 app with capybara 2.6.2, capybara-webkit 1.8.0, and rspec 3.3.0. My tests were running without issue until, for no apparent reason whatsoever, they weren't.
Below is my code (condensed to stay on point):
assessment_controller.rb
def create
#assessment_basic = Assessment::Basic.new(params)
if #assessment_basic.valid?
session[:most_recent_zip_code] = #assessment_basic.zip_code
if household.search.present?
update_household_search(#assessment_basic.zip_code)
end
redirect_to dashboard_path
else
render :new
end
end
private
def household
if #household
#household
elsif session[:household_id]
#household = find_household(session[:household_id])
elsif session[:most_recent_zip_code]
#household = Household.create(household_params)
session[:household_id] = #household.id
end
#household
end
As you can see, this is pretty straight-forward. I am receiving a zip code in the params. I store that zip code for later use and use it to create a household object unless one already exists, in which case I return that instance. If a household object is instantiated, I then store its id in the session, return control back to the action and redirect to the dashboard_path having two variables in session. All of this works well, and has worked well for some time now.
However, when I try to access the variables in the dashboard#index action, none of the session variables I stored are present. The feature works, which suggests that my problem is with the bits running my specs. By the way, the spec passes locally. It is when the code is moved to our CI environment that we get the error. We tried three different CI environments (Circle, Semaphore, and Travis) and they all report the same error:
#<NoMethodError: undefined method 'search' for nil:NilClass>
Which basically means, the household could not be recreated and is therefore nil. A closer look shows the reason the household could not be created from session is that the session was cleared.
Can someone help me identify the component(s) involved in ensuring session values persists during tests? Let me know if you need anything else in order to be able to help me.
Hector
it 'a client visits the referrals page with all providers minus the single stop and tax locations from the dashboard page and ' do
user_visits_the_homepage
enter_zip_code('11217', true)
dashboard_page.expect_to_be_on
dashboard_page.click_browse_local_resources
referrals_page.expect_to_be_on
end
def user_visits_the_homepage
visit '/'
expect(page).to have_content t('welcome.where')
end
def enter_zip_code(zip_code = '11217', remain_dashboard = false)
within '.welcome-form:nth-of-type(1)' do
fill_in t('welcome.where'), with: zip_code
click_on t('welcome.get_started')
end
expect(page).to have_content t('header.dashboard')
click_on t('header.your_profile') unless remain_dashboard
expect(page).to have_field t('activerecord.attributes.client_household_member.zip_code'), with: zip_code unless remain_dashboard
end

Redirecting a request in a routing constraint

I have Sidekiq mounted in my routes file to the /sidekiq endpoint.
I use a constraints option to have it call an external class for validation as a way of preventing non-privelaged users from accessing that endpoint.
# config/routes.rb
mount Sidekiq::Web => "/sidekiq", constraints: Sidekiq::AdminConstraint.new
# lib/sidekiq/admin_constraint.rb
module Sidekiq
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
user = User.find_by_id(request.session[:user_id])
user && Ability.new(user).can?(:manage, :sidekiq)
end
end
end
This setup works great. However, it only lets me return true / false on whether the request should go through or not. It does not let me -
Set a flash message (e.g. "You are not permitted to access that page") and
Redirect to some arbitrary page
In that sense, I'm looking for it to behave more like a controller's before_filter.
Is there a way I can modify the request object that's passed in to implement that redirect?
Thanks!
I don't have idea directly set the flash messages, But we can use in different way.
Use the following solution
In your routes.rb, add the following line in the end of the file
match "*path", :to => "application#error_404"
This basically means, any path that is not defined in your route will end up going to error_404 in application_controller. Its very important to put this at the end of your file
And in your ApplicationController, add
def error_404
redirect_to root_path
end
Thanks

Waiting for AJAX with Capybara + Poltergeist

I've seen various implementations of the wait_for_ajax method that makes Capybara wait until all AJAX requests are completed before moving forward.
I've just switched to using Poltergeist as my JavaScript driver and I'm having trouble getting it to wait for the AJAX to complete on a test (see below)
Below is the implementation that was previously working with Selenium - the only thing I modified was the evaluation script -
Previous: page.evaluate_script("jQuery.active")
Updated: page.evaluate_script("$.active").to_i
If I insert a sleep statement it passes because it has enough time to finish the AJAX call, so I definitely know that's the issue.
Is there any error in this approach?
Thanks!
it "user can log in", js: true do
visit root_path
click_tab("log-in")
# Fill in form
fill_in "user[email]", with: "al-horford#hawks.com"
fill_in "user[password]", with: "sl4mdunkz"
# Click submit and wait for AJAX
within("#log-in") { click_button("Log In")) }
wait_for_ajax
# Expectations
expect(current_path).to eq(home_index_path)
end
def wait_for_ajax
Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
request_count = page.evaluate_script("$.active").to_i
request_count && request_count.zero?
rescue Timeout::Error
end
In my experience, there are some situations wait_for_ajax will not wait for.
In our app, we have a 'to cart'-button that triggers Ajax. It then waits for a callback event before it loads the next page. This is not picked up by wait_for_ajax.
What fixed it, and what is generally a better approach than waiting a given amount of time with sleep(), is to verify that you're on the next page using one of Capybara's waiting finders.
Simply replace expect(current_path).to eq(home_index_path)
with something like expect(page).to have_content("content_home_page_should_have")
That should work.

Ruby on Rails Pundit's current_user is nil in integration test

I'm using the gems pundit and devise. I have a delete link that only shows up if you are an admin. I have an integration test that I would like to verify that the delete link only shows up for admins.
test 'comment delete link shows when it should' do
log_in_as #admin
get movie_path(#movie)
assert_select 'a[href=?]', movie_comment_path(comments(:one), #movie.id)
end
My test_helper.rb looks like this:
...
class ActiveSupport::TestCase
...
def log_in_as(user, options = {})
password = options[:password] || 'password'
if integration_test?
post user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
else
Devise::TestHelpers.sign_in user
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
The response.body looks all right, but indeed there is no delete link. There is one when I run the development server and visit the page myself. I've narrowed this down to the current_user that pundit uses in the policies is being passed in with a value of nil. This is my comment_policy.rb:
class CommentPolicy
attr_reader :current_user, :comment
def initialize(current_user, model)
#current_user = current_user
#comment = model
end
def create?
if #current_user
#current_user.member? or #current_user.content_creator? or #current_user.moderator? or #current_user.admin?
end
end
def destroy?
if #current_user
#current_user == #comment.user or #current_user.moderator? or #current_user.admin?
end
end
end
As a closing remark, I've heard that Rails 5 has opted for integration tests instead of controller tests as we know them from Rails 4 for the default type of tests to be generated for our controllers. If this is the case, devise would be a heck of a lot more useful out of the box when using Rails 5 if the sign_in/sign_out helpers that work in controller tests were made to work in integration tests as well. But would I still have this issue of pundit not knowing what current_user is? I'm assuming this all works fine in controller tests because the current_user is scoped to controllers? Any and all light shed on this topic is much appreciated, but I would really like to figure out how to get integration tests to work with this setup because I have about a billion I want to write right now.
Not that it totally matters, but does it need to be using current_user in the policy or can it just use user in the policy. By this I mean according to the elabs/pundit README on Github I would just use #user and user everywhere instead of current_user. Read the README if I confused you.
Additionally the nil for current_user typically occurs when you don't have a valid CSRF token for your request. When you do this on the website manually by going to localhost:3000 or w/e you are first performing a get on the login path before doing the post on the login path with your credentials. In your integration test I don't seem to see where you are performing that get in order to get the CSRF for your session.
Hope this helps!!!

Understanding Rails/Twitter On-click tweet on behalf of user

As preface, I've followed through some tutorials (i.e. Michael Hartl's) though I'm still fairly novice. Forgive any cloudy terminology.
I am trying to build a simple application in Rails 4 that does the following:
User logs into application (currently working with sign-in-with-twitter link and routing)
get "/auth/:provider/callback" => "sessions#create"
get "/signout" => "sessions#destroy", :as => :signout
Once <% if current_user %> is true, I have the view rendering a partial where there will be a list of simple buttons. When the user clicks a button I want the application to tweet on behalf of the current_user a preset string. Ideally, I'd do this all in ruby/rails.
These button functions are where I'm getting hung up. I've read a fistful of documents but there seem to be a lot of conflicting and old answers. Here's a quick list of the ones I think are closest, though not explicit about sending a tweet from a simple button/link in a view:
http://www.sitepoint.com/ruby-social-gems-twitter/
http://richonrails.com/articles/sending-a-tweet-to-twitter
Some call for controllers, a more robust oauth setup (which I have bundle installed and connected to the dev.twitter application, though not fleshed out beyond keys), and whatever else. It's got me turned around and I'm not yet good enough to synthesize all the information. Any help and direction would be great. Below are some other files in the app that might be helpful.
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Hi!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Bye!"
end
end
And omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, '_priv', '_priv'
end
Eep! I'm the author of the second link (RichOnRails). Did you take a look at the example app included with the tutorial? It does almost exactly what you want. If the tweets are hard coded you could approach it in a couple of different ways. If you take a look at the tweets controller, you'll see it takes a parameter called 'message'. Any message passed to that create method will tweet as the current user.
def create
current_user.tweet(twitter_params[:message])
end
The easiest (but not necessarily best) way to adapt this to fit your needs is to have a form for each tweet, and do a hidden field with the message you wish to tweet. The button becomes a submit for that particular form (you can add remote: true if you want to keep the page from refreshing, then use a bit of javascript to update the UI elements). Hope this helps.