Unable to stub external request - Wrong regexp? - ruby

I'm trying to craft a stub_request that matches all requests to fcm.googleapis.com. Our backend is supposed to send a push notification to its users when new Posts or Comments is created. Our tests trigger a lot of requests to fcm.googleapis.com, that's why I need a generic matcher.
EDIT: The test failed because I had added stub_request to the spec_helper. The failing test was not rspec, but an ordinary ActionController::TestCase test. My bad! :-|
spec/spec_helper.rb
18 RSpec.configure do |config|
19 require_relative "../test/mock_helper"
20 require "webmock/rspec"
21
22 WebMock.disable_net_connect!
23 config.before(:each) do
24 stub_request(:post, "https://fcm.googleapis.com/fcm/send").
25 with(body: /.*/).
26 to_return(status: 200)
27
But when I run the tests it does not look like WebMock cares about my stub_request. What might be wrong?
Running tests
Error:
PostsControllerTest#test_should_create_post:
WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled.
Unregistered request: POST https://fcm.googleapis.com/fcm/send with body
'{"registration_ids":[null],"notification":
{"title":"Fred Flintstone har skrevet en melding i Bedrock Sportsballteam",
"text":"New post!?"},"data":{"notification":{"avatar":null,"group_id":131900578,
"issued_at":"2018-06-25T13:37:28.746+02:00",
"full_name":"Fred Flintstone","id":700,"post_id":980190963,
"role_id":1}}}' with headers {'Authorization'=>'key=KEY', 'Content-Type'=>'application/json'}
You can stub this request with the following snippet:
stub_request(:post, "https://fcm.googleapis.com/fcm/send").
with(:body => "{\"registration_ids\":[null],\"notification\":
{\"title\":\"Fred Flintstone har skrevet en melding i Bedrock Sportsballteam\",
\"text\":\"New post!?\"},\"data\":{\"notification\":
{\"avatar\":null,\"group_id\":131900578,
\"issued_at\":\"2018-06-25T13:37:28.746+02:00\",
\"full_name\":\"Fred Flintstone\",\"id\":700,\"post_id\":980190963,\"role_id\":1}}}",
:headers => {'Authorization'=>'key=KEY',
'Content-Type'=>'application/json'}).
to_return(:status => 200, :body => "", :headers => {})
My backend should send a push notifiction to our users when a new Post is created updated.
app/models/post.rb
16 class Post < ApplicationRecord
25 after_save :send_notifications
82 def send_notifications
83 PUSH_NOTIFICATIONS.new_post_in_group(post: self)
84 end
bin/rails test test/controllers/posts_controller_test.rb:57
57 test "should create post" do
58 assert_difference("Post.count") do
59 assert_difference("PostImage.count", 3) do
60 post :create, params: {
61 group_id: groups(:sportsball).id,
62 post: {
63 text: "New post!?",
64 is_pinned: "true"
73 }
74 }
75
76 post = Post.last
77 assert_equal true, post.is_pinned
78
79 assert_response :created, response.body
80 assert valid_json?(response.body), "Invalid json: #{response.body}"
81
82 json = JSON.parse(response.body).deep_symbolize_keys
83
84 end
85 end
86 end
PushNotifications
class PushNotifications
def initialize
#fcm = FCM.new(ENV["FCM_SERVER_KEY"])
end
def new_post_in_group(post:)
registration_ids = all_users_except_author(post)
author = post.user
group = post.group
return unless registration_ids
options = {
notification: {
title: "#{author.name} har skrevet en melding i #{group.name}",
text: post.text.truncate(27)
},
data: {
notification:
{
avatar: author.avatar,
# comment_id: '646',
group_id: group.id,
issued_at: Time.now,
full_name: author.name,
id: 700, # 700 = new post. The client knows what to do by looking at this id.
post_id: post.id,
role_id: author.role_id(group)
}
}
}
response = #fcm.send(registration_ids, options)
puts "Sendt: #{response}" if ENV["DEBUG"]
end
private
def all_users_except_author(post)
recipients = post.group.users.pluck(:fcm_token)
recipients.delete(post.user.id)
recipients
end
end
config/initializers/PushNotifications.rb
1 require "#{Rails.root}/lib/push_notifications"
2
3 puts "initialize PushNotifications"
4 PUSH_NOTIFICATIONS ||= PushNotifications.new

The test failed because I had added stub_request to the spec_helper. The failing test was not rspec, but an ordinary ActionController::TestCase test. My bad! :-|

Related

Custom RSpec formatter to display passed test and result of except

Is there a way to create a custom formatter where the passed test details with a list of except is showed?
A bit of a background for this question: we are trying to migrate to RSpec for our hardware integration and system test. The results should be pushed to CouchDB. What I am trying to achieve is a reporter that could generate a similar YAML output like the following snippet:
{
"_id": "0006b6f0-c1bd-0135-1a98-455c37fe87f1",
"_rev": "1-9c9786b4b4681ee8493f182d4fc56ef9",
"sha1_repo": "68bb327b540097c10683830f0d82acbe54a47f03",
"steps": [
{
"result": "pass",
"description": "Time for Routing expect OK: 126 micro seconds (DLC and Data also OK)"
},
{
"result": "pass",
"description": "Time for Routing expect OK: 146 micro seconds (DLC and Data also OK)"
},
{
"result": "pass",
"description": "Time for Routing expect OK: 162 micro seconds (DLC and Data also OK)"
}
],
"time_start": "1513119108000",
"time_end": "1513119108000",
"result": "pass",
"testcase_title": "Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
"testcase_id": "TC_1zu1_BAF_Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
"hierarchy": [
"Hardware Integration Test",
"1 - Routing",
"1.1 Normal Routing",
"1zu1_BAF_TestCases",
"CAN_to_CAN"
]
}
With failed test there is no problem to achieve this, but we need also the results from passed test in order to be able to create long term statistics.
I can override the passed event of RSPec but the example object delivers only the description and no more info.
class EliteReporter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def example_passed(passed)
#output.printf "pass \n #{passed.example.description}"
end
end
Thank you in advance for any help.
Finally with the help of my colleague and thanks of the Tip from RSPec Emailing list I could do this.
I have created a Recorder class that collects the test results, than override the Expect methode. This way in the custom formatter I can collect all the passed results:
class ExpectWrapper
def initialize(_expect, _recorder, _description)
#expect = _expect
#recorder = _recorder
#description = _description
end
def to(matcher, failure_message=nil)
begin
expect_ret = #expect.to(matcher, failure_message) # test
# for tests that aggregate failures
if expect_ret.instance_of?(TrueClass)
#recorder.record(matcher.actual, matcher.description, #description)
else
#recorder.record_error(matcher.actual, matcher.description, failure_message, #description)
end
expect_ret
rescue RSpec::Expectations::ExpectationNotMetError => e
# for test that do not aggregate failures
#recorder.record_error(matcher.actual, matcher.description, failure_message, #description)
raise e
end
end
end
class Recorder
def self.start
##data = []
return Recorder.new
end
def record(expect, data, description)
##data << { :pass => true, :expect => expect, :value => data, :description => description }
self
end
def record_error(expect, data, failure_message, description)
##data << { :pass => false, :expect => expect, :value => data, :message => failure_message, :description => description }
self
end
def self.data
##data
end
def expect(object, value, description = "")
return ExpectWrapper.new(object.expect(value), self, description)
end
end
The custom formatter would look the following, is just an example, the data could be than put to JSON and pushed to Couch:
class EliteVerboseFormatter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def initialize(output)
#output = output
end
def example_passed(notification)
#output.puts( format_output(notification.example, Recorder) )
end
def get_test_name( group, description)
"#{group.example.example_group}/#{description}".gsub('RSpec::ExampleGroups::','')
end
def format_output( example, recorder )
test_case = get_test_name( example.example_group, example.description)
str = "**********TEST: #{test_case} ************\n"
recorder.data.each do |d|
str += sprintf("%s: ---> expected '%-10s' to '%-20s' DESC: %s \n", d[:pass] ? 'PASS' : 'FAIL', d[:expect], d[:value], d[:description])
end
str
end
def example_failed(notification)
#output.puts(format_output( notification.example, Recorder))
exception = notification.exception
message_lines = notification.fully_formatted_lines(nil, RSpec::Core::Notifications::NullColorizer)
exception_details = if exception
{
# drop 2 removes the description (regardless of newlines) and leading blank line
:message => message_lines.drop(2).join("\n"),
:backtrace => notification.formatted_backtrace.join("\n"),
}
end
#output.puts RSpec::Core::Formatters::ConsoleCodes.wrap(exception_details[:message], :failure)
end
end
I think you can read the Module: RSpec::Core::Formatters
you might find something helpful.
P.S. I have used Cucumber for many times, and I once wanted to custom cucumber formatter to display every step's details no matter it failed or passed. I finally got the solution by reading cucumber core documents.so I think maybe rspec core document can help you to find the solution.
I find that I cannot put the code in comment, so I put it here.
edit your code as below:
class EliteReporter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def example_passed(example)
example_failed(example)
end
end
I hope it can be helpful: )

rails 2 read file from http post body

I am using rails 2 and I am trying to read a file from http post request body.
def get_file_from_request(request, file_name)
file = Tempfile.new([File.basename(file_name, ".*"), File.extname(file_name)])
Rails.logger.info "#{request.body.size}" # returns 130
file.write(request.body.read)
file
end
If I do
request.inspect
I get following:
...
"rack.version"=>[1, 2], "rack.input"=>#<PhusionPassenger::Utils::TeeInput:0x0000000d3a6148 #len=130, #socket=nil, #bytes_read=130, #tmp=#<StringIO:0x0000000d3a60d0>>, "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>false, "rack.multiprocess"=>true, "rack.run_once"=>false, "rack.url_scheme"=>"http", "rack.hijack?"=>true, "rack.hijack"=>#<Proc:0x0000000d3a5ec8#/data/rbenv/.rbenv/versions/1.9.3-p550/lib/ruby/gems/1.9.1/gems/passenger-4.0.42/lib/phusion_passenger/rack/thread_handler_extension.rb:69 (lambda)>,
...
Are there any obvious problems with my approach?
Can someone help me with extracting files from request body? The file is definitely not 130 bytes. Its is like 3 MB.
EDIT: Here is the controller class
class Api::PhotosController < Api::BaseController
before_filter :verify_session, :except => [:new, :show_image]
def create
Rails.logger.info "upload photo request: params=#{params.inspect}, request=#{request.inspect}"
...
file = get_file_from_request(request, params[:file_name])
...
rescue => e
Rails.logger.info "Could not upload photo: params=#{params.inspect}, exception=#{e.backtrace}"
render_error(e)
end
private
def get_file_from_request(request, file_name)
file = Tempfile.new([File.basename(file_name, ".*"), File.extname(file_name)])
Rails.logger.info "#{request.body.size}" # returns 130
file.write(request.body.read)
file
end
end
In Rails 2 you may have to use request.raw_post. Does that help?

Rails 4 ActiveMailer: email loses attachments

This is in a Rails 4 app. I'm trying to send emails with attachments for the first time. It's a really basic test email:
class Emailer < ActionMailer::Base
def test_attachments
attachments['file.pdf'] = File.read('/path/to/file.pdf')
mail(to: me#me.com, from: sender#me.com, body: "")
end
end
Emailer.test_attachments.deliver
This results in an error. IndexError string not matched Looking at the API docks, it looks like attachments is an instance method, so my next try uses that:
class Emailer < ActionMailer::Base
def test_attachments
mail(to: me#me.com, from: sender#me.com, body: "")
mail.attachments['file.pdf'] = File.read('/path/to/file.pdf')
end
end
Emailer.test_attachments.deliver
This results in the attachment contents sent in the body of the email. Here's the mail instance:
#<Mail::Message:70259446276180, Multipart: false, Headers: <Date: Tue, 08 Jul 2014 12:09:14 -0400>, <From: sender#me.com>, <To: ["me#me.com"]>, <Message-ID: <53bc17c1e3194_122583fe68982dbc473cc#Johns-iMac-3.local.mail>>, <Subject: >, <Mime-Version: 1.0>, <Content-Type: text/html>, <Content-Transfer-Encoding: 7bit>>

How to send binary data through a HTTP request using a Ruby gem?

I'm trying to find out a way to reproduce a HTTP request that sends binary data in the payload as well as sets a Content-Type: binary header, like the following command with cURL:
echo -e '\x14\x00\x00\x00\x70\x69\x6e\x67\x00\x00' | curl -X POST \
-H 'Content-Type: binary' \
-H 'Accept: */*' \
-H 'Accept-Encoding: gzip,deflate,sdch' \
-H 'Accept-Language: en-US,en;q=0.8,pt;q=0.6' \
-H 'Cookie: JSESSIONID=m1q1hkaptxcqjuvruo5qugpf' \
--data-binary #- \
--url 'http://202.12.53.123' \
--trace-ascii /dev/stdout
I've already tried using the REST Client (https://github.com/rest-client/rest-client) and HTTPClient (https://github.com/nahi/httpclient), but unsuccessfully. Using the code below the server responded with HTTP 500. Has anyone done it before or is it not possible for the purpose to which the gems were designed?
Ruby code:
require 'rest-client'
request = RestClient::Request.new(
:method => :post,
:url => 'http://202.12.53.123',
:payload => %w[14 00 00 00 70 69 6e 67 00 00],
:headers => {
:content_type => :binary,
:accept => '*/*',
:accept_encoding => 'gzip,deflate,sdch',
:accept_language => 'en-US,en;q=0.8,pt;q=0.6',
:cookies => {'JSESSIONID' => 'm1q1hkaptxcqjuvruo5qugpf'}
}
)
request.execute
UPDATE (w/ one possible solution)
I ended up running the request with the HTTParty (following the direction given by #DemonKingPiccolo) and it worked. Here's the code:
require 'httparty'
hex_data = "14 00 00 00 70 69 6e 67 00 00"
response = HTTParty.post(
'http://202.12.53.123',
:headers => {
'Content-Type' => 'binary',
'Accept-Encoding' => 'gzip,deflate,sdch',
'Accept-Language' => 'en-US,en;q=0.8,pt;q=0.6'
},
:cookies => {'JSESSIONID' => 'm1q1hkaptxcqjuvruo5qugpf'},
:body => [hex_data.gsub(/\s+/,'')].pack('H*').force_encoding('ascii-8bit')
)
puts response.body, response.code, response.message, response.headers.inspect
The body can also be written as suggested by #gumbo:
%w[14 00 00 00 70 69 6e 67 00 00].map { |h| h.to_i(16) }.map(&:chr).join
I just tried this and it worked like a charm:
require "net/http"
uri = URI("http://example.com/")
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)
req.body = "\x14\x00\x00\x00\x70\x69\x6e\x67\x00\x00"
req.content_type = "application/octet-stream"
http.request(req)
# => #<Net::HTTPOK 200 OK readbody=true>
I verified that the data POSTed correctly using RequestBin.
Net::HTTP is really rough around the edges and not much fun to use (for example, you have to format your Cookie headers manually). Its main benefit is that it's in the standard library. A gem like RestClient or HTTParty might be a better choice, and I'm pretty sure any of them will handle binary data at least as easily.

rails functional test will not destroy

I have a functional test that keeps failing and I'm not sure why. This is part of a forum and the test is to ensure that the author of a post is allowed to delete their own posts.
I am able to destroy the post in the console and in the browser when I try manually, I just can't figure out what is going wrong.
Here is the destroy action of the controller:
def destroy
#post = Post.find(params[:id])
if #post.player_id == current_player || current_player.admin == true # I can't delete anyone else's posts unless I am the administrator.
if #post.topic.posts_count > 1 # if topic is more than one post, delete just the post
#post.destroy
flash[:notice] = "Post was successfully destroyed."
redirect_to topic_path(#post.topic)
else # else, if the topic is only one post, delete the whole thing
#post.topic.destroy
flash[:notice] = "Topic was successfully deleted."
redirect_to forum_path(#post.forum)
end
else # You are not the admin or the topic starter
flash[:notice] = "You do not have rights to delete this post."
redirect_to topic_path(#post.topic)
end
end
Here is the posts.yml file:
one:
id: 1
body: MyText
forum_id: 1
topic_id: 1
player_id: 2
two:
id: 2
body: MyText
forum_id: 1
topic_id: 1
player_id: 2
three:
id: 3
body: MyText
forum_id: 1
topic_id: 2
player_id: 3
Here is the test that keeps failing:
test "should destroy post as author" do
sign_in players(:player2)
assert_difference('Post.count', -1) do # Line 41
delete :destroy, :id => posts(:one)
end
assert_redirected_to topic_url(assigns(:topic))
end
And here is the error I'm getting:
1) Failure: test_should_destroy_post_as_author(PostsControllerTest) [../test/functional/posts_controller_test.rb:41]:
"Post.count" didn't change by -1.
<2> expected but was <3>.
I would greatly appreciate any help with this. I feel like I'm hitting my head against a wall when I'm sure the answer is something simple that I'm missing. Thanks in advance.
I'm not sure why that particular wording is not working, but I fixed it so that when I destroy the post like I do in the console, the test passes.
Instead of: delete :destroy, :id => #post
I used: Post.destroy(#post)

Resources