google-api 0.0.1.alpha → 0.0.1.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ {
2
+ "error": {
3
+ "errors": [
4
+ {
5
+ "domain": "global",
6
+ "reason": "notFound",
7
+ "message": "File not found: asdfasdf"
8
+ }
9
+ ],
10
+ "code": 404,
11
+ "message": "File not found: asdfasdf"
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "kind": "drive#fileList",
3
+ "etag": "\"j-adummyetag/pIy649ByRBzgnLlQWUq3mFq4jbc\"",
4
+ "selfLink": "https://www.googleapis.com/drive/v2/files",
5
+ "items": [
6
+ {
7
+ "kind": "drive#file",
8
+ "id": "adummyidnumber",
9
+ "etag": "\"j-adummyetag/MTM0NjIwMjM0MzYzOQ\"",
10
+ "selfLink": "https://www.googleapis.com/drive/v2/files/adummyidnumber",
11
+ "alternateLink": "https://docs.google.com/a/bryant.edu/folder/d/adummyidnumber/edit",
12
+ "title": "Test Folder",
13
+ "mimeType": "application/vnd.google-apps.folder",
14
+ "labels": {
15
+ "starred": false,
16
+ "hidden": false,
17
+ "trashed": false,
18
+ "restricted": false,
19
+ "viewed": true
20
+ },
21
+ "createdDate": "2012-08-29T01:05:43.651Z",
22
+ "modifiedDate": "2012-08-29T01:05:43.639Z",
23
+ "modifiedByMeDate": "2012-08-29T01:05:43.639Z",
24
+ "lastViewedByMeDate": "2012-08-29T01:05:43.639Z",
25
+ "parents": [
26
+ {
27
+ "kind": "drive#parentReference",
28
+ "id": "adummyparentidnumber",
29
+ "selfLink": "https://www.googleapis.com/drive/v2/files/adummyidnumber/parents/adummyparentidnumber",
30
+ "parentLink": "https://www.googleapis.com/drive/v2/files/adummyparentidnumber",
31
+ "isRoot": true
32
+ }
33
+ ],
34
+ "userPermission": {
35
+ "kind": "drive#permission",
36
+ "etag": "\"j-adummyetag/JxXBnYVV_rnFy38Ll-sTHyzNvF8\"",
37
+ "id": "me",
38
+ "selfLink": "https://www.googleapis.com/drive/v2/files/adummyidnumber/permissions/me",
39
+ "role": "owner",
40
+ "type": "user"
41
+ },
42
+ "quotaBytesUsed": "0",
43
+ "ownerNames": [
44
+ "Alexander Robbin"
45
+ ],
46
+ "lastModifyingUserName": "Alexander Robbin",
47
+ "editable": true,
48
+ "writersCanShare": true
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "access_token": "reallysecureaccesstoken",
3
+ "token_type": "Bearer",
4
+ "expires_in": 3600
5
+ }
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+
3
+ shared_context "api call" do
4
+
5
+ let(:map) { fixture(:discovery_rest_drive)['resources']['files']['methods'] }
6
+ let(:api) { GoogleAPI::API.new(client.access_token, :drive, map) }
7
+ let(:stubbed_response) { OAuth2Response.new }
8
+ before(:each) do
9
+ GoogleAPI.discovered_apis[:drive] = fixture(:discovery_rest_drive)
10
+ client.client.connection = TEST_CONNECTION
11
+ end
12
+
13
+ end
14
+
15
+ describe GoogleAPI::API do
16
+
17
+ let(:object) { User.new }
18
+ let(:client) { GoogleAPI::Client.new(object) }
19
+ before(:each) do
20
+ GoogleAPI.configure do |config|
21
+ config.client_id = 'test id'
22
+ config.client_secret = 'test secret'
23
+ end
24
+ object.authenticated = true
25
+ end
26
+
27
+ describe "#initialize" do
28
+
29
+ let(:api) { GoogleAPI::API.new(client.access_token, :drive, fixture(:discovery_rest_drive)['resources']) }
30
+
31
+ it "should set the access_token attribute" do
32
+ api.access_token.token.should == client.access_token.token
33
+ end
34
+
35
+ it "should set the api attribute" do
36
+ api.api.should == :drive
37
+ end
38
+
39
+ it "should set the map attribute" do
40
+ api.map.should == fixture(:discovery_rest_drive)['resources']
41
+ end
42
+
43
+ end
44
+
45
+ describe "API method generation" do
46
+
47
+ context "when there are methods lower in the tree" do
48
+
49
+ it "should build another GoogleAPI::API class with the correct map" do
50
+ api = GoogleAPI::API.new(client.access_token, :drive, fixture(:discovery_rest_drive)['resources'])
51
+ api.files.should be_an_instance_of(GoogleAPI::API)
52
+ end
53
+
54
+ end
55
+
56
+ context "when the method tree is the lowest it can be" do
57
+
58
+ include_context "api call"
59
+
60
+ it "should call the #build_url method" do
61
+ api.should_receive(:build_url).with(map['list'], {}).and_return(['https://www.googleapis.com/drive/v2/files', {}])
62
+ api.list
63
+ end
64
+
65
+ %w(POST PUT PATCH).each do |http_method|
66
+ context "when the method's HTTP method is #{http_method}" do
67
+
68
+ it "should raise ArgumentError if no body was passed" do
69
+ expect {
70
+ api.insert(media: '/Test/File/Path.txt')
71
+ }.to raise_error(ArgumentError)
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+ context "when the mediaUpload key is present" do
78
+
79
+ it "should call the #upload method" do
80
+ api.should_receive(:upload).with('post', 'https://www.googleapis.com/resumable/upload/drive/v2/files', body: {title: 'Test File.txt'}, media: '/Test/File/Path.txt')
81
+ api.insert(body: {title: 'Test File.txt'}, media: '/Test/File/Path.txt')
82
+ end
83
+
84
+ end
85
+
86
+ context "when it is a normal API method request" do
87
+
88
+ it "should delegate to the #request method" do
89
+ api.should_receive(:request).with('get', 'https://www.googleapis.com/drive/v2/files', {})
90
+ api.list
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ describe "#request" do
100
+
101
+ include_context "api call"
102
+
103
+ it "should build the correct headers hash" do
104
+ ::OAuth2::AccessToken.any_instance.should_receive(:get).with('https://www.googleapis.com/drive/v2/files', headers: {'Content-Type' => 'application/json'}).and_return(stubbed_response)
105
+ api.list
106
+ end
107
+
108
+ it "should convert the body to JSON" do
109
+ ::OAuth2::AccessToken.any_instance.should_receive(:post).with('https://www.googleapis.com/drive/v2/files', body: '{"title":"Test Folder"}', headers: {'Content-Type' => 'application/json'}).and_return(stubbed_response)
110
+ api.insert(body: {title: 'Test Folder'})
111
+ end
112
+
113
+ context "when a request is successful" do
114
+
115
+ it "should only make the request attempt once" do
116
+ ::OAuth2::AccessToken.any_instance.should_receive(:get).once.and_return(stubbed_response)
117
+ api.list
118
+ end
119
+
120
+ it "should return a parsed response if body is present" do
121
+ api.list.should == fixture(:drive_files)
122
+ end
123
+
124
+ it "should return the full Response object if body is blank" do
125
+ api.delete(fileId: 'adummyfileidnumber').should be_an_instance_of(::OAuth2::Response)
126
+ end
127
+
128
+ end
129
+
130
+ context "when a request fails and development mode is turned off" do
131
+
132
+ it "should attempt the request 5 times" do
133
+ ::OAuth2::AccessToken.any_instance.should_receive(:put).exactly(5).times.and_return(OAuth2Response.new(404))
134
+ api.update(fileId: 'adummyfileidnumber', body: {bad: 'attribute'})
135
+ end
136
+
137
+ it "should break out of the attempt loop if the request succeeds" do
138
+ ::OAuth2::AccessToken.any_instance.should_receive(:put).exactly(2).times.and_return(OAuth2Response.new(404))
139
+ ::OAuth2::AccessToken.any_instance.should_receive(:put).once.and_return(stubbed_response)
140
+ api.update(fileId: 'adummyfileidnumber', body: {bad: 'attribute'})
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+ describe "#upload" do
148
+
149
+ include_context "api call"
150
+
151
+ before(:each) { api.stub(:request).and_return(stubbed_response) }
152
+
153
+ it "should build the correct options hash that is passed to the first request" do
154
+ api.should_receive(:request).with('post', 'https://www.googleapis.com/resumable/upload/drive/v2/files', body: {title: 'Test File.txt', mimeType: 'application/x-ruby'}, headers: {'X-Upload-Content-Type' => 'application/x-ruby'})
155
+ api.send(:upload, 'post', 'https://www.googleapis.com/resumable/upload/drive/v2/files', body: {title: 'Test File.txt'}, media: File.expand_path('spec/spec_helper.rb'))
156
+ end
157
+
158
+ it "should change the original headers for the actual file upload" do
159
+ api.should_receive(:request)
160
+ api.should_receive(:request).with(:put, nil, body: File.read(File.expand_path('spec/spec_helper.rb')), headers: {'Content-Type' => 'application/x-ruby', 'Content-Length' => '399'})
161
+ api.send(:upload, 'post', 'https://www.googleapis.com/resumable/upload/drive/v2/files', body: {title: 'Test File.txt'}, media: File.expand_path('spec/spec_helper.rb'))
162
+ end
163
+
164
+ end
165
+
166
+ describe "#build_url" do
167
+
168
+ include_context "api call"
169
+
170
+ context "when the mediaUpload key is present" do
171
+
172
+ it "should use the rootUrl of the discovered API" do
173
+ url, options = api.send(:build_url, map['insert'], media: File.expand_path('spec/spec_helper.rb'))
174
+ url.should == 'https://www.googleapis.com/resumable/upload/drive/v2/files'
175
+ end
176
+
177
+ end
178
+
179
+ context "when it is a normal API method request" do
180
+
181
+ it "should build the final URL" do
182
+ url, options = api.send(:build_url, map['get'], fileId: 'adummyfileidnumber', updateViewedDate: true)
183
+ url.should == 'https://www.googleapis.com/drive/v2/files/adummyfileidnumber?updateViewedDate=true'
184
+ end
185
+
186
+ it "should delete query/path parameters from the options Hash" do
187
+ url, options = api.send(:build_url, map['get'], fileId: 'adummyfileidnumber', updateViewedDate: true)
188
+ options.should == {}
189
+ end
190
+
191
+ it "should raise ArgumentError if the parameter is required and the matching option isn't passed" do
192
+ expect {
193
+ api.send(:build_url, map['get'], updateViewedDate: true)
194
+ }.to raise_error(ArgumentError)
195
+ end
196
+
197
+ end
198
+
199
+ end
200
+
201
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoogleAPI::Client do
4
+
5
+ let(:object) { User.new }
6
+ let(:client) { GoogleAPI::Client.new(object) }
7
+ before(:each) do
8
+ GoogleAPI.configure do |config|
9
+ config.client_id = 'test id'
10
+ config.client_secret = 'test secret'
11
+ end
12
+ object.authenticated = true
13
+ client.client.connection = TEST_CONNECTION
14
+ end
15
+
16
+ describe "#initialize" do
17
+
18
+ it "should set the object attribute" do
19
+ client = GoogleAPI::Client.new(object)
20
+ client.object.should == object
21
+ end
22
+
23
+ it "should raise NoMethodError if the object isn't oauthable" do
24
+ expect {
25
+ GoogleAPI::Client.new(Class.new)
26
+ }.to raise_error(NoMethodError)
27
+ end
28
+
29
+ it "should raise ArgumentError if the object isn't OAuth2 authenticated" do
30
+ object.authenticated = false
31
+ expect {
32
+ GoogleAPI::Client.new(object)
33
+ }.to raise_error(ArgumentError)
34
+ end
35
+
36
+ end
37
+
38
+ describe "#access_token" do
39
+
40
+ it "should call to the OAuth2 Access Token class" do
41
+ client.access_token.token.should == object.oauth_hash[:access_token]
42
+ end
43
+
44
+ context "when the access token is expired" do
45
+
46
+ before(:each) { object.expires_at = Time.now - 86400 }
47
+
48
+ it "should call refresh on the OAuth2 Access Token" do
49
+ stubbed_access_token = Class.new
50
+ stubbed_access_token.stub(:token).and_return('test token')
51
+ ::OAuth2::AccessToken.any_instance.should_receive(:refresh!).and_return(stubbed_access_token)
52
+ client.access_token
53
+ end
54
+
55
+ it "should update the object's stored OAuth information" do
56
+ object.should_receive(:update_access_token!)
57
+ client.access_token
58
+ end
59
+
60
+ end
61
+
62
+ context "when the access token is fresh" do
63
+
64
+ it "should not try and refresh the token again" do
65
+ ::OAuth2::AccessToken.any_instance.should_not_receive(:refresh!)
66
+ client.access_token
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ describe "#client" do
74
+
75
+ it "should create the OAuth2 Client instance" do
76
+ client.client.should be_an_instance_of(::OAuth2::Client)
77
+ end
78
+
79
+ end
80
+
81
+ describe "API method generation" do
82
+
83
+ context "when the API hasn't been discovered" do
84
+
85
+ it "should call out to the Discovery API" do
86
+ GoogleAPI.discovered_apis[:drive].should be_nil
87
+ client.drive
88
+ GoogleAPI.discovered_apis[:drive].should == fixture(:discovery_rest_drive)
89
+ end
90
+
91
+ it "should save the Discovery API map to the cache" do
92
+ client.drive
93
+ GoogleAPI.discovered_apis[:drive].should == fixture(:discovery_rest_drive)
94
+ end
95
+
96
+ end
97
+
98
+ context "when the API has already been discovered" do
99
+
100
+ it "should not fetch the Discovery API map" do
101
+ GoogleAPI.discovered_apis[:drive] = fixture(:discovery_drive)
102
+ ::OAuth2::AccessToken.any_instance.should_not_receive(:get)
103
+ client.drive
104
+ end
105
+
106
+ end
107
+
108
+ it "should build the GoogleAPI::API class" do
109
+ GoogleAPI::API.should_receive(:new).with(an_instance_of(::OAuth2::AccessToken), :drive, fixture(:discovery_rest_drive)['resources'])
110
+ client.drive
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoogleAPI do
4
+
5
+ describe "#configure" do
6
+
7
+ it "should raise an error when the CLIENT ID is blank" do
8
+ expect {
9
+ GoogleAPI.configure do |config|
10
+ config.client_id = nil
11
+ config.client_secret = 'test secret'
12
+ end
13
+ }.to raise_error(ArgumentError)
14
+ end
15
+
16
+ it "should raise an error when the CLIENT SECRET is blank" do
17
+ expect {
18
+ GoogleAPI.configure do |config|
19
+ config.client_id = 'test id'
20
+ config.client_secret = nil
21
+ end
22
+ }.to raise_error(ArgumentError)
23
+ end
24
+
25
+ it "should raise an error when both the CLIENT ID and SECRET are blank" do
26
+ expect {
27
+ GoogleAPI.configure do |config|
28
+ config.client_id = nil
29
+ config.client_secret = nil
30
+ end
31
+ }.to raise_error(ArgumentError)
32
+ end
33
+
34
+ it "should set development mode to false when not passed" do
35
+ GoogleAPI.configure do |config|
36
+ config.client_id = 'test id'
37
+ config.client_secret = 'test secret'
38
+ end
39
+
40
+ GoogleAPI.development_mode.should be_false
41
+ end
42
+
43
+ it "should set development mode when passed" do
44
+ GoogleAPI.configure do |config|
45
+ config.client_id = 'test id'
46
+ config.client_secret = 'test secret'
47
+ config.development_mode = true
48
+ end
49
+
50
+ GoogleAPI.development_mode.should be_true
51
+ end
52
+
53
+ it "should use Rails' logger when Rails is defined" do
54
+ stub_const("Rails", Class.new)
55
+ Rails.stub(:logger).and_return(Logger.new(STDOUT))
56
+
57
+ Rails.should_receive(:logger)
58
+
59
+ GoogleAPI.configure do |config|
60
+ config.client_id = 'test id'
61
+ config.client_secret = 'test secret'
62
+ end
63
+
64
+ end
65
+
66
+ it "should use a STDOUT when Rails is not defined" do
67
+ GoogleAPI.should_receive(:stdout_logger)
68
+
69
+ GoogleAPI.configure do |config|
70
+ config.client_id = 'test id'
71
+ config.client_secret = 'test secret'
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ describe "#discovered_apis" do
78
+
79
+ it "should start as an empty hash" do
80
+ GoogleAPI.discovered_apis.should == {}
81
+ end
82
+
83
+ it "should keep a cache of discovered APIs" do
84
+ GoogleAPI.discovered_apis[:test_api1] = {a: 1, b: 2, c: 3}
85
+
86
+ GoogleAPI.discovered_apis[:test_api1].should == {a: 1, b: 2, c: 3}
87
+ end
88
+
89
+ end
90
+
91
+ describe "#stdout_logger" do
92
+
93
+ let(:logger) { GoogleAPI.stdout_logger }
94
+
95
+ it "should create a new Logger object" do
96
+ logger = GoogleAPI.stdout_logger
97
+ logger.should be_an_instance_of(Logger)
98
+ end
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,17 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter 'spec'
4
+ end
5
+
6
+ require 'rspec'
7
+ require 'google-api'
8
+
9
+ Faraday.default_adapter = :test
10
+
11
+ # Requires supporting files with custom matchers and macros, etc,
12
+ # in ./support/ and its subdirectories.
13
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
14
+
15
+ RSpec.configure do |config|
16
+ config.after(:each) { GoogleAPI.reset_environment! }
17
+ end
@@ -0,0 +1,17 @@
1
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
2
+ stub.post('/o/oauth2/token') { faraday_response(:refresh_access_token) }
3
+ stub.get('/discovery/v1/apis?name=drive&preferred=true') { faraday_response(:discovery_drive) }
4
+ stub.get('/discovery/v1/apis/drive/v2/rest') { faraday_response(:discovery_rest_drive) }
5
+ stub.post('/resumable/upload/drive/v2/files') { [200] }
6
+ stub.get('/drive/v2/files') { faraday_response(:drive_files) }
7
+ stub.delete('/drive/v2/files/adummyfileidnumber') { [204] }
8
+ stub.put('/drive/v2/files/adummyfileidnumber') { faraday_response(:drive_error, 404) }
9
+ end
10
+
11
+ TEST_CONNECTION = Faraday.new do |builder|
12
+ builder.adapter :test, stubs
13
+ end
14
+
15
+ def faraday_response(fixture, code = 200)
16
+ [code, {'Content-Type' => 'application/json'}, fixture(fixture, false)]
17
+ end
@@ -0,0 +1,9 @@
1
+ require 'multi_json'
2
+
3
+ def fixture(name, jsonify = true)
4
+ path = File.expand_path '../../fixtures', __FILE__
5
+
6
+ file = File.read("#{path}/#{name}.json")
7
+
8
+ jsonify ? MultiJson.load(file) : file
9
+ end
@@ -0,0 +1,17 @@
1
+ class OAuth2Response
2
+
3
+ attr_accessor :status
4
+
5
+ def initialize(status_code = 200)
6
+ @status = status_code
7
+ end
8
+
9
+ def headers
10
+ {}
11
+ end
12
+
13
+ def parsed
14
+ fixture(:drive_files)
15
+ end
16
+
17
+ end
@@ -0,0 +1,27 @@
1
+ class User
2
+
3
+ attr_accessor :authenticated, :expires_at
4
+
5
+ def self.oauthable
6
+ true
7
+ end
8
+
9
+ def id
10
+ 1
11
+ end
12
+
13
+ def oauth_hash
14
+ {
15
+ access_token: authenticated ? 'test access token' : nil,
16
+ refresh_token: authenticated ? 'test refresh token' : nil,
17
+ expires_at: expires_at || (authenticated ? Time.now + 3600 : nil)
18
+ }
19
+ end
20
+
21
+ def update_access_token!(access_token)
22
+ end
23
+
24
+ def google
25
+ end
26
+
27
+ end