I'm using Cucumber and Capybara and I'd like a way to simulate the request IP address, like this:
Given the request ip address is "10.1.2.3"
I solved it by passing the IP address in an environment variable:
When /^the request ip address is "([^\"]*)"$/ do |ip_address|
ENV['RAILS_TEST_IP_ADDRESS'] = ip_address
end
application_controller.rb:
before_filter :mock_ip_address
def mock_ip_address
if Rails.env == 'cucumber' || Rails.env == 'test'
test_ip = ENV['RAILS_TEST_IP_ADDRESS']
unless test_ip.nil? or test_ip.empty?
request.instance_eval <<-EOS
def remote_ip
"#{test_ip}"
end
EOS
end
end
end
My mix of Leventix's and Ramon's solutions:
spec/support/remote_ip_monkey_patch.rb
module ActionDispatch
class Request
def remote_ip_with_mocking
test_ip = ENV['RAILS_TEST_IP_ADDRESS']
unless test_ip.nil? or test_ip.empty?
return test_ip
else
return remote_ip_without_mocking
end
end
alias_method_chain :remote_ip, :mocking
end
end
Related
I'm getting something really weird when I execute my tests with rails test.
I'm trying to understand and create my first controller tests and I get something unexpected.
I get this error in the console:
F
Failure:
FeedbacksControllerTest#test_should_create_resource [/Users/Daniel/GitHub/haeapua-rails/test/controllers/feedbacks_controller_test.rb:15]:
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/>
Response body: <html><body>You are being redirected.</body></html>
here is my test
test "should create resource" do
assert_difference 'Feedback.count', 1 do
post feedbacks_url, params: { feedback: { email: "me#myemail.com", summary: "my feedback", rating: 5 } }
end
assert_response :success
assert_redirected_to root_url
end
here is my controller
class FeedbacksController < ApplicationController
# GET /feedbacks/new
def new
#feedback = Feedback.new
# #feedback.user = current_user if user_signed_in?
end
# POST /feedbacks
def create
#feedback = Feedback.new(feedback_params)
# #feedback.user = current_user if user_signed_in?
if #feedback.save
# flash[:info] = "We got it, thanks. Someone will contact you ASAP once we read it"
redirect_to root_url
else
render :new
end
end
private
def feedback_params
params.require(:feedback).permit(:email, :summary, :rating)
end
end
When I put a "puts root_url" in my feedback controller just before the redirect_to I get the value "http://www.example.com/". I search for root_url in my whole code and the only part I have it is in the Controller and in my routes root to: 'static_pages#concept'
I'm using devise, do you think it could be because of that? I'm a bit lost and don't know where to start looking!
It's a weird quirk but rails has two types of url_helpers for routes. something_url and something_path. The first is an absolute path, http://www.awesome.com/something) the second is the relative path (/someurl).
Unless you have a domain name set explicitly on your test environment, use the _path helpers in your tests. If you want to use the _url helpers then you need to configure a base url in your application's test environment (config/environments/test.rb)
Maybe some default config is setting this value. https://github.com/teamcapybara/capybara/blob/master/lib/capybara.rb#L452
Capybara.configure do |config|
# ...
config.default_host = "http://www.example.com"
# ...
end
You have to override this setting in your project config.
If it's not capybara it could be rails issue ? From the input of this thread https://github.com/rspec/rspec-rails/issues/1275 I would try something like this
# config/environment/test.rb
config.action_controller.default_url_options = {
host: '0.0.0.0:3000' # or whatever your host is
}
In the thread I found:
# putting this into initializer of test framework
[ApplicationController, ActionController::Base].each do |klass|
klass.class_eval do
def default_url_options(options = {})
{ :host => "example.com" }.merge(options) # or localhost:8888 or 0.0.0.0:3000 or whatever your server is
end
end
end
I have the following test code.
require "thor"
module Snap
class CLI < Thor
desc 'login', 'Login Credentials'
def login(user,pass)
#username = user
#password = pass
say #password
end
desc 'block', 'Block user'
def block(input)
say #username
end
end
end
If I type Snap login abc xyz in my command line.
I get the the output as xyz.
But when I type Snap block a.
The output i get is just a blank space.
ie: nothing gets stored at username or password.
Why is this and how can I resolve this?
The problem is that the program is terminated in between two invocations of your command. Therefore, all state is lost and consequently the username is lost.
In order to persist variables across multiple invocations of your command, you need to save it to a file. For example, you could save a hidden file in the user's home directory in the yaml format.
CAUTION: be aware that this will store the password in plain text in the config file!
require 'thor'
require 'yaml'
module Snap
class CLI < Thor
def initialize(*args)
super
read_config
end
desc 'login', 'Login Credentials'
def login(user, pass)
#username = user
#password = pass
say #password
write_config
end
desc 'block', 'Block user'
def block(input)
say #username
end
private
CONFIG_FILE = '~/.myprogram.conf'
def write_config
config = {}
config['username'] = #username
config['password'] = #password
File.open(CONFIG_FILE, 'w') do |f|
f.write config.to_yaml
end
end
def read_config
config = YAML.load_file(CONFIG_FILE)
#username = config['username']
#password = config['password']
end
end
end
I have a class which is responsible for dealing with some response from payments gateway.
Let's say:
class PaymentReceiver
def initialize(gateway_response)
#gateway_response = gateway_response
end
def handle_response
if #gateway_response['NC_STATUS'] != '0'
if order
order.fail_payment
else
raise 'LackOfProperOrder'
# Log lack of proper order
end
end
end
private
def order
#order ||= Order.where(id: #gateway_response['orderID']).unpaid.first
end
end
In payload from payment I've NC_STATUS
which is responsible for information if payment succeed and orderID which refers to Order ActiveRecord class byid`.
I would like to test behavior(in rspec):
If PaymentReceiver receives response where NC_STATUS != 0 sends fail_payment to specific Order object referred by orderID.
How you would approach to testing this ? I assume that also design could be bad ...
You have to make refactorization to remove SRP and DIR principles violations.
Something below I'd say:
class PaymentReceiver
def initialize(response)
#response = response
end
def handle_response
if #response.success?
#response.order.pay
else
#response.order.fail_payment
end
end
end
# it wraps output paramteres only !
class PaymentResponse
def initialize(response)
#response = response
end
def order
# maybe we can check if order exists
#order ||= Order.find(#response['orderID'].to_i)
end
def success?
#response['NCSTATUS'] == '0'
end
end
p = PaymentReceiver.new(PaymentResponse({'NCSTATUS' => '0' }))
p.handle_response
Then testing everything is easy.
So currently I am manually directing from a naked domain due to restrictions with my hosting provider (Heroku). Everything works just fine. The problem is that if a users visits mydomain.com/route, a redirect will be issued back to www.mydomain.com without the /route. How would I go about re-appending the route, but still redirecting to www. ?
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :ensure_domain
APP_DOMAIN = 'www.domain.com'
def index
end
def ensure_domain
if Rails.env.production?
if request.env['HTTP_HOST'] != APP_DOMAIN
redirect_to "http://#{APP_DOMAIN}", :status => 301
end
end
end
end
EDIT
I removed my code above from my ApplicationController, and opted for using the refraction gem as suggested by hurikhan77, which solved my problem.
Here is refraction_rules.rb I used.
Refraction.configure do |req|
if req.host == "domain.com"
req.permanent! :host => "www.domain.com"
end
end
I suggest using the refraction gem for this: http://rubygems.org/gems/refraction
Ideally, you would set up rules like that in your web server configuration. Requests would become faster, because they would not even reach the rails stack. There would be no need to add any code to your app either.
However, if you are running in some restricted environment, like heroku, I'd advise adding a rack middleware. (Just for guidelines, can't guarantee if this particular code is bug free)
class Redirector
SUBDOMAIN = 'www'
def initialize(app)
#app = app
end
def call(env)
#env = env
if redirect?
redirect
else
#app.call(env)
end
end
private
def redirect?
# do some regex to figure out if you want to redirect
end
def redirect
headers = {
"location" => redirect_url
}
[302, headers, ["You are being redirected..."]] # 302 for temp, 301 for permanent
end
def redirect_url
scheme = #env["rack.url_scheme"]
if #env['SERVER_PORT'] == '80'
port = ''
else
port = ":#{#env['SERVER_PORT']}"
end
path = #env["PATH_INFO"]
query_string = ""
if !#env["QUERY_STRING"].empty?
query_string = "?" + #env["QUERY_STRING"]
end
host = "://#{SUBDOMAIN}." + domain # this is where we add the subdomain
"#{scheme}#{host}#{path}#{query_string}"
end
def domain
# extract domain from request or get it from an environment variable etc.
end
end
You can also test the whole thing in isolation
describe Redirector do
include Rack::Test::Methods
def default_app
lambda { |env|
headers = {'Content-Type' => "text/html"}
headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
[200, headers, ["default body"]]
}
end
def app()
#app ||= Rack::Lint.new(Redirector.new(default_app))
end
it "redirects unsupported subdomains" do
get "http://example.com/zomg?a=1"
last_response.status.should eq 301
last_response.header['location'].should eq "http://www.example.com/zomg?a=1"
end
# and so on
end
Then you can add it to production (or any preferred environments) only
# production.rb
# ...
config.middleware.insert_after 'ActionDispatch::Static', 'Redirector'
If you want to test it in development, add the same line to development.rb and add a record to your hosts file (usually /etc/hosts) to treat yoursubdomain.localhost as 127.0.0.1
Not sure if this is the best solution but you could regex the request.referrer and pull out anything after .com and append it to the APP_DOMAIN
Or I guess you could just take out everything before the first . in request.env['HTTP_HOST'] add replace with http://www. assuming you don't plan on using subdomains.
I can't get geocoder to work correct as my local ip address is 127.0.0.1 so it can't located where I am correctly.
The request.location.ip shows "127.0.0.1"
How can I use a different ip address (my internet connection ip) so it will bring break more relevant data?
A nice clean way to do it is using MiddleWare. Add this class to your lib directory:
# lib/spoof_ip.rb
class SpoofIp
def initialize(app, ip)
#app = app
#ip = ip
end
def call(env)
env['HTTP_X_FORWARDED_FOR'] = nil
env['REMOTE_ADDR'] = env['action_dispatch.remote_ip'] = #ip
#status, #headers, #response = #app.call(env)
[#status, #headers, #response]
end
end
Then find an IP address you want to use for your development environment and add this to your development.rb file:
config.middleware.use('SpoofIp', '64.71.24.19')
For this I usually use params[:ip] or something in development. That allows me to test other ip addresses for functionality and pretend I'm anywhere in the world.
For example
class ApplicationController < ActionController::Base
def request_ip
if Rails.env.development? && params[:ip]
params[:ip]
else
request.remote_ip
end
end
end
I implemented this slightly different, and this works well for my case.
In application_controller.rb i have a lookup method which calls the Geocoder IP lookup directly passing in the results of request.remote_ip.
def lookup_ip_location
if Rails.env.development?
Geocoder.search(request.remote_ip).first
else
request.location
end
end
Then in config/environments/development.rb i monkey-patched the remote_ip call:
class ActionDispatch::Request
def remote_ip
"71.212.123.5" # ipd home (Denver,CO or Renton,WA)
# "208.87.35.103" # websiteuk.com -- Nassau, Bahamas
# "50.78.167.161" # HOL Seattle, WA
end
end
I just hard code some addresses, but you could do whatever you'd like here.
I had the same question. Here is how I implemented with geocoder.
#gemfile
gem 'httparty', :require => 'httparty', :group => :development
#application_controller
def request_ip
if Rails.env.development?
response = HTTParty.get('http://api.hostip.info/get_html.php')
ip = response.split("\n")
ip.last.gsub /IP:\s+/, ''
else
request.remote_ip
end
end
#controller
ip = request_ip
response = Geocoder.search(ip)
( code part with hostip.info from geo_magic gem, and based on the other answer to this question. )
now you can do something like response.first.state
This is an updated answer for geocoder 1.2.9 to provide a hardcoded IP for development and test environments. Just place this at the bottom of your config/initilizers/geocoder.rb:
if %w(development test).include? Rails.env
module Geocoder
module Request
def geocoder_spoofable_ip_with_localhost_override
ip_candidate = geocoder_spoofable_ip_without_localhost_override
if ip_candidate == '127.0.0.1'
'1.2.3.4'
else
ip_candidate
end
end
alias_method_chain :geocoder_spoofable_ip, :localhost_override
end
end
end
you may also do this
request.safe_location