I can't understand what the below ruby code does. Can anyone give me some explanation. Thanks!
map '/healthz' do
run Healthz.new(logger)
end
The Healthz is:
class Healthz
def initialize(logger)
#logger = logger
end
def call(env)
#logger.debug "healthz access"
healthz = Component.updated_healthz
[200, { 'Content-Type' => 'application/json', 'Content-Length' => healthz.length.to_s }, healthz]
rescue => e
#logger.error "healthz error #{e.inspect} #{e.backtrace.join("\n")}"
raise e
end
end
And the lib used are:
require "eventmachine"
require 'thin'
require "yajl"
require "nats/client"
require "base64"
require 'set'
Since you're using eventmachine and thin, I'd guess that code is some kind of routing code for a simple web application.
That is, it maps the /healtz route of the application to the Healtz class, so that if you start up the app, and point your browser to localhost:<whatever_port_thin_uses>/healtz, it would start up a Healtz.new instance for you.
Since I don't know what Healtz actually does, I've no idea what will actually happen, but my guess is that it's some kind of rack application.
And, as I already stated, this is just my guess, from seeing the list of libs you're using.
Related
I want to get hold of the app instance being tested by rack-test so that I can mock some of its methods. I thought I could simply save the app instance in the app method, but for some strange reason that doesn't work. It seems like rack-test simply uses the instance to get the class, then creates its own instance.
I've made a test to demonstrate my issue (it requires the gems "sinatra", "rack-test" and "rr" to run):
require "sinatra"
require "minitest/spec"
require "minitest/autorun"
require "rack/test"
require "rr"
describe "instantiated app" do
include Rack::Test::Methods
def app
cls = Class.new(Sinatra::Base) do
get "/foo" do
$instance_id = self.object_id
generate_response
end
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am a response"]
end
end
# Instantiate the actual class, and not a wrapped class made by Sinatra
#app = cls.new!
return #app
end
it "should have the same object id inside response handlers" do
get "/foo"
assert_equal $instance_id, #app.object_id,
"Expected object IDs to be the same"
end
it "should trigger mocked instance methods" do
mock(#app).generate_response {
[200, {"Content-Type" => "text/plain"}, "I am MOCKED"]
}
get "/foo"
assert_equal "I am MOCKED", last_response.body
end
end
How come rack-test isn't using the instance I provided? How do I get hold of the instance that rack-test is using, so that I can mock the generate_response method?
Update
I have made no progress. It turns out rack-test creates the tested instance on the fly when the first request is made (i.e. get("/foo")), so it's not possible to mock the app instance before then.
I have used rr's stub.proxy(...) to intercept .new, .new! and .allocate; and added a puts statement with the instance's class name and object_id. I have also added such statements in the tested class' constructor, as well as a request handler.
Here's the output:
From constructor: <TestSubject 47378836917780>
Proxy intercepted new! instance: <TestSubject 47378836917780>
Proxy intercepted new instance: <Sinatra::Wrapper 47378838065200>
From request handler: <TestSubject 47378838063980>
Notice the object ids. The tested instance (printed from the request handler) never went through .new and was never initialized.
So, confusingly, the instance being tested is never created, but somehow exists none the less. My guess was that allocate was being used, but the proxy intercept shows that it doesn't. I ran TestSubject.allocate myself to verify that the intercept works, and it does.
I also added the inherited, included, extended and prepended hooks to the tested class and added print statements, but they were never called. This leaves me completely and utterly stumped as to what kind of horrible black magic rack-test is up to under the hood.
So to summarize: the tested instance is created on the fly when the first request is sent. The tested instance is created by fel magic and dodges all attempts to catch it with a hook, so I can find no way to mock it. It almost feels like the author of rake-test has gone to extraordinary lengths to make sure the app instance can't be touched during testing.
I am still fumbling around for a solution.
Ok, I finally got it.
The problem, all along, turned out to be Sinatra::Base.call. Internally, it does dup.call!(env). In other words, every time you run call, Sinatra will duplicate your app instance and send the request to the duplicate, circumventing all mocks and stubs. That explains why none of the life cycle hooks were triggered, since presumably dup uses some low level C magic to clone the instance (citation needed.)
rack-test doesn't do anything convoluted at all, all it does it call app() to retrieve the app, and then call .call(env) on the app. All I need to do then, is stub out the .call method on my class and make sure Sinatra's magic isn't being inserted anywhere. I can use .new! on my app to stop Sinatra from inserting a wrapper and a stack, and I can use .call! to call my app without Sinatra duplicating my app instance.
Note: I can no longer just create an anonymous class inside the app function, since that would create a new class each time app() is called and leave me unable to mock it.
Here's the test from the question, updated to work:
require "sinatra"
require "minitest/spec"
require "minitest/autorun"
require "rack/test"
require "rr"
describe "sinatra app" do
include Rack::Test::Methods
class TestSubject < Sinatra::Base
get "/foo" do
generate_response
end
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am a response"]
end
end
def app
return TestSubject
end
it "should trigger mocked instance methods" do
stub(TestSubject).call { |env|
instance = TestSubject.new!
mock(instance).generate_response {
[200, {"Content-Type" => "text/plain"}, "I am MOCKED"]
}
instance.call! env
}
get "/foo"
assert_equal "I am MOCKED", last_response.body
end
end
Yes, rack test instantiates new app per each request (possibly to avoid collisions and start with a fresh state.) The option here would be to mock the Sinatra::Base derived class itself, inside app:
require "sinatra"
require "minitest/spec"
require "minitest/autorun"
require "rack/test"
require "rr"
describe "instantiated app" do
include Rack::Test::Methods
def app
Class.new(Sinatra::Base) do
get "/foo" do
generate_response
end
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am a response"]
end
end.prepend(Module.new do # ⇐ HERE
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am MOCKED"]
end
end).new!
end
it "should trigger mocked instance methods" do
get "/foo"
assert_equal "I am MOCKED", last_response.body
end
end
or, to mock app method in a whole.
Here is my rack application:
class MainAppLogic
def initialize
Rack::Server.start(:app =>Server, :server => "WEBrick", :Port => "8080")
end
end
class Server
def self.call(env)
return [200, {},["Hello, World"]]
end
end
When actually run, it behaves as it should and returns "Hello World" to all requests. I'm having trouble convincing rack-test to work with it. Here are my tests:
require "rspec"
require "rack/test"
require "app"
# Rspec config source: https://github.com/shiroyasha/sinatra_rspec
RSpec.configure do |config|
config.include Rack::Test::Methods
end
describe MainAppLogic do
# App method source: https://github.com/shiroyasha/sinatra_rspec
def app
MainAppLogic.new
end
it "starts a server when initialized" do
get "/", {}, "SERVER_PORT" => "8080"
last_response.body.should be != nil
end
end
When I test this, it fails complaining that MainAppLogic is not a rack server, specifically, that it doesn't respond to MainAppLogic.call. How can I let it know to ignore that MainAppLogic isn't a rack server and just place a request to localhost:8080, because there server has started?
First thing: why the custom class to run the app? You can use the rackup tool, which is the de-facto standard for running Rack apps. Some more details on it here.
Your app code then becomes:
class App
def call(env)
return [200, {}, ['Hello, World!']]
end
end
and with the config.ru
require_relative 'app'
run App.new
you can start the app by running rackup in your project's directory.
As for the error, the message is pretty clear. rack-test expects, that the return value of app method would be an instance of a rack app (an object that responds to call method). Take a look what happens in rack-test internals (it's pretty easy to follow, as a tip—focus on these in given order: lib/rack/test/methods.rb#L30 lib/rack/mock_session.rb#L7 lib/rack/test.rb#L244 lib/rack/mock_session.rb#L30. Notice how the Rack::MockSession is instantiated, how it is used when processing requests (e.g. when you call get method in your tests) and finally how the call method on your app is executed.
I hope that now it's clear why the test should look more like this (yes, you don't need to have a server running when executing your tests):
describe App do
def app
App.new
end
it "does a triple backflip" do
get "/"
expect(last_response.body).to eq("Hello, World")
end
end
P.S.
Sorry for the form of links to rack-test, can't add more than 2 with my current points :P
Your app should be the class name, for example instead of:
def app
MainAppLogic.new
end
You have to use
def app
MainAppLogic
end
You shouldn't need to indicate the port for doing the get, because the rack app runs in the context of the tests; so this should be right way:
it "starts a server when initialized" do
get "/"
last_response.body.should be != nil
end
Also, as a recommendation prefer to use the new expect format instead of the should, see http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
And your MainAppLogic, should be something like:
class MainAppLogic < Sinatra::Base
get '/' do
'Hello world'
end
end
I'm writing a Sinatra Rack App and I want to use Warden for authentication. I'm using heroku's toolbelt so I use foreman to run my app. I've found some code that's presumably supposed to get this working. Unfortunately, when I attempt to actually access the Warden env object, it is nil.
I've attempted to use the sinatra_warden gem, but it also has its own bugs (might be related to this one).
config.ru:
require './web.rb'
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp
web.rb:
require 'sinatra'
require 'warden'
require 'data_mapper'
require './config/datamapper.rb'
require './config/warden.rb' # I've tried this inside of MyApp, still didn't work
class MyApp < Sinatra::Base
get '/test' do
env['warden'].authenticate! # env['warden'] is nil :(
end
end
config/warden.rb:
use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = MyApp.new
end
Warden::Manager.serialize_into_session { |user| user.id }
Warden::Manager.serialize_from_session { |id| User.get(id) }
Warden::Manager.before_failure do |env,opts|
# Sinatra is very sensitive to the request method
# since authentication could fail on any type of method, we need
# to set it for the failure app so it is routed to the correct block
env['REQUEST_METHOD'] = "POST"
end
Warden::Strategies.add(:password) do
def valid?
params["email"] || params["password"]
end
def authenticate!
u = User.authenticate(params["email"], params["password"])
u.nil? ? fail!("Could not log in") : success!(u)
end
end
Versions:
Sinatra: 1.1.0
Warden: 1.2.1
Rack: 1.4.1
Ruby: 1.9.3p194
Foreman: 0.60.0
Any ideas how to use Warden the set up I've described?
(P.S. Out of curiosity, what exactly is the env variable?)
Rack internally uses the class Rack::Builder to parse your config.ru file and wrap directives to build up the middleware components.
I believe your builder calls to use in config/warden.rb are getting ignored. It may work to remove the directives from that file and add them to the middleware stack in config.ru:
require './web.rb'
use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = MyApp.new
end
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp
Put a link to your config/warden in your config.ru
require File.dirname(__FILE__) + '/config/warden'
Read the warden readme. Or look right in the lib/warden.rb
I put
Warden.test_mode!
in place of the env call at the /test path and get a nice blank page at
http://localhost:9292/test
Some bloggers have stated that there isn't a lot of documentation for warden but I disagree. There is a whole wiki. see https://github.com/hassox/warden/wiki
Take it slow and find out how to use middleware in Rack. Here's a very good article https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
I think maybe you might want to start out with tests as I found a good example and you could use it with your app.
ENV['RACK_ENV'] = 'test'
require 'test/unit'
require 'rack/test'
require File.dirname(__FILE__) + '/web'
class AuthenticationTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
WardenTest #MyApp
end
def test_without_authentication
get '/protected'
assert_equal 401, last_response.status
end
def test_with_bad_credentials
authorize 'bad', 'boy'
get '/protected'
assert_equal 401, last_response.status
end
def test_with_proper_credentials
authorize 'admin', 'admin'
get '/protected'
assert_equal 200, last_response.status
assert_equal "You're welcome, authenticated client", last_response.body
end
end
Then a few routes added to your app.
helpers do
def protected!
return if authorized?
headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
halt 401, "Not authorized\n"
end
def authorized?
#auth ||= Rack::Auth::Basic::Request.new(request.env)
#auth.provided? and #auth.basic? and #auth.credentials and
#auth.credentials == ['admin', 'admin']
end
end
get '/' do
"Everybody can see this page"
end
get '/protected' do
protected!
"You're welcome, authenticated client"
end
In my experience working with Ruby, it's always a good idea to start out with tests for any new project. I often test little pieces first though just to gain an understanding of how they work.
Once you get a better understanding of Rack, especially Rack::Builder, you can use
map '/test' do
...all the middleware needed
run App
end
and try out different configurations to see which ones work best for your needs as I'm doing while I write this.
Enjoy! ;-)
I'm attempting to build a gem for interacting w/ the Yahoo Placemaker API but I'm running into an issue. When I attempt to run the following code I get:
NameError: uninitialized constant Yahoo::Placemaker::Net
from /Users/Kyle/.rvm/gems/ruby-1.9.2-p290/gems/yahoo-placemaker-0.0.1/lib/yahoo-placemaker.rb:17:in `extract'
from (irb):4
from /Users/Kyle/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
yahoo-placemaker.rb
require "yahoo-placemaker/version"
require 'json'
require 'ostruct'
require 'net/http'
module Yahoo
module Placemaker
def self.extract (text = '')
host = 'wherein.yahooapis.com'
payload = {
'documentContent' => text,
'appid' => APP_ID,
'outputType' => 'json',
'documentType' => 'text/plain'
}
req = Net::HTTP::Post.new('/v1/document')
req.body = to_url_params(payload)
response = Net::HTTP.new(host).start do |http|
http.request(req)
end
json = JSON.parse(response.body)
Yahoo::Placemaker::Result.new(json)
end
end
end
I have yet to figure out how exactly constant name resolution works in Ruby (I think the rules are a bit messy here), but from my experience it could well be that Net is looked up in the current namespace instead of the global one. Try using the fully qualified name:
::Net::HTTP::Post.new
A similar problem could occur in this line:
Yahoo::Placemaker::Result
You should replace it with either ::Yahoo::Placemaker::Result or better Result (as it lives in the current namespace).
Try requiring net/http before. Ruby is falling back to find it in the module if it isn't defined.
require 'net/http'
I have a Sinatra based REST service app and I would like to call one of the resources from within one of the routes, effectively composing one resource from another. E.g.
get '/someresource' do
otherresource = get '/otherresource'
# do something with otherresource, return a new resource
end
get '/otherresource' do
# etc.
end
A redirect will not work since I need to do some processing on the second resource and create a new one from it. Obviously I could a) use RestClient or some other client framework or b) structure my code so all of the logic for otherresource is in a method and just call that, however, it feels like it would be much cleaner if I could just re-use my resources from within Sinatra using their DSL.
Another option (I know this isn't answering your actual question) is to put your common code (even the template render) within a helper method, for example:
helpers do
def common_code( layout = true )
#title = 'common'
erb :common, :layout => layout
end
end
get '/foo' do
#subtitle = 'foo'
common_code
end
get '/bar' do
#subtitle = 'bar'
common_code
end
get '/baz' do
#subtitle = 'baz'
#common_snippet = common_code( false )
erb :large_page_with_common_snippet_injected
end
Sinatra's documentation covers this - essentially you use the underlying rack interface's call method:
http://www.sinatrarb.com/intro.html#Triggering%20Another%20Route
Triggering Another Route
Sometimes pass is not what you want, instead
you would like to get the result of calling another route. Simply use
call to achieve this:
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end
I was able to hack something up by making a quick and dirty rack request and calling the Sinatra (a rack app) application directly. It's not pretty, but it works. Note that it would probably be better to extract the code that generates this resource into a helper method instead of doing something like this. But it is possible, and there might be better, cleaner ways of doing it than this.
#!/usr/bin/env ruby
require 'rubygems'
require 'stringio'
require 'sinatra'
get '/someresource' do
resource = self.call(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/otherresource',
'rack.input' => StringIO.new
)[2].join('')
resource.upcase
end
get '/otherresource' do
"test"
end
If you want to know more about what's going on behind the scenes, I've written a few articles on the basics of Rack you can read. There is What is Rack? and Using Rack.
This may or may not apply in your case, but when I’ve needed to create routes like this, I usually try something along these lines:
%w(main other).each do |uri|
get "/#{uri}" do
#res = "hello"
#res.upcase! if uri == "other"
#res
end
end
Building on AboutRuby's answer, I needed to support fetching static files in lib/public as well as query paramters and cookies (for maintaining authenticated sessions.) I also chose to raise exceptions on non-200 responses (and handle them in the calling functions).
If you trace Sinatra's self.call method in sinatra/base.rb, it takes an env parameter and builds a Rack::Request with it, so you can dig in there to see what parameters are supported.
I don't recall all the conditions of the return statements (I think there were some Ruby 2 changes), so feel free to tune to your requirements.
Here's the function I'm using:
def get_route url
fn = File.join(File.dirname(__FILE__), 'public'+url)
return File.read(fn) if (File.exist?fn)
base_url, query = url.split('?')
begin
result = self.call('REQUEST_METHOD' => 'GET',
'PATH_INFO' => base_url,
'QUERY_STRING' => query,
'rack.input' => StringIO.new,
'HTTP_COOKIE' => #env['HTTP_COOKIE'] # Pass auth credentials
)
rescue Exception=>e
puts "Exception when fetching self route: #{url}"
raise e
end
raise "Error when fetching self route: #{url}" unless result[0]==200 # status
return File.read(result[2].path) if result[2].is_a? Rack::File
return result[2].join('') rescue result[2].to_json
end