I wrote most of my Rspec specs, but I'm facing one important problem. I've set a routing constraint on all my routes (which by itself might be debatable). Only administrators which have allowed Ip-addresses (that are stored in a separate IpAddress model) can access certain areas in my application.
Long story short, I want to mock or stub my constraint model so that I can freely access everything within my specs.
My constraint looks like this:
class IpAddressConstraint
def initialize
#ips = IpAddress.select('number')
end
def matches?(request)
if #ips.find_by_number(request.remote_ip).present? || Rails.env.test? #<- temporary solution
true
else
if #current_backend_user.present?
backend_user_sign_out
else
raise ActionController::RoutingError.new('Not Found')
end
end
end
end
MyApp::Application.routes.draw do
constraints IpConstraint.new do
#all routes
end
end
What is the best way I can test this routing constraint in Rspec? Currently I've added a conditional so that if I'm in my test environment I could just skip these constraints altogether. It would be better if I could somehow simulate this constraint.
What about something like this:
describe "Some Feature" do
context "from allowed ip" do
before(:each) {IpAddress.create(number: '127.0.0.1')}
it "should allow access to foo" do
.....
end
....
end
context "from non allowed ip" do
it "shouldn't allow access to foo" do
.....
end
end
Then you could extract the the IP address create to either A helper module or a function especially if you need to do more complex setup. IF you always wanted it to be there you could add it in your spec_helper file config block to run before each/every spec, but then you would have a harder time testing that it successfully blocks non authorized ips.
Related
I am trying to use a different warden strategy to authenticate my action cable end points.
But the strategy is not getting called. I tried to place warden.authenticate!(:action_cable_auth) in a controller to test but none of the debug statements are getting printed on console.
Below are the relevant part of the code.
config/initializers/warden.rb
Warden::Strategies.add(:action_cable_auth) do
def valid?
#check if its a websocket request & for action cable?
#Rails.logger.error request.inspect
p 'checking if strategy is valid?'
true
end
def authenticate!
p 'unauthenticate the user'
fail!('user not active')
end
end
in my controller
warden.authenticate!(:action_cable_auth)
Assuming that you are setting your initializer in the proper place, please recall that if your session is already instantiated somewhere else (for example if you authenticate the user at the point your action is being called, then your strategy will never be called.
This is basically how warden works: if some valid? strategy returns a success! then no other will be called as soon as any authenticate! method in the list of strategies is successful.
Please also be sure that if you want your strategy up the list of strategies to check you may need to also shift it up on the list, such as:
manager.default_strategies(scope: :user).unshift(:action_cable_auth)
Where the manager is your Warden::Manager instance. The scope may also be optional (this is an example where the user scope is used alongside Devise), but you may check your instance .default_strategies to figure out where it is and where you want it.
I have a feature with 4 scenarios. I would like to use the value of 1 variable I set in scenario 1 across different steps and in Scenario 2.
I use $ but this is not set. I am assuming $ value remains the same across a feature
When(/^the user goes to manageusers, picks one of the secondary users$/) do
click_link "Admin"
click_link "Manage Users"
emailofuser=ENV["email"].to_s
atpos = emailofuser.index('#')
emailofuser = emailofuser[0,atpos]
page.body.to_s.scan(/<td>(.*?)#ABC.com<\/td>/).flatten().each do |w|
if "#{w}" != emailofuser
$secondaryUserEmail = "#{w}" + "#ABC.com"
break
end
end
end
When(/^the secondary user logs in with password "([^"]*)"$/) do |arg|
if getURL != URI.parse(current_url)
visit getURL
end
find(:xpath,"//input[#id='user_email']").set($secondaryUserEmail )
fill_in "user_password", :with => arg
click_button "Sign in"
end
In the Above Step, the steps are in 1 scenario in a feature file and I also have the same step secondary user in Scenario 2 within a feature. the variable $secondaryuserEmail some times does not get set and login as a secondary user fails.
Whats the best way for me to declare variables that I can access across steps within a scenario and across scenarios within a feature.
You should find out why $secondaryuserEmail does not get set. That sounds like a bug somewhere in the app you're testing. If it's not a bug, you should try to handle the exception.
To your original question, it might be a good idea to set variables in helper methods, then call these methods to access them using instance variables. Most people generally recommend against sharing variables across scenarios but I've used helper methods in order to store variables throughout my specs that normally go unchanged.
I've created a form builder in rails that allows users to construct their own forms. Many of the form inputs supply straight strings back to Rails (e.g. a text field). Some provide arrays of values (like date choosers). Right now I'm storing the values for these in a serialised column. It works well, and lets me re-display the custom forms when an error occurs with minimal effort. The problem is that someone entered:
--------
into a text field and ActiveRecord raised an error saying: You tried to assign already serialized content to value. This is disabled due to security issues.
I get that the string looks like YAML, but I'm wondering if there's a more graceful way around this as the user was just entering a bunch of dashes to indicate he had no phone number. I'd like it to basically fail gracefully and perhaps drop the value or store it as a serialised string if there is such a thing.
In Rails 3.0.20 LTS they've patched the code to check for YAML strings being sent to serialised columns. I've overridden the assignment method on my model to fix the string instead of raising an error:
module ActiveRecord
module AttributeMethods
module Write
extend ActiveSupport::Concern
included do
attribute_method_suffix "="
end
module ClassMethods
protected
def define_method_attribute=(attr_name)
if self.serialized_attributes[attr_name]
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
if new_value.is_a?(String) and new_value =~ /^---/
raise ActiveRecordError, "You tried to assign already serialized content to #{attr_name}. This is disabled due to security issues."
end
write_attribute(attr_name, new_value)
end
elsif attr_name =~ /^[a-zA-Z_]\w*[!?=]?$/
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
else
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
write_attribute(attr_name, new_value)
end
end
end
end
...
I wanted to use super(new_value) here to allow the original method to make the assignment but unfortunately it seemed to be bypassing the check (thus bypassing the security measure too).
def value=(new_value)
if new_value.is_a?(String) and new_value =~ /^---/
new_value.gsub!(/^-+/, '-')
end
write_attribute(:value, new_value)
end
I am implementing background email processing with Resque using the resque_mailer gem (https://github.com/zapnap/resque_mailer). I was able to get it to work for all my emails except the ones sent by Devise.
I went through a bunch of SO questions, and blog posts (for instance http://teeparham.posterous.com/send-devise-emails-with-resque) but could not find a way to get it to work.
What are the precise steps to follow to get resque_mailer to work with Devise?
I went through tee's answer and several resources online, but couldn't find a working solution.
After a few days of reading through resque-mailer and devise code, a solution that worked for me. Thanks to tee for gist which put me in right direction.
Assuming your app/mailers/application_mailer.rb looks similar to
class ApplicationMailer < ActionMailer::Base
include Resque::Mailer # This will add a `self.perform` class method, which we will overwrite in DeviseResqueMailer
end
In config/initializers/devise.rb
Devise.parent_mailer = "ApplicationMailer"
Devise.setup do |config|
config.mailer = 'DeviseResqueMailer'
end
In the resource class which uses devise, overwrite the send_devise_notification method to send resource class and id instead of object to prevent marshalling
# app/models/user.rb
protected
def send_devise_notification(notification, *args)
# Based on https://github.com/zapnap/resque_mailer/blob/64d2be9687e320de4295c1bd1b645f42bd547743/lib/resque_mailer.rb#L81
# Mailer may completely skip Resque::Mailer in certain cases - and will fail as we write custom handle in DeviseResqueMailer assuming mails are handled via resque
# So in those cases, don't retain original devise_mailer so things work properly
if ActionMailer::Base.perform_deliveries && Resque::Mailer.excluded_environments.exclude?(Rails.env.to_sym)
# Originally devise_mailer.send(notification, self, *args).deliver
# Modified to ensure devise mails are safely sent via resque
resource_id, resource_class = self.id, self.class.name
devise_mailer.send(notification, {resource_id: resource_id, resource_class: resource_class}, *args).deliver
else
super
end
end
Finally, in app/mailers/devise_resque_mailer.rb, fetch the record again from the database and continue
class DeviseResqueMailer < Devise::Mailer
def self.perform(action, *args)
# Hack to prevent RuntimeError - Could not find a valid mapping for admin.attributes
record_hash = args.shift
record = record_hash["resource_class"].constantize.find(record_hash["resource_id"])
args.unshift(record)
super # From resque-mailer
end
end
I feel this approach is a better than using devise-async as all the mails go through same code path. Its easier to control and overwrite if needed.
I'd take a look at devise-async. Looks like it fits your use case. Devise Async
I've got the following model
class User < ActiveRecord::Base
before_create :set_some_values
private
def set_some_values
#do something
end
end
In specs I'm using Fabrication gem to create objects but I can't find a way to stub the set_some_values method. I tried
User.any_instance.stub!(:set_some_values).and_return(nil)
but Fabrication seems to ignore this. Is it possible to do?
This is why I don't like ActiveRecord callbacks -- because if you want to have nothing to do with a callback (because, say, you're making a call to an external service inside the callback) you still have to be concerned about stubbing it out. Yes you could stub out methods inside the callback, but it's the same problem, and actually it's a bit worse because now you are concerned about something inside a method you want nothing to do with.
As usual there are multiple options here.
One option which I've used a lot in the past is, add a condition to your callback that turns it off by default. So your Post class could look like:
class Post
before_save :sync_with_store, :if => :syncing_with_store?
def syncing_with_store?; #syncing_with_store; end
attr_writer :syncing_with_store
def sync_with_store
# make an HTTP request or something
end
end
Now wherever you really want to call the callback (perhaps in your controller or wherever), you can set post.syncing_with_store = true before you call post.save.
The downside to this approach is, it's something that you (and other devs working with you) have to keep in mind, and it's not really obvious that you have to do this. On the other hand, if you forget to do this, nothing bad happens.
Another option is to use a fake class. Say you have a Post that pushes its data to an external data store on save. You could extract the code that does the pushing to a separate class (e.g. Pusher) which would be accessible at Post.pusher_service. By default, though, this would be set to a fake Pusher class that responds to the same interface but does nothing. So like:
class Post
class << self
attr_accessor :pusher_service
end
self.pusher_service = FakePostPusher
before_save :sync_with_store
def sync_with_store
self.class.pusher_service.run(self)
end
end
class FakePostPusher
def self.run(post)
new(post).run
end
def initialize(post)
#post = post
end
def run
# do nothing
end
end
class PostPusher < FakePostPusher
def run
# actually make the HTTP request or whatever
end
end
In your production environment file, you'd set Post.pusher_service = Pusher. In individual tests or test cases, you'd make a subclass of Post -- let(:klass) { Class.new(Post) } -- and set klass.pusher_service = Pusher (that way you don't permanently set it and affect future tests).
The third approach, which I have been experimenting with, is this: simply don't use ActiveRecord callbacks. This is something I picked up from Gary Bernhardt's screencasts (which, by the way, are pretty amazing). Instead, define a service class that wraps the act of creating a post. Something like:
class PostCreator
def self.run(attrs={})
new(attrs).run
end
def initialize(attrs={})
#post = Post.new(attrs)
end
def run
if #post.save
make_http_request
return true
else
return false
end
end
def make_http_request
# ...
end
end
This way PostCreator.run(attrs) is the de facto way of creating a post instead of going through Post. Now to test saves within Post, there's no need to stub out callbacks. If you want to test the PostCreator process, there's no magic going on, you can easily stub out whichever methods you want or test them independently. (You could argue that stubbing out methods here is the same as stubbing out AR callbacks, but I think it's more explicit what's going on.) Obviously this only handles post creation, but you could do the same for post updating too.
Anyway, different ideas, pick your poison.
The #set_some_values method here is called when you call #save on the record. So it has nothing to do with the constructor and therefore you don't need to stub User.any_instance -- just make your record and then do a partial stub, as in:
record.stub(:set_some_values)
record.save