Resolve merge conflict on gemspec
[arvados.git] / spec / google / api_client / service_spec.rb
1 # encoding:utf-8
2
3 # Copyright 2013 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 require 'spec_helper'
18
19 require 'google/api_client'
20 require 'google/api_client/service'
21
22 fixtures_path = File.expand_path('../../../fixtures', __FILE__)
23
24 describe Google::APIClient::Service do
25   include ConnectionHelpers
26
27   APPLICATION_NAME = 'API Client Tests'
28
29   it 'should error out when called without an API name or version' do
30     (lambda do
31       Google::APIClient::Service.new
32     end).should raise_error(ArgumentError)
33   end
34
35   it 'should error out when called without an API version' do
36     (lambda do
37       Google::APIClient::Service.new('foo')
38     end).should raise_error(ArgumentError)
39   end
40
41   it 'should error out when the options hash is not a hash' do
42     (lambda do
43       Google::APIClient::Service.new('foo', 'v1', 42)
44     end).should raise_error(ArgumentError)
45   end
46
47   describe 'with the AdSense Management API' do
48
49     it 'should make a valid call for a method with no parameters' do
50       conn = stub_connection do |stub|
51         stub.get('/adsense/v1.3/adclients') do |env|
52           [200, {}, '{}']
53         end
54       end
55       adsense = Google::APIClient::Service.new(
56         'adsense',
57         'v1.3',
58         {
59           :application_name => APPLICATION_NAME,
60           :authenticated => false,
61           :connection => conn,
62           :cache_store => nil
63         }
64       )
65
66       req = adsense.adclients.list.execute()
67       conn.verify
68     end
69
70     it 'should make a valid call for a method with parameters' do
71       conn = stub_connection do |stub|
72         stub.get('/adsense/v1.3/adclients/1/adunits') do |env|
73           [200, {}, '{}']
74         end
75       end
76       adsense = Google::APIClient::Service.new(
77         'adsense',
78         'v1.3',
79         {
80           :application_name => APPLICATION_NAME,
81           :authenticated => false,
82           :connection => conn,
83           :cache_store => nil
84         }
85       )
86       req = adsense.adunits.list(:adClientId => '1').execute()
87     end
88
89     it 'should make a valid call for a deep method' do
90       conn = stub_connection do |stub|
91         stub.get('/adsense/v1.3/accounts/1/adclients') do |env|
92           [200, {}, '{}']
93         end
94       end
95       adsense = Google::APIClient::Service.new(
96         'adsense',
97         'v1.3',
98         {
99           :application_name => APPLICATION_NAME,
100           :authenticated => false,
101           :connection => conn,
102           :cache_store => nil
103         }
104       )
105       req = adsense.accounts.adclients.list(:accountId => '1').execute()
106     end
107
108     describe 'with no connection' do
109       before do
110         @adsense = Google::APIClient::Service.new('adsense', 'v1.3',
111           {:application_name => APPLICATION_NAME, :cache_store => nil})
112       end
113
114       it 'should return a resource when using a valid resource name' do
115         @adsense.accounts.should be_a(Google::APIClient::Service::Resource)
116       end
117
118       it 'should throw an error when using an invalid resource name' do
119         (lambda do
120            @adsense.invalid_resource
121         end).should raise_error
122       end
123
124       it 'should return a request when using a valid method name' do
125         req = @adsense.adclients.list
126         req.should be_a(Google::APIClient::Service::Request)
127         req.method.id.should == 'adsense.adclients.list'
128         req.parameters.should be_nil
129       end
130
131       it 'should throw an error when using an invalid method name' do
132         (lambda do
133            @adsense.adclients.invalid_method
134         end).should raise_error
135       end
136
137       it 'should return a valid request with parameters' do
138         req = @adsense.adunits.list(:adClientId => '1')
139         req.should be_a(Google::APIClient::Service::Request)
140         req.method.id.should == 'adsense.adunits.list'
141         req.parameters.should_not be_nil
142         req.parameters[:adClientId].should == '1'
143       end
144     end
145   end
146
147   describe 'with the Prediction API' do
148
149     it 'should make a valid call with an object body' do
150       conn = stub_connection do |stub|
151         stub.post('/prediction/v1.5/trainedmodels?project=1') do |env|
152           env.body.should == '{"id":"1"}'
153           [200, {}, '{}']
154         end
155       end
156       prediction = Google::APIClient::Service.new(
157         'prediction',
158         'v1.5',
159         {
160           :application_name => APPLICATION_NAME,
161           :authenticated => false,
162           :connection => conn,
163           :cache_store => nil
164         }
165       )
166       req = prediction.trainedmodels.insert(:project => '1').body({'id' => '1'}).execute()
167       conn.verify
168     end
169
170     it 'should make a valid call with a text body' do
171       conn = stub_connection do |stub|
172         stub.post('/prediction/v1.5/trainedmodels?project=1') do |env|
173           env.body.should == '{"id":"1"}'
174           [200, {}, '{}']
175         end
176       end
177       prediction = Google::APIClient::Service.new(
178         'prediction',
179         'v1.5',
180         {
181           :application_name => APPLICATION_NAME,
182           :authenticated => false,
183           :connection => conn,
184           :cache_store => nil
185         }
186       )
187       req = prediction.trainedmodels.insert(:project => '1').body('{"id":"1"}').execute()
188       conn.verify
189     end
190
191     describe 'with no connection' do
192       before do
193         @prediction = Google::APIClient::Service.new('prediction', 'v1.5',
194           {:application_name => APPLICATION_NAME, :cache_store => nil})
195       end
196
197       it 'should return a valid request with a body' do
198         req = @prediction.trainedmodels.insert(:project => '1').body({'id' => '1'})
199         req.should be_a(Google::APIClient::Service::Request)
200         req.method.id.should == 'prediction.trainedmodels.insert'
201         req.body.should == {'id' => '1'}
202         req.parameters.should_not be_nil
203         req.parameters[:project].should == '1'
204       end
205
206       it 'should return a valid request with a body when using resource name' do
207         req = @prediction.trainedmodels.insert(:project => '1').training({'id' => '1'})
208         req.should be_a(Google::APIClient::Service::Request)
209         req.method.id.should == 'prediction.trainedmodels.insert'
210         req.training.should == {'id' => '1'}
211         req.parameters.should_not be_nil
212         req.parameters[:project].should == '1'
213       end
214     end
215   end
216
217   describe 'with the Drive API' do
218
219     before do
220       @metadata = {
221         'title' => 'My movie',
222         'description' => 'The best home movie ever made'
223       }
224       @file = File.expand_path('files/sample.txt', fixtures_path)
225       @media = Google::APIClient::UploadIO.new(@file, 'text/plain')
226     end
227
228     it 'should make a valid call with an object body and media upload' do
229       conn = stub_connection do |stub|
230         stub.post('/upload/drive/v1/files?uploadType=multipart') do |env|
231           env.body.should be_a Faraday::CompositeReadIO
232           [200, {}, '{}']
233         end
234       end
235       drive = Google::APIClient::Service.new(
236         'drive',
237         'v1',
238         {
239           :application_name => APPLICATION_NAME,
240           :authenticated => false,
241           :connection => conn,
242           :cache_store => nil
243         }
244       )
245       req = drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media).execute()
246       conn.verify
247     end
248
249     describe 'with no connection' do
250       before do
251         @drive = Google::APIClient::Service.new('drive', 'v1',
252           {:application_name => APPLICATION_NAME, :cache_store => nil})
253       end
254
255       it 'should return a valid request with a body and media upload' do
256         req = @drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media)
257         req.should be_a(Google::APIClient::Service::Request)
258         req.method.id.should == 'drive.files.insert'
259         req.body.should == @metadata
260         req.media.should == @media
261         req.parameters.should_not be_nil
262         req.parameters[:uploadType].should == 'multipart'
263       end
264
265       it 'should return a valid request with a body and media upload when using resource name' do
266         req = @drive.files.insert(:uploadType => 'multipart').file(@metadata).media(@media)
267         req.should be_a(Google::APIClient::Service::Request)
268         req.method.id.should == 'drive.files.insert'
269         req.file.should == @metadata
270         req.media.should == @media
271         req.parameters.should_not be_nil
272         req.parameters[:uploadType].should == 'multipart'
273       end
274     end
275   end
276
277   describe 'with the Discovery API' do
278     it 'should make a valid end-to-end request' do
279       discovery = Google::APIClient::Service.new('discovery', 'v1',
280           {:application_name => APPLICATION_NAME, :authenticated => false,
281            :cache_store => nil})
282       result = discovery.apis.get_rest(:api => 'discovery', :version => 'v1').execute
283       result.should_not be_nil
284       result.data.name.should == 'discovery'
285       result.data.version.should == 'v1'
286     end
287   end
288 end
289
290
291 describe Google::APIClient::Service::Result do
292
293   describe 'with the plus API' do
294     before do
295       @plus = Google::APIClient::Service.new('plus', 'v1',
296           {:application_name => APPLICATION_NAME, :cache_store => nil})
297       @reference = Google::APIClient::Reference.new({
298         :api_method => @plus.activities.list.method,
299         :parameters => {
300           'userId' => 'me',
301           'collection' => 'public',
302           'maxResults' => 20
303         }
304       })
305       @request = @plus.activities.list(:userId => 'me', :collection => 'public',
306         :maxResults => 20)
307
308       # Response double
309       @response = double("response")
310       @response.stub(:status).and_return(200)
311       @response.stub(:headers).and_return({
312         'etag' => '12345',
313         'x-google-apiary-auth-scopes' =>
314           'https://www.googleapis.com/auth/plus.me',
315         'content-type' => 'application/json; charset=UTF-8',
316         'date' => 'Mon, 23 Apr 2012 00:00:00 GMT',
317         'cache-control' => 'private, max-age=0, must-revalidate, no-transform',
318         'server' => 'GSE',
319         'connection' => 'close'
320       })
321     end
322
323     describe 'with a next page token' do
324       before do
325         @body = <<-END_OF_STRING
326           {
327             "kind": "plus#activityFeed",
328             "etag": "FOO",
329             "nextPageToken": "NEXT+PAGE+TOKEN",
330             "selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
331             "nextLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN",
332             "title": "Plus Public Activity Feed for ",
333             "updated": "2012-04-23T00:00:00.000Z",
334             "id": "123456790",
335             "items": []
336           }
337           END_OF_STRING
338         @response.stub(:body).and_return(@body)
339         base_result = Google::APIClient::Result.new(@reference, @response)
340         @result = Google::APIClient::Service::Result.new(@request, base_result)
341       end
342
343       it 'should indicate a successful response' do
344         @result.error?.should be_false
345       end
346
347       it 'should return the correct next page token' do
348         @result.next_page_token.should == 'NEXT+PAGE+TOKEN'
349       end
350
351       it 'generate a correct request when calling next_page' do
352         next_page_request = @result.next_page
353         next_page_request.parameters.should include('pageToken')
354         next_page_request.parameters['pageToken'].should == 'NEXT+PAGE+TOKEN'
355         @request.parameters.each_pair do |param, value|
356           next_page_request.parameters[param].should == value
357         end
358       end
359
360       it 'should return content type correctly' do
361         @result.media_type.should == 'application/json'
362       end
363
364       it 'should return the body correctly' do
365         @result.body.should == @body
366       end
367
368       it 'should return the result data correctly' do
369         @result.data?.should be_true
370         @result.data.class.to_s.should ==
371             'Google::APIClient::Schema::Plus::V1::ActivityFeed'
372         @result.data.kind.should == 'plus#activityFeed'
373         @result.data.etag.should == 'FOO'
374         @result.data.nextPageToken.should == 'NEXT+PAGE+TOKEN'
375         @result.data.selfLink.should ==
376             'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
377         @result.data.nextLink.should ==
378             'https://www.googleapis.com/plus/v1/people/foo/activities/public?' +
379             'maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN'
380         @result.data.title.should == 'Plus Public Activity Feed for '
381         @result.data.id.should == "123456790"
382         @result.data.items.should be_empty
383       end
384     end
385
386     describe 'without a next page token' do
387       before do
388         @body = <<-END_OF_STRING
389           {
390             "kind": "plus#activityFeed",
391             "etag": "FOO",
392             "selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
393             "title": "Plus Public Activity Feed for ",
394             "updated": "2012-04-23T00:00:00.000Z",
395             "id": "123456790",
396             "items": []
397           }
398           END_OF_STRING
399         @response.stub(:body).and_return(@body)
400         base_result = Google::APIClient::Result.new(@reference, @response)
401         @result = Google::APIClient::Service::Result.new(@request, base_result)
402       end
403
404       it 'should not return a next page token' do
405         @result.next_page_token.should == nil
406       end
407
408       it 'should return content type correctly' do
409         @result.media_type.should == 'application/json'
410       end
411
412       it 'should return the body correctly' do
413         @result.body.should == @body
414       end
415
416       it 'should return the result data correctly' do
417         @result.data?.should be_true
418         @result.data.class.to_s.should ==
419             'Google::APIClient::Schema::Plus::V1::ActivityFeed'
420         @result.data.kind.should == 'plus#activityFeed'
421         @result.data.etag.should == 'FOO'
422         @result.data.selfLink.should ==
423             'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
424         @result.data.title.should == 'Plus Public Activity Feed for '
425         @result.data.id.should == "123456790"
426         @result.data.items.should be_empty
427       end
428     end
429
430     describe 'with JSON error response' do
431       before do
432         @body = <<-END_OF_STRING
433          {
434           "error": {
435            "errors": [
436             {
437              "domain": "global",
438              "reason": "parseError",
439              "message": "Parse Error"
440             }
441            ],
442            "code": 400,
443            "message": "Parse Error"
444           }
445          }
446          END_OF_STRING
447         @response.stub(:body).and_return(@body)
448         @response.stub(:status).and_return(400)
449         base_result = Google::APIClient::Result.new(@reference, @response)
450         @result = Google::APIClient::Service::Result.new(@request, base_result)
451       end
452
453       it 'should return error status correctly' do
454         @result.error?.should be_true
455       end
456
457       it 'should return the correct error message' do
458         @result.error_message.should == 'Parse Error'
459       end
460
461       it 'should return the body correctly' do
462         @result.body.should == @body
463       end
464     end
465
466     describe 'with 204 No Content response' do
467       before do
468         @response.stub(:body).and_return('')
469         @response.stub(:status).and_return(204)
470         @response.stub(:headers).and_return({})
471         base_result = Google::APIClient::Result.new(@reference, @response)
472         @result = Google::APIClient::Service::Result.new(@request, base_result)
473       end
474
475       it 'should indicate no data is available' do
476         @result.data?.should be_false
477       end
478
479       it 'should return nil for data' do
480         @result.data.should == nil
481       end
482
483       it 'should return nil for media_type' do
484         @result.media_type.should == nil
485       end
486     end
487   end
488 end
489
490 describe Google::APIClient::Service::BatchRequest do
491   describe 'with the discovery API' do
492     before do
493       @discovery = Google::APIClient::Service.new('discovery', 'v1',
494           {:application_name => APPLICATION_NAME, :authorization => nil,
495            :cache_store => nil})
496     end
497
498     describe 'with two valid requests' do
499       before do
500         @calls = [
501           @discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
502           @discovery.apis.get_rest(:api => 'discovery', :version => 'v1')
503         ]
504       end
505
506       it 'should execute both when using a global callback' do
507         block_called = 0
508         batch = @discovery.batch(@calls) do |result|
509           block_called += 1
510           result.status.should == 200
511         end
512
513         batch.execute
514         block_called.should == 2
515       end
516
517       it 'should execute both when using individual callbacks' do
518         call1_returned, call2_returned = false, false
519         batch = @discovery.batch
520
521         batch.add(@calls[0]) do |result|
522           call1_returned = true
523           result.status.should == 200
524           result.call_index.should == 0
525         end
526
527         batch.add(@calls[1]) do |result|
528           call2_returned = true
529           result.status.should == 200
530           result.call_index.should == 1
531         end
532
533         batch.execute
534         call1_returned.should == true
535         call2_returned.should == true
536       end
537     end
538
539     describe 'with a valid request and an invalid one' do
540       before do
541         @calls = [
542           @discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
543           @discovery.apis.get_rest(:api => 'invalid', :version => 'invalid')
544         ]
545       end
546
547       it 'should execute both when using a global callback' do
548         block_called = 0
549         batch = @discovery.batch(@calls) do |result|
550           block_called += 1
551           if result.call_index == 0
552             result.status.should == 200
553           else
554             result.status.should >= 400
555             result.status.should < 500
556           end
557         end
558
559         batch.execute
560         block_called.should == 2
561       end
562
563       it 'should execute both when using individual callbacks' do
564         call1_returned, call2_returned = false, false
565         batch = @discovery.batch
566
567         batch.add(@calls[0]) do |result|
568           call1_returned = true
569           result.status.should == 200
570           result.call_index.should == 0
571         end
572
573         batch.add(@calls[1]) do |result|
574           call2_returned = true
575           result.status.should >= 400
576           result.status.should < 500
577           result.call_index.should == 1
578         end
579
580         batch.execute
581         call1_returned.should == true
582         call2_returned.should == true
583       end
584     end
585   end
586 end