passing arguments in rspec shared example - ruby

I am a bit new with Rspec.
Here is my problem
I have an example which could be shared
shared example
RSpec.shared_examples "coupons_shared" do |arg1,email,coupon1,coupon2|
it "present_coupons" do
post_rest_url = "https://blahblahblah=" + "#{arg1}" + "&email=" + "#{email}"
json_request = <<END_OF_MESSAGE
[
"#{coupon1}",
"#{coupon2}"
]
END_OF_MESSAGE
header = {:accept => "application/json",:content_type => "application/json"}
resp = RestClient.post(post_rest_url, json_request, header)
json_obj = JSON.parse(resp)
expect(json_obj[0]["ccode"]).to include("#{coupon1}")
expect(json_obj[0]["ccode"]).to include("#{coupon2}")
end
end
shared example file location is in \spec\support\shared_examples
In the actual spec file I have an example which gets the coupon and then need to present using the shared example
describe "enrol_cust" do
cust_id = '1'
coupons = []
header_basic_auth = {:accept => "application/json",:content_type => "application/json"}
random_no = (rand(999) + 10)
random_no = random_no.to_s
email = "johndoe" + "#{random_no}" + "#gmail.com"
st = 1111
st = st.to_i
before(:each) do
#dob = 20.years.ago(Date.today).strftime("%m-%d-%Y")
end
it "enrol_cust" do
post_rest_url = "https://blahblah?st=" + "#{st}"
json_request = <<END_OF_MESSAGE
{
"email": "#{email}",
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "#{#dob}",
}
END_OF_MESSAGE
header = header_basic_auth
resp = RestClient.post(post_rest_url, json_request, header)
json_obj = JSON.parse(resp)
cust_id = json_obj["cid"]
end
# above example gets customer id
it "get_list" do
get_rest_url = "https://blahblah=" + "#{cust_id}" + "&st=" + "#{st}"
header = header_basic_auth
resp = RestClient.get(get_rest_url, header)
json_obj = JSON.parse(resp)
coupons = json_obj.collect {|x| x["cccode"]}
end
# above example gets coupons
# I have tried printing out the coupons and I can see the correct coupons in this example
include_examples "coupons_shared" ,"#{st}","#{email}","#{coupons[0]}","#{coupons[1]}"
When I try to pass the parameters, st and email is passed correctly. However, coupons[0] and coupons[1] is always passed as ""
I am not sure what am I missing here.

To pass an argument to a shared example, wrap the variable inside the example block (without listing them as you have as arguments in the example):
RSpec.shared_examples "coupons_shared" do
...code that includes your variables coupon1, coupon2 etc..
end
include_examples "coupons_shared" do
coupon1 = coupons[0]
...and so on (also works with let)...
let(:coupon1) { coupons[0] }
end
I would also strongly suggest that you stub out the HTTP requests so they aren't hitting an actual server every time you run your tests and consider using FactoryBot (or fixtures if that's your preference) to clean up a lot of the variable assignments.

Related

BulkEnvelopesApi Docusign request data format

I am stuck when using BulkEnvelopesApi rom Docusign API for Ruby. It keep on returning me this error:
DocuSign_eSign::ApiError (Bad Request):
Edit: After #kiddorails suggestion to include debug = True, the error message is:
{ "errorCode": "UNSPECIFIED_ERROR", "message": "Value cannot be
null.\r\nParameter name: stream" }
I am using this gem: https://github.com/docusign/docusign-ruby-client
Is there anything wrong with the format of how I input the data?
Here's my code for reference.
def self.send_env()
self.auth # To initialize the #api_client
t1 = DocuSign_eSign::TemplatesApi.new(#api_client)
signer_placeholder ={"signers":[{"email":"bulk#recipient.com","name":"Bulk Recipient",
"routingOrder":1,"recipientId":"1","roleName":"Fruit",
"isBulkRecipient":"True"}]}
t1.update_recipients(account_id=ENV["ACCOUNT_ID_LIVE"], template_id=ENV["TEMPLATE_ID_LIVE"], template_recipients=signer_placeholder)
br = DocuSign_eSign::BulkRecipient.new(
{
"accessCode": '112233',
"email": 'berry#gmail.com',
"name": 'Berry',
"rowNumber":1
}
)
brr = DocuSign_eSign::BulkRecipientsRequest.new({"bulkRecipients": Array(br)})
bea = DocuSign_eSign::BulkEnvelopesApi.new(#api_client)
bea.update_recipients(account_id=ENV["ACCOUNT_ID_LIVE"], envelope_id=ENV["TEMPLATE_ID_LIVE"],
recipient_id="1",bulk_recipients_request=brr)
I am able to call other Docusign APIs using this similar data format. Only unable to work for BulkEnvelopesAPI.
I am thinking whether is there anything wrong with the source code for this BulkEnvelopesAPI function.
Thank you for reading!
With #kiddorails help, I've managed to solve it.
Here's the solution:
First, edit the source code in this format. Main idea is to change the Content-Type into 'text/csv', and input a string into the body.
def update_recipients_with_http_info(account_id, envelope_id, recipient_id, bulk_recipients_request)
if #api_client.config.debugging
#api_client.config.logger.debug "Calling API: BulkEnvelopesApi.update_recipients ..."
end
# verify the required parameter 'account_id' is set
fail ArgumentError, "Missing the required parameter 'account_id' when calling BulkEnvelopesApi.update_recipients" if account_id.nil?
# verify the required parameter 'envelope_id' is set
fail ArgumentError, "Missing the required parameter 'envelope_id' when calling BulkEnvelopesApi.update_recipients" if envelope_id.nil?
# verify the required parameter 'recipient_id' is set
fail ArgumentError, "Missing the required parameter 'recipient_id' when calling BulkEnvelopesApi.update_recipients" if recipient_id.nil?
# resource path
local_var_path = "/v2/accounts/{accountId}/envelopes/{envelopeId}/recipients/{recipientId}/bulk_recipients".sub('{format}','json').sub('{' + 'accountId' + '}', account_id.to_s).sub('{' + 'envelopeId' + '}', envelope_id.to_s).sub('{' + 'recipientId' + '}', recipient_id.to_s)
# query parameters
query_params = {}
# header parameters
header_params = {}
# HTTP header 'Accept' (if needed)
header_params['Accept'] = #api_client.select_header_accept(['application/json'])
header_params['Content-Type'] = 'text/csv'
# form parameters
form_params = {}
# http body (model)
# post_body = #api_client.object_to_http_body(bulk_recipients_request)
# puts bulk_recipients_request
# post_body = body_params
auth_names = []
data, status_code, headers = #api_client.call_api(:PUT, local_var_path,
:header_params => header_params,
:query_params => query_params,
:form_params => form_params,
:body => bulk_recipients_request,
:auth_names => auth_names,
:return_type => 'BulkRecipientsSummaryResponse')
if #api_client.config.debugging
#api_client.config.logger.debug "API called: BulkEnvelopesApi#update_recipients\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
end
return data, status_code, headers
end
end
Secondly, set bulk_recipients_request to be a string (with newlines for each row of data)
"rowNumber,email,name,Accesscode\n"+"1,berry#gmail.com,Berry,11223\n"

How to make persistent HTTP requests using multiple threads in Ruby/Faraday?

I'm using faraday with net-http-persistent adapter to make HTTP requests.
I want to optimise my requests by making them execute asynchronously but as I need a persistent connection I keep getting errors such as too many connections reset which I assume is due to the fact that I have multiple threads creating new connections.
I tried changing my adapter to typhoeus but as the connection is not persistent the final result of executing all request is not as expected.
My goal is to add items to a basket by making this HTTP requests. Without the persistent connection items are not added to the basket.
So, my question is:
Is it possible to make persistent HTTP requests reusing the connection between threads? If so, how can this be achieved?
Here is a piece of my code:
Create the connection:
Faraday.new do |c|
c.use :cookie_jar, jar: cookie_jar
c.options.open_timeout = 5
c.options.timeout = 10
c.request :url_encoded
c.response :logger, logger
c.adapter :net_http_persistent do |http| # yields Net::HTTP::Persistent
http.idle_timeout = 2
end
end
Creating threads and getting the result of each one of them
result = []
threads = []
total_items = items.size
items.each_slice(5) do |sliced_items|
# Create a thread for a batch of 5 items and store its result
threads << Thread.new do
Thread.current[:output] = browser.add_all_items(sliced_items)
end
end
# Wait for all threads to finish their work and store their output into result
threads.each do |t|
t.join
result << t[:output]
end
add_all_items and add_to_cart methods:
# Add a batch of items by the key passed (id, gtin, url)
def add_all_items(items_info, key)
results = []
items_info.each do |item|
begin
add_to_cart(item[key], item[:quantity])
item[:message] = nil
rescue => e
item[:message] = e.message
puts "---------- BACKTRACE -------------- \n #{e.backtrace}"
end
puts "\n--------- MESSAGE = #{item[:message]} --------- \n"
results << item
puts "-------- RESULTS #{results}"
end
results
end
def add_to_cart(url, count = 1)
response = connection.get(url) do |req|
req.headers["User-Agent"] = #user_agent
end
doc = Nokogiri::HTML(response.body)
stoken = doc.search('form.js-oxProductForm input[name=stoken]').attr('value').value
empty_json = '""'
product_id = get_item_id(url)
data = { #removed payload for security reasons }
# Using example.com for question purposes
response = connection.post('https://www.example.com/index.php?') do |req|
req.headers["Origin"] = "https://www.example.com"
req.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
req.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
req.headers["Referer"] = url
req.headers["Pragma"] = "no-cache"
req.headers["Accept-Language"] = "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"
req.headers["User-Agent"] = #user_agent
req.headers["Cache-Control"] = "no-cache"
req.headers["Connection"] = "keep-alive"
req.headers["DNT"] ="1"
req.headers["Content-Length"] = data.size.to_s
req.headers["Accept"] = "*/*"
req.headers["X-Requested-With"] = "XMLHttpRequest"
req.headers["Connection"] = "keep-alive"
req.body = data
end
begin
json = JSON.parse(response.body)
raise "Could not add item: #{json['message']}" if json['success'] != 1 || json['item'] != product_id
rescue JSON::ParserError => e
puts "JSON Error"
end
end
def get_item_id(url)
response = connection.get(url) do |req|
req.headers["User-Agent"] = #user_agent
end
doc = Nokogiri::HTML(response.body)
doc.search('.js-oxProductForm input[name=aid]').attr('value').value
end
Thanks in advance.

Can mechanize for Ruby filter the contents of a <select> by class?

Sorry, but I didn't find the documentation enlightening at all. Basically, I am trying to iterate through a where some options are not valid. The ones I want have 'class="active"'. Can I do that with mechanize? Here's what I have so far:
class Scraper
def init
mech = Mechanize.new
page = mech.get('url')
#Now go through the <select> to get product numbers for the different flavors
form = page.form_with(:id => 'twister')
select = form.field_with(:name => 'dropdown_selected_flavor_name')
select.options.each do |o|
if (o.text != "")
value = o
end
productNumber = trim_pn(value.to_s[2..12])
puts productNumber
end
end
#Checks validity of product number and removes excess characters if necessary
def trim_pn(pn)
if (pn[0] == ",")
pn = pn[1..-1]
end
return pn
end
end
p = Scraper.new
p.init
All that does is grabs the product number and removes some extra info that I don't want. I thought replacing the .each do with this:
select.options_with(:class => 'active').each do |o|
if (o.text != "")
value = o
end
end
But that throws "undefined method 'dom_class' for Mechanize:Form:Option blah blah." Is there are different way I should be approaching this?

Ruby <NoMethodError>

I'm using the following script to download FFFFound entire directory images but im having an issue, after the first batch of files Im getting this error message:
ffffound_mirror_db.rb:45in 'block in populate_db': undefined method 'inner_html' for nil:NilClass <NoMethodError>
from ffffound_mirror_db.rb:39:in 'each'
from ffffound_mirror_db.rb:39:in 'populate_db'
from ffffound_mirror_db.rb:190:in <main>
I'm trying to download all the 99 pages of a directory so the offset has to increase 25 every batch, eg: 0, 25, 50, 75, 100
#!/usr/bin/ruby
require 'rubygems'
require 'etc'
require 'hpricot'
require 'json'
require 'open-uri'
require 'sqlite3'
require 'time'
require 'date'
require 'fileutils'
def populate_db(db, user, type)
domain = "http://ffffound.com/"
offset = 0
images_sql = <<EOS
INSERT OR REPLACE INTO
images (id, url, src, title, orig_url, orig_src, count, date, related)
values (:id, :ffffound_url, :ffffound_img, :title, :orig_url, :orig_img, :count, :date, :rel)
EOS
images_ins = db.prepare(images_sql)
# related_ins = db.prepare( "insert into related values (?, ?, ?)" )
img = []
while
if user == "all" # wow, this is naughty
doc = Hpricot(open("#{ domain }/?offset=#{ offset }&"))
else
doc = Hpricot(open("#{ domain }/home/#{ user }/#{ type }/?offset=#{ offset }&"))
end
images = (doc/"blockquote.asset")
puts "Got #{ images.size.to_s } images at offset #{ offset.to_s }"
break if (images.size == 0)
images.each do |image|
# can I make this block into a method somehow?
info = {}
# image title
title_elem = (image/"div.title")
info[:title] = title_elem.at("a").inner_html
# original source image
src_elem = (image/"div.title")
info[:orig_url] = src_elem.at("a")["href"]
# from description, break out img url, date posted (relative!), count
desc_elem = (image/"div.description")
desc = desc_elem.inner_html
info[:orig_img] = desc.gsub(/<br ?\/?>.*/, "")
datestr = desc.gsub(/.*<br ?\/?>/, "")
datestr = datestr.gsub(/<a h.*/, "")
datestr = datestr+" +0900" # ffffound uses Japanese time, UTC +0900
begin
dt = Time.parse(datestr)
rescue
end
info[:date] = dt.to_i
count = desc_elem.at("a").inner_text
count = count.gsub(/[\D]/, "")
info[:count] = count
# ffffound image URL and page URL, and ffffound ID (could generate
# URL from ID but would lose ?c form; src would lose _m)
image_block = (image/"table td")
ffffound_url = image_block.at("a")['href']
ffffound_img = image_block.at("img")['src']
id = ffffound_img
id = ffffound_img.split('/')[6]
id = id.gsub(/_.*/, "")
info[:id] = id
info[:ffffound_url] = ffffound_url
info[:ffffound_img] = ffffound_img
download_file(ffffound_img, id)
# might as well get related asset IDs
rel = Array.new
relateds = (image/"div.related_to_item_xs")
relateds.each do |related|
path = related.at("a")['href']
id = path[ path.index(/\//, 2)+1 .. -1 ]
rel.push(id)
# TODO normalised table for related IDs
end
info[:rel] = rel.join(",")
img.unshift(info)
# put in db
images_ins.execute(info)
end
break if (images.size < 25) # more efficient than doing another fetch
offset = offset + 25
end
puts "Got #{ img.size } images"
end
def create_db(db)
images = <<EOC
CREATE TABLE IF NOT EXISTS
images (id TEXT PRIMARY KEY,
url TEXT,
src TEXT,
title TEXT,
orig_url TEXT,
orig_src TEXT,
date INTEGER,
count INTEGER,
related TEXT,
posted BOOL);
EOC
related = <<EOC
CREATE TABLE IF NOT EXISTS
related (id INTEGER PRIMARY KEY,
source INTEGER
related INTEGER);
EOC
tumblr = <<EOC
CREATE TABLE tumblr (id INTEGER PRIMARY KEY,
ffffound_id TEXT,
tumblr_id INTEGER,
create_status INTEGER,
edit_status INTEGER);
EOC
db.execute(images)
db.execute(related)
return true
end
def download_file(url, id)
# TODO file type awareness
# does it exist?
if not File.exist?('images/'+id+'.jpg')
writeOut = open("images/"+id+'.jpg', 'wb')
writeOut.write(open(url).read)
writeOut.close
puts ' downloaded ' + id
end
end
def create_paths()
['images', 'db'].each do |path|
if not File.exist?(path)
FileUtils.mkdir(path)
end
end
end
# this needs work (is there a more idiomatic way to do this?)
user = ARGV[0]
type = ARGV[1] || 'found'
if not user
puts "A ffffound username must be supplied"
exit
else
if user == "--all"
puts "Invoked for all posts"
user = "all"
end
puts "Invoked for posts by #{user} of type #{type}"
end
create_paths()
path = 'db/ffffound-'+user+'.db' # ick
db = SQLite3::Database.new(path)
create_db(db)
populate_db(db, user, type)
exit
# puts img.to_json
# DONE puts img.to_database_table(s)
In these 2 parts:
title_elem = (image/"div.title")
info[:title] = title_elem.at("a").inner_html
desc_elem = (image/"div.description")
desc = desc_elem.inner_html
You don't check if elem exists, but calling inner_html right after. So, basically, if desc_elem is nil, you call nil.inner_html which raises exception, because there is no such method for nil.
To fix this, replace corresponding lines with(for Ruby without Rails)
title_elem = (image/"div.title")
info[:title] = title_elem.at("a").inner_html unless title_elem.at("a").nil?
desc_elem = (image/"div.description")
desc = desc_elem.inner_html unless desc_elem.nil?
In Rails there is a .try method, which prevents such exception raising, so
title_elem = (image/"div.title")
info[:title] = title_elem.at("a").try(:inner_html)
desc_elem = (image/"div.description")
desc = desc_elem.try(:inner_html)
is a solution if you are using Rails.

How to return a particular value from a method?

I have this code that tries to return a value from a method:
temp = "123"
return temp
and I have this line that calls the method and assigns the return value:
person_connections = #client.get_person_connections(:id => current_user_id )
but when I try to inspect person_connections, it shows some different object string. Any idea how to return the actual value of the temp variable?
def get_person_connections(options = {})
person_id = options[:id]
path = "/people/id=" + person_id + ":(num-connections)"
query_connections(path, options)
self
end
and
private
def query_connections(path, options={})
fields = options.delete(:fields) || LinkedIn.default_profile_fields
if options.delete(:public)
path +=":public"
elsif fields
path +=":(#{fields.map{ |f| f.to_s.gsub("_","-") }.join(',')})"
end
headers = options.delete(:headers) || {}
params = options.map { |k,v| v.is_a?(Array) ? v.map{|i| "#{k}=#{i}"}.join("&") : "#{k}=#{v}" }.join("&")
path += "?#{params}" if not params.empty?
temp_var = get(path, headers)
hash = JSON.parse(temp_var)
conn = hash["numConnections"]
end
As Samy said in a comment:
In Ruby, the last statement will be returned.
So if we take a look at get_person_connections, we see that the last line is self. What it means is that it returns the instance on which the method was called, #client in this case.
Additional notes: the solution would be to remove self, although if the method is used elsewhere be careful as returning self is often used to allow chaining of methods (though it hardly makes sense to do that on a get method).

Resources