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