How to use lotus router with Rack::Builder::map - ruby

Is there a way to use map and the (lotus)router namespacing together? Below is a sample config.ru I'm trying to get running as a demo.
require 'bundler'
Bundler.require
module Demo
class Application
def initialize
#app = Rack::Builder.new do
map '/this_works' do
run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["this_works"]]}
end
map '/api' do
run Lotus::Router.new do
get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
end
end
end
end
def call(env)
#app.call(env)
end
end
end
run Demo::Application.new

Your problem is due to the precedence of do..end in method calls. In your code the section
run Lotus::Router.new do
get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
end
is parsed by Ruby as
run(Lotus::Router.new) do
get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
end
In other words the block is passed to run, not to Lotus::Router.new as you intended, and run simply ignores the block.
To fix it you need to ensure the block is associated with the constructor of the router rather than the call to run. There are a couple of ways to do this. You could use {...} rather than do...end, as that has a higher precedence:
run Lotus::Router.new {
#...
}
An alternative would be to assign the router to a local variable, and use that as the argument to run:
router = Lotus::Router.new do
#...
end
run router

Related

Configure expect in rspec

I want to implement rspec with expect. I tried this:
RSpec:
describe WechatRequestBuilder do
let(:request_builder) { described_class.new(env: 'test_env') }
let(:trx_types) { ['wechat'] }
let(:trx_type) { 'wechat' }
let(:gateway) { 'wechat' }
let(:currency) { 'CNY' }
let(:base_params) { request_builder.send(:base_params) }
it_behaves_like 'request builder', true
context '#submit!' do
it "sends test transactions" do
allow(request_builder).to receive(:process_trx).with(trx_types, gateway)
binding.pry
request_builder.submit!
expect(request_builder.submit!).to receive(:process_trx).with(trx_types, gateway)
end
end
end
Request modifier:
class RequestModifier
def get_trx_type(request_body)
doc = Nokogiri::XML(request_body)
doc.search("transaction_type").first.text
end
end
I tried to find some object with binding.pry but without a luck:
[1] pry(#<RSpec::ExampleGroups::WechatRequestBuilder::Submit>)> request_builder
=> #<WechatRequestBuilder:0x007ffc1af4fd80 #env="test_env", #request_modifier=#<RequestModifier:0x007ffc1af4fd30>>
Can you give e some example based on the above code what should I configure as 'expect'? Currently I get:
(nil).process_trx(["wechat"], "wechat")
expected: 1 time with arguments: (["wechat"], "wechat")
received: 0 times

ruby rackup: is it possible to programatically add a mapping from within another mapping?

I have a .ru file and can set up mappings without issue ( the 'register' mapping below ).
However I want services to be able to register themselves by hitting a url so I want to be able to add new mappings on the fly from within others mappings.
The below code does not work though. What am I doing wrong and is this possible?
Thanks!
map '/register' do
run Proc.new { |env|
# inside of register i want to add another mapping.
# obviously 'bar' would be a value read out of env
map '/bar' do
run Proc.new{ |env| ['200', { 'Content-Type' => 'text/html' },'bar' }
end
[ '200', {'Content-Type' => 'text/html'}, "registered"]
}
end
I don't think there's a way to add routes after-the-fact using map. One alternative is to use Rack::URLMap to define your app. You'll need to maintain your own list of registered routes (as a hash) and call Rack::URLMap#remap every time you add a new route to the hash:
url_map = Rack::URLMap.new
routes = {
"/register" => lambda do |env|
routes["/bar"] = lambda do |env|
[ "200", {"Content-Type" => "text/plain"}, ["bar"] ]
end
url_map.remap(routes)
[ "200", {"Content-Type" => "text/plain"}, ["registered"] ]
end
}
url_map.remap(routes)
run url_map
Note that you could do with with just the hash, but URLMap provides some nice conveniences, including 404 handling. It's actually a really nice little class and worth reading if you have five minutes to spare.
If you were so inclined, you could turn this into a tidy little class:
class Application
def initialize
#routes = {}
#url_map = Rack::URLMap.new
register_route "/register" do |env|
# When "/register" is requested, register the new route "/bar"
register_route "/bar" do |env|
[ 200, {"Content-Type" => "text/plain"}, ["bar"] ]
end
[ 200, {"Content-Type" => "text/plain"}, ["registered"] ]
end
end
def call(env)
#url_map.call(env)
end
private
def register_route(path, &block)
#routes[path] = block
#url_map.remap(#routes)
end
end
run Application.new
According to https://rack.github.io/, "To use Rack, provide an "app": an object that responds to the call method, taking the environment hash as a parameter, and returning an Array with three elements:
The HTTP response code
A Hash of headers
The response body, which must respond to each"
Your third element will not respond to each. Maybe wrap it in an array?

Ruby stubbing with faraday, can't get it to work

Sorry for the title, I'm too frustrated to come up with anything better right now.
I have a class, Judge, which has a method #stats. This stats method is supposed to send a GET request to an api and get some data as response. I'm trying to test this and stub the stats method so that I don't perform an actual request. This is what my test looks like:
describe Judge do
describe '.stats' do
context 'when success' do
subject { Judge.stats }
it 'returns stats' do
allow(Faraday).to receive(:get).and_return('some data')
expect(subject.status).to eq 200
expect(subject).to be_success
end
end
end
end
This is the class I'm testing:
class Judge
def self.stats
Faraday.get "some-domain-dot-com/stats"
end
end
This currently gives me the error: Faraday does not implement: get
So How do you stub this with faraday? I have seen methods like:
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
stub.get('http://stats-api.com') { [200, {}, 'Lorem ipsum'] }
end
But I can't seem to apply it the right way. What am I missing here?
Note that Faraday.new returns an instance of Faraday::Connection, not Faraday. So you can try using
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return("some data")
Note that I don't know if returning "some data" as shown in your question is correct, because Faraday::Connection.get should return a response object, which would include the body and status code instead of a string. You might try something like this:
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(
double("response", status: 200, body: "some data")
)
Here's a rails console that shows the class you get back from Faraday.new
$ rails c
Loading development environment (Rails 4.1.5)
2.1.2 :001 > fara = Faraday.new
=> #<Faraday::Connection:0x0000010abcdd28 #parallel_manager=nil, #headers={"User-Agent"=>"Faraday v0.9.1"}, #params={}, #options=#<Faraday::RequestOptions (empty)>, #ssl=#<Faraday::SSLOptions (empty)>, #default_parallel_manager=nil, #builder=#<Faraday::RackBuilder:0x0000010abcd990 #handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>, #url_prefix=#<URI::HTTP:0x0000010abcd378 URL:http:/>, #proxy=nil>
2.1.2 :002 > fara.class
=> Faraday::Connection
Coming to this late, but incase anyone else is too, this is what worked for me - a combination of the approaches above:
let(:json_data) { File.read Rails.root.join("..", "fixtures", "ror", "501100000267.json") }
before do
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(
double(Faraday::Response, status: 200, body: json_data, success?: true)
)
end
Faraday the class has no get method, only the instance does. Since you are using this in a class method what you can do is something like this:
class Judge
def self.stats
connection.get "some-domain-dot-com/stats"
end
def self.connection=(val)
#connection = val
end
def self.connection
#connection ||= Faraday.new(some stuff to build up connection)
end
end
Then in your test you can just set up a double:
let(:connection) { double :connection, get: nil }
before do
allow(connection).to receive(:get).with("some-domain-dot-com/stats").and_return('some data')
Judge.connection = connection
end
I ran into the same problem with Faraday::Adapter::Test::Stubs erroring with Faraday does not implement: get. It seems you need to set stubs to a Faraday adapter, like so:
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
stub.get("some-domain-dot-com/stats") { |env| [200, {}, 'egg'] }
end
test = Faraday.new do |builder|
builder.adapter :test, stubs
end
allow(Faraday).to receive(:new).and_return(test)
expect(Judge.stats.body).to eq "egg"
expect(Judge.stats.status).to eq 200
A better way to do this, rather than using allow_any_instance_of, is to set the default connection for Faraday, so that Faraday.get will use the connection you setup in your tests.
For example:
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
before do
stubs.get('/maps/api/place/details/json') do |_env|
[
200,
{ 'Content-Type': 'application/json' },
{ 'result' => { 'photos' => [] } }.to_json
]
end
Faraday.default_connection = conn
end
after do
Faraday.default_connection = nil
end

How to test the request (not response) of a Rack middleware?

I can see how to test the response of a rack middleware, but how to test the request?
That is, how to test when the middleware changes the request going to the app?
Working in RSpec and Sinatra.
I presume you're meaning testing whether it's changing env...
A middleware goes something like:
class Foo
def initialize(app)
#app = app
end
def call(env)
# do stuff with env ...
status, headers, response = #app.call(env)
# do stuff with status, headers and response
[status, headers, response]
end
end
You could initialize it with a bogus app (or a lambda, for that matter) that returns a dummy response after doing some tests:
class FooTester
attr_accessor :env
def call(env)
# check that env == #env and whatever else you need here
[200, {}, '']
end
end
#Denis's answer would work, but I'd personally prefer an alternative, which is to put the middleware in a bare Rack app (be it Sinatra or whatever) and just pass the request on as the response and test that. It's how most Rack middleware is specced. That, and unit testing the internals of the middleware.
For example, it's what I've done here with a fork of Rack Clicky
Edit: testing middleware separately from the main app.
require 'lib/rack/mymiddelware.rb'
require 'sinatra/base'
describe "My Middleware" do
let(:app) {
Sinatra.new do
use MyMiddleware
get('/') { request.env.inspect }
end
}
let(:expected) { "Something you expect" }
before do
get "/"
end
subject { last_response.body }
it { should == expected }
end

using send_file in deferred sinatra request

I'm trying to return a file during an async sinatra request, something like this:
aget "/test" do
if(File.exists?("test.tar"))
send_file("test.tar", :filename => "test.tar", :type => "application/octet-stream")
return
end
EM.defer(proc{
# create test.tar
},
proc{ |r|
send_file("test.tar", :filename => "test.tar", :type => "application/octet-stream")
})
However it seems that when I do that, I get an error:
wrong number of arguments (0 for 1)
file: file.rb
location: call
line: 29
BACKTRACE:
/var/lib/gems/1.9.1/gems/rack-1.4.1/lib/rack/file.rb in call
def call(env)
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in block in body
response.body = Array(async_handle_exception {response.body.call})
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in async_handle_exception
yield
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in body
response.body = Array(async_handle_exception {response.body.call})
/var/lib/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb in invoke
body(res.pop)
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in block in async_catch_execute
invoke { halt h }
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in async_handle_exception
yield
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in async_catch_execute
async_handle_exception do
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in block in async_schedule
native_async_schedule { async_catch_execute(&b) }
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in call
end.each { |j| j.call }
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in block in run_deferred_callbacks
end.each { |j| j.call }
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in each
#next_tick_mutex.synchronize do
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in run_deferred_callbacks
#next_tick_mutex.synchronize do
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in run_machine
run_machine
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in run
run_machine
/var/lib/gems/1.9.1/gems/thin-1.3.1/lib/thin/backends/base.rb in start
EventMachine.run(&starter)
/var/lib/gems/1.9.1/gems/thin-1.3.1/lib/thin/server.rb in start
#backend.start
/var/lib/gems/1.9.1/gems/rack-1.4.1/lib/rack/handler/thin.rb in run
server.start
/var/lib/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb in run!
handler.run self, :Host => bind, :Port => port do |server|
/var/lib/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/main.rb in block in <module:Sinatra>
at_exit { Application.run! if $!.nil? && Application.run? }
end
The error you are receiving is cause you are using a function that requires 1 argument and you haven't supplied any. If you can show us the code around line 29 I or somebody can point it out for you.

Resources