I have created a function that makes a HTTParty get request. It raises a custom error message that i need to test. I tried to stub the request using Webmock in the test but it is raising a <Net::OpenTimeout>. How can i stub the get request if the url is dynamically constructed?
def function(a , b)
# some logic , dynamic url constructed
response = HTTParty.get(url, headers: {"Content-Type" =>
"application/json"})
if response.code != 200
raise CustomError.new <<~EOF
Error while fetching job details.
Response code: #{response.code}
Response body: #{response.body}
EOF
end
JSON.parse(response.body)
for the test
def test_function
WebMock.stub_request(:get, url).with(:headers => {'Content-
Type'=>'application/json'}).to_return(:status => 500)
# HTTParty.stub(get: fake_response)
err = assert_raises CustumError do
c.function(a , b)
end
WebMock allows you to use "wildcard matching" so you can stub requests matching a regular expression:
WebMock.stub_request(:get, /example/).to_return(status: 500)
Related
I've got pure Ruby app where I want to create request to external API. To do so I'm using standard Ruby Net::HTTP like below:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
def initialize(payload:)
#payload = payload
end
def post_quiz
handle_response(Net::HTTP.post_form("#{BASE_URI}#{QUIZ_PATH}", options))
end
attr_reader :payload
private
def options
{
basic_auth: basic_auth,
body: payload.to_json,
headers: headers
}
end
def basic_auth
{
username: Settings.ln_username,
password: Settings.ln_password
}
end
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
def handle_response(response)
return response.body if response.success?
end
end
But instead of response I'm getting an error:
NoMethodError: undefined method `user' for #String:0x00007f80eef9e6f8
Did you mean? super
/Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527:in `post_form'
I don't have any user there, what is it?
Net::HTTP.post_form is used to send FormData pairs - its not what you want to send JSON and it doesn't even allow you to send headers (You're actually putting them in the request body!).
If you want to send a POST request with HTTP Basic auth and custom headers and JSON body you need to create the request object manually:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
attr_reader :payload
def initialize(payload:)
#payload = payload
end
def post_quiz
url = URI.join(BASE_URI, QUIZ_PATH)
request = Net::HTTP::Post.new(url, headers)
request.basic_auth = Settings.ln_username, Settings.ln_password
request.body = #payload.to_json
# open a connection to the server
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
http.request(request)
end
handle_response(response)
end
private
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
# How to respond from an API client is a whole topic in itself but a tuple or hash might
# be a better choice as it lets consumers decide what to do with the response and handle stuff like logging
# errors
def handle_response(response)
# Net::HTTP doesn't have a success? method - you're confusing it with HTTParty
case response
when Net::HTTPSuccess, Net::HTTPCreated
response.body
else
false
end
end
end
Here is the source code that raises the error:
def HTTP.post_form(url, params)
req = Post.new(url)
req.form_data = params
>> req.basic_auth url.user, url.password if url.user
start(url.hostname, url.port,
:use_ssl => url.scheme == 'https' ) {|http|
http.request(req)
}
end
From the docs:
post_form(url, params)
Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.
That means Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) fixes it. You are currently sending a string as url instead of a URI.
I have a module called token wrapper, in which there is a method getToken:
def Tokenwrapper.getToken
uri = URI.parse("[URL REDACTED]/api/authenticate")
request = Net::HTTP::Post.new(uri)
request.basic_auth("email#domain.com", "pass")
request.content_type = "application/json"
request["Accept"] = "application/json"
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
response
end
When I attempt to test it with the following assertion:
assert_equal("#<Net::HTTPOK:",Tokenwrapper.getToken[0..13])
I get this error:
NoMethodError: undefined method 'downcase' for 0..13:Range
I don't manually invoke the downcase method, and I don't see any reason that ruby should be doing so automatically. Why is this happening and how do I make my test run?
I'll be honest I don't know a lot about HTTP API responses and how this area of networking operates, so I'd appreciate any resources as well as answers to this question.
The response object has a [] method that provides access to a header from the response. When you're trying to do getToken[0..13] this is the method that's actually being called.
This [] is expecting a call like response['Content-Type'] and uses downcase on the value passed in in order to handle header names case insensitively.
If you want to check the first few characters from the string representation of the response you can convert the response to a string and compare it, like this:
assert_equal("#<Net::HTTPOK:",Tokenwrapper.getToken.to_s[0..13])
Alternatively, can you use an assertion on the HTTP status code, e.g.
assert_equal(200, Tokenwrapper.getToken.code)
I have an endpoint in my Sinatra application that will be receiving binary data as part of the body. The other application sending it data will have a Faraday request that looks like this:
connection = Faraday.new(url: "https://example.com/post_data") do |conn|
conn.request :multipart
conn.adapter :net_http
conn.headers['Content-Type'] = 'octet/stream'
end
#response ||= connection.post do |req|
req.params = { :host => host,
:verification => "false"}
req.body = my_base64_encoded_binary
end
Then, in Sinatra, I will have an endpoint that receives those request parameters and binary data and passes it along to a model, like so:
post '/post_data' do
request.body.rewind
payload = request.body.read
raise Sinatra::NotFound unless payload and params[:host]
output = MyOutput.new(params, payload)
content_type 'application/json'
body output.data
end
When I try to test this endpoint using Rack::Test helpers, I end up in a weird situation. I can't seem to create the proper mock in order to test this endpoint properly. Based on some manual testing with PostMan, I'm pretty certain my endpoint works properly. It's just the test that's holding me up. Here is the spec:
it "should return a json response" do
post(url, :host => host, :verification => "false") do |req|
req.body = [my_binary]
req.content_type = "application/octet-stream"
end
expect(last_response.status).to eq(200)
expect(last_response.content_type).to eq("application/json")
end
And when I inspect what the incoming request looks like in the test, it does not contain a proper body. params is properly set to the host and verification settings I set, but the body is also being set to the params (inspected through payload = request.body.read) instead of the binary.
Is there a different way to set up the mock so that the binary properly is set to the body of the request, and the parameters are still set to the request parameters properly?
The answer is that the only way to post the body is where I was adding the params in the rack test helper. I needed to take the params and move them into the query string of the URL in the request, and then only add the binary as the body of the post request in the test helper.
let(:url) { "http://example.com/post_data?host=>#{host}&verification=#{verification}" }
it "should return a json response" do
post(url, my_binary)
expect(last_response.status).to eq(200)
expect(last_response.content_type).to eq("application/json")
end
I am writing a Sinatra-based API, and want to protect certain endpoints with an API key, validating the key before the route is processed.
I understand why throwing an error in the before block doesn't work, because the begin/rescue statements haven't been called yet, however I want a JSON response to be sent back to the client with the error message as a JSON object.
How would I do this?
namespace '/v1/sponser/:key' do
before do
if APIHelper.valid_key?(params[:key]) == false
throw 'Error, invalid API key'
# is it possible to return a JSON response from the before statement here?
end
end
get '/test' do
begin
json status: 200, body: 'just a test'
rescue => error
json status: 404, error: error
end
end
end
I would consider using halt:
before do
unless APIHelper.valid_key?(params[:key])
halt 404, { 'Content-Type' => 'application/json' },
{ error: 'Error, invalid API key' }.to_json
end
end
get '/test' do
json status: 200, body: 'just a test'
end
You can use halt method to return response with specific code, body and headers. So it looks like this:
before do
halt 401, {'Content-Type' => 'application/json'}, '{"Message": "..."}'
end
It looks sloppy so you can just redirect to another url, that provide some service
this is my code and I don't know how to debug it because I just get an "internal server error":
I am trying to HTTP POST to an external ASPX:
def upload
uri = 'https://api.postalmethods.com/2009-02-26/PostalWS.asmx' #postalmethods URI
#https://api.postalmethods.com/2009-02-26/PostalWS.asmx?op=UploadFile
#http://www.postalmethods.com/method/2009-02-26/UploadFile
#postalcard = Postalcard.find(:last)
#Username=string&Password=string&MyFileName=string&FileBinaryData=string&FileBinaryData=string&Permissions=string&Description=string&Overwrite=string
filename = #postalcard.postalimage.original_filename
filebinarydata = File.open("#{#postalcard.postalimage.path}",'rb')
body = "Username=me&Password=sekret&MyFileName=#{filename}&FileBinaryData=#{filebinarydata}"
#response = RestClient.post(uri,
body, #body as string
{"Content-Type" => 'application/x-www-form-urlencoded',
"Content-Length" => #postalcard.postalimage.size} # end headers
) #close arguments to Restclient.post
end
Turns on PostalMethods had an error and bug on their HTTP POST.
It only takes SOAP so I would need Savon.