diff --git a/.travis.yml b/.travis.yml index b2a21e056..224edfd93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ rvm: - 1.9.2 - 1.9.3 - 2.0.0 - - rbx-18mode - - rbx-19mode + - 2.1.0 + - rbx - jruby-18mode - jruby-19mode - jruby-20mode diff --git a/CHANGELOG.md b/CHANGELOG.md index af8a93e54..d7687cb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ -# 0.7.0.rc2 +# 0.7.0 +* Remove CLI +* SUpport for automatic retires & backoff. Off by default, enable by setting `retries` on `APIClient` +* Experimental new interface (see `Google::APIClient::Service`) * Fix warnings when using Faraday separately * Support Google Compute Engine service accounts * Enable gzip compression for responses -* Upgrade to Faraday 0.9.x. Resolves multiple issues with query parameter encodings. +* Upgrade to Faraday 0.9.0. Resolves multiple issues with query parameter encodings. * Use bundled root certificates for verifying SSL certificates * Rewind media when retrying uploads diff --git a/Gemfile b/Gemfile index d1305c6a3..8b4cb4c17 100644 --- a/Gemfile +++ b/Gemfile @@ -6,10 +6,11 @@ gem 'signet', '>= 0.5.0' gem 'addressable', '>= 2.3.2' gem 'uuidtools', '>= 2.1.0' gem 'autoparse', '>= 0.3.3' -gem 'faraday', '>= 0.9.0.rc5' +gem 'faraday', '>= 0.9.0' gem 'multi_json', '>= 1.0.0' gem 'extlib', '>= 0.9.15' gem 'jwt', '~> 0.1.5' +gem 'retriable', '>= 1.4' gem 'jruby-openssl', :platforms => :jruby group :development do @@ -18,6 +19,13 @@ group :development do gem 'kramdown' end + +platforms :rbx do + gem 'rubysl', '~> 2.0' + gem 'psych' +end + + group :examples do gem 'sinatra' end @@ -29,4 +37,5 @@ group :test, :development do gem 'rcov', '>= 0.9.9', :platform => :mri_18 end + gem 'idn', :platform => :mri_18 diff --git a/bin/google-api b/bin/google-api deleted file mode 100755 index c5082fe7c..000000000 --- a/bin/google-api +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/env ruby - -bin_dir = File.expand_path("..", __FILE__) -lib_dir = File.expand_path("../lib", bin_dir) - -$LOAD_PATH.unshift(lib_dir) -$LOAD_PATH.uniq! - -OAUTH_SERVER_PORT = 12736 - -require 'rubygems' -require 'optparse' -require 'faraday' -require 'webrick' -require 'google/api_client/version' -require 'google/api_client' -require 'google/api_client/auth/installed_app' -require 'irb' - -ARGV.unshift('--help') if ARGV.empty? - -module Google - class APIClient - class CLI - - # Initialize with default parameter values - def initialize(argv) - @options = { - :command => 'execute', - :rpcname => nil, - :verbose => false - } - @argv = argv.clone - if @argv.first =~ /^[a-z0-9][a-z0-9_-]*$/i - self.options[:command] = @argv.shift - end - if @argv.first =~ /^[a-z0-9_-]+\.[a-z0-9_\.-]+$/i - self.options[:rpcname] = @argv.shift - end - end - - attr_reader :options - attr_reader :argv - - def command - return self.options[:command] - end - - def rpcname - return self.options[:rpcname] - end - - def parser - @parser ||= OptionParser.new do |opts| - opts.banner = "Usage: google-api " + - "(execute | [command]) [options] [-- ]" - - opts.separator "\nAvailable options:" - - opts.on( - "--scope ", String, "Set the OAuth scope") do |s| - options[:scope] = s - end - opts.on( - "--client-id ", String, - "Set the OAuth client id or key") do |k| - options[:client_credential_key] = k - end - opts.on( - "--client-secret ", String, - "Set the OAuth client secret") do |s| - options[:client_credential_secret] = s - end - opts.on( - "--api ", String, - "Perform discovery on API") do |s| - options[:api] = s - end - opts.on( - "--api-version ", String, - "Select api version") do |id| - options[:version] = id - end - opts.on( - "--content-type ", String, - "Content-Type for request") do |f| - # Resolve content type shortcuts - case f - when 'json' - f = 'application/json' - when 'xml' - f = 'application/xml' - when 'atom' - f = 'application/atom+xml' - when 'rss' - f = 'application/rss+xml' - end - options[:content_type] = f - end - opts.on( - "-u", "--uri ", String, - "Sets the URI to perform a request against") do |u| - options[:uri] = u - end - opts.on( - "--discovery-uri ", String, - "Sets the URI to perform discovery") do |u| - options[:discovery_uri] = u - end - opts.on( - "-m", "--method ", String, - "Sets the HTTP method to use for the request") do |m| - options[:http_method] = m - end - opts.on( - "--requestor-id ", String, - "Sets the email address of the requestor") do |e| - options[:requestor_id] = e - end - - opts.on("-v", "--verbose", "Run verbosely") do |v| - options[:verbose] = v - end - opts.on("-h", "--help", "Show this message") do - puts opts - exit - end - opts.on("--version", "Show version") do - puts "google-api-client (#{Google::APIClient::VERSION::STRING})" - exit - end - - opts.separator( - "\nAvailable commands:\n" + - " oauth-2-login Log a user into an API with OAuth 2.0\n" + - " list List the methods available for an API\n" + - " execute Execute a method on the API\n" + - " irb Start an interactive client session" - ) - end - end - - def parse! - self.parser.parse!(self.argv) - symbol = self.command.gsub(/-/, "_").to_sym - if !COMMANDS.include?(symbol) - STDERR.puts("Invalid command: #{self.command}") - exit(1) - end - self.send(symbol) - end - - def client - require 'yaml' - config_file = File.expand_path('~/.google-api.yaml') - authorization = nil - if File.exist?(config_file) - config = open(config_file, 'r') { |file| YAML.load(file.read) } - else - config = {} - end - if config["mechanism"] - authorization = config["mechanism"].to_sym - end - - client = Google::APIClient.new( - :application_name => 'Ruby CLI', - :application_version => Google::APIClient::VERSION::STRING, - :authorization => authorization) - - case authorization - when :oauth_1 - STDERR.puts('OAuth 1 is deprecated. Please reauthorize with OAuth 2.') - client.authorization.client_credential_key = - config["client_credential_key"] - client.authorization.client_credential_secret = - config["client_credential_secret"] - client.authorization.token_credential_key = - config["token_credential_key"] - client.authorization.token_credential_secret = - config["token_credential_secret"] - when :oauth_2 - client.authorization.scope = options[:scope] - client.authorization.client_id = config["client_id"] - client.authorization.client_secret = config["client_secret"] - client.authorization.access_token = config["access_token"] - client.authorization.refresh_token = config["refresh_token"] - else - # Dunno? - end - - if options[:discovery_uri] - if options[:api] && options[:version] - client.register_discovery_uri( - options[:api], options[:version], options[:discovery_uri] - ) - else - STDERR.puts( - 'Cannot register a discovery URI without ' + - 'specifying an API and version.' - ) - exit(1) - end - end - - return client - end - - def api_version(api_name, version) - v = version - if !version - if client.preferred_version(api_name) - v = client.preferred_version(api_name).version - else - v = 'v1' - end - end - return v - end - - COMMANDS = [ - :oauth_2_login, - :list, - :execute, - :irb, - ] - - def oauth_2_login - require 'signet/oauth_2/client' - require 'yaml' - if !options[:client_credential_key] || - !options[:client_credential_secret] - STDERR.puts('No client ID and secret supplied.') - exit(1) - end - if options[:access_token] - config = { - "mechanism" => "oauth_2", - "scope" => options[:scope], - "client_id" => options[:client_credential_key], - "client_secret" => options[:client_credential_secret], - "access_token" => options[:access_token], - "refresh_token" => options[:refresh_token] - } - config_file = File.expand_path('~/.google-api.yaml') - open(config_file, 'w') { |file| file.write(YAML.dump(config)) } - exit(0) - else - flow = Google::APIClient::InstalledAppFlow.new( - :port => OAUTH_SERVER_PORT, - :client_id => options[:client_credential_key], - :client_secret => options[:client_credential_secret], - :scope => options[:scope] - ) - - oauth_client = flow.authorize - if oauth_client - config = { - "mechanism" => "oauth_2", - "scope" => options[:scope], - "client_id" => oauth_client.client_id, - "client_secret" => oauth_client.client_secret, - "access_token" => oauth_client.access_token, - "refresh_token" => oauth_client.refresh_token - } - config_file = File.expand_path('~/.google-api.yaml') - open(config_file, 'w') { |file| file.write(YAML.dump(config)) } - end - exit(0) - end - end - - def list - api_name = options[:api] - unless api_name - STDERR.puts('No API name supplied.') - exit(1) - end - #client = Google::APIClient.new(:authorization => nil) - if options[:discovery_uri] - if options[:api] && options[:version] - client.register_discovery_uri( - options[:api], options[:version], options[:discovery_uri] - ) - else - STDERR.puts( - 'Cannot register a discovery URI without ' + - 'specifying an API and version.' - ) - exit(1) - end - end - version = api_version(api_name, options[:version]) - api = client.discovered_api(api_name, version) - rpcnames = api.to_h.keys - puts rpcnames.sort.join("\n") - exit(0) - end - - def execute - client = self.client - - # Setup HTTP request data - request_body = '' - input_streams, _, _ = IO.select([STDIN], [], [], 0) - request_body = STDIN.read || '' if input_streams - headers = [] - if options[:content_type] - headers << ['Content-Type', options[:content_type]] - elsif request_body - # Default to JSON - headers << ['Content-Type', 'application/json'] - end - - if options[:uri] - # Make request with URI manually specified - uri = Addressable::URI.parse(options[:uri]) - if uri.relative? - STDERR.puts('URI may not be relative.') - exit(1) - end - if options[:requestor_id] - uri.query_values = uri.query_values.merge( - 'xoauth_requestor_id' => options[:requestor_id] - ) - end - method = options[:http_method] - method ||= request_body == '' ? 'GET' : 'POST' - method.upcase! - response = client.execute(:http_method => method, :uri => uri.to_str, - :headers => headers, :body => request_body) - puts response.body - exit(0) - else - # Make request with URI generated from template and parameters - if !self.rpcname - STDERR.puts('No rpcname supplied.') - exit(1) - end - api_name = options[:api] || self.rpcname[/^([^\.]+)\./, 1] - version = api_version(api_name, options[:version]) - api = client.discovered_api(api_name, version) - method = api.to_h[self.rpcname] - if !method - STDERR.puts( - "Method #{self.rpcname} does not exist for " + - "#{api_name}-#{version}." - ) - exit(1) - end - parameters = self.argv.inject({}) do |accu, pair| - name, value = pair.split('=', 2) - accu[name] = value - accu - end - if options[:requestor_id] - parameters['xoauth_requestor_id'] = options[:requestor_id] - end - begin - result = client.execute( - :api_method => method, - :parameters => parameters, - :merged_body => request_body, - :headers => headers - ) - puts result.response.body - exit(0) - rescue ArgumentError => e - puts e.message - exit(1) - end - end - end - - def irb - $client = self.client - # Otherwise IRB will misinterpret command-line options - ARGV.clear - IRB.start(__FILE__) - end - - def help - puts self.parser - exit(0) - end - end - end -end - -Google::APIClient::CLI.new(ARGV).parse! diff --git a/google-api-client.gemspec b/google-api-client.gemspec index 6b91979a9..2fb70ade3 100644 --- a/google-api-client.gemspec +++ b/google-api-client.gemspec @@ -2,16 +2,15 @@ Gem::Specification.new do |s| s.name = "google-api-client" - s.version = "0.7.0.rc2" + s.version = "0.7.0" - s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version= + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Bob Aman", "Steve Bazyl"] - s.date = "2013-09-09" + s.date = "2014-01-16" s.description = "The Google API Ruby Client makes it trivial to discover and access supported\nAPIs.\n" s.email = "sbazyl@google.com" - s.executables = ["google-api"] s.extra_rdoc_files = ["README.md"] - s.files = ["lib/cacerts.pem", "lib/compat", "lib/compat/multi_json.rb", "lib/google", "lib/google/api_client", "lib/google/api_client.rb", "lib/google/api_client/auth", "lib/google/api_client/auth/compute_service_account.rb", "lib/google/api_client/auth/file_storage.rb", "lib/google/api_client/auth/installed_app.rb", "lib/google/api_client/auth/jwt_asserter.rb", "lib/google/api_client/auth/key_utils.rb", "lib/google/api_client/auth/pkcs12.rb", "lib/google/api_client/batch.rb", "lib/google/api_client/client_secrets.rb", "lib/google/api_client/discovery", "lib/google/api_client/discovery.rb", "lib/google/api_client/discovery/api.rb", "lib/google/api_client/discovery/media.rb", "lib/google/api_client/discovery/method.rb", "lib/google/api_client/discovery/resource.rb", "lib/google/api_client/discovery/schema.rb", "lib/google/api_client/environment.rb", "lib/google/api_client/errors.rb", "lib/google/api_client/gzip.rb", "lib/google/api_client/logging.rb", "lib/google/api_client/media.rb", "lib/google/api_client/railtie.rb", "lib/google/api_client/reference.rb", "lib/google/api_client/request.rb", "lib/google/api_client/result.rb", "lib/google/api_client/service_account.rb", "lib/google/api_client/version.rb", "lib/google/inflection.rb", "spec/fixtures", "spec/fixtures/files", "spec/fixtures/files/privatekey.p12", "spec/fixtures/files/sample.txt", "spec/fixtures/files/secret.pem", "spec/google", "spec/google/api_client", "spec/google/api_client/batch_spec.rb", "spec/google/api_client/discovery_spec.rb", "spec/google/api_client/gzip_spec.rb", "spec/google/api_client/media_spec.rb", "spec/google/api_client/request_spec.rb", "spec/google/api_client/result_spec.rb", "spec/google/api_client/service_account_spec.rb", "spec/google/api_client_spec.rb", "spec/spec_helper.rb", "tasks/gem.rake", "tasks/git.rake", "tasks/metrics.rake", "tasks/spec.rake", "tasks/wiki.rake", "tasks/yard.rake", "CHANGELOG.md", "CONTRIBUTING.md", "Gemfile", "LICENSE", "README.md", "Rakefile", "bin/google-api"] + s.files = ["lib/cacerts.pem", "lib/compat", "lib/compat/multi_json.rb", "lib/google", "lib/google/api_client", "lib/google/api_client.rb", "lib/google/api_client/auth", "lib/google/api_client/auth/compute_service_account.rb", "lib/google/api_client/auth/file_storage.rb", "lib/google/api_client/auth/installed_app.rb", "lib/google/api_client/auth/jwt_asserter.rb", "lib/google/api_client/auth/key_utils.rb", "lib/google/api_client/auth/pkcs12.rb", "lib/google/api_client/batch.rb", "lib/google/api_client/client_secrets.rb", "lib/google/api_client/discovery", "lib/google/api_client/discovery.rb", "lib/google/api_client/discovery/api.rb", "lib/google/api_client/discovery/media.rb", "lib/google/api_client/discovery/method.rb", "lib/google/api_client/discovery/resource.rb", "lib/google/api_client/discovery/schema.rb", "lib/google/api_client/environment.rb", "lib/google/api_client/errors.rb", "lib/google/api_client/gzip.rb", "lib/google/api_client/logging.rb", "lib/google/api_client/media.rb", "lib/google/api_client/railtie.rb", "lib/google/api_client/reference.rb", "lib/google/api_client/request.rb", "lib/google/api_client/result.rb", "lib/google/api_client/service", "lib/google/api_client/service.rb", "lib/google/api_client/service/batch.rb", "lib/google/api_client/service/request.rb", "lib/google/api_client/service/resource.rb", "lib/google/api_client/service/result.rb", "lib/google/api_client/service/simple_file_store.rb", "lib/google/api_client/service/stub_generator.rb", "lib/google/api_client/service_account.rb", "lib/google/api_client/version.rb", "lib/google/inflection.rb", "spec/fixtures", "spec/fixtures/files", "spec/fixtures/files/privatekey.p12", "spec/fixtures/files/sample.txt", "spec/fixtures/files/secret.pem", "spec/google", "spec/google/api_client", "spec/google/api_client/batch_spec.rb", "spec/google/api_client/discovery_spec.rb", "spec/google/api_client/gzip_spec.rb", "spec/google/api_client/media_spec.rb", "spec/google/api_client/request_spec.rb", "spec/google/api_client/result_spec.rb", "spec/google/api_client/service_account_spec.rb", "spec/google/api_client/service_spec.rb", "spec/google/api_client/simple_file_store_spec.rb", "spec/google/api_client_spec.rb", "spec/spec_helper.rb", "tasks/gem.rake", "tasks/git.rake", "tasks/metrics.rake", "tasks/spec.rake", "tasks/wiki.rake", "tasks/yard.rake", "CHANGELOG.md", "CONTRIBUTING.md", "Gemfile", "LICENSE", "README.md", "Rakefile"] s.homepage = "http://code.google.com/p/google-api-ruby-client/" s.licenses = ["Apache 2.0"] s.rdoc_options = ["--main", "README.md"] @@ -27,7 +26,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency(%q, [">= 2.3.2"]) s.add_runtime_dependency(%q, [">= 2.1.0"]) s.add_runtime_dependency(%q, [">= 0.3.3"]) - s.add_runtime_dependency(%q, [">= 0.9.0.rc5"]) + s.add_runtime_dependency(%q, [">= 0.9.0"]) s.add_runtime_dependency(%q, [">= 1.0.0"]) s.add_runtime_dependency(%q, [">= 0.9.15"]) s.add_runtime_dependency(%q, [">= 0.1.5"]) @@ -39,7 +38,7 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 2.3.2"]) s.add_dependency(%q, [">= 2.1.0"]) s.add_dependency(%q, [">= 0.3.3"]) - s.add_dependency(%q, [">= 0.9.0.rc5"]) + s.add_dependency(%q, [">= 0.9.0"]) s.add_dependency(%q, [">= 1.0.0"]) s.add_dependency(%q, [">= 0.9.15"]) s.add_dependency(%q, [">= 0.1.5"]) @@ -52,7 +51,7 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 2.3.2"]) s.add_dependency(%q, [">= 2.1.0"]) s.add_dependency(%q, [">= 0.3.3"]) - s.add_dependency(%q, [">= 0.9.0.rc5"]) + s.add_dependency(%q, [">= 0.9.0"]) s.add_dependency(%q, [">= 1.0.0"]) s.add_dependency(%q, [">= 0.9.15"]) s.add_dependency(%q, [">= 0.1.5"]) diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb index bb82000c3..dd8be6ab0 100644 --- a/lib/google/api_client.rb +++ b/lib/google/api_client.rb @@ -17,6 +17,7 @@ require 'faraday' require 'multi_json' require 'compat/multi_json' require 'stringio' +require 'retriable' require 'google/api_client/version' require 'google/api_client/logging' @@ -107,6 +108,7 @@ module Google self.auto_refresh_token = options.fetch(:auto_refresh_token) { true } self.key = options[:key] self.user_ip = options[:user_ip] + self.retries = options.fetch(:retries) { 0 } @discovery_uris = {} @discovery_documents = {} @discovered_apis = {} @@ -230,6 +232,13 @@ module Google # The base path. Should almost always be '/discovery/v1'. attr_accessor :discovery_path + ## + # Number of times to retry on recoverable errors + # + # @return [FixNum] + # Number of retries + attr_accessor :retries + ## # Returns the URI for the directory document. # @@ -424,6 +433,8 @@ module Google # Verifies an ID token against a server certificate. Used to ensure that # an ID token supplied by an untrusted client-side mechanism is valid. # Raises an error if the token is invalid or missing. + # + # @deprecated Use the google-id-token gem for verifying JWTs def verify_id_token! require 'jwt' require 'openssl' @@ -533,6 +544,8 @@ module Google # authenticated, `false` otherwise. # - (TrueClass, FalseClass) :gzip (default: true) - # `true` if gzip enabled, `false` otherwise. + # - (FixNum) :retries - + # # of times to retry on recoverable errors # # @return [Google::APIClient::Result] The result from the API, nil if batch. # @@ -547,7 +560,7 @@ module Google # ) # # @see Google::APIClient#generate_request - def execute(*params) + def execute!(*params) if params.first.kind_of?(Google::APIClient::Request) request = params.shift options = params.shift || {} @@ -572,53 +585,60 @@ module Google request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil? request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false + request.headers['Content-Type'] ||= '' request.parameters['key'] ||= self.key unless self.key.nil? request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil? connection = options[:connection] || self.connection request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false + tries = 1 + (options[:retries] || self.retries) + Retriable.retriable :tries => tries, + :on => [TransmissionError], + :interval => lambda {|attempts| (2 ** attempts) + rand} do + result = request.send(connection, true) - result = request.send(connection) - if result.status == 401 && request.authorization.respond_to?(:refresh_token) && auto_refresh_token - begin - logger.debug("Attempting refresh of access token & retry of request") - request.authorization.fetch_access_token! - result = request.send(connection, true) - rescue Signet::AuthorizationError - # Ignore since we want the original error + case result.status + when 200...300 + result + when 301, 302, 303, 307 + request = generate_request(request.to_hash.merge({ + :uri => result.headers['location'], + :api_method => nil + })) + raise RedirectError.new(result.headers['location'], result) + when 400...500 + if result.status == 401 && request.authorization.respond_to?(:refresh_token) && auto_refresh_token + begin + logger.debug("Attempting refresh of access token & retry of request") + request.authorization.fetch_access_token! + rescue Signet::AuthorizationError + # Ignore since we want the original error + end + end + raise ClientError.new(result.error_message || "A client error has occurred", result) + when 500...600 + raise ServerError.new(result.error_message || "A server error has occurred", result) + else + raise TransmissionError.new(result.error_message || "A transmission error has occurred", result) end end - - return result end ## - # Same as Google::APIClient#execute, but raises an exception if there was - # an error. + # Same as Google::APIClient#execute!, but does not raise an exception for + # normal API errros. # # @see Google::APIClient#execute - def execute!(*params) - result = self.execute(*params) - if result.error? - error_message = result.error_message - case result.response.status - when 400...500 - exception_type = ClientError - error_message ||= "A client error has occurred." - when 500...600 - exception_type = ServerError - error_message ||= "A server error has occurred." - else - exception_type = TransmissionError - error_message ||= "A transmission error has occurred." - end - raise exception_type, error_message + def execute(*params) + begin + return self.execute!(*params) + rescue TransmissionError => e + return e.result end - return result end - + protected - + ## # Resolves a URI template against the client's configured base. # @@ -646,6 +666,7 @@ module Google end end + end require 'google/api_client/version' diff --git a/lib/google/api_client/errors.rb b/lib/google/api_client/errors.rb index c85eeec7c..f12ad6daa 100644 --- a/lib/google/api_client/errors.rb +++ b/lib/google/api_client/errors.rb @@ -19,6 +19,17 @@ module Google # An error which is raised when there is an unexpected response or other # transport error that prevents an operation from succeeding. class TransmissionError < StandardError + attr_reader :result + def initialize(message = nil, result = nil) + super(message) + @result = result + end + end + + ## + # An exception that is raised if a redirect is required + # + class RedirectError < TransmissionError end ## diff --git a/lib/google/api_client/version.rb b/lib/google/api_client/version.rb index 209446216..410bca64c 100644 --- a/lib/google/api_client/version.rb +++ b/lib/google/api_client/version.rb @@ -23,7 +23,7 @@ if !defined?(::Google::APIClient::VERSION) MAJOR = 0 MINOR = 7 TINY = 0 - PATCH = 'rc2' + PATCH = nil STRING = [MAJOR, MINOR, TINY, PATCH].compact.join('.') end end diff --git a/spec/google/api_client/discovery_spec.rb b/spec/google/api_client/discovery_spec.rb index fb1927b30..0b0f89c22 100644 --- a/spec/google/api_client/discovery_spec.rb +++ b/spec/google/api_client/discovery_spec.rb @@ -89,6 +89,7 @@ describe Google::APIClient do conn = stub_connection do |stub| stub.get('/discovery/v1/apis/prediction/v1.2/rest?userIp=127.0.0.1') do |env| + [200, {}, '{}'] end end CLIENT.execute( @@ -104,6 +105,7 @@ describe Google::APIClient do CLIENT.key = 'qwerty' conn = stub_connection do |stub| stub.get('/discovery/v1/apis/prediction/v1.2/rest?key=qwerty') do |env| + [200, {}, '{}'] end end request = CLIENT.execute( @@ -120,6 +122,7 @@ describe Google::APIClient do CLIENT.user_ip = '127.0.0.1' conn = stub_connection do |stub| stub.get('/discovery/v1/apis/prediction/v1.2/rest?key=qwerty&userIp=127.0.0.1') do |env| + [200, {}, '{}'] end end request = CLIENT.execute( @@ -187,6 +190,7 @@ describe Google::APIClient do conn = stub_connection do |stub| stub.post('/prediction/v1.2/training?data=12345') do |env| env[:body].should == '' + [200, {}, '{}'] end end request = CLIENT.execute( @@ -204,6 +208,7 @@ describe Google::APIClient do # to a CGI-escaped semicolon (%3B) instead. stub.post('/prediction/v1.2/training?data=12345%3B67890') do |env| env[:body].should == '' + [200, {}, '{}'] end end request = CLIENT.execute( @@ -218,6 +223,7 @@ describe Google::APIClient do conn = stub_connection do |stub| stub.post('/prediction/v1.2/training?data=1&data=2') do |env| env.params['data'].should include('1', '2') + [200, {}, '{}'] end end request = CLIENT.execute( @@ -231,6 +237,7 @@ describe Google::APIClient do it 'should generate requests against the correct URIs' do conn = stub_connection do |stub| stub.post('/prediction/v1.2/training?data=12345') do |env| + [200, {}, '{}'] end end request = CLIENT.execute( @@ -244,6 +251,7 @@ describe Google::APIClient do it 'should generate requests against the correct URIs' do conn = stub_connection do |stub| stub.post('/prediction/v1.2/training?data=12345') do |env| + [200, {}, '{}'] end end request = CLIENT.execute( @@ -264,6 +272,7 @@ describe Google::APIClient do conn = stub_connection do |stub| stub.post('/prediction/v1.2/training') do |env| env[:url].host.should == 'testing-domain.example.com' + [200, {}, '{}'] end end @@ -284,6 +293,7 @@ describe Google::APIClient do stub.post('/prediction/v1.2/training?data=12345') do |env| env[:request_headers].should have_key('Authorization') env[:request_headers]['Authorization'].should =~ /^OAuth/ + [200, {}, '{}'] end end @@ -303,6 +313,7 @@ describe Google::APIClient do stub.post('/prediction/v1.2/training?data=12345') do |env| env[:request_headers].should have_key('Authorization') env[:request_headers]['Authorization'].should =~ /^Bearer/ + [200, {}, '{}'] end end @@ -363,6 +374,7 @@ describe Google::APIClient do stub.post('/prediction/v1.2/training') do |env| env[:request_headers].should have_key('Content-Type') env[:request_headers]['Content-Type'].should == 'application/json' + [200, {}, '{}'] end end CLIENT.authorization = :oauth_2 @@ -415,6 +427,7 @@ describe Google::APIClient do it 'should generate requests against the correct URIs' do conn = stub_connection do |stub| stub.get('/plus/v1/people/107807692475771887386/activities/public') do |env| + [200, {}, '{}'] end end @@ -485,6 +498,7 @@ describe Google::APIClient do it 'should generate requests against the correct URIs' do conn = stub_connection do |stub| stub.get('/adsense/v1.3/adclients') do |env| + [200, {}, '{}'] end end request = CLIENT.execute( @@ -515,6 +529,7 @@ describe Google::APIClient do it 'should succeed when validating parameters in a correct call' do conn = stub_connection do |stub| stub.get('/adsense/v1.3/reports?dimension=DATE&endDate=2010-01-01&metric=PAGE_VIEWS&startDate=2000-01-01') do |env| + [200, {}, '{}'] end end (lambda do @@ -553,6 +568,7 @@ describe Google::APIClient do stub.get('/adsense/v1.3/reports?dimension=DATE&dimension=PRODUCT_CODE'+ '&endDate=2010-01-01&metric=CLICKS&metric=PAGE_VIEWS&'+ 'startDate=2000-01-01') do |env| + [200, {}, '{}'] end end (lambda do @@ -593,6 +609,7 @@ describe Google::APIClient do 'startDate=2000-01-01') do |env| env.params['dimension'].should include('DATE', 'PRODUCT_CODE') env.params['metric'].should include('CLICKS', 'PAGE_VIEWS') + [200, {}, '{}'] end end request = CLIENT.execute( diff --git a/spec/google/api_client/service_spec.rb b/spec/google/api_client/service_spec.rb index e322797c6..b5cb3d058 100644 --- a/spec/google/api_client/service_spec.rb +++ b/spec/google/api_client/service_spec.rb @@ -49,6 +49,7 @@ describe Google::APIClient::Service do it 'should make a valid call for a method with no parameters' do conn = stub_connection do |stub| stub.get('/adsense/v1.3/adclients') do |env| + [200, {}, '{}'] end end adsense = Google::APIClient::Service.new( @@ -69,6 +70,7 @@ describe Google::APIClient::Service do it 'should make a valid call for a method with parameters' do conn = stub_connection do |stub| stub.get('/adsense/v1.3/adclients/1/adunits') do |env| + [200, {}, '{}'] end end adsense = Google::APIClient::Service.new( @@ -87,6 +89,7 @@ describe Google::APIClient::Service do it 'should make a valid call for a deep method' do conn = stub_connection do |stub| stub.get('/adsense/v1.3/accounts/1/adclients') do |env| + [200, {}, '{}'] end end adsense = Google::APIClient::Service.new( @@ -147,6 +150,7 @@ describe Google::APIClient::Service do conn = stub_connection do |stub| stub.post('/prediction/v1.5/trainedmodels?project=1') do |env| env.body.should == '{"id":"1"}' + [200, {}, '{}'] end end prediction = Google::APIClient::Service.new( @@ -167,6 +171,7 @@ describe Google::APIClient::Service do conn = stub_connection do |stub| stub.post('/prediction/v1.5/trainedmodels?project=1') do |env| env.body.should == '{"id":"1"}' + [200, {}, '{}'] end end prediction = Google::APIClient::Service.new( @@ -224,6 +229,7 @@ describe Google::APIClient::Service do conn = stub_connection do |stub| stub.post('/upload/drive/v1/files?uploadType=multipart') do |env| env.body.should be_a Faraday::CompositeReadIO + [200, {}, '{}'] end end drive = Google::APIClient::Service.new( diff --git a/spec/google/api_client_spec.rb b/spec/google/api_client_spec.rb index 937df7b82..40e8d7ed2 100644 --- a/spec/google/api_client_spec.rb +++ b/spec/google/api_client_spec.rb @@ -114,6 +114,7 @@ describe Google::APIClient do @connection = stub_connection do |stub| stub.post('/prediction/v1.2/training?data=12345') do |env| env[:request_headers]['Authorization'].should == 'Bearer 12345' + [200, {}, '{}'] end end end @@ -166,4 +167,87 @@ describe Google::APIClient do ) end end + + describe 'when retiries enabled' do + before do + client.retries = 2 + end + + after do + @connection.verify + end + + it 'should follow redirects' do + client.authorization = nil + @connection = stub_connection do |stub| + stub.get('/foo') do |env| + [302, {'location' => 'https://www.google.com/bar'}, '{}'] + end + stub.get('/bar') do |env| + [200, {}, '{}'] + end + end + + client.execute( + :uri => 'https://www.gogole.com/foo', + :connection => @connection + ) + end + + it 'should refresh tokens on 401 tokens' do + client.authorization.access_token = '12345' + expect(client.authorization).to receive(:fetch_access_token!) + + @connection = stub_connection do |stub| + stub.get('/foo') do |env| + [401, {}, '{}'] + end + stub.get('/foo') do |env| + [200, {}, '{}'] + end + end + + client.execute( + :uri => 'https://www.gogole.com/foo', + :connection => @connection + ) + end + + it 'should retry on 500 errors' do + client.authorization = nil + + @connection = stub_connection do |stub| + stub.get('/foo') do |env| + [500, {}, '{}'] + end + stub.get('/foo') do |env| + [200, {}, '{}'] + end + end + + client.execute( + :uri => 'https://www.gogole.com/foo', + :connection => @connection + ).status.should == 200 + + end + + it 'should fail after max retries' do + client.authorization = nil + count = 0 + @connection = stub_connection do |stub| + stub.get('/foo') do |env| + count += 1 + [500, {}, '{}'] + end + end + + client.execute( + :uri => 'https://www.gogole.com/foo', + :connection => @connection + ).status.should == 500 + count.should == 3 + end + + end end diff --git a/tasks/gem.rake b/tasks/gem.rake index 82b35e2e0..8924f05e0 100644 --- a/tasks/gem.rake +++ b/tasks/gem.rake @@ -18,7 +18,6 @@ namespace :gem do s.description = PKG_DESCRIPTION s.license = 'Apache 2.0' s.files = PKG_FILES.to_a - s.executables << 'google-api' s.extra_rdoc_files = %w( README.md ) s.rdoc_options.concat ['--main', 'README.md'] @@ -28,7 +27,7 @@ namespace :gem do s.add_runtime_dependency('addressable', '>= 2.3.2') s.add_runtime_dependency('uuidtools', '>= 2.1.0') s.add_runtime_dependency('autoparse', '>= 0.3.3') - s.add_runtime_dependency('faraday', '>= 0.9.0.rc5') + s.add_runtime_dependency('faraday', '>= 0.9.0') s.add_runtime_dependency('multi_json', '>= 1.0.0') s.add_runtime_dependency('extlib', '>= 0.9.15') s.add_runtime_dependency('jwt', '>= 0.1.5')