Automatic Airbrake errors with plain Ruby (no Rails or Sinatra) - ruby

Is there a way to integrate Airbrake with a pure Ruby project (not rails or sinatra) so that unanticipated errors get reported? I have it set up and I am able to catch errors by calling Airbrake.notify_or_ignore and passing in the exception, but I can't get it to report errors without explicitly calling this.
The following is the code that works for explicitly calling Airbrake.notify but doesn't work for sending errors to Airbrake without explicitly calling notify:
require 'airbrake'
Airbrake.configure do |config|
config.api_key = ENV['AIRBRAKE_API_KEY']
config.development_environments = []
config.ignore_only = []
end
I tried adding Rack as a middleware with the following code:
require 'rack'
require 'airbrake'
Airbrake.configure do |config|
config.api_key = ENV['AIRBRAKE_API_KEY']
config.development_environments = []
config.ignore_only = []
end
app = Rack::Builder.app do
run lambda { |env| raise "Rack down" }
end
use Airbrake::Rack
run app
But I get an "undefined method `use' for main:Object (NoMethodError)"
Any thoughts?

Copied from Mark's comment's link to airbrake for future googlers:
# code at http://gist.github.com/3350
# tests at http://gist.github.com/3354
class Airbrake < ActiveResource::Base
self.site = "http://your_account.airbrake.io"
class << self
##auth_token = 'your_auth_token'
def find(*arguments)
arguments = append_auth_token_to_params(*arguments)
super(*arguments)
end
def append_auth_token_to_params(*arguments)
opts = arguments.last.is_a?(Hash) ? arguments.pop : {}
opts = opts.has_key?(:params) ? opts : opts.merge(:params => {})
opts[:params] = opts[:params].merge(:auth_token => ##auth_token)
arguments << opts
arguments
end
end
end
class Error < Airbrake
end
# Errors are paginated. You get 30 at a time.
#errors = Error.find :all
#errors = Error.find :all, :params => { :page => 2 }

Related

Uninitialized constant NameError in Rspec

When I run rails c, I can call the following class and the method works:
test = SlackService::BoardGameNotifier
test.create_alert("test")
>>method works
I'm trying to set this up in rspec like this:
require 'spec_helper'
require 'slack-notifier'
RSpec.describe SlackService::BoardGameNotifier do
describe '#notify' do
#notifier = SlackService::BoardGameNotifier
it 'pings Slack' do
error = nil
message = "test"
expect(notifier).to receive(:ping).with(message)
notifier.send_message()
end
end
end
But I keep getting the error:
NameError:
uninitialized constant SlackService
Does this have to do with how I set up the module?
My current setup:
slack_service/board_game_notifier.rb
module SlackService
class BoardGameNotifier < BaseNotifier
WEBHOOK_URL = Rails.configuration.x.slack.url
DEFAULT_OPTIONS = {
channel: "board-games-channel",
text: "board games alert",
username: "bot",
}
def create_alert(message)
message #testing
end
end
end
slack_service/base_notifier.rb
module SlackService
class BaseNotifier
include Singleton
def initialize
webhook_url = self.class::WEBHOOK_URL
options = self.class::DEFAULT_OPTIONS
#notifier = Slack::Notifier.new(webhook_url, options)
end
def self.send_message
message = instance.create_alert("test")
instance.notify(message)
end
def notify(message)
#notifier.post blocks: message
end
end
end
Add this to your spec_helper.rb
# spec_helper.rb
ENV["RAILS_ENV"] ||= "test"
require File.expand_path("../config/environment", __dir__)
When running RSpec, Rails doesn't automatically boot up, and therefore doesn't automatically load all the libraries.
Also, I'd suggest creating a .rspec in your app's root folder with the following lines so that spec_helper is automatically loaded for all your RSpec tests:
# .rspec
--format documentation
--color
--require spec_helper
I would use the described_class from Rspec
require 'spec_helper'
require 'slack-notifier'
RSpec.describe ::SlackService::BoardGameNotifier do
describe '#notify' do
it 'pings Slack' do
error = nil
message = "test"
expect(described_class).to receive(:ping).with(message)
notifier.send_message()
end
end
end

Unable to use any_instance on Twitter gem's user_timline

I am still quite fresh to Ruby, and especially testing in Ruby. Hopefully the code is not a trainwreck :) I am having issues using any_instance with the Twitter gem, while it works fine on my own classes.
This is (what I believe) the relevant code
require 'twitter'
require 'minitest/unit'
require 'mocha/mini_test'
omitting for brevity....
args = { id: 573536452149182464, id_str: 73536452149182464, text: 'This is an initial tweet from the user'}
initial_tweet = ::Twitter::Tweet.new(args)
::Twitter::REST::Timelines.any_instance.stubs(:user_timeline).returns(initial_tweet)
The code produces the following error:
Minitest::UnexpectedError: NoMethodError: undefined method `any_instance|' for Twitter::REST::Timelines:Module
Are principles to stubbing gems different, am I approaching it wrong?
EDIT: I have added the entire code for the two classes below.
twitter.rb
require 'rubygems'
require 'cinch'
require 'cinch/commands'
require 'twitter'
require 'shorturl'
module Gigabot
module Commands
class Twitter
include Cinch::Plugin
include Cinch::Commands
def initialize(bot)
super(bot)
#client = create_client
#follow = config[:follow]
#channels = bot.config.channels
#latest_tweets = Hash.new
set_initial_tweets
end
timer 60, method: :twitter_update
def twitter_update
#follow.each do |user|
new_tweet = #client.user_timeline(user, options = {exclude_replies: true}).first
if #latest_tweets[user] != new_tweet
short_url = ShortURL.shorten("https://twitter.com/#{user}/status/#{new_tweet.id}")
reply = Format(:bold, "<#{user}> ") + "#{new_tweet.full_text} [#{short_url}]"
reply = reply.gsub(/\n/,' ')
#channels.each {|channel| Channel(channel).send(reply)}
#latest_tweets[user] = new_tweet
end
end
end
private
def create_client
::Twitter::REST::Client.new do |c|
c.consumer_key = config[:consumer_key]
c.consumer_secret = config[:consumer_secret]
c.access_token = config[:access_token]
c.access_token_secret = config[:access_token_secret]
end
end
def set_initial_tweets
#follow.each do |user|
#latest_tweets[user] = #client.user_timeline(user, options = {exclude_replies: true}).first
end
end
end
end
end
twitter_test.rb
require 'twitter'
require 'minitest/unit'
require 'mocha/mini_test'
require File.dirname(__FILE__) + '/../../../helper'
require File.dirname(__FILE__) + '/../../../../lib/gigabot/commands/twitter'
module Gigabot
module Commands
class TwitterTest < TestCase
def setup
bot = Cinch::Bot.new
bot.loggers.level = :fatal
bot.config.plugins.options[Twitter] = {
consumer_key: 'test_key',
consumer_secret: 'test_key_secret',
access_token: 'test_access_token',
access_token_secret: 'test_access_token_secret',
follow: %w(follow1 follow2)
}
args = { id: 573536452149182464, id_str: 73536452149182464, text: 'This is an initial tweet from the user'}
initial_tweet = ::Twitter::Tweet.new(args)
::Twitter::REST::Timelines.any_instance.stubs(:user_timeline).returns(initial_tweet)
#plugin = Twitter.new(bot)
end
def test_create_twitter_client_on_initialize
refute_nil(#plugin.instance_variable_get(:#client))
end
end
end
end

How to test if some specific rack middleware is being used?

To be more particular, I'm talking about sentry-raven and sinatra here. I saw examples testing sinatra applications, or middlewares. But I didn't see ones testing if some particular middleware is present. Or should I be testing behavior, not configuration (or how should I call it)?
The important thing (I'd say) is the behaviour, but if you wish to check for middleware there are 2 ways I'd suggest after taking a delve into the Sinatra source (there are possibly much easier/better ways):
The env
In the Sinatra source there's a method that uses the env to check if a middleware is already present:
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? #app.call(env) : super
end
You could do the same thing in a route, e.g.
get "/env-keys" do
env.keys.inspect
end
It'll only show you the middleware if it's inserted something in env hash, e.g.
class MyBad
def initialize app, options={}
#app = app
#options = options
end
def call env
#app.call env.merge("mybad" => "I'm sorry!")
end
end
output:
["SERVER_SOFTWARE", "SERVER_NAME", "rack.input", "rack.version", "rack.errors", "rack.multithread", "rack.multiprocess", "rack.run_once", "REQUEST_METHOD", "REQUEST_PATH", "PATH_INFO", "REQUEST_URI", "HTTP_VERSION", "HTTP_HOST", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_ACCEPT", "HTTP_USER_AGENT", "HTTP_DNT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "GATEWAY_INTERFACE", "SERVER_PORT", "QUERY_STRING", "SERVER_PROTOCOL", "rack.url_scheme", "SCRIPT_NAME", "REMOTE_ADDR", "async.callback", "async.close", "rack.logger", "mybad", "rack.request.query_string", "rack.request.query_hash", "sinatra.route"]
It's near the end of that list.
The middleware method
There's also a method called middleware in Sinatra::Base:
# Middleware used in this class and all superclasses.
def middleware
if superclass.respond_to?(:middleware)
superclass.middleware + #middleware
else
#middleware
end
end
Call it in the class definition of a modular app and you can get the middlewares in an array:
require 'sinatra/base'
class AnExample < Sinatra::Base
use MyBad
warn "self.middleware = #{self.middleware}"
output:
self.middleware = [[MyBad, [], nil]]
There may be a way to get it from Sinatra::Application, but I haven't looked.
With help from ruby-raven guys, we've got this:
ENV['RACK_ENV'] = 'test'
# the app: start
require 'sinatra'
require 'sentry-raven'
Raven.configure(true) do |config|
config.dsn = '...'
end
use Raven::Rack
get '/' do
'Hello, world!'
end
# the app: end
require 'rspec'
require 'rack/test'
Raven.configure do |config|
logger = ::Logger.new(STDOUT)
logger.level = ::Logger::WARN
config.logger = logger
end
describe 'app' do
include Rack::Test::Methods
def app
#app || Sinatra::Application
end
class TestRavenError < StandardError; end
it 'sends errors to sentry' do
#app = Class.new Sinatra::Application do
get '/' do
raise TestRavenError
end
end
allow(Raven.client).to receive(:send).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven.client).to have_received(:send)
end
end
Or if raven sending requests is in the way (when tests fail because of raven sending requests, and not because of the underlying error), one can disable them:
Raven.configure(true) do |config|
config.should_send = Proc.new { false }
end
And mock Raven.send_or_skip instead:
...
allow(Raven).to receive(:send_or_skip).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven).to have_received(:send_or_skip)
...

Should I be using EM::Synchrony::Multi or EM::Synchrony::FiberIterator with Goliath?

Maybe this is the wrong approach, but I'm trying to parallelize em-hiredis puts and lookups in Goliath with EM::Synchrony::Multi or EM::Synchrony::FiberIterator. However, I can't seem to access basic values initialized in the config. I keep getting method_missing errors.
Here's the basic watered down version of what I'm trying to do:
/lib/config/try.rb
config['redisUri'] = 'redis://localhost:6379/0'
config['redis_db'] ||= EM::Hiredis.connect
config['user_agent'] = "MyCrawler Mozilla/5.0 Compat etc."
Here's the basic Goliath Setup
/try.rb
require "goliath"
require "em-hiredis"
require "em-synchrony/fiber_iterator"
require "em-synchrony/em-hiredis"
require "em-synchrony/em-multi"
class Try < Goliath::API
use Goliath::Rack::Params
use Goliath::Rack::DefaultMimeType
def response(env)
case env['REQUEST_PATH']
when "/start" then
start_crawl()
body = "STARTING"
[200, {}, body]
end
end
def start_crawl
urls = ["http://www.example.com/",
"http://www.example.com/photos/",
"http://www.example.com/video/",
]
EM::Synchrony::FiberIterator.new(urls, 3).each do |url|
p "#{user_agent}"
redis_db.sadd 'test_queue', url
end
# multi = EM::Synchrony::Multi.new
# urls.each_with_index do |url, index|
# p "#{user_agent}"
# multi.add index, redis_db.sadd('test_queue', url)
# end
end
end
However, I keep getting errors where Goliath doesn't know what user_agent is or redis_db which were initialized in the config.
[936:INFO] 2012-09-21 23:47:10 :: Starting server on 0.0.0.0:9000 in development mode. Watch out for stones.
/Users/ewu/.rvm/gems/ruby-1.9.3-p194#crawler/gems/goliath-1.0.0/lib/goliath/api.rb:143:in `method_missing': undefined local variable or method `user_agent' for #<Try:0x007ff5a431c4e0 #opts={}> (NameError)
from ./lib/try.rb:27:in `block in start_crawl'
from /Users/ewu/.rvm/gems/ruby-1.9.3-p194#crawler/gems/em-synchrony-1.0.2/lib/em-synchrony/fiber_iterator.rb:10:in `call'
from /Users/ewu/.rvm/gems/ruby-1.9.3-p194#crawler/gems/em-synchrony-1.0.2/lib/em-synchrony/fiber_iterator.rb:10:in `block (2 levels) in each'
...
...
...
Ideally I'd be able to get FiberIterator working, because I have additional conditionals to check for:
EM::Synchrony::FiberIterator.new(urls, 3).each do |new_url}
is_member = redis_db.sismember('crawled_urls', new_url)
is_member += redis_db.sismember('queued_urls', new_url)
if is_member == 0
redis_db.lpush 'crawl_queue', new_url
redis_db.sadd 'queued_urls', new_url
end
end
I don't think your config file is getting loaded. The name of try.rb needs to match the name of the robojin.rb file in the config directory.

How do I use omniauth in rspec for sinatra?

Shortened version:
Using the omniauth gem for sinatra, I can't get rspec log in to work and keep my session for subsequent requests.
Based on suggestions from http://benprew.posterous.com/testing-sessions-with-sinatra, and turning off sessions, I've isolated the problem to this:
app.send(:set, :sessions, false) # From http://benprew.posterous.com/testing-sessions-with-sinatra
get '/auth/google_oauth2/callback', nil, {"omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2] }
# last_request.session => {"uid"=>"222222222222222222222", :flash=>{:success=>"Welcome"}}
# last_response.body => ""
follow_redirect!
# last_request.session => {:flash=>{}}
# last_response.body => Html for the homepage, which is what I want
How do I get rspec to follow the redirect and retain the session variables? Is this possible in Sinatra?
From http://benprew.posterous.com/testing-sessions-with-sinatra, it seems like I'd have to send the session variables on each get/post request that I require login for, but this wouldn't work in the case of redirects.
The details:
I'm trying to use the omniauth gem in sinatra with the following setup:
spec_helper.rb
ENV['RACK_ENV'] = 'test'
# Include web.rb file
require_relative '../web'
# Include factories.rb file
require_relative '../test/factories.rb'
require 'rspec'
require 'rack/test'
require 'factory_girl'
require 'ruby-debug'
# Include Rack::Test in all rspec tests
RSpec.configure do |conf|
conf.include Rack::Test::Methods
conf.mock_with :rspec
end
web_spec.rb
describe "Authentication:" do
before do
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:google_oauth2, {
:uid => '222222222222222222222',
:info => {
:email => "someone#example.com",
:name => 'Someone'
}
})
end
describe "Logging in as a new user" do
it "should work" do
get '/auth/google_oauth2/'
last_response.body.should include("Welcome")
end
end
end
When trying to authenticate, I get a <h1>Not Found</h1> response. What am I missing?
On the Integration testing page of the omniauth docs, it mentions adding two environment variables:
before do
request.env["devise.mapping"] = Devise.mappings[:user]
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
But seems to be for rails only, as I added
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
to my before block in my spec and I get this error:
Failure/Error: request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
ArgumentError:
wrong number of arguments (0 for 1)
Edit:
Calling get with
get '/auth/google_oauth2/', nil, {"omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2]}
seems to give me last_request.env["omniauth.auth"] equal to
{"provider"=>"google_oauth2", "uid"=>"222222222222222222222", "info"=>{"email"=>"someone#example.com", "name"=>"Someone"}}
which seems right, but last_response.body still returns
<h1>Not Found</h1>
A partial answer...
The callback url works better, with the added request environment variables:
get '/auth/google_oauth2/callback', nil, {"omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2]}
follow_redirect!
last_response.body.should include("Welcome")
However, this doesn't work with sessions after the redirect, which is required for my app to know someone is logged in. Updated the question to reflect this.
Using this gist (originating from https://stackoverflow.com/a/3892401/111884) to store session data, I got my tests to store the session, allowing me to pass the session to further requests.
There might be an easier way though.
Setup code:
# Omniauth settings
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:google_oauth2, {
:uid => '222222222222222222222',
:info => {
:email => "someone#example.com",
:name => 'Someone'
}
})
# Based on https://gist.github.com/375973 (from https://stackoverflow.com/a/3892401/111884)
class SessionData
def initialize(cookies)
#cookies = cookies
#data = cookies['rack.session']
if #data
#data = #data.unpack("m*").first
#data = Marshal.load(#data)
else
#data = {}
end
end
def [](key)
#data[key]
end
def []=(key, value)
#data[key] = value
session_data = Marshal.dump(#data)
session_data = [session_data].pack("m*")
#cookies.merge("rack.session=#{Rack::Utils.escape(session_data)}", URI.parse("//example.org//"))
raise "session variable not set" unless #cookies['rack.session'] == session_data
end
end
def login!(session)
get '/auth/google_oauth2/callback', nil, { "omniauth.auth" => OmniAuth.config.mock_auth[:google_oauth2] }
session['uid'] = last_request.session['uid']
# Logged in user should have the same uid as login credentials
session['uid'].should == OmniAuth.config.mock_auth[:google_oauth2]['uid']
end
# Based on Rack::Test::Session::follow_redirect!
def follow_redirect_with_session_login!(session)
unless last_response.redirect?
raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
end
get(last_response["Location"], {}, { "HTTP_REFERER" => last_request.url, "rack.session" => {"uid" => session['uid']} })
end
def get_with_session_login(path)
get path, nil, {"rack.session" => {"uid" => session['uid']}}
end
Sample rspec code:
describe "Authentication:" do
def session
SessionData.new(rack_test_session.instance_variable_get(:#rack_mock_session).cookie_jar)
end
describe "Logging in as a new user" do
it "should create a new account with the user's name" do
login!(session)
last_request.session[:flash][:success].should include("Welcome")
get_with_session_login "/"
follow_redirect_with_session_login!(session)
last_response.body.should include("Someone")
end
end
end

Resources