DELETE method with a payload using Ruby Rest:Client? - ruby

While I don't think it is very restful to have to include a payload in a DELETE request. I ran into an instance where I am testing a service that requires a payload for DELETE. Might there be a way using Ruby's Rest Client to accomplish this? Unfortunately, I am having a hard time with this one.
#json_request = '{"user_id": 5, "meta_data": "foo"}'
resource = RestClient::Resource.new "http://www.foo.com/some/process"
#response_update = resource.delete(#json_request, :content_type => :json, :accept => :json)
Output:
ArgumentError:
wrong number of arguments (2 for 0..1)

Try this
RestClient::Request.execute(:method => 'delete', :url => "http://www.foo.com", :payload => json_data)

Currently it's not possible with that gem. You can see a PL addressing that. Maybe you could fork it and pull those changes to your own fork of the rest-client gem.
The pull request https://github.com/rest-client/rest-client/pull/98

As a very modern update, from the ReadMe
RestClient::Request.execute(method: :delete, url: 'http://example.com/resource',
payload: 'foo', headers: {myheader: 'bar'})

Related

Ruby rest-client params with json (and quotes)

This works (on ubuntu):
curl -v -g 'https://example.com/api/path/to/service?json={"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}'
And this works too (in ruby):
require 'rest-client'
resource = RestClient::Resource.new(
'https://example.com/api/path/to/service?json={"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}')
resp = resource.get
However, I would like to split request host and body (in ruby).
I tried this:
require 'rest-client'
resource = RestClient::Resource.new(
'https://example.com/api/path/to/service')
resp = resource.get(:data => 'json={"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}', :content_type => :json, :accept => :json)
but the server returns:
# => 400 BadRequest | application/json 49 bytes
No such encoding: "utf8"
and also with this variation:
resp = resource.get(:json => '{"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}', :content_type => :json, :accept => :json)
and equivalent with :payload => but got same error result.
I have looked stackoverflow thin but nothing I tried seems to work. I have a feeling it is something with the way ruby is escaping the double quotes before sending - just a guess.
Any ideas?
All help would be greatly appreciated. Even a link to the right stackoverflow answer :-)
Thanks.

Regex with webmock in aws-sdk not working

I'm trying to use webmock with rspec to stub out requests to Aws but I can't seem to get the regex to work for SQS polling. If I run rspec, webmock generates a 'correct' stub for me to use in a before(:each) block, in my spec_helper.rb like this:
You can stub this request with the following snippet:
stub_request(:post, "https://sqs.us-west-2.amazonaws.com/123456789012/backlog").
with(:body => "Action=ReceiveMessage&AttributeName.1=All&MaxNumberOfMessages=1&MessageAttributeName.1=All&QueueUrl=https%3A%2F%2Fsqs.us-west-2.amazonaws.com%2F123456789012%2Fbacklog&Version=2012-11-05&VisibilityTimeout=0&WaitTimeSeconds=20",
:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'', 'Authorization'=>'AWS4-HMAC-SHA256 Credential=MY_ACCESS_KEY/20150726/us-west-2/sqs/aws4_request, SignedHeaders=content-type;host;user-agent;x-amz-content-sha256;x-amz-date, Signature=large_alpha-numeric-signature', 'Content-Length'=>'224', 'Content-Type'=>'application/x-www-form-urlencoded; charset=utf-8', 'Host'=>'sqs.us-west-2.amazonaws.com', 'User-Agent'=>'aws-sdk-ruby2/2.1.7 ruby/2.2.2 x86_64-darwin14', 'X-Amz-Content-Sha256'=>'69336339ae76cf370477d4dsaf667as0b5dd8d25762c7c78sad8a', 'X-Amz-Date'=>'20150726T143009Z'}).
to_return(:status => 200, :body => "", :headers => {})
So in my spec_helper.rb I have
RSpec.configure do |config|
config.before(:each) do
stub_request(:post, "https://sqs.us-west-2.amazonaws.com/123456789012/backlog").
with(:body => "Action=ReceiveMessage&AttributeName.1=All&MaxNumberOfMessages=1&MessageAttributeName.1=All&QueueUrl=https%3A%2F%2Fsqs.us-west-2.amazonaws.com%2F123456789012%2Fbacklog&Version=2012-11-05&VisibilityTimeout=0&WaitTimeSeconds=20",
:headers => {'Accept'=>'*/*',
'Accept-Encoding'=>'',
'Authorization'=>"AWS4-HMAC-SHA256 Credential=MY_ACCESS_KEY/20150726/us-west-2/sqs/aws4_request, SignedHeaders=content-type;host;user-agent;x-amz-content-sha256;x-amz-date, Signature=" + /"^[a-zA-Z0-9]*$"/,
'Content-Length'=>'224',
'Content-Type'=>'application/x-www-form-urlencoded; charset=utf-8',
'Host'=>'sqs.us-west-2.amazonaws.com',
'User-Agent'=>'aws-sdk-ruby2/2.1.7 ruby/2.2.2 x86_64-darwin14',
'X-Amz-Content-Sha256'=>'694236339ae76cf370477d4dsaf667as0b5dd8d25762c7c78sad8a',
'X-Amz-Date'=>""+ /"^[a-zA-Z0-9]*$"/}).
to_return(:status => 200, :body => "", :headers => {})
end
The areas I'm trying to use regex against are the Signature and the X-Amz-Date because they're the only two that seem to change between different attempts to run the rspec.
The problem is the regex seems to not be working because even though I've added it into the spec_helper.rb, every time I run the suite, I get back the recommended stub from webmock instead of a passing or failing test. It should be passing at this point, from what I understand from the webmock docs and several tutorials.
How should I change this to get webmock to work for my test suite against Aws SQS polling?
I've been bashing my head against my desk for a few days now so any help is much appreciated.
Signature is likely generated using a byproduct of Time.now and I'm guessing you don't actually want to test for that. Instead simply do:
stub_request(:post, "https://sqs.us-west-2.amazonaws.com/123456789012/backlog").and_return(:status => 200, :body => "", :headers => {})
If you want to be even less specific on the URL (like ommiting that ID) you can even use a regex match:
stub_request(:post, /amazonaws.com/).and_return(:status => 200, :body => "", :headers => {})
I have the same problem and have the solution only for 'X-Amz-Date'.
Since it is date in special format, use Timecop.freeze block around your mock and method call.
Timecop.freeze do
stub_request(:get, "https://s3.eu-central-1.amazonaws.com/test_bucket/test").
with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'', 'Authorization'=>'AWS4-HMAC-SHA256 Credential=access_key/20190814/eu-central-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=9e6a6a209a0cb05346a058c12ef706ebff185ae3b72f3e542e1becbc97e8ea7a', 'Content-Length'=>'0', 'Content-Type'=>'', 'Host'=>'s3.eu-central-1.amazonaws.com', 'User-Agent'=>'aws-sdk-ruby2/2.9.44 ruby/2.6.3 x86_64-darwin18 resources', 'X-Amz-Content-Sha256'=>'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 'X-Amz-Date'=>"#{time.utc.iso8601(0).gsub(/[^\p{Alnum}]/, '')}"}).
to_return(status: 200, body: "", headers: {})
AwsService.bucket.object("test").get
end
The regexp leaves only alphanumerical characters and Timecop takes care of the time.

Stub multipart requests with webmock/rspec

I have been trying for a while to stub multipart requests using webmock and have not found a satisfying solution.
Ideally, I would like to stub the request as follow:
stub_request(:post, 'http://test.api.com').with(:body => { :file1 => File.new('filepath1'), file2 => File.new('filepath2') })
However, this does not seem to work and RSpec complains that the request has not been stubbed. The non-stubbed request is printed:
stub_request(:post, "http://test.api.com").
with(:body => "--785340\r\nContent-Disposition: form-data; name=\"file1\"; filename=\"filepath1\"\r\nContent-Type: text/plain\r\n\r\nhello\r\n--785340\r\nContent-Disposition: form-data; name=\"file2\"; filename=\"filepath2\"\r\nContent-Type: text/plain\r\n\r\nhello2\r\n--785340\r\n",
:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', 'Content-Length'=>'664', 'Content-Type'=>'multipart/form-data; boundary=785340', 'User-Agent'=>'Ruby'}).
to_return(:status => 200, :body => "", :headers => {})
Of course, I can't really follow this suggestion because the boundaries are generated dynamically. Any idea how I could properly stub these requests?
Thanks!
Bruno
Kind of late but I'll leave an answer for future overflowers and googlers.
I had the same problem and used Rack::Multipart::Parser in conjunction with Webmock as a work around. Quick and dirty code should look something like this (warning: uses activesupport extensions):
stub_request(:post, 'sample.com').with do |req|
env = req.headers.transform_keys { |key| key.underscore.upcase }
.merge('rack.input' => StringIO.new(req.body))
parsed_request = Rack::Multipart::Parser.new(env).parse
# Expectations:
assert_equal parsed_request["file1"][:tempfile].read, "hello world"
end
WebMock doesn't support multipart requests at the moment. Check author's comment here for more info: https://github.com/vcr/vcr/issues/295#issuecomment-20181472
I suggest you consider one of the following routes:
stubbing without matching the post multipart body
wrapping the request in a method with file path arguments and setting more fine-grained expectation on this method
using VCR for mocking external requests in integration tests
Here's a workaround using WebMock with regex to match multipart/form-data requests, especially handy for testing uploading of images:
stub_request(:post, 'sample.com').with do |req|
# Test the headers.
assert_equal req.headers['Accept'], 'application/json'
assert_equal req.headers['Accept-Encoding'], 'gzip, deflate'
assert_equal req.headers['Content-Length'], 796588
assert_match %r{\Amultipart/form-data}, req.headers['Content-Type']
assert_equal req.headers['User-Agent'], 'Ruby'
# Test the body. This will exclude the image blob data which follow these lines in the body
req.body.lines[1].match('Content-Disposition: form-data; name="FormParameterNameForImage"; filename="image_filename.jpeg"').size >= 1
req.body.lines[2].match('Content-Type: img/jpeg').size >= 1
end
Testing only the headers could also have been done using the normal WebMock way of using stub_request(:post, 'sample.com').with(headers: {'Accept' => 'application/json}), and simply not include any body specification in the with clause.

Ruby RestClient converts XML to Hash

I need to send a POST request as an XML string but I get odd results. The code:
require 'rest_client'
response = RestClient.post "http://127.0.0.1:2000", "<tag1>text</tag1>", :content_type => "text/xml"
I expect to receive "<tag1>text</tag1>" as the parameter on the request server. Instead, I get "tag1"=>"text". It converts the XML to a hash. Why is that? Any way around this?
Try this:
response = RestClient.post "http://127.0.0.1:2000",
"<tag1>text</tag1>",
{:accept => :xml, :content_type => :xml}
I think you just needed to specify the ":accept" to let it know you wanted to receive it in the XML format. Assuming it's your own server, you can debug on the server and see the request format used is probably html.
Hope that helps.
Instead of using RestClient, use Ruby's built-in Open::URI for GET requests or something like Net::HTTP or the incredibly powerful Typhoeus:
uri = URI('http://www.example.com/search.cgi')
res = Net::HTTP.post_form(uri, 'q' => 'ruby', 'max' => '50')
In Typhoeus, you'd use:
res = Typhoeus::Request.post(
'http://localhost:3000/posts',
:params => {
:title => 'test post',
:content => 'this is my test'
}
)
Your resulting page, if it's in XML will be easy to parse using Nokogiri:
doc = Nokogiri::XML(res.body)
At that point you'll have a fully parsed DOM, ready to be searched, using Nokogiri's search methods, such as search and at, or any of their related methods.

how RestClient gem work in ruby

I'm using Rest-client gem in ruby.My code is as follows..,
require 'rest_client'
puts RestClient.get 'http://localhost:3000/articles'
puts RestClient.put 'http://localhost:3000/', {:params => {:Bat => 'ball'}}
RestClient.post 'http://localhost:3000/articles', {:params => {:Name => 'list1', 'Content' => 'Article1'}}
I refer the URL which runs in rails application, the user can can create, delete, edit,list the articles using the above url.For put,delete,post,get methods it produces the html code of the URL in my prompt.But it cannnot able to insert the post/delete an item from the list via ruby code.
It is possible in RestClient?
I think the problem here is that you need to be authenticated. You can do that with RestClient but you will need to chain your calls. See the rest_client Readme how to do that.

Resources