Hi I have a controller file at app/controllers/lti_controller.rb and I need to add Test for the POST #launch endpoint which is defined as /lti/launch in routes.rb and working as intended.
Problem
I need to send some data to the /lti/launch in the encoding format x-www-form-urlencoded and by using Net::HTTP I am doing that from the spec file.
But as Net::HTTP requires host and port for their operation and I have given localhost:3000 as of now and which is going to the development environment.
Question
How to access the host and port of the test environment in Rspec 3.10 such that I can pass them in spec file ?
Is there any way to use post '/lti/launch' , req.body : data in x-www-form-urlencoded format` ? and also get the host and port of the test environment ?
My Spec file
spec/requests/lti_spec.rb
# frozen_string_literal: true
require "rails_helper"
require 'uri'
require 'net/http'
describe LtiController, type: :request do
before do
# shared keys for lms access
#oauth_consumer_key_fromlms = 'some_keys'
#oauth_shared_secret_fromlms = 'some_secrets'
end
describe "#launch" do
before do
# creation of assignment and required users
#mentor = FactoryBot.create(:user)
#group = FactoryBot.create(:group, mentor: #mentor)
#member = FactoryBot.create(:user)
FactoryBot.create(:group_member, user: #member, group: #group)
#assignment = FactoryBot.create(:assignment, group: #group, lti_consumer_key: #oauth_consumer_key_fromlms, lti_shared_secret: #oauth_shared_secret_fromlms, status: "open")
end
def launch_uri
launch_url = "http://0.0.0.0:3000/lti/launch"
URI(launch_url)
end
let(:parameters) {
{
'launch_url' => launch_uri().to_s,
'user_id' => #member.id,
'launch_presentation_return_url' => 'http://localhost:3000/tool_return',
'lti_version' => 'LTI-1p0',
'lti_message_type' => 'basic-lti-launch-request',
'resource_link_id' => '88391-e1919-bb3456',
'lis_person_contact_email_primary' => #member.email,
'tool_consumer_info_product_family_code' => 'moodle',
'context_title' => 'sample Course',
'lis_outcome_service_url' => 'http://localhost:3000/grade_passback',
'lis_result_sourcedid' => SecureRandom.hex(10)
}
}
def consumer_data(oauth_consumer_key_fromlms, oauth_shared_secret_fromlms, parameters)
consumer = IMS::LTI::ToolConsumer.new(oauth_consumer_key_fromlms, oauth_shared_secret_fromlms, parameters)
allow(consumer).to receive(:to_params).and_return(parameters)
consumer.generate_launch_data
end
context "lti parameters are valid" do
it "returns success if assignment key and secret are ok and group member is present" do
data = consumer_data(#oauth_consumer_key_fromlms, #oauth_shared_secret_fromlms, parameters)
response = Net::HTTP.post_form(launch_uri(), data)
expect(response.code).to eq("200")
end
end
end
end
My Controller file
app/controllers/lti_controller.rb
class LtiController < ApplicationController
skip_before_action :verify_authenticity_token, only: :launch # for lti integration
before_action :set_group_assignment, only: %i[launch]
before_action :set_lti_params, only: %i[launch]
after_action :allow_iframe_lti, only: %i[launch]
def launch
session[:is_lti]=true # the lti session starting
require 'oauth/request_proxy/action_controller_request'
if #assignment.blank?
# if no assignment is found
flash[:notice] = t("lti.launch.notice_no_assignment")
render :launch_error, status: 401
return
end
if #group.present? # if there is a valid group based for the lti_token_key
#provider = IMS::LTI::ToolProvider.new(
params[:oauth_consumer_key], # lms_oauth_consumer_key
#assignment.lti_shared_secret, # the group's lti_token
params
)
if !#provider.valid_request?(request) # checking the lti request from the lms end
render :launch_error, status: 401
return
end
lms_lti_host = URI.join #launch_url_from_lms, '/' # identifies the domain and saves in session
session[:lms_domain]=lms_lti_host
#user = User.find_by(email: #email_from_lms) # find user by matching email with circuitverse and lms
if #user.present? # user is present in cv
if #user.id == #group.mentor_id # user is teacher
sign_in(#user) # passwordless sign_in the user as the authenticity is verified via lms
lms_auth_success_notice = t("lti.launch.notice_lms_auth_success_teacher", email_from_lms: #email_from_lms, lms_type: #lms_type, course_title_from_lms: #course_title_from_lms)
redirect_to group_assignment_path(#group, #assignment), notice: lms_auth_success_notice # if auth_success send to group page
else
user_in_group = GroupMember.find_by(user_id:#user.id,group_id:#group.id) # check if the user belongs to the cv group
if user_in_group.present? # user is member of the group
# render the button
flash[:notice] = t("lti.launch.notice_students_open_in_cv")
create_project_if_student_present() # create project with lis_result_sourced_id for the student
render :open_incv, status: 200
else # user is not a member of the group
# send the user an email
flash[:notice] = t("lti.launch.notice_ask_teacher")
render :launch_error, status: 401
end
end
else # no such user in circuitverse,showing a notice to create an account in cv
flash[:notice] = t("lti.launch.notice_no_account_in_cv", email_from_lms: #email_from_lms )
render :launch_error, status: 400
end
else # there is no valid group present for the lti_consumer_key
flash[:notice] = t("lti.launch.notice_invalid_group")
render :launch_error, status: 400
end
end
def allow_iframe_lti
return unless session[:is_lti]
response.headers["X-FRAME-OPTIONS"] = "ALLOW-FROM #{session[:lms_domain]}"
end
def create_project_if_student_present
#user = User.find_by(email: #email_from_lms)
#project = Project.find_by(author_id: #user.id, assignment_id: #assignment.id) # find if the project is already present
if #project.blank? # if not then create one
#project = #user.projects.new
#project.name = "#{#user.name}/#{#assignment.name}"
#project.assignment_id = #assignment.id
#project.project_access_type = "Private"
#project.build_project_datum
#project.lis_result_sourced_id = params[:lis_result_sourcedid] # this param is required for grade submission
#project.save
end
end
private
def set_group_assignment # query db and check lms_oauth_consumer_key is equal to which assignment and find the group also
#assignment = Assignment.find_by(lti_consumer_key: params[:oauth_consumer_key])
if #assignment.present?
#group =#assignment.group
end
end
def set_lti_params # get some of the parameters from the lti request
#email_from_lms = params[:lis_person_contact_email_primary] # the email from the LMS
#lms_type = params[:tool_consumer_info_product_family_code] # type of lms like moodle/canvas
#course_title_from_lms = params[:context_title] # the course titile from lms
#launch_url_from_lms = params[:launch_presentation_return_url]
session[:lis_outcome_service_url] = params[:lis_outcome_service_url] # requires for grade submission
session[:oauth_consumer_key] = params[:oauth_consumer_key] # requires for grade submission
end
end
Sample data needed to be sent
{"oauth_consumer_key"=>"some_keys", "oauth_signature_method"=>"HMAC-SHA1", "oauth_timestamp"=>"1627879512", "oauth_nonce"=>"Id3rLYZBqMvnRuSpisMEEgFkLFnkxZPS2oqoyBJZLM", "oauth_version"=>"1.0", "context_title"=>"sample Course", "launch_presentation_return_url"=>"http://localhost:3000/tool_return", "launch_url"=>"http://0.0.0.0:3000/lti/launch", "lis_outcome_service_url"=>"http://localhost:3000/grade_passback", "lis_person_contact_email_primary"=>"chung#towne-littel.org", "lis_result_sourcedid"=>"3e87e3aa8f5056260a12", "lti_message_type"=>"basic-lti-launch-request", "lti_version"=>"LTI-1p0", "resource_link_id"=>"88391-e1919-bb3456", "tool_consumer_info_product_family_code"=>"moodle", "user_id"=>"461", "oauth_signature"=>"lgsHKJxHolBU1rTZ5M9zXg688hU="}
You don't need to know the host and port. And you should not perform the request directly with Net::HTTP manually. get/post/... helpers handle that. https://relishapp.com/rspec/rspec-rails/v/5-0/docs/request-specs/request-spec
I'm using Koudoku for subscriptions. I want to do different things after receiving a Stripe webhook.
In the docs, it shows you can add a callback like so:
Koudoku.setup do |config|
config.subscriptions_owned_by = :user
config.stripe_publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
config.stripe_secret_key = ENV['STRIPE_SECRET_KEY']
# add webhooks
config.subscribe 'charge.failed', YourChargeFailed
end
What I can't figure out how to write the YourChargeFailed part. I've tried something like:
config.subscribe 'order.payment_succeeded', ActiveRecord::Subscription.after_successful_payment
but I get undefined method after_successful_payment for #<Class:0x007fb845849b30>
How can I successfully subscribe to Stripe events, capture the return data, and initiate a callback function?
Thanks!
UPDATE
Here is what I've tried, and the corresponding errors I'm receiving:
purchases_helper.rb
module PurchasesHelper
require 'stripe'
def stripe_webhook(event)
puts 'Purchases Helper'
puts 'invoice.payment_succeeded'
#customer = Stripe::Customer.retrieve(event[:data][:object][:customer])
#user = User.find_by(email: #customer[:email])
#badge = Badge.find_by(condition: '2019Purchase')
#badges_user = BadgesUser.find_by(user_id: #user.id, badge_id: #badge.id)
# if #badges_user === nil
# BadgesUser.create(user_id: user.id, badge_id: badge.id)
# end
puts 'badge created'
end
end
initializers/koudoku.rb
Koudoku.setup do |config|
include ::PurchasesHelper
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
::PurchasesHelper.stripe_webhook(event)
end
end
ERROR:
undefined method `stripe_webhook' for PurchasesHelper:Module excluded from capture: Not configured to send/capture in environment 'development'
NoMethodError (undefined method `stripe_webhook' for PurchasesHelper:Module):
Another attempt:
Koudoku.setup do |config|
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
PurchasesHelper.stripe_webhook(event)
end
end
ERROR:
undefined method `stripe_webhook' for PurchasesHelper:Module excluded from capture: Not configured to send/capture in environment 'development'
NoMethodError (undefined method `stripe_webhook' for PurchasesHelper:Module):
3rd Attempt:
Koudoku.setup do |config|
include PurchasesHelper
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
stripe_webhook(event)
end
end
ERROR:
A copy of PurchasesHelper has been removed from the module tree but is still active! excluded from capture: Not configured to send/capture in environment 'development'
ArgumentError (A copy of PurchasesHelper has been removed from the module tree but is still active!):
I see only one problem with your code.
module PurchasesHelper
require 'stripe'
def self.stripe_webhook(event) # NB self.
puts 'Purchases Helper'
puts 'invoice.payment_succeeded'
#customer = Stripe::Customer.retrieve(event[:data][:object][:customer])
#user = User.find_by(email: #customer[:email])
#badge = Badge.find_by(condition: '2019Purchase')
#badges_user = BadgesUser.find_by(user_id: #user.id, badge_id: #badge.id)
# if #badges_user === nil
# BadgesUser.create(user_id: user.id, badge_id: badge.id)
# end
puts 'badge created'
end
end
and then you call it by saying
Koudoku.setup do |config|
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
PurchasesHelper.stripe_webhook(event)
end
end
This should work
Wait but Why?!
Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits.
provide a namespace and prevent name clashes
implement the mixin facility (when you include them)
You've defined an instance method on the Module that when included it will appear on every instance of the object.
but you are not doing that in this case. You want to call stripe_webhook on the Module itself.
adding self. stripe_webhook in this case = PurchasesHelper. stripe_webhook which is the way to define a methods on the class/module.
You can even do more freaky stuff like:
class Animal
def self.all
%w[dog cat bird]
end
end
def Animal.include?(a)
self.all.include?(a)
end
Animal.include?('cat') # true
Animal.include?('virus') # false
so you can even define methods on the Animal class outside the scope of the class and it will work.
To sum up:
in this example:
module PurchasesHelper
def self.stripe_webhook(event)
#...
end
end
is equal to
module PurchasesHelper
def PurchasesHelper.stripe_webhook(event)
#...
end
end
which is why just adding self allows you to call PurchasesHelper.stripe_webhook
On Koudoku doc's, it says it actually uses stripe_event to handle that https://github.com/integrallis/stripe_event
So, looking on the strip_event examples, you can pass a block and do whatever you need or pass something that respond to the call method https://github.com/integrallis/stripe_event#usage
After recently upgrading to Ruby on Rails 5.0 from 4.2, I have been unable to run rake db:migrate, or rails console. I think it best to solve the console error first and it seems to give the followin more informative errors:
Here is the full stack trace if I correctly understand your request. Thank you.
$ rails c
/Users/my_username/.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0/lib/action_controller/test_case.rb:49:in `initialize': wrong number of arguments (given 0, expected 2) (ArgumentError)
from /Users/my_username/.rvm/gems/ruby-2.3.0/bundler/gems/draper-57a514133bc2/lib/draper/view_context/build_strategy.rb:41:in `new'
from /Users/my_username/.rvm/gems/ruby-2.3.0/bundler/gems/draper-57a514133bc2/lib/draper/view_context/build_strategy.rb:41:in `block in controller'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/andand-1.3.3/lib/andand.rb:60:in `me'
from /Users/my_username/.rvm/gems/ruby-2.3.0/bundler/gems/draper-57a514133bc2/lib/draper/view_context/build_strategy.rb:40:in `controller'
from /Users/my_username/.rvm/gems/ruby-2.3.0/bundler/gems/draper-57a514133bc2/lib/draper/view_context/build_strategy.rb:30:in `call'
from /Users/my_username/.rvm/gems/ruby-2.3.0/bundler/gems/draper-57a514133bc2/lib/draper/view_context.rb:49:in `build'
from /Users/my_username/.rvm/gems/ruby-2.3.0/bundler/gems/draper-57a514133bc2/lib/draper/railtie.rb:63:in `block in <class:Railtie>'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/railtie.rb:226:in `block in run_console_blocks'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/railtie.rb:247:in `each'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/railtie.rb:247:in `each_registered_block'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/railtie.rb:226:in `run_console_blocks'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/application.rb:463:in `block in run_console_blocks'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/engine/railties.rb:13:in `each'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/engine/railties.rb:13:in `each'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/application.rb:463:in `run_console_blocks'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/engine.rb:442:in `load_console'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-5.0.0/lib/rails/commands/console.rb:34:in `initialize'
from /Users/my_username/.rvm/gems/ruby-2.3.0/gems/railties-
--clipped 441 characters to enter stack overflow question edit
I understand that the test_case.rb part of the actionpack-5.0.0 gem expects two arguments. After opening this code in Textmate I see the 'initialize' method is part of the TestRequest Class in the following test_case.rb file:
require 'rack/session/abstract/id'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys'
require 'action_controller/template_assertions'
require 'rails-dom-testing'
module ActionController
# :stopdoc:
class Metal
include Testing::Functional
end
module Live
# Disable controller / rendering threads in tests. User tests can access
# the database on the main thread, so they could open a txn, then the
# controller thread will open a new connection and try to access data
# that's only visible to the main thread's txn. This is the problem in #23483
remove_method :new_controller_thread
def new_controller_thread # :nodoc:
yield
end
end
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
DEFAULT_ENV.delete 'PATH_INFO'
def self.new_session
TestSession.new
end
# Create a new test request with default `env` values
def self.create
env = {}
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
env["rack.request.cookie_hash"] = {}.with_indifferent_access
new(default_env.merge(env), new_session)
end
def self.default_env
DEFAULT_ENV
end
private_class_method :default_env
def initialize(env, session)
super(env)
self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
#custom_param_parsers = {
xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
}
end
def query_string=(string)
set_header Rack::QUERY_STRING, string
end
def content_type=(type)
set_header 'CONTENT_TYPE', type
end
def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
non_path_parameters = {}
path_parameters = {}
parameters.each do |key, value|
if query_string_keys.include?(key)
non_path_parameters[key] = value
else
if value.is_a?(Array)
value = value.map(&:to_param)
else
value = value.to_param
end
path_parameters[key] = value
end
end
if get?
if self.query_string.blank?
self.query_string = non_path_parameters.to_query
end
else
if ENCODER.should_multipart?(non_path_parameters)
self.content_type = ENCODER.content_type
data = ENCODER.build_multipart non_path_parameters
else
fetch_header('CONTENT_TYPE') do |k|
set_header k, 'application/x-www-form-urlencoded'
end
case content_mime_type.to_sym
when nil
raise "Unknown Content-Type: #{content_type}"
when :json
data = ActiveSupport::JSON.encode(non_path_parameters)
when :xml
data = non_path_parameters.to_xml
when :url_encoded_form
data = non_path_parameters.to_query
else
#custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
data = non_path_parameters.to_query
end
end
set_header 'CONTENT_LENGTH', data.length.to_s
set_header 'rack.input', StringIO.new(data)
end
fetch_header("PATH_INFO") do |k|
set_header k, generated_path
end
path_parameters[:controller] = controller_path
path_parameters[:action] = action
self.path_parameters = path_parameters
end
ENCODER = Class.new do
include Rack::Test::Utils
def should_multipart?(params)
# FIXME: lifted from Rack-Test. We should push this separation upstream
multipart = false
query = lambda { |value|
case value
when Array
value.each(&query)
when Hash
value.values.each(&query)
when Rack::Test::UploadedFile
multipart = true
end
}
params.values.each(&query)
multipart
end
public :build_multipart
def content_type
"multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
end
end.new
private
def params_parsers
super.merge #custom_param_parsers
end
end
class LiveTestResponse < Live::Response
# Was the response successful?
alias_method :success?, :successful?
# Was the URL not found?
alias_method :missing?, :not_found?
# Was there a server-side error?
alias_method :error?, :server_error?
end
# Methods #destroy and #load! are overridden to avoid calling methods on the
# #store object, which does not exist for the TestSession class.
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
def initialize(session = {})
super(nil, nil)
#id = SecureRandom.hex(16)
#data = stringify_keys(session)
#loaded = true
end
def exists?
true
end
def keys
#data.keys
end
def values
#data.values
end
def destroy
clear
end
def fetch(key, *args, &block)
#data.fetch(key.to_s, *args, &block)
end
private
def load!
#id
end
end
# Superclass for ActionController functional tests. Functional tests allow you to
# test a single controller action per test method. This should not be confused with
# integration tests (see ActionDispatch::IntegrationTest), which are more like
# "stories" that can involve multiple controllers and multiple actions (i.e. multiple
# different HTTP requests).
#
# == Basic example
#
# Functional tests are written as follows:
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
#
# For example:
#
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
# post(:create, params: { book: { title: "Love Hina" }})
#
# # Asserts that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
#
# # Asserts that the controller really put the book in the database.
# assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
#
# You can also send a real document in the simulated HTTP request.
#
# def test_create
# json = {book: { title: "Love Hina" }}.to_json
# post :create, json
# end
#
# == Special instance variables
#
# ActionController::TestCase will also automatically provide the following instance
# variables for use in the tests:
#
# <b>#controller</b>::
# The controller instance that will be tested.
# <b>#request</b>::
# An ActionController::TestRequest, representing the current HTTP
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>#response</b>::
# An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>#response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
#
# (Earlier versions of \Rails required each functional test to subclass
# Test::Unit::TestCase and define #controller, #request, #response in +setup+.)
#
# == Controller is automatically inferred
#
# ActionController::TestCase will automatically infer the controller under test
# from the test class name. If the controller cannot be inferred from the test
# class name, you can explicitly set it with +tests+.
#
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
# tests WidgetController
# end
#
# == \Testing controller internals
#
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
# can be used against. These collections are:
#
# * session: Objects being saved in the session.
# * flash: The flash objects currently in the session.
# * cookies: \Cookies being sent to the user on this request.
#
# These collections can be used just like any other hash:
#
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
# On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
#
# == Manipulating session and cookie variables
#
# Sometimes you need to set up the session and cookie variables for a test.
# To do this just assign a value to the session or cookie collection:
#
# session[:key] = "value"
# cookies[:key] = "value"
#
# To clear the cookies for a test just clear the cookie collection:
#
# cookies.clear
#
# == \Testing named routes
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
#
# assert_redirected_to page_url(title: 'foo')
class TestCase < ActiveSupport::TestCase
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess
include ActiveSupport::Testing::ConstantLookup
include Rails::Dom::Testing::Assertions
attr_reader :response, :request
module ClassMethods
# Sets the controller class name. Useful if the name can't be inferred from test class.
# Normalizes +controller_class+ before using.
#
# tests WidgetController
# tests :widget
# tests 'widget'
def tests(controller_class)
case controller_class
when String, Symbol
self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
when Class
self.controller_class = controller_class
else
raise ArgumentError, "controller class must be a String, Symbol, or Class"
end
end
def controller_class=(new_class)
self._controller_class = new_class
end
def controller_class
if current_controller_class = self._controller_class
current_controller_class
else
self.controller_class = determine_default_controller_class(name)
end
end
def determine_default_controller_class(name)
determine_constant_from_test_name(name) do |constant|
Class === constant && constant < ActionController::Metal
end
end
end
# Simulate a GET request with the given parameters.
#
# - +action+: The controller action to call.
# - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
# - +body+: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
# Example sending parameters, session and setting a flash message:
#
# get :show,
# params: { id: 7 },
# session: { user_id: 1 },
# flash: { notice: 'This is flash message' }
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
res = process_with_kwargs("GET", action, *args)
cookies.update res.cookies
res
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
def post(action, *args)
process_with_kwargs("POST", action, *args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
def patch(action, *args)
process_with_kwargs("PATCH", action, *args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
def put(action, *args)
process_with_kwargs("PUT", action, *args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
def delete(action, *args)
process_with_kwargs("DELETE", action, *args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
def head(action, *args)
process_with_kwargs("HEAD", action, *args)
end
def xml_http_request(*args)
ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
xhr and xml_http_request methods are deprecated in favor of
`get :index, xhr: true` and `post :create, xhr: true`
MSG
#request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
#request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
__send__(*args).tap do
#request.env.delete 'HTTP_X_REQUESTED_WITH'
#request.env.delete 'HTTP_ACCEPT'
end
end
alias xhr :xml_http_request
# Simulate an HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
# - +method+: Request method used to send the HTTP request. Possible values
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
# - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
# - +body+: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
#
# Example calling +create+ action and sending two params:
#
# process :create,
# method: 'POST',
# params: {
# user: { name: 'Gaurish Sharma', email: 'user#example.com' }
# },
# session: { user_id: 1 },
# flash: { notice: 'This is flash message' }
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
def process(action, *args)
check_required_ivars
if kwarg_request?(args)
parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
else
http_method, parameters, session, flash = args
format = nil
if parameters.is_a?(String) && http_method != 'HEAD'
body = parameters
parameters = nil
end
if parameters || session || flash
non_kwarg_request_warning
end
end
if body
#request.set_header 'RAW_POST_DATA', body
end
if http_method
http_method = http_method.to_s.upcase
else
http_method = "GET"
end
parameters ||= {}
if format
parameters[:format] = format
end
#html_document = nil
self.cookies.update #request.cookies
self.cookies.update_cookies_from_jar
#request.set_header 'HTTP_COOKIE', cookies.to_header
#request.delete_header 'action_dispatch.cookies'
#request = TestRequest.new scrub_env!(#request.env, #request.session)
#response = build_response #response_klass
#response.request = #request
#controller.recycle!
#request.set_header 'REQUEST_METHOD', http_method
parameters = parameters.symbolize_keys
generated_extras = #routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
generated_path = generated_path(generated_extras)
query_string_keys = query_parameter_names(generated_extras)
#request.assign_parameters(#routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
#request.session.update(session) if session
#request.flash.update(flash || {})
if xhr
#request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
#request.fetch_header('HTTP_ACCEPT') do |k|
#request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
end
end
#request.fetch_header("SCRIPT_NAME") do |k|
#request.set_header k, #controller.config.relative_url_root
end
begin
#controller.recycle!
#controller.dispatch(action, #request, #response)
ensure
#request = #controller.request
#response = #controller.response
#request.delete_header 'HTTP_COOKIE'
if #request.have_cookie_jar?
unless #request.cookie_jar.committed?
#request.cookie_jar.write(#response)
self.cookies.update(#request.cookie_jar.instance_variable_get(:#cookies))
end
end
#response.prepare!
if flash_value = #request.flash.to_session_value
#request.session['flash'] = flash_value
else
#request.session.delete('flash')
end
if xhr
#request.delete_header 'HTTP_X_REQUESTED_WITH'
#request.delete_header 'HTTP_ACCEPT'
end
#request.query_string = ''
#response.sent!
end
#response
end
def controller_class_name
#controller.class.anonymous? ? "anonymous" : #controller.class.controller_path
end
def generated_path(generated_extras)
generated_extras[0]
end
def query_parameter_names(generated_extras)
generated_extras[1] + [:controller, :action]
end
def setup_controller_request_and_response
#controller = nil unless defined? #controller
#response_klass = ActionDispatch::TestResponse
if klass = self.class.controller_class
if klass < ActionController::Live
#response_klass = LiveTestResponse
end
unless #controller
begin
#controller = klass.new
rescue
warn "could not construct controller #{klass}" if $VERBOSE
end
end
end
#request = TestRequest.create
#response = build_response #response_klass
#response.request = #request
if #controller
#controller.request = #request
#controller.params = {}
end
end
def build_response(klass)
klass.create
end
included do
include ActionController::TemplateAssertions
include ActionDispatch::Assertions
class_attribute :_controller_class
setup :setup_controller_request_and_response
end
private
def scrub_env!(env)
env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
env.delete 'action_dispatch.request.query_parameters'
env.delete 'action_dispatch.request.request_parameters'
env
end
def process_with_kwargs(http_method, action, *args)
if kwarg_request?(args)
args.first.merge!(method: http_method)
process(action, *args)
else
non_kwarg_request_warning if args.any?
args = args.unshift(http_method)
process(action, *args)
end
end
REQUEST_KWARGS = %i(params session flash method body xhr)
def kwarg_request?(args)
args[0].respond_to?(:keys) && (
(args[0].key?(:format) && args[0].keys.size == 1) ||
args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
)
end
def non_kwarg_request_warning
ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.
Examples:
get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
MSG
end
def document_root_element
html_document.root
end
def check_required_ivars
# Sanity check for required instance variables so we can give an
# understandable error message.
[:#routes, :#controller, :#request, :#response].each do |iv_name|
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
end
end
end
def html_format?(parameters)
return true unless parameters.key?(:format)
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
end
end
include Behavior
end
# :startdoc:
end
Here is the error when I run rake db:migrate:
rake aborted!
NameError: uninitialized constant ActiveSupport
/Users/my_username/projects/zoner/config/application.rb:1:in `
Here is the application.rb file's first lines:
ActiveSupport.halt_callback_chains_on_return_false = false
require File.expand_path('../boot', __FILE__)
require File.expand_path('../initializers/decent_exposure', __FILE__)
require 'rails/all'
require 'active_support'
require 'active_support/core_ext'
If there is someone that can help identify the errors in this code that will help me add the necessary arguments to the TestRequest class that would be very helpful. Thank you.
Update from #scott-w (August 2016):
The correct location is now: gem 'draper', github: 'drapergem/draper'
the rails-5 branch has been merged into master and no longer exists
Original: The issue with rails console error is caused by draper gem as seen from the trace. As it stands now, master (main) branch of draper doesn't support Rails 5. It seems to be know for some long time, but before Rails 5 was officially released as stable, it wasn't really put action on.
Try this fork with few fixes embedded ("old" solution)
gem 'draper', github: 'audionerd/draper', branch: 'rails5'
Taken from here
Or better (seems to become current solution)
gem 'draper', github: 'drapergem/draper', branch: 'rails-5'
The work on this kind of official branch is going as we speak :)
https://github.com/drapergem/draper/commits/rails-5
Or get rid of draper for Rails 5, as it doesn't support it in its master branch yet (seems to change in the coming days)
Update -> rails-5 branch was merged into master so Rails 5 are now supported
How would I write a method to be used in rspec testing to access pages that require a username and password for HTTP Digest Authentication. For example, this test...
it "edit" do
http_login
post :edit, id: #post
assigns[:post].should eq(#post)
end
needs http_login method to be something like this...
def http_login
user = {"username" =>
Digest::MD5.hexdigest(["username","Application","password"].join(":"))}
request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Digest.encode_credentials(?,?,?,?)
end
My question is what do I put in the four arguments for the encode credentials. The arguments are going to be http_method, credentials, password, password_is_ha1 but I'm unsure how to write http_method and credentials to implement in the tests.
Solution here: https://gist.github.com/1282275
recopied here for posterity
# Adds support for http digest authentication in Rails 3
# Inspired by: http://lightyearsoftware.com/2009/04/testing-http-digest-authentication-in-rails/
# Place this code in test/test_helper.rb
# In your test, call authenticate_with_http_digest prior to calling get, post, put or delete
# Tested with Rails 3.0.7
class ActionController::TestCase
require 'digest/md5'
def authenticate_with_http_digest(user = API_USERNAME, password = API_PASSWORD, realm = API_REALM)
ActionController::Base.class_eval { include ActionController::Testing }
#controller.instance_eval %Q(
alias real_process_with_new_base_test process_with_new_base_test
def process_with_new_base_test(request, response)
credentials = {
:uri => request.url,
:realm => "#{realm}",
:username => "#{user}",
:nonce => ActionController::HttpAuthentication::Digest.nonce(request.env['action_dispatch.secret_token']),
:opaque => ActionController::HttpAuthentication::Digest.opaque(request.env['action_dispatch.secret_token'])
}
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Digest.encode_credentials(request.request_method, credentials, "#{password}", false)
real_process_with_new_base_test(request, response)
end
)
end
end
Here's a solution for RSpec 3.1 and Rails 4 HTTP Digest Auth testing: https://gist.github.com/murbanski/6b971a3edc91b562acaf