I m building an app which reacts to http requests.
So far it was ok to check the log file and the tables. I used curl to send the request like:
curl -u test -X POST http://127.0.0.1:3000/api/information -d ''
But now i included some kind of response mechanism. My question now is what port do i have to use for my responses?
Is it port 80 (std http port) ? And are there some CLI tools available which allow to handle a session?
The port depends on your config and which arguments you use when starting the server. By default rails start on port 3000 - using port 80 will in most cases require the use of sudo. You can see the port in the output when starting the rails server.
$ rails server
=> Booting WEBrick
=> Rails 4.2.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
There are several browser extensions such as Postman which provide a GUI for sending REST requests - in the way that is much easier than trying to piece it all together in a monster cURL invocation.
This is useful for debugging - but manually testing your applications is very error-prone and known to be an incomplete and flawed approach*. Instead, you should consider using automated tests.
Example of an RSpec request spec:
# spec/requests/pets_api_spec.rb
require "rails_helper"
RSpec.describe "Pets API", type: :request do
subject { response }
let(:json) { JSON.parse(response.body, symbolize_keys: true) }
let(:pet) { Pet.create(name: 'Spot') }
describe "viewing a Pet" do
before { get pet_path(pet) }
it { should have_http_status :ok }
it "has the correct JSON response" do
expect(json[:type]).to eq 'Pet'
expect(json[:data][:name]).to eq 'Spot'
end
end
describe "creating a Pet" do
let(:valid_session) do
# setup session here.
end
before do
post "/pets", { type: 'Pet', data: { name: 'Doge' } }, valid_session
end
it { should have_http_status :created }
# ...
end
end
Related
I'm using Ruby on Rails 5 and I need to execute the following command in my application:
curl -F 'client_id=126581840734567' -F 'client_secret=678ebe1b3b8081231aab27dff738313' -F 'grant_type=authorization_code' -F 'redirect_uri=https://uri.com/' -F 'code=AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q' https://api.instagram.com/oauth/access_token
so that it returns something like:
{"access_token": "IGQVJYS0k8V6ZACRC10WjYxQWtyMVRZAN8VXamh0RVBZAYi34RkFlOUxXZnTJsbjlEfnFJNmprQThmQ4hTckpFUmJEaXZAnQlNYa25aWURnX3hpO12NV1VMWDNMWmdIT3FicnJfZAVowM3VldlVWZAEViN1ZAidHlyU2VDMUNuMm2V", "user_id": 17231445640157812}
Is there a way to make Rails execute those types of commands? I was trying the following:
uri = URI.parse('https://api.instagram.com/oauth/access_token')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data({
"client_id" => "126581840734567",
"client_secret" => "678ebe1b3b8081231aab27dff738313",
"grant_type" => "authorization_code",
"redirect_uri" => "http://nace.network/",
"code" => params[:code]
})
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(request)
end
but I get the following error:
end of file reached
in this line:
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(request)
end
You're using HTTPS, so you need to add this to your code:
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
res = http.request(request)
end
But if you don't need persistent connections, you could also use this:
res = Net::HTTP.post_form(uri,
"client_id" => "126581840734567",
"client_secret" => "678ebe1b3b8081231aab27dff738313",
"grant_type" => "authorization_code",
"redirect_uri" => "http://nace.network/",
"code" => params[:code]
)
Also, you could consider using a library like Faraday, which is a lot easier to deal with.
Edit
This is from TinMan's comment below, sound points.
Using cURL from inside Ruby or Rails is extremely valuable. There is an incredible amount of functionality inside cURL that isn't implemented in Rails or Ruby; Even Ruby's HTTP clients have a hard time replicating it, so cURL is very acceptable depending on the needs of the application. And, depending on the application, because cURL is in compiled C, it could easily outrun pure Ruby clients.
Curl is a means of issuing HTTP (or HTTPs) requests from the command line.
You don't want to use CURL in Rails. You want to issue HTTP requests from within Rails. Using curl is okay, it's one way to issue HTTP requests from with Rails.
We can refine that down further to, you want to issue HTTP requests from Ruby. Narrowing/distilling down to the most basic version of the problem is always good to do.
We knew all this already probably - still worth writing down for us all to benefit from!
Use HTTP in Ruby
We want to use a HTTP Client. There are many but, for this I'm going to use Faraday (a gem) 'cause I like it.
You've made a good start with Ruby's built in NET:HTTP but I prefer Faraday's DSL. It results in more readable and extendable code.
So, here is a class! I barely tested this so, use as a starting point. Make sure you write some unit tests for it.
# This is a Plain Old Ruby Object (PORO)
# It will work in Rails but, isn't Rails specific.
require 'faraday' # This require is needed as it's a PORO.
class InstagramOAuth
attr_reader :code
# The code parameter will likely change frequently, so we provide it
# at run time.
def initialize(code)
#code = code
end
def get_token
connection.get('/oauth/access_token') do |request|
request.params[:code] = code
end
end
private
def connection
#connection ||= Faraday.new(
url: instagram_api_url,
params: params,
ssl: { :ca_path => https_certificate_location }
)
end
def instagram_api_url
#url ||= 'https://api.instagram.com'
end
# You need to find out where these are for your self.
def https_certificate_location
'/usr/lib/ssl/certs'
end
def params
# These params likely won't change to often so we set a write time
# in the class like this.
{
client_id: '126581840734567',
client_secret: '678ebe1b3b8081231aab27dff738313',
grant_type: 'authorization_code',
redirect_uri: 'https://uri.com/'
}
end
end
# How do we use it? Like so
# Your big old authorisation code from your question
code = 'AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU'\
'7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz'\
'5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H'\
'-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q'
# This will return a Faraday::Response object but, what is in it?
response = InstagramOAuth.new(code).get_token
# Now we've got a Hash
response_hash = response.to_hash
puts 'Request made'
puts "Request full URL: #{response_hash[:url]}"
puts "HTTP status code: #{response_hash[:status]}"
puts "HTTP response body: #{response_hash[:body]}"
When I ran the snippet above I got the following. The class works, you just need to tweak the request params until you get what you want. Hopefully the class demonstrates how to send HTTP requests in Ruby/Rails.
Request made
Request full URL: https://api.instagram.com/oauth/access_token?client_id=126581840734567&client_secret=678ebe1b3b8081231aab27dff738313&code=AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q&grant_type=authorization_code&redirect_uri=https%3A%2F%2Furi.com%2F
HTTP status code: 405
HTTP response body:
Additional Reading
. https://lostisland.github.io/faraday/usage/
. https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates
I have a project which used wisper https://github.com/krisleech/wisper to provide publisher and subscribers functionalities.
The gem works perfectly under development and production modes. However, when I try to add some tests for them (rake test:integration), the newly added tests refused to work. The publisher (maybe also the listener) in the tests mode stopped working anymore.
Core::Request.subscribe(Listener::Studentlistener, async: true)
Core::Request.subscribe(Listener::Tutorlistener, async: true)
I used the sidekiq as a async backend, i used wisper-sidekiq gem to handle the async requests, not sure if this would be the problem?
,puma as the server, MRI ruby 2.0.0
Do I have to a set up something in order for the test to run?
it "Student can get latest status after looking for xxx tutor" do
post api_v1_students_request_look_for_xxx_tutor_path,
{ subject: 'nothing' },
{ "AUTHORIZATION" => "xxx"}
expect(response).to be_success
get api_v1_students_status_path, nil,
{ "AUTHORIZATION" => "xxx"}
expect(response).to be_success
json_response = JSON.parse(response.body)
expect(json_response['state']).to eq('matching')
end
The listener should receive the publishing between these two posts and update the state to be "matching". However, now when I run rspec the test failed because the publisher never publish anything and hence the state is not updated correctly.
Even the authors are relying on some mocking/stubbing in the integrations tests, so that might be the correct way.
class MyCommand
include Wisper::Publisher
def execute(be_successful)
if be_successful
broadcast('success', 'hello')
else
broadcast('failure', 'world')
end
end
end
describe Wisper do
it 'subscribes object to all published events' do
listener = double('listener')
expect(listener).to receive(:success).with('hello')
command = MyCommand.new
command.subscribe(listener)
command.execute(true)
end
https://github.com/krisleech/wisper/blob/master/spec/lib/integration_spec.rb
When I run rspec to run all tests, they all pass, but when I give a specific filename, it fails.
$ rspec spec/controllers/refinery/books/admin/books_controller_spec.rb
2) Refinery::Books::Admin::BooksController GET :new responds 200 success
Failure/Error: login_admin_user #Gives 404
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: POST http://localhost:8981/solr/default/update?wt=ruby with body '<?xml ...' with headers {'Content-Type'=>'text/xml'}
You can stub this request with the following snippet:
stub_request(:post, "http://localhost:8981/solr/default/update?wt=ruby").
with(:body => "<?xml ...",
:headers => {'Content-Type'=>'text/xml'}).
to_return(:status => 200, :body => "", :headers => {})
registered request stubs:
stub_request(:post, "http://localhost:8983/solr/test/update?wt=ruby").
with(:headers => {'Content-Type'=>'text/xml'})
============================================================
# ./lib/rsolr/connection.rb:15:in `execute'
# (eval):2:in `post'
# ./spec/support/controller_macros.rb:21:in `login_admin_user'
I have it mocked & stubbed already, so why is it failing? The only thing I can see is the existing stub is '/test/' and the request is '/default/'. How did that change?
Ok I ran into this problem again on another spec. Here is the spec:
https://gist.github.com/starrychloe/1d79d9925f9b79ae5c01
I did find this solr/solr.xml
<?xml version="1.0" encoding="UTF-8" ?>
<solr persistent="false">
<cores adminPath="/admin/cores" host="${host:}" hostPort="${jetty.port:}">
<core name="default" instanceDir="." dataDir="default/data"/>
<core name="development" instanceDir="." dataDir="development/data"/>
<core name="test" instanceDir="." dataDir="test/data"/>
</cores>
</solr>
It's like rspec is running in production environment, even if I explicitely give the environment
$ RAILS_ENV=test rspec spec/controllers/refinery/books/books_controller_spec.rb
I think it is an rspec problem. rspec -v: version 2.14.7.
I added stub_request(:post, "http://localhost:8981/solr/default/update?wt=ruby").to_return(:status => 200, :body => "", :headers => {}) before login_admin_user but I don't think that's the root solution.
It is probably interacting with another test.
Try running the suite in random order
rspec spec --order rand
and/or try running all the tests for that controller or or controllers.
You are using webmock, which disables all HTTP connections by default.
You can modify you Gemfile to disable auto require as followed:
gem "webmock", :require => false
And require webmock where you need it (e.g., require 'webmock/rspec'). After disabling HTTP connections for certain tests, remember to WebMock.allow_net_connect! to allow real http connections for other tests.
Another alternative is to WebMock.disable_net_connect!(:allow_localhost => true) to disable external requests while allowing localhost.
SOLVED: I had done a few things wrong, all of which involved my controller RECEIVING the data. There was not anything wrong with the methods below on SENDING the data.
1: I was not using #report.save in my reportController#create
2: I was not passing params[:report] in my controller
3: I added "skip_before_filter :verify_authenticity_token" to my applicaiton controller to stop warnings in the logs.
Solved. data insertion successful.
=====ORIG. Question Below=====
I need an external program to issue a command that inserts stuff into a Ruby on Rails database.
I understand the security implications of this, but because this application is not public facing, it is not really an issue.
This is the workflow i am looking to achieve:
REST client > RAILS > create new DB TABLE row
For purposes of example: my route.rb file contains
resources :reports
so i am able to CRUD using those routes. I just cant seem to get my rest client to work correctly.
UPDATE:
I have tried a RUBY rest client AND curl command in ONE, to no avail.
require 'rest_client'
require 'json'
hash_to_send = {:test_name => 'Fake Name', :pass_fail => 'pass',:run_id => 1111, :category => 'Fake Category'}
#formulate CURL attempt
myCommand = "curl -H 'Content-Type: application/json' -X POST http://localhost:8889/report.json -d #{hash_to_send.to_json} > deleteme.html"
#execute CURL attempt
`#{myCommand}` # RESULT--> 795: unexpected token at 'test_name:Fake Name'
#Make Ruby rest client attempt
response = RestClient.post( "http://localhost:8889/report.json",
hash_to_send.to_json,
:content_type => :json, :accept => :json
)
#debug info
puts myCommand # Returns --> {"test_name":"Fake Name","pass_fail":"pass","run_id":1111,"category":"Fake Category"}
Instead of curl in command-line, use ruby script and handle REST calls and JSON conversion by gems. For example, using rest-client gem (https://github.com/archiloque/rest-client) and standard json gem you can write:
require 'rest_client'
require 'json'
response = RestClient.post( "http://localhost:8889/report.json",
params_in_hash.to_json,
{ :content_type => :json, :accept => :json }
)
I am building a 2-Legged OAuth provider for my api. Everything is hooked up properly and I can make signed calls from the rails console. The problem I have is that I am having trouble integrating OAuth into the controller_spec.
Here is an example of a working call on my server:
coneybeare $ rails c test
Loading test environment (Rails 3.2.0)
rails test: main
>> consumer = OAuth::Consumer.new("one_key", "MyString", :site => [REDACTED])
# => #<OAuth::Consumer:0x007f9d01252268 #key="one_key", #secret="MyString", #options={:signature_method=>"HMAC-SHA1", :request_token_path=>"/oauth/request_token", :authorize_path=>"/oauth/authorize", :access_token_path=>"/oauth/access_token", :proxy=>nil, :scheme=>:header, :http_method=>:post, :oauth_version=>"1.0", :site=>[REDACTED]}>
ruby: main
>> req = consumer.create_signed_request(:get, "/api/v1/client_applications.json", nil)
# => #<Net::HTTP::Get GET>
ruby: main
>> res = Net::HTTP.start([REDACTED]) {|http| http.request(req) }
# => #<Net::HTTPOK 200 OK readbody=true>
ruby: main
>> puts res.body
{"client_applications":[{"id":119059960,"name":"FooBar1","url":"http://test1.com"},{"id":504489040,"name":"FooBar2","url":"http://test2.com"}]}
# => nil
And here is what I am doing in my controller tests:
require 'oauth/client/action_controller_request'
describe Api::ClientApplicationsController do
include OAuthControllerSpecHelper
…
…
it "assigns all client_applications as #client_applications" do
consumer = OAuth::Consumer.new("one_key", "MyString", :site => [REDACTED])
ActionController::TestRequest.use_oauth=true
#request.configure_oauth(consumer)
#request.apply_oauth!
puts "request.env['Authorization'] = #{#request.env['Authorization']}"
get :index, {:api_version => 'v1', :format => :json}
response.should be_success # Just this for now until I can get authorization, then proper controller testing
end
end
The output of that test:
request.env['Authorization'] = OAuth oauth_consumer_key="one_key", oauth_nonce="gzAbvBSWyFtIYKfuokMAdu6VnH39EHeXvebbH2qUtE", oauth_signature="juBkJo5K0WLu9mYqHVC3Ar%2FATUs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1328474800", oauth_version="1.0"
1) Api::ClientApplicationsController GET index assigns all client_applications as #client_applications
Failure/Error: response.should be_success
expected success? to return true, got false
And the corresponding server call from the rails log:
Processing by Api::ClientApplicationsController#index as JSON
Parameters: {"api_version"=>1}
Rendered text template (0.0ms)
Filter chain halted as #<OAuth::Controllers::ApplicationControllerMethods::Filter:0x007f85a51a8858 #options={:interactive=>false, :strategies=>:two_legged}, #strategies=[:two_legged]> rendered or redirected
Completed 401 Unauthorized in 15ms (Views: 14.1ms | ActiveRecord: 0.0ms)
(0.2ms) ROLLBACK
I just can't figure out why it's not working :/ Am I making an obvious mistake?
If you'd like to test it in a request spec and actually need to test without stubbing, you can build an OAuth consumer and sign a request like this:
#access_token = FactoryGirl.create :access_token
#consumer = OAuth::Consumer.new(#access_token.app.key, #access_token.app.secret, :site => "http://www.example.com/")
#path = "/path/to/request"
#request = #consumer.create_signed_request(:get, #path, OAuth::AccessToken.new(#consumer, #access_token.token, #access_token.secret))
get #path, nil, { 'HTTP_AUTHORIZATION' => #request.get_fields('authorization').first }
I would take a look as to how the Omniauth test helpers work, specifically these files: https://github.com/intridea/omniauth/tree/master/lib/omniauth/test. See their wiki page on integration testing for ideas of how this is set up. I realize that you're building a provider, not a client, but this may be a good starting point. Also, as some of the commenters have already said, I don't know if you can do this with a controller test; you may need a request or integration test to fully simulate the rack environment.
Turns out that the best way to test my controller was the simplest as well. Instead of trying to sign each test so the controller gets the right information (something that indeed does belong in a request spec not a controller spec), I figured out that I could just give the controller the information it needed manually.
To do this, I simply had to stub 2 methods:
fixtures :client_applications
before(:each) do
#client_application1 = client_applications(:client_application1)
Api::ClientApplicationsController::Authenticator.any_instance.stub(:allow?).and_return(true)
controller.stub(:client_application).and_return(#client_application1)
end
Stubbing the allow? method caused the rack auth to be fooled into thinking it was authenticated. allow? also set the client_application based on the credentials though, so I had to stub that as well. Now that the auth is out of the way, I can test my controller properly.