I'm using service which sends Webhooks to my application. I want to write RSpec test for handling them. It's important to have this request exactly the same (remote caller IP, headers with encrypted content).
I tried to save request as json:
class WebhookController < ApplicationController
def some_callback
File.open('temp/request_example.json','w') do |f|
f.write request.to_json
end
end
end
so I could later do:
describe WebhookController do
subject { get :some_callback, JSON.parse(File.open('temp/request_example.json')) }
it 'does something' do;end
end
but unfortunately you cannot call request.to_json(request.to_json
IOError: not opened for reading). You can't either get directly to request.body or request.headers.
How to save such request for later usage in tests? Is there any gem for it?
Related
I'm writing a system test for an app that uses active storage. The view I'm testing includes presigned urls to objects on amazon s3. I'd obviously like to avoid sending GET requests to s3 in my tests. Instead, I'd like to "stub" active storage so that the urls for my attachments all point to one particular file on my test machine ("/test_data/placeholder.jpg"), rather than to s3.
My view code uses url_for to generate s3 urls. How can I stub url_for to return my one specific desired url?
Excerpts of my failed attempt follow.
In my view (user.jbuilder.json):
json.user_thumbnail = url_for #user.photo.variant(resize: "100x100")
In my tests:
class UserPageTest < ApplicationSystemTestCase
test "thumbnail appears on user page" do
User.any_instance.stubs(:photo).returns(stub(:url => "/test_data/placeholder_thumbnail.jpg") ))
visit '/user_profile/123'
end
end
When I run the test I get this error:
2021-11-04 20:26:40 -0400 Rack app ("GET /user/123.json" - (127.0.0.1)): #<Minitest::Assertion: unexpected invocation: #<Mock:0x7ff448c52af8>.to_model()
In the end I accomplished this by monkey patching activestorage inside my tests. I redirect to different placeholder files depending on the class of the object I'm dealing with.
ActiveStorage::Blobs::RedirectController.class_eval do
def show
case #blob.attachments.first.record.class.name
when "Video"
redirect_to "/test_data/video.mp4"
when "Photo"
redirect_to "/test_data/photo.png"
else
raise StandardError.new "unhandled redirection in ActiveStorage::Blobs::RedirectController monkey patch"
end
end
# include ActiveStorage::SetBlob
# def show
# expires_in ActiveStorage.service_urls_expire_in
# redirect_to #blob.url(disposition: params[:disposition])
# end
end
ActiveStorage::Representations::RedirectController.class_eval do
def show
case #blob.attachments.first.record.class.name
when "Video"
redirect_to "/test_data/video.mp4"
when "Photo"
redirect_to "/test_data/photo.png"
else
raise StandardError.new "unhandled redirection in ActiveStorage::Blobs::RedirectController monkey patch"
end
end
end
With Rspec, I am trying to build a spec testing some basic http requests. I'm making a rookie mistake somewhere and need help finding it.
I am purposely making the spec fail with a nonsense expectation so the error message will tell me what I'm getting -- once I figure things out I'll correct the expectation:
user = create(:member)
json_data = {email: user.email, password: user.password}.to_json
post "api/v1/users/sign_in", json_data, format: :json
expect(last_response.body).to eq "foobar"
api/v1/users/sign_in routes to the following controller:
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params
end
end
This gives the error:
expected: "foobar"
got: "{\"{\\"email\\":\\"7abdiel_roob#smithrau.biz\\",\\"password\\":\\"12345678\\"}\"=>nil,
\"action\"=>\"create\", \"controller\"=>\"api/v1/sessions\"}"
Ok great. My data is getting to the server and the server sends it back, which is what I want. In my next step I try to grab the email. I change the controller to
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params[:email]
end
end
and I get
expected: "foobar"
got: " "
I looks to me that the params hash is using the JSOn data I sent in the request as the name of a key, not actually a value. Or maybe this is a strong_params thing? I've tried many things and can't seem to pull the data I want out of the params object.
What is happening is that you are double encoding the JSON data which you are sending in your spec.
json_data = {email: user.email, password: user.password}
post "api/v1/users/sign_in", json_data, format: :json
RSpec will automatically encode the request body as JSON for you.
I would like to print out response body generated by my app to stdout/stderr for debugging purposes. The traffic is server-server so I cannot use client tools to get hold of http traffic.
There is a mention of puts #response.body in http://api.rubyonrails.org/classes/ActionDispatch/Response.html, however in my app controller #response is undefined. Is there a way for me to print response body to logs in my rails app, and if so, how?
Based on the answer given, did it like this:
after_filter :print_response_body, :only => [:index]
def print_response_body
$stderr.puts response.body
end
In your controller, try
after_filter do
puts response.body
end
I am coding in Ruby-on-Rails
I would like to send a http request to another service but not wait for a response.
Pseudocode:
def notification
require 'net/http'
...
# send net/http request
Net::HTTP.post_form(url, params)
render :text => "Rendered quickly as did not wait for response from POST"
end
Is there any way to send the POST request and not wait for a response and just to quickly render the page?
You can try delayed_job. It is mainly used to run processes in background. Once you install delayed_job you can do like this.
require 'net/http'
def notification
...
your_http_request #calling method
render :text => "Rendered quickly as did not wait for response from POST"
end
def your_http_request
# send net/http request
Net::HTTP.post_form(url, params)
#do your stuff like where to save response
end
handle_asynchronously :your_http_request
My app creates a unique email for each user, and users send email to that address for processing. Using Sendgrid, I've piped incoming emails to my domain (hosted on Heroku) to an address:
site.com/receive_email
I use the TO field to determine the user, since the email address is randomly generated.
I've experimented using an external script like Mailman, but since I'm hosted on Heroku I'd need to have a worker running full time to keep this process going. Not really looking for that at the moment for this test app.
That leaves processing it as a POST request. I have access to POST hash (params["subject"], etc.) at receive_emails.
This is where I get stuck
Would you suggest to deal with raw data from the POST params, or can I use something like Mailman or ActionMailer to process the email for me?
I haven't used Sendgrid to turn emails into post requests, but it works fine with cloudmailin, a heroku addon. Here is an example where someone sends an email to your application, it is processed by cloudmailin/sendgrid and turned into a post, and then sends it to your controller, and then the controller looks through the message params, finds the sender from the email address, and if the sender doesn't already exist, creates an account for her:
class CreateUserFromIncomingEmailController < ApplicationController
require 'mail'
skip_before_filter :verify_authenticity_token
parse_message(params[:message])
def create
User.find_or_create_by_email(#sender)
end
private
def parse_message(message_params)
#message = Mail.new(message_params)
#recipients = #message.to
#sender = #message.from.first
end
end
Good luck.
ActionMailer already depends on the Mail gem, you could use it to parse the incoming email and extract the parts that you want. It is specially useful to deal with multipart emails.
require 'mail'
class IncomingEmails < ApplicationController
skip_before_filter :verify_authenticity_token
def receive_email
comment = Comment.new(find_user, message_body)
comment.save
rescue
# Reject the message
logger.error { "Incoming email with invalid data." }
end
private
def email_message
#email_message ||= Mail.new(params[:message])
# Alternatively, if you don't have all the info wrapped in a
# params[:message] parameter:
#
# Mail.new do
# to params[:to]
# from params[:from]
# subject params[:subject]
# body params[:body]
# end
end
def find_user
# Find the user by the randomly generated secret email address, using
# the email found in the TO header of the email.
User.find_by_secret_email(email_message.to.first) or raise "Unknown User"
end
def message_body
# The message body could contain more than one part, for example when
# the user sends an html and a text version of the message. In that case
# the text version will come in the `#text_part` of the mail object.
text_part = email_message.multipart? ? email_message.text_part : email_message.body
text_part.decoded
end
end