googl 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,11 @@
1
- = googl
1
+ = googl
2
2
 
3
3
  Google URL Shortener API in Ruby
4
4
 
5
+ == Continuous Integration
6
+
7
+ http://travis-ci.org/zigotto/googl.png Travis[http://travis-ci.org/zigotto/googl]
8
+
5
9
  == Basic Usage
6
10
 
7
11
  === Shorten a long URL
@@ -45,7 +49,60 @@ Go to http://goo.gl to see URL statistics.
45
49
 
46
50
  === OAuth
47
51
 
48
- TODO
52
+ Google supports three flows of OAuth 2.0
53
+
54
+ * The <b>client-side</b> flow for JavaScript applications running in a browser. (TODO)
55
+ * The <b>server-side</b> flow for web applications with servers that can securely store persistent information.
56
+ * The <b>native application</b> flow for desktop and mobile applications.
57
+
58
+ Today, gem googl support only <b>server-side</b> and <b>native application</b>.
59
+
60
+ ==== Server-side web applications
61
+
62
+ You'll need to register your application with Google to get a <em>client_id</em> and record the <em>redirect_uri</em> you'd like to use.
63
+ See the Registering[http://code.google.com/apis/accounts/docs/OAuth2.html#Registering] your app with Google section for details on how to register.
64
+
65
+ client = Googl::OAuth2.server("client_id", "client_secret", "redirect_uri")
66
+
67
+ Redirect your users to
68
+
69
+ client.authorize_url
70
+
71
+ After the user approves access or chooses not to, we'll redirect to the <em>redirect_uri</em> you passed us and append either an authorization code.
72
+
73
+ client.request_access_token(params["code"])
74
+ client.authorized?
75
+ => true
76
+
77
+ Now you can get a user's history, shorten url, etc...
78
+
79
+ history = client.history
80
+ history.total_items
81
+ => 19
82
+
83
+ url = Googl.shorten('https://github.com/zigotto/googl')
84
+ url.short_url
85
+ => http://goo.gl/DWDfi
86
+
87
+ ==== Native applications
88
+
89
+ You'll need to register your application with Google to get a <em>client_id</em> and record the special <em>redirect_uri: urn:ietf:wg:oauth:2.0:oob</em>.
90
+ See the Registering[http://code.google.com/apis/accounts/docs/OAuth2.html#Registering] your app with Google section for details on how to register.
91
+
92
+ When you use this redirect_uri, instead of redirecting the user's browser to a page on your site with an authorization code, Google will display the authorization code or error response in the title of the page and a text field with instructions for the user to copy and paste it in to your application. Your application can either monitor the window title of a web-view or browser window it launches and close it once the authorization code appears or prompt the user to copy and paste the code.
93
+
94
+ client = Googl::OAuth2.native("client_id", "client_secret")
95
+
96
+ Open a browser or a webview to the OAuth dialog at
97
+
98
+ client.authorize_url
99
+
100
+ In the native app flow, after the user approves access, we'll display the authorization code in the title of the page and in a text input with instructions for the user to copy and paste the code to your application
101
+
102
+ code = "copied code"
103
+ client.request_access_token(code)
104
+ client.authorized?
105
+ => true
49
106
 
50
107
  == Analytics
51
108
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.5.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{googl}
8
- s.version = "0.4.3"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jesus Lopes"]
12
- s.date = %q{2011-03-16}
12
+ s.date = %q{2011-05-01}
13
13
  s.description = %q{Small library for Google URL Shortener API}
14
14
  s.email = %q{jlopes@zigotto.com.br}
15
15
  s.extra_rdoc_files = [
@@ -27,11 +27,13 @@ Gem::Specification.new do |s|
27
27
  "lib/googl/base.rb",
28
28
  "lib/googl/client_login.rb",
29
29
  "lib/googl/expand.rb",
30
+ "lib/googl/oauth2/native.rb",
31
+ "lib/googl/oauth2/server.rb",
32
+ "lib/googl/oauth2/utils.rb",
30
33
  "lib/googl/request.rb",
31
34
  "lib/googl/ruby_extensions.rb",
32
35
  "lib/googl/shorten.rb",
33
- "spec/client_spec.rb",
34
- "spec/expand_spec.rb",
36
+ "lib/googl/utils.rb",
35
37
  "spec/fixtures/client_login_invalid.json",
36
38
  "spec/fixtures/client_login_valid.json",
37
39
  "spec/fixtures/expand.json",
@@ -42,25 +44,37 @@ Gem::Specification.new do |s|
42
44
  "spec/fixtures/expand_removed.json",
43
45
  "spec/fixtures/history.json",
44
46
  "spec/fixtures/history_projection_clicks.json",
47
+ "spec/fixtures/oauth2/native.json",
48
+ "spec/fixtures/oauth2/native_invalid.json",
49
+ "spec/fixtures/oauth2/native_token_expires.json",
50
+ "spec/fixtures/oauth2/server.json",
51
+ "spec/fixtures/oauth2/server_invalid.json",
52
+ "spec/fixtures/oauth2/server_token_expires.json",
45
53
  "spec/fixtures/shorten.json",
46
54
  "spec/fixtures/shorten_authenticated.json",
47
55
  "spec/fixtures/shorten_invalid_content_type.json",
48
- "spec/request_spec.rb",
56
+ "spec/googl/client_spec.rb",
57
+ "spec/googl/expand_spec.rb",
58
+ "spec/googl/oauth2/native_spec.rb",
59
+ "spec/googl/oauth2/server_spec.rb",
60
+ "spec/googl/request_spec.rb",
61
+ "spec/googl/shorten_spec.rb",
49
62
  "spec/shared_examples.rb",
50
- "spec/shorten_spec.rb",
51
63
  "spec/spec_helper.rb"
52
64
  ]
53
65
  s.homepage = %q{http://github.com/zigotto/googl}
54
66
  s.licenses = ["MIT"]
55
67
  s.require_paths = ["lib"]
56
- s.rubygems_version = %q{1.5.3}
68
+ s.rubygems_version = %q{1.4.2}
57
69
  s.summary = %q{Wrapper for Google URL Shortener API}
58
70
  s.test_files = [
59
- "spec/client_spec.rb",
60
- "spec/expand_spec.rb",
61
- "spec/request_spec.rb",
71
+ "spec/googl/client_spec.rb",
72
+ "spec/googl/expand_spec.rb",
73
+ "spec/googl/oauth2/native_spec.rb",
74
+ "spec/googl/oauth2/server_spec.rb",
75
+ "spec/googl/request_spec.rb",
76
+ "spec/googl/shorten_spec.rb",
62
77
  "spec/shared_examples.rb",
63
- "spec/shorten_spec.rb",
64
78
  "spec/spec_helper.rb"
65
79
  ]
66
80
 
@@ -2,6 +2,7 @@ require 'httparty'
2
2
  require 'ostruct'
3
3
  require 'json'
4
4
 
5
+ require 'googl/utils'
5
6
  require 'googl/base'
6
7
  require 'googl/request'
7
8
  require 'googl/shorten'
@@ -9,7 +10,12 @@ require 'googl/expand'
9
10
  require 'googl/client_login'
10
11
  require 'googl/ruby_extensions'
11
12
 
13
+ require 'googl/oauth2/utils'
14
+ require 'googl/oauth2/native'
15
+ require 'googl/oauth2/server'
16
+
12
17
  module Googl
18
+ extend self
13
19
 
14
20
  # Creates a new short URL
15
21
  #
@@ -17,7 +23,7 @@ module Googl
17
23
  # url.short_url
18
24
  # => "http://goo.gl/ump4S"
19
25
  #
20
- def self.shorten(url=nil)
26
+ def shorten(url=nil)
21
27
  raise ArgumentError.new("URL to shorten is required") if url.blank?
22
28
  Googl::Shorten.new(url)
23
29
  end
@@ -79,7 +85,7 @@ module Googl
79
85
  #
80
86
  # For mor details, see http://code.google.com/intl/pt-BR/apis/urlshortener/v1/reference.html#resource_url
81
87
  #
82
- def self.expand(url=nil, options={})
88
+ def expand(url=nil, options={})
83
89
  raise ArgumentError.new("URL to expand is required") if url.blank?
84
90
  options = {:shortUrl => url, :projection => nil}.merge!(options)
85
91
  Googl::Expand.new(options)
@@ -95,8 +101,43 @@ module Googl
95
101
  #
96
102
  # Go to http://goo.gl to see URL statistics.
97
103
  #
98
- def self.client(email, passwd)
104
+ def client(email, passwd)
99
105
  Googl::ClientLogin.new(email, passwd)
100
106
  end
101
107
 
108
+ # OAuth 2.0
109
+ #
110
+ # Google supports three flows of OAuth 2.0
111
+ #
112
+ # * client-side
113
+ # * server-side
114
+ # * native application
115
+ #
116
+ # Now, gem googl support only client-side and native application.
117
+ #
118
+ module OAuth2
119
+ extend self
120
+
121
+ # OAuth 2.0 for server-side web applications
122
+ #
123
+ # The server-side flow for web applications with servers that can securely store persistent information
124
+ #
125
+ # client = Googl::OAuth2.server("client_id", "client_secret", "redirect_uri")
126
+ #
127
+ def server(client_id, client_secret, redirect_uri)
128
+ Googl::OAuth2::Server.new(client_id, client_secret, redirect_uri)
129
+ end
130
+
131
+ # OAuth 2.0 for native applications
132
+ #
133
+ # The native application flow for desktop and mobile applications
134
+ #
135
+ # client = Googl::OAuth2.native("client_id", "client_secret")
136
+ #
137
+ def native(client_id, client_secret)
138
+ Googl::OAuth2::Native.new(client_id, client_secret)
139
+ end
140
+
141
+ end
142
+
102
143
  end
@@ -26,12 +26,6 @@ module Googl
26
26
  "#{short_url}.info"
27
27
  end
28
28
 
29
- private
30
-
31
- def modify_headers(item)
32
- Googl::Request.headers.merge!(item)
33
- end
34
-
35
29
  end
36
30
 
37
31
  end
@@ -1,9 +1,8 @@
1
1
  module Googl
2
2
 
3
- class ClientLogin < Base
3
+ class ClientLogin
4
4
 
5
- API_URL = "https://www.google.com/accounts/ClientLogin"
6
- API_HISTORY_URL = "https://www.googleapis.com/urlshortener/v1/url/history"
5
+ include Googl::Utils
7
6
 
8
7
  attr_accessor :code, :items
9
8
 
@@ -11,13 +10,13 @@ module Googl
11
10
  #
12
11
  def initialize(email, passwd)
13
12
  modify_headers('Content-Type' => 'application/x-www-form-urlencoded')
14
- resp = Googl::Request.post(API_URL, :body => params.merge!('Email' => email, 'Passwd' => passwd))
15
- @code = resp.code
13
+ resp = post(API_CLIENT_LOGIN_URL, :body => params.merge!('Email' => email, 'Passwd' => passwd))
14
+ self.code = resp.code
16
15
  if resp.code == 200
17
16
  token = resp.split('=').last.gsub(/\n/, '')
18
17
  modify_headers("Authorization" => "GoogleLogin auth=#{token}")
19
18
  else
20
- raise Exception.new("#{resp.code} #{resp.parsed_response}")
19
+ raise exception("#{resp.code} #{resp.parsed_response}")
21
20
  end
22
21
  end
23
22
 
@@ -26,7 +25,7 @@ module Googl
26
25
  # See Googl.client
27
26
  #
28
27
  def shorten(url)
29
- Googl::Shorten.new(url)
28
+ Googl.shorten(url)
30
29
  end
31
30
 
32
31
  # Gets a user's history of shortened URLs. (Authenticated)
@@ -46,11 +45,12 @@ module Googl
46
45
  # history = client.history(:projection => :analytics_clicks)
47
46
  #
48
47
  def history(options={})
49
- resp = options.blank? ? Googl::Request.get(API_HISTORY_URL) : Googl::Request.get(API_HISTORY_URL, :query => options)
50
- if resp.code == 200
51
- @items = resp.parsed_response.to_openstruct
48
+ resp = options.blank? ? get(Googl::Utils::API_HISTORY_URL) : get(Googl::Utils::API_HISTORY_URL, :query => options)
49
+ case resp.code
50
+ when 200
51
+ self.items = resp.parsed_response.to_openstruct
52
52
  else
53
- raise Exception.new("#{resp.code} #{resp.parsed_response}")
53
+ raise exception("#{resp.code} #{resp.parsed_response}")
54
54
  end
55
55
  end
56
56
 
@@ -2,9 +2,9 @@ module Googl
2
2
 
3
3
  class Expand < Base
4
4
 
5
- API_URL = "https://www.googleapis.com/urlshortener/v1/url"
5
+ include Googl::Utils
6
6
 
7
- attr_accessor :long_url, :analytics, :status, :short_url
7
+ attr_accessor :long_url, :analytics, :status, :short_url, :created
8
8
 
9
9
  # Expands a short URL or gets creation time and analytics. See Googl.expand
10
10
  #
@@ -12,14 +12,15 @@ module Googl
12
12
 
13
13
  options.delete_if {|key, value| value.nil?}
14
14
 
15
- resp = Googl::Request.get(API_URL, :query => options)
15
+ resp = get(API_URL, :query => options)
16
16
  if resp.code == 200
17
- @long_url = resp['longUrl']
18
- @analytics = resp['analytics'].to_openstruct if resp.has_key?('analytics')
19
- @status = resp['status']
20
- @short_url = resp['id']
17
+ self.created = resp['created'] if resp.has_key?('created')
18
+ self.long_url = resp['longUrl']
19
+ self.analytics = resp['analytics'].to_openstruct if resp.has_key?('analytics')
20
+ self.status = resp['status']
21
+ self.short_url = resp['id']
21
22
  else
22
- raise Exception.new("#{resp.code} #{resp.message}")
23
+ raise exception("#{resp.code} #{resp.message}")
23
24
  end
24
25
  end
25
26
 
@@ -0,0 +1,25 @@
1
+ module Googl
2
+ module OAuth2
3
+
4
+ class Native
5
+
6
+ include Googl::Utils
7
+ include Googl::OAuth2::Utils
8
+
9
+ def initialize(client_id, client_secret)
10
+ self.client_id = client_id
11
+ self.client_secret = client_secret
12
+ end
13
+
14
+ def authorize_url
15
+ make_authorize_url("urn:ietf:wg:oauth:2.0:oob")
16
+ end
17
+
18
+ def request_access_token(code)
19
+ request_token(code)
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ module Googl
2
+ module OAuth2
3
+
4
+ class Server
5
+
6
+ include Googl::Utils
7
+ include Googl::OAuth2::Utils
8
+
9
+ attr_accessor :redirect_uri
10
+
11
+ def initialize(client_id, client_secret, redirect_uri)
12
+ self.client_id = client_id
13
+ self.client_secret = client_secret
14
+ self.redirect_uri = redirect_uri
15
+ end
16
+
17
+ def authorize_url
18
+ make_authorize_url(redirect_uri)
19
+ end
20
+
21
+ def request_access_token(code)
22
+ request_token(code, redirect_uri)
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,58 @@
1
+ module Googl
2
+ module OAuth2
3
+
4
+ module Utils
5
+
6
+ attr_accessor :client_id, :client_secret, :access_token, :refresh_token, :expires_in, :expires_at
7
+ attr_accessor :items
8
+
9
+ def expires?
10
+ expires_at < Time.now
11
+ end
12
+
13
+ def authorized?
14
+ !access_token.nil? && !refresh_token.nil? && !expires_in.nil? && !expires_at.nil?
15
+ end
16
+
17
+ # Gets a user's history of shortened URLs.
18
+ #
19
+ def history(options={})
20
+ return unless authorized?
21
+ resp = options.blank? ? get(Googl::Utils::API_HISTORY_URL) : get(Googl::Utils::API_HISTORY_URL, :query => options)
22
+ case resp.code
23
+ when 200
24
+ self.items = resp.parsed_response.to_openstruct
25
+ else
26
+ raise exception("#{resp.code} #{resp.parsed_response}")
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def request_token(code, request_uri="urn:ietf:wg:oauth:2.0:oob")
33
+ params = "code=#{code}&client_id=#{client_id}&client_secret=#{client_secret}&redirect_uri=#{request_uri}&grant_type=authorization_code"
34
+ modify_headers('Content-Type' => 'application/x-www-form-urlencoded')
35
+ resp = post("https://accounts.google.com/o/oauth2/token", :body => params)
36
+ case resp.code
37
+ when 200
38
+ modify_headers("Authorization" => "OAuth #{resp["access_token"]}")
39
+ self.access_token = resp["access_token"]
40
+ self.refresh_token = resp["refresh_token"]
41
+ self.expires_in = resp["expires_in"]
42
+ self.expires_at = Time.now + expires_in if expires_in
43
+ self
44
+ when 401
45
+ raise exception("#{resp.code} #{resp.parsed_response["error"]["message"]}")
46
+ else
47
+ raise exception("#{resp.code} #{resp.parsed_response["error"]}")
48
+ end
49
+ end
50
+
51
+ def make_authorize_url(redirect_uri)
52
+ "https://accounts.google.com/o/oauth2/auth?client_id=#{client_id}&redirect_uri=#{redirect_uri}&scope=#{Googl::Utils::SCOPE_URL}&response_type=code"
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -2,7 +2,7 @@ module Googl
2
2
 
3
3
  class Shorten < Base
4
4
 
5
- API_URL = 'https://www.googleapis.com/urlshortener/v1/url'
5
+ include Googl::Utils
6
6
 
7
7
  attr_accessor :short_url, :long_url
8
8
 
@@ -11,12 +11,12 @@ module Googl
11
11
  def initialize(long_url)
12
12
  modify_headers('Content-Type' => 'application/json')
13
13
  options = {"longUrl" => long_url}.to_json
14
- resp = Googl::Request.post(API_URL, :body => options)
14
+ resp = post(API_URL, :body => options)
15
15
  if resp.code == 200
16
- @short_url = resp['id']
17
- @long_url = resp['longUrl']
16
+ self.short_url = resp['id']
17
+ self.long_url = resp['longUrl']
18
18
  else
19
- raise Exception.new(resp.parsed_response)
19
+ raise exception(resp.parsed_response)
20
20
  end
21
21
  end
22
22
 
@@ -0,0 +1,30 @@
1
+ module Googl
2
+
3
+ module Utils # :nodoc:
4
+
5
+ API_URL = 'https://www.googleapis.com/urlshortener/v1/url'
6
+ API_HISTORY_URL = "https://www.googleapis.com/urlshortener/v1/url/history"
7
+ API_CLIENT_LOGIN_URL = "https://www.google.com/accounts/ClientLogin"
8
+ SCOPE_URL = "https://www.googleapis.com/auth/urlshortener"
9
+
10
+ private
11
+
12
+ def modify_headers(item)
13
+ Googl::Request.headers.merge!(item)
14
+ end
15
+
16
+ def post(url, params={})
17
+ Googl::Request.post(url, params)
18
+ end
19
+
20
+ def get(url, params={})
21
+ Googl::Request.get(url, params)
22
+ end
23
+
24
+ def exception(msg)
25
+ Exception.new(msg)
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,12 @@
1
+ HTTP/1.1 200 OK
2
+ Content-Type: application/json
3
+ Date: Thu, 17 Mar 2011 02:45:33 GMT
4
+ Expires: Thu, 17 Mar 2011 02:45:33 GMT
5
+ Cache-Control: private, max-age=0
6
+ X-Content-Type-Options: nosniff
7
+ X-Frame-Options: SAMEORIGIN
8
+ X-XSS-Protection: 1; mode=block
9
+ Server: GSE
10
+ Transfer-Encoding: chunked
11
+
12
+ {"access_token":"1/YCzoGAYT8XUuOifjNh_KqA","expires_in":3600,"refresh_token":"1/x_31GvgzdgHDMkRep5i8YxFlq76w3yjFu9Dp72Op-pI"}
@@ -0,0 +1,12 @@
1
+ HTTP/1.1 400 Bad Request
2
+ Content-Type: application/json
3
+ Date: Thu, 17 Mar 2011 03:05:33 GMT
4
+ Expires: Thu, 17 Mar 2011 03:05:33 GMT
5
+ Cache-Control: private, max-age=0
6
+ X-Content-Type-Options: nosniff
7
+ X-Frame-Options: SAMEORIGIN
8
+ X-XSS-Protection: 1; mode=block
9
+ Server: GSE
10
+ Transfer-Encoding: chunked
11
+
12
+ {"error":"invalid_token"}
@@ -0,0 +1,27 @@
1
+ HTTP/1.1 401 Unauthorized
2
+ WWW-Authenticate: AuthSub realm="https://gaiastaging.corp.google.com/accounts/AuthSubRequest" allowed-scopes="https://www.googleapis.com/auth/urlshortener"
3
+ Content-Type: application/json; charset=UTF-8
4
+ Date: Fri, 18 Mar 2011 23:32:07 GMT
5
+ Expires: Fri, 18 Mar 2011 23:32:07 GMT
6
+ Cache-Control: private, max-age=0
7
+ X-Content-Type-Options: nosniff
8
+ X-Frame-Options: SAMEORIGIN
9
+ X-XSS-Protection: 1; mode=block
10
+ Server: GSE
11
+ Transfer-Encoding: chunked
12
+
13
+ {
14
+ "error": {
15
+ "errors": [
16
+ {
17
+ "domain": "global",
18
+ "reason": "invalid",
19
+ "message": "Invalid Credentials",
20
+ "locationType": "header",
21
+ "location": "Authorization"
22
+ }
23
+ ],
24
+ "code": 401,
25
+ "message": "Invalid Credentials"
26
+ }
27
+ }
@@ -0,0 +1,12 @@
1
+ HTTP/1.1 200 OK
2
+ Content-Type: application/json
3
+ Date: Fri, 18 Mar 2011 02:11:50 GMT
4
+ Expires: Fri, 18 Mar 2011 02:11:50 GMT
5
+ Cache-Control: private, max-age=0
6
+ X-Content-Type-Options: nosniff
7
+ X-Frame-Options: SAMEORIGIN
8
+ X-XSS-Protection: 1; mode=block
9
+ Server: GSE
10
+ Transfer-Encoding: chunked
11
+
12
+ {"access_token":"1/9eNgoHDXi-1u1fDzZ2wLLGATiaQZnWPB51nTvo8n9Sw","expires_in":3600,"refresh_token":"1/gvmLC5XlU0qRPIBR3mt7OBBfEoTKB6i2T-Gu4dBDupw"}
@@ -0,0 +1,12 @@
1
+ HTTP/1.1 400 Bad Request
2
+ Content-Type: application/json
3
+ Date: Fri, 18 Mar 2011 14:12:06 GMT
4
+ Expires: Fri, 18 Mar 2011 14:12:06 GMT
5
+ Cache-Control: private, max-age=0
6
+ X-Content-Type-Options: nosniff
7
+ X-Frame-Options: SAMEORIGIN
8
+ X-XSS-Protection: 1; mode=block
9
+ Server: GSE
10
+ Transfer-Encoding: chunked
11
+
12
+ {"error":"invalid_token"}
@@ -0,0 +1,27 @@
1
+ HTTP/1.1 401 Unauthorized
2
+ WWW-Authenticate: AuthSub realm="https://gaiastaging.corp.google.com/accounts/AuthSubRequest" allowed-scopes="https://www.googleapis.com/auth/urlshortener"
3
+ Content-Type: application/json; charset=UTF-8
4
+ Date: Sat, 19 Mar 2011 00:00:35 GMT
5
+ Expires: Sat, 19 Mar 2011 00:00:35 GMT
6
+ Cache-Control: private, max-age=0
7
+ X-Content-Type-Options: nosniff
8
+ X-Frame-Options: SAMEORIGIN
9
+ X-XSS-Protection: 1; mode=block
10
+ Server: GSE
11
+ Transfer-Encoding: chunked
12
+
13
+ {
14
+ "error": {
15
+ "errors": [
16
+ {
17
+ "domain": "global",
18
+ "reason": "invalid",
19
+ "message": "Invalid Credentials",
20
+ "locationType": "header",
21
+ "location": "Authorization"
22
+ }
23
+ ],
24
+ "code": 401,
25
+ "message": "Invalid Credentials"
26
+ }
27
+ }
@@ -60,6 +60,15 @@ describe Googl::Expand do
60
60
 
61
61
  subject { Googl.expand('http://goo.gl/DWDfi', :projection => :full) }
62
62
 
63
+ describe "#created" do
64
+ let(:element) { subject.created }
65
+
66
+ it "should be the time url was shortened" do
67
+ element.should == Time.iso8601("2011-01-13T03:48:10.309+00:00")
68
+ end
69
+
70
+ end
71
+
63
72
  describe "#all_time" do
64
73
  let(:element) { subject.analytics.all_time }
65
74
 
@@ -67,7 +76,7 @@ describe Googl::Expand do
67
76
  it_should_behave_like 'a period'
68
77
 
69
78
  it "should rename id to label" do
70
- element.countries.first.label.should == "BR"
79
+ element.countries.first.label.should == "BR"
71
80
  end
72
81
  end
73
82
 
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ describe Googl::OAuth2::Native do
4
+
5
+ before :each do
6
+ fake_urls? true
7
+ end
8
+
9
+ subject do
10
+ Googl::OAuth2.native("185706845724.apps.googleusercontent.com", "DrBLCdCQ3gOybHrj7TPz/B0N")
11
+ end
12
+
13
+ describe "#initialize" do
14
+
15
+ it "should assign client_id" do
16
+ subject.client_id.should == "185706845724.apps.googleusercontent.com"
17
+ end
18
+
19
+ it "should assign client_secret" do
20
+ subject.client_secret.should == "DrBLCdCQ3gOybHrj7TPz/B0N"
21
+ end
22
+
23
+ end
24
+
25
+ describe "#authorize_url" do
26
+
27
+ it { subject.should respond_to(:authorize_url) }
28
+
29
+ it "should return url for authorize" do
30
+ subject.authorize_url.should == "https://accounts.google.com/o/oauth2/auth?client_id=185706845724.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/urlshortener&response_type=code"
31
+ end
32
+
33
+ it "should include the client_id" do
34
+ subject.authorize_url.should be_include("client_id=185706845724.apps.googleusercontent.com")
35
+ end
36
+
37
+ it "should include the redirect_uri" do
38
+ subject.authorize_url.should be_include("redirect_uri=urn:ietf:wg:oauth:2.0:oob")
39
+ end
40
+
41
+ it "should include the scope" do
42
+ subject.authorize_url.should be_include("scope=https://www.googleapis.com/auth/urlshortener")
43
+ end
44
+
45
+ it "should include the response_type" do
46
+ subject.authorize_url.should be_include("response_type=code")
47
+ end
48
+
49
+ end
50
+
51
+ describe "#request_access_token" do
52
+
53
+ it { subject.should respond_to(:request_access_token) }
54
+
55
+ context "with valid code" do
56
+
57
+ let(:native) { subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ") }
58
+
59
+ it "should return a access_token" do
60
+ native.access_token.should == "1/YCzoGAYT8XUuOifjNh_KqA"
61
+ end
62
+
63
+ it "should return a refresh_token" do
64
+ native.refresh_token.should == "1/x_31GvgzdgHDMkRep5i8YxFlq76w3yjFu9Dp72Op-pI"
65
+ end
66
+
67
+ it "should return a expires_in" do
68
+ native.expires_in.should == 3600
69
+ end
70
+
71
+ end
72
+
73
+ context "with invalid code" do
74
+ it "should raise error" do
75
+ lambda { subject.request_access_token("my_invalid_code") }.should raise_error(/400 invalid_token/)
76
+ end
77
+ it "should raise Invalid Credentials on 401 response" do
78
+ lambda { subject.request_access_token("4/JvkEhCtr7tv1A60ENmubQT-cosRl") }.should raise_error(/401 Invalid Credentials/)
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ describe "#expires_at" do
85
+
86
+ before do
87
+ @now = Time.now
88
+ Time.stub!(:now).and_return(@now)
89
+ end
90
+
91
+ let(:native) { subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ") }
92
+
93
+ it "should be a time representation of #expires_in" do
94
+ native.expires_at.should == (@now + 3600)
95
+ end
96
+
97
+ end
98
+
99
+ describe "#expires?" do
100
+
101
+ before :each do
102
+ Time.stub!(:now).and_return(Time.parse("2011-04-23 15:30:00"))
103
+ subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ")
104
+ end
105
+
106
+ it "should be true if access token expires" do
107
+ Time.stub!(:now).and_return(Time.parse("2011-04-23 18:30:00"))
108
+ subject.expires?.should be_true
109
+ end
110
+
111
+ it "should be false if access token not expires" do
112
+ subject.expires?.should be_false
113
+ end
114
+
115
+ end
116
+
117
+ describe "#authorized?" do
118
+
119
+ it "should return false if client is not authorized" do
120
+ subject.authorized?.should be_false
121
+ end
122
+
123
+ let(:native) { subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ") }
124
+
125
+ it "should return true if client is authorized" do
126
+ native.authorized?.should be_true
127
+ end
128
+
129
+ end
130
+
131
+ context "when gets a user history of shortened" do
132
+
133
+ it { subject.should respond_to(:history) }
134
+
135
+ it "should not return when client not authorized" do
136
+ subject.history.should be_nil
137
+ end
138
+
139
+ context "if authorized" do
140
+
141
+ let(:native) { subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ") }
142
+ let(:history) { native.history }
143
+
144
+ it { history.should respond_to(:total_items) }
145
+ it { history.should respond_to(:items_per_page) }
146
+ it { history.should respond_to(:items) }
147
+
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe Googl::OAuth2::Server do
4
+
5
+ before :each do
6
+ fake_urls? true
7
+ end
8
+
9
+ subject do
10
+ Googl::OAuth2.server("438834493660.apps.googleusercontent.com", "8i4iJJkFTukWhNpxTU1b2Zhi", "http://gooogl.heroku.com/back")
11
+ end
12
+
13
+ describe "#initialize" do
14
+
15
+ it "should assign client_id" do
16
+ subject.client_id.should == "438834493660.apps.googleusercontent.com"
17
+ end
18
+
19
+ it "should assign client_secret" do
20
+ subject.client_secret.should == "8i4iJJkFTukWhNpxTU1b2Zhi"
21
+ end
22
+
23
+ it "should assign redirect_uri" do
24
+ subject.redirect_uri.should == "http://gooogl.heroku.com/back"
25
+ end
26
+
27
+ end
28
+
29
+ describe "#authorize_url" do
30
+
31
+ it { subject.should respond_to(:authorize_url) }
32
+
33
+ it "should return url for authorize" do
34
+ subject.authorize_url.should == "https://accounts.google.com/o/oauth2/auth?client_id=438834493660.apps.googleusercontent.com&redirect_uri=http://gooogl.heroku.com/back&scope=https://www.googleapis.com/auth/urlshortener&response_type=code"
35
+ end
36
+
37
+ it "should include the client_id" do
38
+ subject.authorize_url.should be_include("client_id=438834493660.apps.googleusercontent.com")
39
+ end
40
+
41
+ it "should include the redirect_uri" do
42
+ subject.authorize_url.should be_include("redirect_uri=http://gooogl.heroku.com/back")
43
+ end
44
+
45
+ it "should include the scope" do
46
+ subject.authorize_url.should be_include("scope=https://www.googleapis.com/auth/urlshortener")
47
+ end
48
+
49
+ it "should include the response_type" do
50
+ subject.authorize_url.should be_include("response_type=code")
51
+ end
52
+
53
+ end
54
+
55
+ describe "#request_access_token" do
56
+
57
+ it { subject.should respond_to(:request_access_token) }
58
+
59
+ context "with valid code" do
60
+
61
+ let(:server) { subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH") }
62
+
63
+ it "should return a access_token" do
64
+ server.access_token.should == "1/9eNgoHDXi-1u1fDzZ2wLLGATiaQZnWPB51nTvo8n9Sw"
65
+ end
66
+
67
+ it "should return a refresh_token" do
68
+ server.refresh_token.should == "1/gvmLC5XlU0qRPIBR3mt7OBBfEoTKB6i2T-Gu4dBDupw"
69
+ end
70
+
71
+ it "should return a expires_in" do
72
+ server.expires_in.should == 3600
73
+ end
74
+
75
+ end
76
+
77
+ context "with invalid code" do
78
+ it "should raise error" do
79
+ lambda { subject.request_access_token("my_invalid_code") }.should raise_error(/400 invalid_token/)
80
+ end
81
+ it "should raise Invalid Credentials on 401 response" do
82
+ lambda { subject.request_access_token("4/JvkEhCtr7tv1A60ENmubQT-cosRl") }.should raise_error(/401 Invalid Credentials/)
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ describe "#expires_at" do
89
+
90
+ before do
91
+ @now = Time.now
92
+ Time.stub!(:now).and_return(@now)
93
+ end
94
+
95
+ let(:server) { subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH") }
96
+
97
+ it "should be a time representation of #expires_in" do
98
+ server.expires_at.should == (@now + 3600)
99
+ end
100
+
101
+ end
102
+
103
+ describe "#expires?" do
104
+
105
+ before :each do
106
+ Time.stub!(:now).and_return(Time.parse("2011-04-23 15:30:00"))
107
+ subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH")
108
+ end
109
+
110
+ it "should be true if access token expires" do
111
+ Time.stub!(:now).and_return(Time.parse("2011-04-23 18:30:00"))
112
+ subject.expires?.should be_true
113
+ end
114
+
115
+ it "should be false if access token not expires" do
116
+ subject.expires?.should be_false
117
+ end
118
+
119
+ end
120
+
121
+ describe "#authorized?" do
122
+
123
+ it "should return false if client is not authorized" do
124
+ subject.authorized?.should be_false
125
+ end
126
+
127
+ let(:server) { subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH") }
128
+
129
+ it "should return true if client is authorized" do
130
+ server.authorized?.should be_true
131
+ end
132
+
133
+ end
134
+
135
+ context "when gets a user history of shortened" do
136
+
137
+ it { subject.should respond_to(:history) }
138
+
139
+ it "should not return when client not authorized" do
140
+ subject.history.should be_nil
141
+ end
142
+
143
+ context "if authorized" do
144
+
145
+ let(:server) { subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH") }
146
+ let(:history) { server.history }
147
+
148
+ it { history.should respond_to(:total_items) }
149
+ it { history.should respond_to(:items_per_page) }
150
+ it { history.should respond_to(:items) }
151
+
152
+ end
153
+
154
+ end
155
+
156
+ end
@@ -75,7 +75,7 @@ def fake_urls?(status)
75
75
  :headers => {'Authorization'=>'GoogleLogin auth=DQAAAK8AAAC9ahL-o7g', 'Content-Type'=>'application/json'}).
76
76
  to_return(load_fixture('shorten_authenticated.json'))
77
77
 
78
- # History
78
+ # History for ClientLogin
79
79
  stub_request(:get, "https://www.googleapis.com/urlshortener/v1/url/history").
80
80
  with(:headers => {'Authorization'=>'GoogleLogin auth=DQAAAK8AAAC9ahL-o7g'}).
81
81
  to_return(load_fixture('history.json'))
@@ -84,6 +84,53 @@ def fake_urls?(status)
84
84
  stub_request(:get, "https://www.googleapis.com/urlshortener/v1/url/history?projection=analytics_clicks").
85
85
  with(:headers => {'Authorization'=>'GoogleLogin auth=DQAAAK8AAAC9ahL-o7g'}).
86
86
  to_return(load_fixture('history_projection_clicks.json'))
87
+
88
+ # OAuth 2.0 for native applications
89
+ stub_request(:post, "https://accounts.google.com/o/oauth2/token").
90
+ with(:body => "code=4/SuSud6RqPojUXsPpeh-wSVCwnmTQ&client_id=185706845724.apps.googleusercontent.com&client_secret=DrBLCdCQ3gOybHrj7TPz/B0N&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code",
91
+ :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
92
+ to_return(load_fixture('oauth2/native.json'))
93
+
94
+ # OAuth 2.0 for native applications (invalid token)
95
+ stub_request(:post, "https://accounts.google.com/o/oauth2/token").
96
+ with(:body => "code=my_invalid_code&client_id=185706845724.apps.googleusercontent.com&client_secret=DrBLCdCQ3gOybHrj7TPz/B0N&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code",
97
+ :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
98
+ to_return(load_fixture('oauth2/native_invalid.json'))
99
+
100
+ # OAuth 2.0 for native applications (expired token)
101
+ stub_request(:post, "https://accounts.google.com/o/oauth2/token").
102
+ with(:body => "code=4/JvkEhCtr7tv1A60ENmubQT-cosRl&client_id=185706845724.apps.googleusercontent.com&client_secret=DrBLCdCQ3gOybHrj7TPz/B0N&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code",
103
+ :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
104
+ to_return(load_fixture('oauth2/native_token_expires.json'))
105
+
106
+ # OAuth 2.0 for native applications (history)
107
+ stub_request(:get, "https://www.googleapis.com/urlshortener/v1/url/history").
108
+ with(:headers => {'Authorization'=>'OAuth 1/YCzoGAYT8XUuOifjNh_KqA', 'Content-Type'=>'application/x-www-form-urlencoded'}).
109
+ to_return(load_fixture('history.json'))
110
+
111
+ # OAuth 2.0 for server-side web applications
112
+ stub_request(:post, "https://accounts.google.com/o/oauth2/token").
113
+ with(:body => "code=4/z43CZpNmqd0IO3dR1Y_ouase13CH&client_id=438834493660.apps.googleusercontent.com&client_secret=8i4iJJkFTukWhNpxTU1b2Zhi&redirect_uri=http://gooogl.heroku.com/back&grant_type=authorization_code",
114
+ :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
115
+ to_return(load_fixture('oauth2/server.json'))
116
+
117
+ # OAuth 2.0 for server-side web applications (invalid token)
118
+ stub_request(:post, "https://accounts.google.com/o/oauth2/token").
119
+ with(:body => "code=my_invalid_code&client_id=438834493660.apps.googleusercontent.com&client_secret=8i4iJJkFTukWhNpxTU1b2Zhi&redirect_uri=http://gooogl.heroku.com/back&grant_type=authorization_code",
120
+ :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
121
+ to_return(load_fixture('oauth2/server_invalid.json'))
122
+
123
+ # OAuth 2.0 for server-side web applications (expired token)
124
+ stub_request(:post, "https://accounts.google.com/o/oauth2/token").
125
+ with(:body => "code=4/JvkEhCtr7tv1A60ENmubQT-cosRl&client_id=438834493660.apps.googleusercontent.com&client_secret=8i4iJJkFTukWhNpxTU1b2Zhi&redirect_uri=http://gooogl.heroku.com/back&grant_type=authorization_code",
126
+ :headers => {'Content-Type'=>'application/x-www-form-urlencoded'}).
127
+ to_return(load_fixture('oauth2/server_token_expires.json'))
128
+
129
+ # OAuth 2.0 for server-side web applications (history)
130
+ stub_request(:get, "https://www.googleapis.com/urlshortener/v1/url/history").
131
+ with(:headers => {'Authorization'=>'OAuth 1/9eNgoHDXi-1u1fDzZ2wLLGATiaQZnWPB51nTvo8n9Sw'}).
132
+ to_return(load_fixture('history.json'))
133
+
87
134
  else
88
135
  WebMock.allow_net_connect!
89
136
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: googl
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 3
10
- version: 0.4.3
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jesus Lopes
@@ -15,12 +15,14 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-16 00:00:00 -03:00
18
+ date: 2011-05-01 00:00:00 -03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ name: httparty
22
24
  type: :runtime
23
- requirement: &id001 !ruby/object:Gem::Requirement
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
26
  none: false
25
27
  requirements:
26
28
  - - ">="
@@ -31,12 +33,12 @@ dependencies:
31
33
  - 6
32
34
  - 1
33
35
  version: 0.6.1
34
- name: httparty
35
- version_requirements: *id001
36
- prerelease: false
36
+ requirement: *id001
37
37
  - !ruby/object:Gem::Dependency
38
+ prerelease: false
39
+ name: json
38
40
  type: :runtime
39
- requirement: &id002 !ruby/object:Gem::Requirement
41
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
42
  none: false
41
43
  requirements:
42
44
  - - ">="
@@ -47,12 +49,12 @@ dependencies:
47
49
  - 4
48
50
  - 6
49
51
  version: 1.4.6
50
- name: json
51
- version_requirements: *id002
52
- prerelease: false
52
+ requirement: *id002
53
53
  - !ruby/object:Gem::Dependency
54
+ prerelease: false
55
+ name: rspec
54
56
  type: :development
55
- requirement: &id003 !ruby/object:Gem::Requirement
57
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
58
  none: false
57
59
  requirements:
58
60
  - - ~>
@@ -63,12 +65,12 @@ dependencies:
63
65
  - 3
64
66
  - 0
65
67
  version: 2.3.0
66
- name: rspec
67
- version_requirements: *id003
68
- prerelease: false
68
+ requirement: *id003
69
69
  - !ruby/object:Gem::Dependency
70
+ prerelease: false
71
+ name: bundler
70
72
  type: :development
71
- requirement: &id004 !ruby/object:Gem::Requirement
73
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
74
  none: false
73
75
  requirements:
74
76
  - - ~>
@@ -79,12 +81,12 @@ dependencies:
79
81
  - 0
80
82
  - 0
81
83
  version: 1.0.0
82
- name: bundler
83
- version_requirements: *id004
84
- prerelease: false
84
+ requirement: *id004
85
85
  - !ruby/object:Gem::Dependency
86
+ prerelease: false
87
+ name: jeweler
86
88
  type: :development
87
- requirement: &id005 !ruby/object:Gem::Requirement
89
+ version_requirements: &id005 !ruby/object:Gem::Requirement
88
90
  none: false
89
91
  requirements:
90
92
  - - ~>
@@ -95,12 +97,12 @@ dependencies:
95
97
  - 5
96
98
  - 2
97
99
  version: 1.5.2
98
- name: jeweler
99
- version_requirements: *id005
100
- prerelease: false
100
+ requirement: *id005
101
101
  - !ruby/object:Gem::Dependency
102
+ prerelease: false
103
+ name: rcov
102
104
  type: :development
103
- requirement: &id006 !ruby/object:Gem::Requirement
105
+ version_requirements: &id006 !ruby/object:Gem::Requirement
104
106
  none: false
105
107
  requirements:
106
108
  - - ">="
@@ -109,12 +111,12 @@ dependencies:
109
111
  segments:
110
112
  - 0
111
113
  version: "0"
112
- name: rcov
113
- version_requirements: *id006
114
- prerelease: false
114
+ requirement: *id006
115
115
  - !ruby/object:Gem::Dependency
116
+ prerelease: false
117
+ name: webmock
116
118
  type: :development
117
- requirement: &id007 !ruby/object:Gem::Requirement
119
+ version_requirements: &id007 !ruby/object:Gem::Requirement
118
120
  none: false
119
121
  requirements:
120
122
  - - ~>
@@ -125,9 +127,7 @@ dependencies:
125
127
  - 6
126
128
  - 2
127
129
  version: 1.6.2
128
- name: webmock
129
- version_requirements: *id007
130
- prerelease: false
130
+ requirement: *id007
131
131
  description: Small library for Google URL Shortener API
132
132
  email: jlopes@zigotto.com.br
133
133
  executables: []
@@ -148,11 +148,13 @@ files:
148
148
  - lib/googl/base.rb
149
149
  - lib/googl/client_login.rb
150
150
  - lib/googl/expand.rb
151
+ - lib/googl/oauth2/native.rb
152
+ - lib/googl/oauth2/server.rb
153
+ - lib/googl/oauth2/utils.rb
151
154
  - lib/googl/request.rb
152
155
  - lib/googl/ruby_extensions.rb
153
156
  - lib/googl/shorten.rb
154
- - spec/client_spec.rb
155
- - spec/expand_spec.rb
157
+ - lib/googl/utils.rb
156
158
  - spec/fixtures/client_login_invalid.json
157
159
  - spec/fixtures/client_login_valid.json
158
160
  - spec/fixtures/expand.json
@@ -163,12 +165,22 @@ files:
163
165
  - spec/fixtures/expand_removed.json
164
166
  - spec/fixtures/history.json
165
167
  - spec/fixtures/history_projection_clicks.json
168
+ - spec/fixtures/oauth2/native.json
169
+ - spec/fixtures/oauth2/native_invalid.json
170
+ - spec/fixtures/oauth2/native_token_expires.json
171
+ - spec/fixtures/oauth2/server.json
172
+ - spec/fixtures/oauth2/server_invalid.json
173
+ - spec/fixtures/oauth2/server_token_expires.json
166
174
  - spec/fixtures/shorten.json
167
175
  - spec/fixtures/shorten_authenticated.json
168
176
  - spec/fixtures/shorten_invalid_content_type.json
169
- - spec/request_spec.rb
177
+ - spec/googl/client_spec.rb
178
+ - spec/googl/expand_spec.rb
179
+ - spec/googl/oauth2/native_spec.rb
180
+ - spec/googl/oauth2/server_spec.rb
181
+ - spec/googl/request_spec.rb
182
+ - spec/googl/shorten_spec.rb
170
183
  - spec/shared_examples.rb
171
- - spec/shorten_spec.rb
172
184
  - spec/spec_helper.rb
173
185
  has_rdoc: true
174
186
  homepage: http://github.com/zigotto/googl
@@ -200,14 +212,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
212
  requirements: []
201
213
 
202
214
  rubyforge_project:
203
- rubygems_version: 1.5.3
215
+ rubygems_version: 1.4.2
204
216
  signing_key:
205
217
  specification_version: 3
206
218
  summary: Wrapper for Google URL Shortener API
207
219
  test_files:
208
- - spec/client_spec.rb
209
- - spec/expand_spec.rb
210
- - spec/request_spec.rb
220
+ - spec/googl/client_spec.rb
221
+ - spec/googl/expand_spec.rb
222
+ - spec/googl/oauth2/native_spec.rb
223
+ - spec/googl/oauth2/server_spec.rb
224
+ - spec/googl/request_spec.rb
225
+ - spec/googl/shorten_spec.rb
211
226
  - spec/shared_examples.rb
212
- - spec/shorten_spec.rb
213
227
  - spec/spec_helper.rb