Devise – Test user timeout in feature/integration specs using Warden

Devise is an excellent framework for strapping authentication features onto your Rails app. One of the very handy modules that provides session timeout features is :timeoutable.

Being a responsible test-driven developer, you start writing tests to ensure your application behaves correctly when the User tries to perform an action that is not allowed after their session timed out. But how to simulate that 30 minutes have gone by? (default config.timeout_in = 30.minutes)

A brief search of the nets offers a few pointers to overriding the Devise User.timedout? method but that doesn’t really help our feature spec when ensuring that User was redirected to the Login page upon performing a session-protected action.

Here’s one solution:

Devise is built on top of Warden, so let’s see if we can’t leverage Warden’s test helpers to simulate our timed out user:

Include Warden::Test::Helpers to put Warden into test mode:

# settings_page_spec.rb

include Warden::Test::Helpers

or

# spec/rails_helper.rb

RSpec.configure do |config|
  ...
    config.include Warden::Test::Helpers, type: :feature
end

Modify the Rack proxy via Warden.on_next_request in order to simulate that the User has been timed out by Devise::SessionsController:

# settings_page_spec.rb

it 'does not allow updating password' do
  expect(current_path).to eq(user_settings_path(user))
  clink_link 'Manage Password'
    expect(page).to have_button('Update Password')
    Warden.on_next_request do |proxy|
      proxy.set_user(nil)
      click_button 'Update Password'
      expect(current_path).to eq(login_path)
    end
end

We now have a method of simulating timed out behavior that does not involve playing games with time elapsed.

Redis cache – avoid timeout using ping

Are you getting those mysterious Airbrakes telling you a Timeout has occurred trying to talk to Redis and its kept you awake at night worrying what your poor users see while your (hopefully) slave instance takes over for your master in your redis-cluster?

Worry no more! Because we’re going to proactively ping our current Redis server connection to see if its up and hope to catch it napping before our users do. Ping is available via the redis library but how to get access to it from our Rails app?

Here’s how we’ll schedule a ping every 30 minutes. I’m using Rufus but you can use your scheduling gem of your choice:

require 'rufus-scheduler'
scheduler = Rufus::Scheduler.new

# keep-alive...
scheduler.every '30m' do
  store = ActiveSupport::Cache.lookup_store(MyApp::Application.config.cache_store)
  Rails.logger.info("Pinging Redis via cache-store ...")
  store.instance_variable_get(:@data).ping
end

As long as your session and cache stores both use the same cache server (but hopefully with different keys such as /sessions and /cache, respectively) you can use the above method of retrieving the current Redis client connection held in the @data instance variable of the ActiveSupport::Cache::RedisStore retrieved by ActiveSupport::Cache.lookup_store.

Enjoy!

P.s. want connection pooling? check out this cool contribution by @findchris on github: https://github.com/redis-store/redis-activesupport/issues/22